How to Use the OpenWeatherMap API in a Firefox Extension
Weather data is one of the most commonly requested features in new tab extensions. The Weather & Clock Dashboard uses OpenWeatherMap's free tier. Here's a complete guide.
Getting an API Key
- Sign up at openweathermap.org — free tier allows 60 calls/minute, 1M calls/month
- Go to API keys in your account dashboard
- Copy the default API key (or create a new one)
- Keys become active within 10 minutes
Free tier limits: 60 requests/minute, 1,000,000 requests/month — more than enough for a browser extension.
The Current Weather API
const API_KEY = 'your_api_key_here';
const BASE_URL = 'https://api.openweathermap.org/data/2.5';
async function getCurrentWeather(city) {
const url = `${BASE_URL}/weather?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=imperial`;
const response = await fetch(url);
if (!response.ok) {
if (response.status === 404) throw new Error(`City "${city}" not found`);
if (response.status === 401) throw new Error('Invalid API key');
throw new Error(`Weather API error: ${response.status}`);
}
return response.json();
}
// Response structure:
// {
// name: "San Francisco",
// sys: { country: "US" },
// weather: [{ main: "Clear", description: "clear sky", icon: "01d" }],
// main: { temp: 72, feels_like: 70, humidity: 65, temp_min: 68, temp_max: 75 },
// wind: { speed: 12, deg: 270 },
// visibility: 10000,
// dt: 1699123456 // Unix timestamp
// }
Handling Units
OWM supports three unit systems:
const UNITS = {
imperial: { temp: '°F', speed: 'mph', param: 'imperial' },
metric: { temp: '°C', speed: 'm/s', param: 'metric' },
standard: { temp: 'K', speed: 'm/s', param: 'standard' }
};
async function getWeather(city, unitSystem = 'imperial') {
const { param } = UNITS[unitSystem];
const url = `${BASE_URL}/weather?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=${param}`;
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
return {
city: data.name,
country: data.sys.country,
temp: Math.round(data.main.temp),
feelsLike: Math.round(data.main.feels_like),
humidity: data.main.humidity,
description: data.weather[0].description,
icon: data.weather[0].icon,
windSpeed: Math.round(data.wind.speed),
unitLabel: UNITS[unitSystem].temp,
speedLabel: UNITS[unitSystem].speed,
};
}
The 3-Day Forecast API
OWM's /forecast endpoint returns 5-day forecasts in 3-hour intervals:
async function getForecast(city, days = 3) {
const url = `${BASE_URL}/forecast?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=imperial`;
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
// Group by day (take the noon reading for each day)
const dailyMap = new Map();
data.list.forEach(item => {
const date = new Date(item.dt * 1000);
const dayKey = date.toLocaleDateString();
const hour = date.getHours();
// Prefer readings around noon (12-15h)
if (!dailyMap.has(dayKey) || Math.abs(hour - 13) < Math.abs(dailyMap.get(dayKey).hour - 13)) {
dailyMap.set(dayKey, {
hour,
date,
temp: Math.round(item.main.temp),
tempMin: Math.round(item.main.temp_min),
tempMax: Math.round(item.main.temp_max),
description: item.weather[0].description,
icon: item.weather[0].icon,
humidity: item.main.humidity,
});
}
});
// Skip today, return next N days
const today = new Date().toLocaleDateString();
return Array.from(dailyMap.values())
.filter(d => d.date.toLocaleDateString() !== today)
.slice(0, days);
}
Weather Icons
OWM provides icon codes like 01d (clear day), 10n (rain night). Use their CDN:
function getIconUrl(iconCode) {
return `https://openweathermap.org/img/wn/${iconCode}@2x.png`;
}
// Or map to your own emoji/SVG for better control:
const ICON_MAP = {
'01d': '☀️', // Clear sky day
'01n': '🌙', // Clear sky night
'02d': '⛅', // Few clouds day
'02n': '☁️', // Few clouds night
'03d': '☁️', // Scattered clouds
'03n': '☁️',
'04d': '☁️', // Broken clouds
'04n': '☁️',
'09d': '🌧️', // Shower rain
'09n': '🌧️',
'10d': '🌦️', // Rain day
'10n': '🌧️', // Rain night
'11d': '⛈️', // Thunderstorm
'11n': '⛈️',
'13d': '❄️', // Snow
'13n': '❄️',
'50d': '🌫️', // Mist
'50n': '🌫️',
};
Geolocation Fallback
For users who want automatic location detection:
async function getWeatherByCoords(lat, lon) {
const url = `${BASE_URL}/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=imperial`;
const res = await fetch(url);
return res.json();
}
async function autoDetectWeather() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error('Geolocation not supported'));
return;
}
navigator.geolocation.getCurrentPosition(
async (position) => {
const data = await getWeatherByCoords(
position.coords.latitude,
position.coords.longitude
);
resolve(data);
},
(error) => reject(new Error(`Geolocation: ${error.message}`)),
{ timeout: 10000 }
);
});
}
Storing the API Key Securely
For an extension, don't hardcode the API key in source. Use browser.storage.local and let users enter their own key:
async function getApiKey() {
const { apiKey } = await browser.storage.local.get('apiKey');
if (!apiKey) throw new Error('No API key configured');
return apiKey;
}
async function setApiKey(key) {
await browser.storage.local.set({ apiKey: key });
}
Or for a simpler approach with a bundled key, at least use an environment variable at build time:
// In your build process:
const OWM_KEY = process.env.OWM_API_KEY;
Rate Limiting
With 1M free requests/month, one user refreshing every 10 minutes for 30 days = 4,320 requests — well within limits. But if your extension has many users, consider:
const REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes
let lastFetch = 0;
async function fetchWeatherIfStale(city) {
const now = Date.now();
if (now - lastFetch < REFRESH_INTERVAL) {
return getCachedWeather();
}
lastFetch = now;
return getWeather(city);
}
The complete implementation is live in the Weather & Clock Dashboard Firefox extension.
Top comments (0)