{"id":114,"date":"2026-06-27T23:55:14","date_gmt":"2026-06-27T23:55:14","guid":{"rendered":"https:\/\/wolfsickness.com\/?page_id=114"},"modified":"2026-06-27T23:55:14","modified_gmt":"2026-06-27T23:55:14","slug":"make-weather-map","status":"publish","type":"page","link":"https:\/\/wolfsickness.com\/?page_id=114","title":{"rendered":"Weather Map Tutorial"},"content":{"rendered":"<h1>How to Build a Custom Interactive Weather Map in WordPress (Without Plugins)<\/h1>\n\n<p>If you run a WordPress site, you already know the golden rule: <strong>too many plugins will ruin your page speed.<\/strong><\/p>\n\n<p>I recently needed to add a sleek, global weather dashboard and interactive map to the site. Every weather widget plugin I tested was either heavily bloated, required a paid API subscription, or simply looked terrible and broke the theme.<\/p>\n\n<p>Instead of compromising, I decided to build my own from scratch.<\/p>\n\n<p>In this tutorial, I&#8217;ll show you exactly how to build a fully responsive, real-time weather dashboard and interactive map using Vanilla JavaScript, <strong>Leaflet.js<\/strong>, and the <strong>Open-Meteo API<\/strong>. Best of all? It requires zero API keys, zero plugins, and completely bypasses WordPress\u2019s annoying habit of breaking custom scripts.<\/p>\n\n<hr>\n\n<h2>The Tech Stack<\/h2>\n\n<p>To keep the widget lightweight and fast, we are avoiding heavy libraries like jQuery. We only need three things:<\/p>\n\n<ul>\n    <li><strong>HTML\/CSS (Flexbox):<\/strong> To create the grid of weather cards that seamlessly wraps on mobile devices.<\/li>\n    <li><strong>Open-Meteo API:<\/strong> A fantastic, free, open-source weather API. It doesn&#8217;t require registration or API keys, meaning you won&#8217;t suddenly hit a paywall if your site gets a spike in traffic.<\/li>\n    <li><strong>Leaflet.js:<\/strong> The leading open-source JavaScript library for mobile-friendly interactive maps. We will use this to plot our weather data on a world map.<\/li>\n<\/ul>\n\n<hr>\n\n<h2>The WordPress JavaScript Trap (And How to Beat It)<\/h2>\n\n<p>If you have ever tried pasting custom JavaScript into a WordPress &#8220;Custom HTML&#8221; block, you\u2019ve likely watched it silently crash. This happens for two main reasons:<\/p>\n\n<ul>\n    <li><strong>Script Stripping:<\/strong> WordPress security protocols and caching plugins often block or defer external <code>&lt;script src=\"...\"&gt;<\/code> tags, meaning your map engine never actually loads.<\/li>\n    <li><strong>The wpautop Filter:<\/strong> The WordPress editor tries to be helpful by automatically inserting invisible paragraph <code>&lt;p&gt;<\/code> and <code>&lt;br&gt;<\/code> tags wherever it sees an empty line in your code. This completely shatters JavaScript syntax.<\/li>\n<\/ul>\n\n<h3>The Workaround<\/h3>\n<p>To get around this, the code below uses a <strong>dynamic injection<\/strong> function. Instead of putting the Leaflet map scripts directly in the HTML, our Vanilla JavaScript sneaks them into the site&#8217;s <code>&lt;head&gt;<\/code> <em>after<\/em> the page loads.<\/p>\n\n<p>We also heavily <strong>minify<\/strong> the core JavaScript logic. By removing the line breaks, WordPress has nowhere to insert its formatting tags, ensuring the code runs perfectly every time.<\/p>\n\n<hr>\n\n<h2>The Complete Custom Code<\/h2>\n\n<p>To add this to your site, simply open your WordPress page editor, add a <strong>Custom HTML<\/strong> block, and paste the entire block of code below. It is fully self-contained and pre-loaded with 24 global cities.<\/p>\n\n<pre><code style=\"display:block; padding:15px; background:#f4f4f4; border-radius:8px; overflow-x:auto; font-size:12px;\">\n&lt;div id=\"weather-dashboard\" style=\"background: #f8fafc; padding: 15px; border-radius: 16px; font-family: Arial, sans-serif; width: 100%; box-sizing: border-box;\"&gt;\n&lt;div style=\"text-align: center; margin-bottom: 20px;\"&gt;\n&lt;button id=\"toggle-unit\" style=\"padding: 8px 16px; cursor: pointer; border-radius: 8px; border: 1px solid #cbd5e1; background: #ffffff; font-weight: bold; color: #334155; transition: all 0.2s;\"&gt;Switch to \u00b0F&lt;\/button&gt;\n&lt;div id=\"status\" style=\"margin-top: 8px; color: #3b82f6; font-size: 13px; font-weight: bold;\"&gt;Loading map resources...&lt;\/div&gt;\n&lt;\/div&gt;\n&lt;div id=\"weather-grid\" style=\"display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-bottom: 20px;\"&gt;&lt;\/div&gt;\n&lt;div id=\"weather-map\" style=\"height: 450px; width: 100%; border-radius: 12px; border: 1px solid #e2e8f0; background: #e2e8f0; display: flex; align-items: center; justify-content: center; color: #64748b; z-index: 1;\"&gt;\nWaiting for map engine...\n&lt;\/div&gt;\n&lt;\/div&gt;\n\n&lt;style&gt;\n.w-card { background: #fff; padding: 12px; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); text-align: center; border: 1px solid #e2e8f0; width: 135px; flex-grow: 1; max-width: 180px; box-sizing: border-box; }\n.w-card h3 { font-size: 13px; margin: 0 0 6px 0; color: #1e293b; }\n.w-card .time { font-size: 11px; color: #64748b; margin-bottom: 6px; }\n.w-card .icon { font-size: 26px; margin: 6px 0; }\n.w-card .temp { font-size: 18px; font-weight: bold; color: #0f172a; margin: 4px 0;}\n.w-card .details { font-size: 11px; color: #64748b; margin-top: 2px; }\n.m-popup { text-align: center; font-family: Arial, sans-serif; min-width: 130px; }\n.m-popup h4 { margin: 0 0 4px 0; font-size: 14px; color: #1e293b; }\n.m-popup .temp { font-size: 18px; font-weight: bold; color: #0f172a; margin: 4px 0; }\n.m-popup .details { font-size: 11px; color: #64748b; margin-bottom: 4px; }\n&lt;\/style&gt;\n\n&lt;script&gt;\n(function(){\nlet isCelsius=true; let cache=[]; let map=null; let markersLayer=null;\nconst cities=[{name:\"\ud83c\uddfa\ud83c\uddf8 Los Angeles\",lat:34.05,lon:-118.24},{name:\"\ud83c\uddf2\ud83c\uddfd Mexico City\",lat:19.43,lon:-99.13},{name:\"\ud83c\udde8\ud83c\udde6 Toronto\",lat:43.65,lon:-79.38},{name:\"\ud83c\uddfa\ud83c\uddf8 New York\",lat:40.71,lon:-74.00},{name:\"\ud83c\udde6\ud83c\uddf7 Buenos Aires\",lat:-34.60,lon:-58.38},{name:\"\ud83c\udde7\ud83c\uddf7 Rio\",lat:-22.90,lon:-43.17},{name:\"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f Glasgow\",lat:55.86,lon:-4.25},{name:\"\ud83c\uddec\ud83c\udde7 London\",lat:51.50,lon:-0.12},{name:\"\ud83c\uddec\ud83c\udde7 Chelmsford\",lat:51.73,lon:0.47},{name:\"\ud83c\uddeb\ud83c\uddf7 Paris\",lat:48.85,lon:2.35},{name:\"\ud83c\uddee\ud83c\uddf9 Rome\",lat:41.90,lon:12.49},{name:\"\ud83c\udde9\ud83c\uddea Berlin\",lat:52.52,lon:13.40},{name:\"\ud83c\uddf8\ud83c\uddea Stockholm\",lat:59.33,lon:18.07},{name:\"\ud83c\uddff\ud83c\udde6 Cape Town\",lat:-33.92,lon:18.42},{name:\"\ud83c\uddea\ud83c\uddec Cairo\",lat:30.04,lon:31.23},{name:\"\ud83c\udde6\ud83c\uddea Dubai\",lat:25.20,lon:55.27},{name:\"\ud83c\uddee\ud83c\uddf3 Mumbai\",lat:19.07,lon:72.87},{name:\"\ud83c\uddf9\ud83c\udded Bangkok\",lat:13.75,lon:100.50},{name:\"\ud83c\uddf8\ud83c\uddec Singapore\",lat:1.28,lon:103.83},{name:\"\ud83c\udde8\ud83c\uddf3 Beijing\",lat:39.90,lon:116.40},{name:\"\ud83c\uddf0\ud83c\uddf7 Seoul\",lat:37.56,lon:126.98},{name:\"\ud83c\uddef\ud83c\uddf5 Tokyo\",lat:35.68,lon:139.69},{name:\"\ud83c\udde6\ud83c\uddfa Sydney\",lat:-33.86,lon:151.20},{name:\"\ud83c\uddf3\ud83c\uddff Auckland\",lat:-36.85,lon:174.76}];\nfunction getI(c){if(c===0)return\"\u2600\ufe0f\";if(c&lt;=3)return\"\u2601\ufe0f\";if(c&lt;=48)return\"\ud83c\udf2b\ufe0f\";if(c&lt;=67)return\"\ud83c\udf27\ufe0f\";if(c&lt;=82)return\"\ud83c\udf26\ufe0f\";return\"\u26c8\ufe0f\";}\nfunction fmtT(c){return isCelsius?Math.round(c)+\"\u00b0C\":Math.round(c*9\/5+32)+\"\u00b0F\";}\nfunction renderUI(){\nconst grid=document.getElementById('weather-grid'); if(!grid||cache.length===0)return; let html=\"\";\ncache.forEach(c=&gt;{\nif(c.error){html+=`&lt;div class=\"w-card\"&gt;&lt;h3&gt;${c.name}&lt;\/h3&gt;&lt;div style=\"color:#ef4444;font-size:11px;\"&gt;Offline&lt;\/div&gt;&lt;\/div&gt;`;return;}\nhtml+=`&lt;div class=\"w-card\"&gt;&lt;h3&gt;${c.name}&lt;\/h3&gt;&lt;div class=\"time\"&gt;\ud83d\udd52 ${c.time}&lt;\/div&gt;&lt;div class=\"icon\"&gt;${getI(c.code)}&lt;\/div&gt;&lt;div class=\"temp\"&gt;${fmtT(c.temp)}&lt;\/div&gt;&lt;div class=\"details\"&gt;Feels like ${fmtT(c.feelsLike)}&lt;\/div&gt;&lt;div class=\"details\"&gt;\ud83d\udca8 ${Math.round(c.wind)} km\/h&lt;\/div&gt;&lt;\/div&gt;`;\n}); grid.innerHTML=html;\nif(markersLayer){markersLayer.clearLayers();\ncache.forEach(c=&gt;{if(c.error)return;\nconst p=`&lt;div class=\"m-popup\"&gt;&lt;h4&gt;${c.name}&lt;\/h4&gt;&lt;div class=\"details\"&gt;\ud83d\udd52 Local: ${c.time}&lt;\/div&gt;&lt;div class=\"temp\"&gt;${getI(c.code)} ${fmtT(c.temp)}&lt;\/div&gt;&lt;div class=\"details\"&gt;Feels like: ${fmtT(c.feelsLike)}&lt;\/div&gt;&lt;div class=\"details\"&gt;\ud83d\udca8 ${Math.round(c.wind)} km\/h&lt;\/div&gt;&lt;\/div&gt;`;\nL.marker([c.lat,c.lon]).bindPopup(p).addTo(markersLayer);});}}\nasync function loadW(){\nconst st=document.getElementById('status'); st.textContent=\"Fetching live data...\";\ntry{const proms=cities.map(async city=&gt;{\ntry{const res=await fetch(`https:\/\/api.open-meteo.com\/v1\/forecast?latitude=${city.lat}&amp;longitude=${city.lon}&amp;current=temperature_2m,apparent_temperature,weather_code,wind_speed_10m&amp;timezone=auto`);\nif(!res.ok)throw new Error(\"API Err\"); const data=await res.json();\nlet lTime=\"N\/A\"; try{lTime=new Date().toLocaleTimeString(\"en-GB\",{timeZone:data.timezone,hour:\"2-digit\",minute:\"2-digit\"});}catch(e){lTime=new Date().toLocaleTimeString(\"en-GB\",{hour:\"2-digit\",minute:\"2-digit\"});}\nreturn{name:city.name,lat:city.lat,lon:city.lon,temp:data.current.temperature_2m,feelsLike:data.current.apparent_temperature,code:data.current.weather_code,wind:data.current.wind_speed_10m,time:lTime,error:false};\n}catch(e){return{name:city.name,error:true};}});\ncache=await Promise.all(proms); renderUI();\nst.textContent=\"Last updated: \"+new Date().toLocaleTimeString(\"en-GB\",{hour:\"2-digit\",minute:\"2-digit\"}); st.style.color=\"#64748b\";\n}catch(err){st.textContent=\"Error connecting to weather API.\"; st.style.color=\"#ef4444\";}}\nfunction initApp(){\ntry{document.getElementById('weather-map').innerHTML=\"\";\nmap=L.map('weather-map').setView([25,0],2);\nL.tileLayer('https:\/\/{s}.tile.openstreetmap.org\/{z}\/{x}\/{y}.png',{maxZoom:18}).addTo(map);\nmarkersLayer=L.layerGroup().addTo(map);\nloadW();\nconst tBtn=document.getElementById('toggle-unit');\nif(tBtn){tBtn.addEventListener('click',function(){isCelsius=!isCelsius; this.textContent=isCelsius?\"Switch to \u00b0F\":\"Switch to \u00b0C\"; renderUI();});}\nsetInterval(loadW,300000);\n}catch(e){document.getElementById('status').textContent=\"Map initialization failed: \"+e.message;}}\nconst loadL=()=&gt;{\nconst css=document.createElement('link'); css.rel='stylesheet'; css.href='https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/leaflet\/1.9.4\/leaflet.css'; document.head.appendChild(css);\nconst js=document.createElement('script'); js.src='https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/leaflet\/1.9.4\/leaflet.js';\njs.onload=()=&gt;{initApp();};\njs.onerror=()=&gt;{document.getElementById('status').textContent=\"Network block: Cloudflare\/CDN denied Map load.\"; document.getElementById('status').style.color=\"#ef4444\";};\ndocument.head.appendChild(js);};\nloadL();\n})();\n&lt;\/script&gt;\n<\/code><\/pre>\n\n<hr>\n\n<h2>Customizing Your Locations<\/h2>\n\n<p>You aren&#8217;t locked into the 24 cities I provided above. If you want to customize the map for your own audience, look for this variable inside the <code>&lt;script&gt;<\/code> tag:<\/p>\n\n<pre><code style=\"display:block; padding:15px; background:#f4f4f4; border-radius:8px; overflow-x:auto;\">const cities=[{name:\"\ud83c\uddfa\ud83c\uddf8 Los Angeles\",lat:34.05,lon:-118.24}, ... ];<\/code><\/pre>\n\n<p>To add a new city, simply grab its latitude and longitude coordinates from Google Maps, pick a flag emoji, and drop it into the array following the same format. The script handles the timezone math, the API fetching, and the map marker placement entirely on its own.<\/p>\n\n<p>Happy coding, and enjoy your new blazing-fast weather map.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to Build a Custom Interactive Weather Map in WordPress (Without Plugins) If you run a WordPress site, you already know the golden rule: too many plugins will ruin your page speed. I recently needed to add a sleek, global weather dashboard and interactive map to the site. Every weather widget plugin I tested was [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-114","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.9 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Weather Map Tutorial - Wolfsickness<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/wolfsickness.com\/?page_id=114\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Weather Map Tutorial - Wolfsickness\" \/>\n<meta property=\"og:description\" content=\"How to Build a Custom Interactive Weather Map in WordPress (Without Plugins) If you run a WordPress site, you already know the golden rule: too many plugins will ruin your page speed. I recently needed to add a sleek, global weather dashboard and interactive map to the site. Every weather widget plugin I tested was [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/wolfsickness.com\/?page_id=114\" \/>\n<meta property=\"og:site_name\" content=\"Wolfsickness\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/wolfsickness.com\\\/?page_id=114\",\"url\":\"https:\\\/\\\/wolfsickness.com\\\/?page_id=114\",\"name\":\"Weather Map Tutorial - Wolfsickness\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wolfsickness.com\\\/#website\"},\"datePublished\":\"2026-06-27T23:55:14+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/wolfsickness.com\\\/?page_id=114#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/wolfsickness.com\\\/?page_id=114\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/wolfsickness.com\\\/?page_id=114#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/wolfsickness.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Weather Map Tutorial\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/wolfsickness.com\\\/#website\",\"url\":\"https:\\\/\\\/wolfsickness.com\\\/\",\"name\":\"Wolfsickness\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/wolfsickness.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Weather Map Tutorial - Wolfsickness","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/wolfsickness.com\/?page_id=114","og_locale":"en_US","og_type":"article","og_title":"Weather Map Tutorial - Wolfsickness","og_description":"How to Build a Custom Interactive Weather Map in WordPress (Without Plugins) If you run a WordPress site, you already know the golden rule: too many plugins will ruin your page speed. I recently needed to add a sleek, global weather dashboard and interactive map to the site. Every weather widget plugin I tested was [&hellip;]","og_url":"https:\/\/wolfsickness.com\/?page_id=114","og_site_name":"Wolfsickness","twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/wolfsickness.com\/?page_id=114","url":"https:\/\/wolfsickness.com\/?page_id=114","name":"Weather Map Tutorial - Wolfsickness","isPartOf":{"@id":"https:\/\/wolfsickness.com\/#website"},"datePublished":"2026-06-27T23:55:14+00:00","breadcrumb":{"@id":"https:\/\/wolfsickness.com\/?page_id=114#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/wolfsickness.com\/?page_id=114"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/wolfsickness.com\/?page_id=114#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/wolfsickness.com\/"},{"@type":"ListItem","position":2,"name":"Weather Map Tutorial"}]},{"@type":"WebSite","@id":"https:\/\/wolfsickness.com\/#website","url":"https:\/\/wolfsickness.com\/","name":"Wolfsickness","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/wolfsickness.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"}]}},"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wolfsickness.com\/index.php?rest_route=\/wp\/v2\/pages\/114","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wolfsickness.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/wolfsickness.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/wolfsickness.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wolfsickness.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=114"}],"version-history":[{"count":1,"href":"https:\/\/wolfsickness.com\/index.php?rest_route=\/wp\/v2\/pages\/114\/revisions"}],"predecessor-version":[{"id":115,"href":"https:\/\/wolfsickness.com\/index.php?rest_route=\/wp\/v2\/pages\/114\/revisions\/115"}],"wp:attachment":[{"href":"https:\/\/wolfsickness.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=114"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}