OpenWeatherMap API for Browser Extensions: A Practical Guide
If you're building a browser extension that shows weather data, OpenWeatherMap is the go-to choice. Their free tier is genuinely useful, the API is well-documented, and it works well for extensions that call the API on-demand (rather than from a server).
Here's what I learned building Weather & Clock Dashboard for Firefox.
Getting Started: Free Tier Limits
The OpenWeatherMap free tier gives you:
- 60 calls per minute
- 1,000,000 calls per month
- Access to Current Weather Data API
- Access to 5 Day / 3 Hour Forecast API
For a new tab extension, even with 10,000 active users opening 20 tabs per day, you'd need ~200,000 calls per day (~6M/month). With 10-minute caching, that drops to ~600,000/month — comfortably under the free limit.
But here's the thing: every user needs their own API key. You cannot bundle your API key in a browser extension that you distribute publicly. The key would be extractable by anyone who reads your extension's source code, and someone would burn through your quota immediately.
The Right Architecture: User-Provided API Keys
// Extension popup/settings page
async function saveApiKey(key) {
await chrome.storage.local.set({ owmApiKey: key });
}
// In newtab.js
async function getWeather(city) {
const { owmApiKey } = await chrome.storage.local.get('owmApiKey');
if (!owmApiKey) {
// Show API key setup prompt
showApiKeyPrompt();
return;
}
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${owmApiKey}&units=metric`;
const response = await fetch(url);
if (!response.ok) {
if (response.status === 401) { showApiKeyError('Invalid API key'); }
return;
}
return response.json();
}
This approach:
- Keeps user data private (their key, their account)
- Keeps your distribution simple (no backend needed)
- Avoids API key extraction from your extension
- Puts clear quota responsibility on each user
Smart Caching to Minimize API Calls
For a new tab extension, the user sees the page briefly each tab open. Calling the API every single time is wasteful and hits rate limits fast.
const CACHE_DURATION = 10 * 60 * 1000; // 10 minutes
async function getWeatherWithCache(city) {
const cacheKey = `weather_${city}`;
const cached = JSON.parse(localStorage.getItem(cacheKey) || 'null');
if (cached && (Date.now() - cached.timestamp) < CACHE_DURATION) {
return cached.data; // Use cached data
}
const data = await fetchWeatherFromAPI(city);
localStorage.setItem(cacheKey, JSON.stringify({
data,
timestamp: Date.now()
}));
return data;
}
With 10-minute caching, a user opening 100 tabs in an hour only triggers 6 API calls instead of 100.
Handling the 5-Day Forecast
The /data/2.5/forecast endpoint returns 40 data points (every 3 hours for 5 days). For a dashboard, you typically want one reading per day:
async function getForecast(city, apiKey) {
const url = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${apiKey}&units=metric&cnt=40`;
const response = await fetch(url);
const data = await response.json();
// Group by day and take noon reading (or first available)
const byDay = {};
data.list.forEach(item => {
const date = new Date(item.dt * 1000);
const dayKey = date.toDateString();
const hour = date.getHours();
// Prefer readings around noon (12-15h)
if (!byDay[dayKey] || (hour >= 12 && hour <= 15)) {
byDay[dayKey] = item;
}
});
// Return next 3 days (excluding today)
const today = new Date().toDateString();
return Object.entries(byDay)
.filter(([day]) => day !== today)
.slice(0, 3)
.map(([day, data]) => ({
date: day,
temp_min: Math.round(data.main.temp_min),
temp_max: Math.round(data.main.temp_max),
icon: data.weather[0].icon,
description: data.weather[0].main
}));
}
One Icon API Trick You'll Need
OpenWeatherMap weather icons are served at https://openweathermap.org/img/wn/{icon}@2x.png. But if you want nicer icons, you can map their weather condition codes to emoji or custom SVGs:
function weatherCodeToEmoji(iconCode) {
const code = iconCode.substring(0, 2); // '01', '02', etc.
const isDay = iconCode.endsWith('d');
const map = {
'01': isDay ? '☀️' : '🌙',
'02': isDay ? '⛅' : '☁️',
'03': '☁️',
'04': '☁️',
'09': '🌧️',
'10': isDay ? '🌦️' : '🌧️',
'11': '⛈️',
'13': '❄️',
'50': '🌫️'
};
return map[code] || '🌡️';
}
Geolocation vs. Manual City Entry
For an extension that shows your local weather, auto-detecting location is ideal — but requires user permission and can fail. Always build both:
async function initWeather() {
// Try geolocation first
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(
async (pos) => {
const { latitude, longitude } = pos.coords;
const url = `...?lat=${latitude}&lon=${longitude}&appid=${apiKey}`;
// ... fetch and display
},
() => {
// Geolocation denied or failed
showCityInputForm();
},
{ timeout: 5000, maximumAge: 60 * 60 * 1000 } // Cache for 1 hour
);
} else {
showCityInputForm();
}
}
The Full Stack for Weather & Clock Dashboard
Here's what works in production:
- API: OpenWeatherMap free tier, user-provided key
- Caching: 10-minute localStorage cache
- Units: Auto-detect metric/imperial by location, user-overridable
- Fallback: Manual city entry if geolocation denied
- Error handling: Specific messages for 401 (bad key), 404 (city not found), network errors
The extension is open source — feel free to look at the implementation.
Questions about the implementation? Drop them in the comments.
Top comments (0)