Build a Weather Chrome Extension: Real-Time Forecasts in Your Browser
Weather information is one of the most frequently accessed pieces of data on the internet. Whether users are planning their commute, scheduling outdoor activities, or deciding what to wear, having instant access to weather forecasts directly from the browser provides immense value. Building a weather Chrome extension is an excellent project for developers looking to create something practical and commercially viable.
In this comprehensive guide, we will walk you through the complete process of building a weather Chrome extension from scratch. You will learn how to set up the project structure, integrate a weather API, design an attractive popup interface, and publish your extension to the Chrome Web Store. By the end of this tutorial, you will have a fully functional weather extension that users can install and use daily.
Why Build a Weather Chrome Extension?
Before diving into the code, let us explore why weather extensions remain one of the most popular categories in the Chrome Web Store. Understanding the motivation behind this project will help you design a better product.
Market Demand and User Needs
Weather applications consistently rank among the most downloaded and used browser extensions. Users appreciate having weather information available without opening a separate application or navigating to a weather website. A well-designed weather extension saves time and provides immediate value. According to industry data, weather-related applications see retention rates significantly higher than many other extension categories, indicating strong user demand for this functionality.
The convenience factor cannot be overstated. When users open a new tab or click the extension icon, they want to see the current conditions and forecast at a glance. This frictionless access to weather data is what makes weather extensions so valuable to users and profitable for developers.
Technical Learning Opportunities
From a development perspective, building a weather extension teaches you several valuable skills that apply to many other extension projects. You will work with external APIs, handle asynchronous data fetching, manage user preferences with local storage, and create responsive popup interfaces. These are transferable skills that you can apply to productivity tools, finance trackers, news aggregators, and countless other extension types.
Additionally, weather extensions require careful attention to performance and data caching. Users expect instant results, but weather APIs often have rate limits. Learning how to balance these competing requirements will make you a better developer.
Monetization Potential
Weather extensions offer multiple monetization pathways. You can implement a freemium model where basic features are free but advanced features require payment. For example, you might offer hourly forecasts to paid users while limiting free users to daily summaries. Alternatively, you can display contextual advertisements or partner with weather services for affiliate commissions.
Project Prerequisites and Setup
Before starting the development process, ensure you have the necessary tools and accounts in place. This section covers everything you need to begin building your weather extension.
Required Tools
You will need a modern code editor such as Visual Studio Code, which provides excellent support for JavaScript development and file management. Node.js and npm are required for managing dependencies and running development scripts. Git should be installed for version control throughout the development process.
A Chromium-based browser like Google Chrome or Brave is essential for testing your extension during development. Chrome provides built-in developer tools specifically designed for extension debugging.
Weather API Selection
Choosing the right weather API is crucial for your extension’s functionality and cost. Several providers offer free tiers suitable for small to medium-sized projects:
OpenWeatherMap provides a generous free tier with current weather data, forecasts, and weather conditions. Their API is well-documented and widely used in the developer community. The free tier includes 60 calls per minute, which is sufficient for most extension use cases.
WeatherAPI.com offers a free tier with current weather, forecasts, and historical data. Their API is known for reliability and fast response times. The free tier includes one million calls per month, making it excellent for extensions with growing user bases.
Open-Meteo is a completely free, open-source weather API with no API key required. While the service is free, it may not have all the premium features of paid alternatives. However, for learning purposes and basic implementations, Open-Meteo is an excellent choice.
For this tutorial, we will use OpenWeatherMap due to its extensive documentation and widespread adoption. Register for a free API key at their website before proceeding.
Creating the Project Structure
Create a new folder for your extension project. Inside this folder, create the following file structure:
weather-extension/
├── manifest.json
├── popup.html
├── popup.css
├── popup.js
├── background.js
├── icons/
│ ├── icon16.png
│ ├── icon48.png
│ └── icon128.png
└── styles.css
This structure follows Chrome extension best practices. The manifest.json file defines your extension’s configuration. The popup files handle the user interface that appears when users click the extension icon. Background.js manages long-running tasks and API communication. The icons folder contains the various sized icons needed for the extension.
Creating the Manifest File
The manifest.json file is the foundation of every Chrome extension. It tells Chrome about your extension’s capabilities, permissions, and files. For our weather extension, we need to declare specific permissions and specify the extension’s metadata.
{
"manifest_version": 3,
"name": "Weather Now",
"version": "1.0.0",
"description": "Get real-time weather forecasts directly in your browser",
"permissions": [
"storage",
"notifications"
],
"host_permissions": [
"https://api.openweathermap.org/*"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"background": {
"service_worker": "background.js"
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
This manifest declares that our extension uses Manifest V3, the current standard for Chrome extensions. We request storage permissions to save user preferences, notifications for weather alerts, and host permissions to fetch data from the OpenWeatherMap API. The action section defines what happens when users click the extension icon, which is displaying our popup.html file.
Building the Popup Interface
The popup is the primary user interface for most Chrome extensions. When users click your extension icon, they expect to see weather information immediately. Let us design a clean, informative popup that displays current conditions and forecasts.
HTML Structure
Open popup.html and add the following structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather Now</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>Weather Now</h1>
<button id="settings-btn" class="icon-btn" aria-label="Settings">
⚙️
</button>
</header>
<div id="loading" class="loading">
<div class="spinner"></div>
<p>Fetching weather data...</p>
</div>
<div id="weather-content" class="weather-content hidden">
<div class="current-weather">
<div class="location">
<span id="city-name">Loading...</span>
<button id="refresh-btn" class="icon-btn" aria-label="Refresh">🔄</button>
</div>
<div class="temperature">
<img id="weather-icon" src="" alt="Weather icon">
<span id="temp-value">--</span>
<span class="unit">°C</span>
</div>
<div class="description" id="weather-description">--</div>
<div class="details">
<div class="detail-item">
<span class="label">Humidity</span>
<span id="humidity">--</span>
</div>
<div class="detail-item">
<span class="label">Wind</span>
<span id="wind">--</span>
</div>
<div class="detail-item">
<span class="label">Feels Like</span>
<span id="feels-like">--</span>
</div>
</div>
</div>
<div class="forecast">
<h2>5-Day Forecast</h2>
<div id="forecast-list" class="forecast-list">
<!-- Forecast items will be inserted here -->
</div>
</div>
</div>
<div id="settings-panel" class="settings-panel hidden">
<h2>Settings</h2>
<div class="form-group">
<label for="city-input">City Name</label>
<input type="text" id="city-input" placeholder="Enter city name">
</div>
<div class="form-group">
<label for="unit-select">Temperature Unit</label>
<select id="unit-select">
<option value="metric">Celsius (°C)</option>
<option value="imperial">Fahrenheit (°F)</option>
</select>
</div>
<div class="form-group">
<label for="api-key-input">API Key</label>
<input type="text" id="api-key-input" placeholder="Enter your OpenWeatherMap API key">
</div>
<div class="button-group">
<button id="save-settings" class="btn primary">Save</button>
<button id="cancel-settings" class="btn secondary">Cancel</button>
</div>
</div>
<div id="error-message" class="error hidden">
<p id="error-text">An error occurred. Please check your settings.</p>
<button id="retry-btn" class="btn">Retry</button>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
This HTML structure provides a complete user interface with loading states, weather display, forecast information, and a settings panel. The layout includes current weather conditions, a five-day forecast, and user preferences for city selection, temperature units, and API key configuration.
Styling the Popup
Create popup.css to style your extension:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
width: 320px;
min-height: 400px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
}
.container {
padding: 16px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.header h1 {
font-size: 18px;
color: white;
}
.icon-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 50%;
width: 32px;
height: 32px;
cursor: pointer;
font-size: 16px;
transition: background 0.2s;
}
.icon-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.loading {
text-align: center;
padding: 40px 0;
color: white;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.weather-content {
background: white;
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.hidden {
display: none !important;
}
.current-weather {
text-align: center;
}
.location {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
#city-name {
font-size: 18px;
font-weight: 600;
color: #333;
}
.temperature {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 8px;
}
#weather-icon {
width: 64px;
height: 64px;
}
#temp-value {
font-size: 48px;
font-weight: 700;
color: #333;
}
.unit {
font-size: 24px;
color: #666;
margin-left: 4px;
}
.description {
font-size: 16px;
color: #666;
text-transform: capitalize;
margin-bottom: 16px;
}
.details {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
padding-top: 16px;
border-top: 1px solid #eee;
}
.detail-item {
text-align: center;
}
.detail-item .label {
display: block;
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
.detail-item span:last-child {
font-size: 14px;
font-weight: 600;
color: #333;
}
.forecast {
margin-top: 20px;
}
.forecast h2 {
font-size: 14px;
color: #333;
margin-bottom: 12px;
}
.forecast-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.forecast-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
background: #f8f9fa;
border-radius: 8px;
}
.forecast-day {
font-weight: 600;
color: #333;
flex: 1;
}
.forecast-icon {
width: 32px;
height: 32px;
}
.forecast-temp {
font-weight: 600;
color: #333;
min-width: 60px;
text-align: right;
}
.settings-panel {
background: white;
border-radius: 12px;
padding: 16px;
}
.settings-panel h2 {
font-size: 18px;
color: #333;
margin-bottom: 16px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 6px;
}
.form-group input,
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
}
.button-group {
display: flex;
gap: 8px;
}
.btn {
flex: 1;
padding: 10px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.9;
}
.btn.primary {
background: #667eea;
color: white;
}
.btn.secondary {
background: #e0e0e0;
color: #333;
}
.error {
background: white;
border-radius: 12px;
padding: 16px;
text-align: center;
}
.error p {
color: #e74c3c;
margin-bottom: 12px;
}
This CSS creates an attractive gradient background with a clean white card for the weather information. The design is responsive and works well on different screen sizes. Loading animations and hover effects provide a polished user experience.
Implementing the JavaScript Logic
The JavaScript files handle all the functionality of your extension. We will split the logic between popup.js for user interface interactions and background.js for API communication and caching.
Popup JavaScript
Create popup.js to handle user interactions and display weather data:
// State management
const state = {
apiKey: '',
city: 'London',
unit: 'metric',
weatherData: null,
forecastData: null
};
// DOM Elements
const elements = {
loading: document.getElementById('loading'),
weatherContent: document.getElementById('weather-content'),
errorMessage: document.getElementById('error-message'),
settingsPanel: document.getElementById('settings-panel'),
cityName: document.getElementById('city-name'),
tempValue: document.getElementById('temp-value'),
weatherIcon: document.getElementById('weather-icon'),
weatherDescription: document.getElementById('weather-description'),
humidity: document.getElementById('humidity'),
wind: document.getElementById('wind'),
feelsLike: document.getElementById('feels-like'),
forecastList: document.getElementById('forecast-list'),
cityInput: document.getElementById('city-input'),
unitSelect: document.getElementById('unit-select'),
apiKeyInput: document.getElementById('api-key-input')
};
// Initialize extension
document.addEventListener('DOMContentLoaded', async () => {
await loadSettings();
await fetchWeatherData();
setupEventListeners();
});
// Load settings from storage
async function loadSettings() {
return new Promise((resolve) => {
chrome.storage.local.get(['apiKey', 'city', 'unit'], (result) => {
state.apiKey = result.apiKey || '';
state.city = result.city || 'London';
state.unit = result.unit || 'metric';
elements.cityInput.value = state.city;
elements.unitSelect.value = state.unit;
elements.apiKeyInput.value = state.apiKey;
resolve();
});
});
}
// Setup event listeners
function setupEventListeners() {
document.getElementById('settings-btn').addEventListener('click', showSettings);
document.getElementById('save-settings').addEventListener('click', saveSettings);
document.getElementById('cancel-settings').addEventListener('click', hideSettings);
document.getElementById('refresh-btn').addEventListener('click', fetchWeatherData);
document.getElementById('retry-btn').addEventListener('click', fetchWeatherData);
}
// Show settings panel
function showSettings() {
elements.weatherContent.classList.add('hidden');
elements.settingsPanel.classList.remove('hidden');
}
// Hide settings panel
function hideSettings() {
elements.settingsPanel.classList.add('hidden');
elements.weatherContent.classList.remove('hidden');
}
// Save settings
function saveSettings() {
state.city = elements.cityInput.value.trim() || 'London';
state.unit = elements.unitSelect.value;
state.apiKey = elements.apiKeyInput.value.trim();
chrome.storage.local.set({
city: state.city,
unit: state.unit,
apiKey: state.apiKey
});
hideSettings();
fetchWeatherData();
}
// Fetch weather data
async function fetchWeatherData() {
if (!state.apiKey) {
showError('Please enter your API key in settings.');
return;
}
showLoading();
try {
const currentWeather = await fetchCurrentWeather();
const forecast = await fetchForecast();
state.weatherData = currentWeather;
state.forecastData = forecast;
displayWeather();
displayForecast();
showWeatherContent();
} catch (error) {
console.error('Weather fetch error:', error);
showError('Failed to fetch weather data. Please check your settings.');
}
}
// Fetch current weather
async function fetchCurrentWeather() {
const url = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(state.city)}&units=${state.unit}&appid=${state.apiKey}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
// Fetch forecast
async function fetchForecast() {
const url = `https://api.openweathermap.org/data/2.5/forecast?q=${encodeURIComponent(state.city)}&units=${state.unit}&appid=${state.apiKey}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
// Display current weather
function displayWeather() {
const weather = state.weatherData;
elements.cityName.textContent = weather.name;
elements.tempValue.textContent = Math.round(weather.main.temp);
elements.weatherDescription.textContent = weather.weather[0].description;
elements.humidity.textContent = `${weather.main.humidity}%`;
elements.wind.textContent = `${weather.wind.speed} ${state.unit === 'metric' ? 'm/s' : 'mph'}`;
elements.feelsLike.textContent = `${Math.round(weather.main.feels_like)}°`;
const iconCode = weather.weather[0].icon;
elements.weatherIcon.src = `https://openweathermap.org/img/wn/${iconCode}@2x.png`;
elements.weatherIcon.alt = weather.weather[0].main;
}
// Display forecast
function displayForecast() {
const forecast = state.forecastData;
const dailyData = [];
// Get one forecast per day
const seenDates = new Set();
for (const item of forecast.list) {
const date = item.dt_txt.split(' ')[0];
if (!seenDates.has(date) && dailyData.length < 5) {
seenDates.add(date);
dailyData.push(item);
}
}
elements.forecastList.innerHTML = dailyData.map(item => {
const date = new Date(item.dt * 1000);
const dayName = date.toLocaleDateString('en-US', { weekday: 'short' });
const temp = Math.round(item.main.temp);
const iconCode = item.weather[0].icon;
return `
<div class="forecast-item">
<span class="forecast-day">${dayName}</span>
<img class="forecast-icon" src="https://openweathermap.org/img/wn/${iconCode}.png" alt="Weather icon">
<span class="forecast-temp">${temp}°</span>
</div>
`;
}).join('');
}
// UI State functions
function showLoading() {
elements.weatherContent.classList.add('hidden');
elements.errorMessage.classList.add('hidden');
elements.settingsPanel.classList.add('hidden');
elements.loading.classList.remove('hidden');
}
function showWeatherContent() {
elements.loading.classList.add('hidden');
elements.errorMessage.classList.add('hidden');
elements.settingsPanel.classList.add('hidden');
elements.weatherContent.classList.remove('hidden');
}
function showError(message) {
elements.loading.classList.add('hidden');
elements.weatherContent.classList.add('hidden');
elements.settingsPanel.classList.add('hidden');
elements.errorMessage.classList.remove('hidden');
document.getElementById('error-text').textContent = message;
}
This JavaScript implementation handles all user interactions, from loading settings to fetching weather data and displaying it in the popup. The code includes error handling, loading states, and smooth transitions between different views.
Background Service Worker
Create background.js to handle extension lifecycle events and potential background tasks:
// Background service worker for Weather Now extension
chrome.runtime.onInstalled.addListener(() => {
console.log('Weather Now extension installed');
// Set default values
chrome.storage.local.set({
city: 'London',
unit: 'metric',
apiKey: ''
});
});
// Handle messages from popup if needed
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'getWeather') {
// Handle background weather fetching if needed
sendResponse({ success: true });
}
return true;
});
Testing Your Extension
Before publishing your extension, thorough testing is essential. Chrome provides built-in tools for loading and debugging extensions during development.
Loading the Extension
Open Chrome and navigate to chrome://extensions/. Enable “Developer mode” using the toggle in the top right corner. Click “Load unpacked” and select your extension folder. The extension icon should appear in your browser toolbar.
Testing Functionality
Click the extension icon to open the popup. If you have not configured your API key, the extension will prompt you to enter one in settings. After entering your OpenWeatherMap API key and saving, the extension should display current weather and forecast information.
Test the following scenarios:
- Initial Load: Verify that the loading spinner appears while fetching data
- Data Display: Confirm that all weather information displays correctly
- Settings Changes: Modify settings and verify the extension responds appropriately
- Error Handling: Test with invalid API keys or city names to ensure errors display properly
- Unit Conversion: Toggle between Celsius and Fahrenheit and verify temperature conversions
Debugging
If issues occur, right-click the extension icon and select “Inspect popup” to open developer tools. The Console tab displays JavaScript errors and logs. Use console.log statements throughout your code to track variable values and execution flow.
Publishing to the Chrome Web Store
Once your extension is tested and working correctly, you can publish it to the Chrome Web Store for millions of users to discover and install.
Prepare for Publication
Before publishing, ensure your extension meets Chrome Web Store policies. Review your code for any security vulnerabilities, ensure you have proper privacy practices, and verify that all external resources are accessible.
Create screenshots and a promotional video demonstrating your extension’s features. These assets help users understand your extension’s value proposition.
Developer Account
Create a developer account at the Chrome Web Store Developer Dashboard. You will need to pay a one-time registration fee of $5. This account allows you to publish and manage your extensions.
Upload and Publish
Package your extension as a ZIP file containing all necessary files except the .git folder. Upload the ZIP file through the developer dashboard, fill in the store listing details including description, screenshots, and category, then submit for review. Google typically reviews submissions within hours to a few days.
Advanced Features and Improvements
Once your basic weather extension is working, consider adding these advanced features to make your extension stand out from competitors.
Geolocation Support
Implement automatic location detection using the browser’s geolocation API. This eliminates the need for users to manually enter their city. You can detect the user’s coordinates and reverse-geocode them to get the city name.
Weather Notifications
Add desktop notifications for severe weather alerts, significant temperature changes, or daily weather summaries. This feature keeps users engaged with your extension even when the popup is not open.
Multiple Locations
Allow users to track weather in multiple cities simultaneously. Store an array of locations and display a dropdown or list for quick switching between cities.
Widget Support
Implement support for Chrome’s new widget platform, allowing users to add your weather information directly to the new tab page as a widget.
Conclusion
Building a weather Chrome extension is an excellent project that teaches valuable development skills while creating a genuinely useful tool. You have learned how to set up a Chrome extension project, create a manifest file, build an attractive popup interface, integrate a weather API, and handle user interactions with JavaScript.
The weather extension you built demonstrates core concepts applicable to countless other extension projects. The patterns you learned here—API integration, state management, error handling, and UI design—transfer directly to productivity tools, finance trackers, news readers, and more.
As you continue developing Chrome extensions, remember to prioritize user experience, respect privacy, and iterate based on user feedback. The Chrome extension ecosystem offers tremendous opportunities for developers who create quality products that solve real problems.
Start with this weather extension, gather user feedback, and continue improving your creation. With dedication and attention to quality, your extension could become a valuable tool used by thousands or even millions of users worldwide.
Frequently Asked Questions
How do I get a free OpenWeatherMap API key?
Visit the OpenWeatherMap website and sign up for a free account. Navigate to the API keys section in your account dashboard to generate a new key. Note that newly generated keys may take up to a few hours to become active.
Can I use a different weather API with this extension?
Yes, the extension architecture allows you to swap weather providers. Simply modify the API URLs in the fetchCurrentWeather and fetchForecast functions to point to your preferred weather service.
How do I add more forecast days?
Modify the displayForecast function to change the number of days displayed. Adjust the CSS to accommodate longer forecast lists if needed.
Is my API key secure in Chrome storage?
Chrome storage is sandboxed and only accessible by your extension. However, for enhanced security, consider implementing key obfuscation or using a backend service to proxy API requests.
Can I monetize this extension?
Yes, you can monetize extensions through the Chrome Web Store payment system. Consider implementing a freemium model with premium features or displaying contextual advertisements.