Every new tab is a blank canvas. I wanted mine to tell me the weather and the time in the cities I care about — without opening another app or asking a backend I control.
So I built Weather & Clock Dashboard — a Firefox new tab extension with live weather, world clocks, a search bar, and light/dark mode.
The weather API problem
Most weather APIs have free tiers, but they all share one problem for a browser extension: they require an API key, and you can't hide an API key in a browser extension. The extension's source is fully readable — about:debugging in Firefox will show you every file.
The solution is wttr.in, a command-line weather service that also has a JSON API requiring no key:
async function fetchWeather() {
const url = "https://wttr.in/" + encodeURIComponent(location) + "?format=j1";
const resp = await fetch(url);
const data = await resp.json();
renderWeather(data);
}
The j1 format returns current conditions plus 3-day forecast. The API is rate-limited but generous enough for a new tab use case — you're checking it once when the tab opens, not on a timer.
World clocks without a library
The world clock feature uses the Intl.DateTimeFormat API — no external dependencies:
function formatZoneTime(zone) {
return new Intl.DateTimeFormat(undefined, {
timeZone: zone,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
}).format(new Date());
}
// Update every second
setInterval(() => {
document.querySelectorAll(".worldclock-row").forEach(row => {
row.querySelector(".time").textContent = formatZoneTime(row.dataset.zone);
});
}, 1000);
Intl.DateTimeFormat accepts IANA timezone identifiers like "America/New_York" or "Asia/Tokyo". The user picks from a dropdown of common zones. All conversion logic is in the browser — nothing leaves the device.
Storing preferences with browser.storage.local
The extension remembers which world clocks the user added and whether they prefer dark or light mode:
const storage = browser.storage.local;
async function load(key, fallback) {
try {
const result = await storage.get(key);
return result[key] !== undefined ? result[key] : fallback;
} catch { return fallback; }
}
function save(key, value) {
storage.set({ [key]: value });
}
The browser.storage.local API stores data tied to the extension — it survives browser restarts, respects private browsing, and syncs via Firefox Sync if the user enables it.
Why no backend
The extension has zero server infrastructure. The only outbound request is to wttr.in for weather data. No tracking, no telemetry, no analytics server.
This matters for trust. When someone installs a new tab extension, they're handing it significant surface area — it runs on every new tab. A no-backend extension is easier to audit: the entire codebase is locally inspectable and there's no server that could be changed after review.
Content Security Policy
The manifest restricts what the extension can connect to:
"content_security_policy": "script-src 'self'; object-src 'self'; connect-src 'self' https://wttr.in"
connect-src only allows the wttr.in domain. Even if there were an XSS vector in the extension, it couldn't exfiltrate data to an arbitrary server.
The search bar
The extension includes a search bar. I used Yahoo search via a referral feed — it generates a small revenue stream that makes the extension sustainable without ads or premium tiers.
The implementation is a plain HTML form submit:
searchForm.addEventListener("submit", (e) => {
e.preventDefault();
const q = searchInput.value.trim();
if (!q) return;
window.location.href = "https://searchfan.net/s?pub=14&feed=35&q=" + encodeURIComponent(q);
});
Users who want a different default search engine can ignore the search bar entirely — the weather and clocks are the main value.
Install it
Weather & Clock Dashboard on Firefox Add-ons
Free, local-first, no account required. If you have cities you check the time in frequently, the world clock panel is worth the install alone.
This is part of a series of posts about building Firefox extensions. Each one explores a different part of the WebExtensions API.
Top comments (0)