DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

OpenWeatherMap API for Browser Extensions: A Practical Guide

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();
}
Enter fullscreen mode Exit fullscreen mode

This approach:

  1. Keeps user data private (their key, their account)
  2. Keeps your distribution simple (no backend needed)
  3. Avoids API key extraction from your extension
  4. 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;
}
Enter fullscreen mode Exit fullscreen mode

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
    }));
}
Enter fullscreen mode Exit fullscreen mode

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] || '🌡️';
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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)