<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Weather Clock Dash</title>
    <description>The latest articles on DEV Community by Weather Clock Dash (@weatherclockdash).</description>
    <link>https://dev.to/weatherclockdash</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3910435%2F52218e1d-311f-44f9-a774-6612776873be.png</url>
      <title>DEV Community: Weather Clock Dash</title>
      <link>https://dev.to/weatherclockdash</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/weatherclockdash"/>
    <language>en</language>
    <item>
      <title>Browser Storage for Extension Developers: localStorage vs. browser.storage.local</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 22:36:48 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/browser-storage-for-extension-developers-localstorage-vs-browserstoragelocal-2gg</link>
      <guid>https://dev.to/weatherclockdash/browser-storage-for-extension-developers-localstorage-vs-browserstoragelocal-2gg</guid>
      <description>&lt;p&gt;When building a Firefox extension, you have two main options for client-side storage. Choosing wrong will either lock you out of sync features or break your extension on private browsing.&lt;/p&gt;

&lt;p&gt;Here's the practical difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Short Answer
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;browser.storage.local&lt;/code&gt;. Not &lt;code&gt;localStorage&lt;/code&gt;. Not &lt;code&gt;sessionStorage&lt;/code&gt;. Not &lt;code&gt;indexedDB&lt;/code&gt; (unless you have very specific needs).&lt;/p&gt;

&lt;p&gt;Here's why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not localStorage?
&lt;/h2&gt;

&lt;p&gt;In a regular web page, &lt;code&gt;localStorage&lt;/code&gt; is straightforward. But in an extension:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't work in background scripts.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In a background script — THIS WILL FAIL&lt;/span&gt;
&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ReferenceError: window is not defined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Background scripts don't have a &lt;code&gt;window&lt;/code&gt; object. They run in a service worker context (MV3) or background page context (MV2), neither of which has &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't sync across devices.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even if you use localStorage in a content script or popup, it's device-local only. &lt;code&gt;browser.storage.sync&lt;/code&gt; can sync across Firefox installations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private browsing isolation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Firefox, extensions can be configured to run in private browsing. &lt;code&gt;localStorage&lt;/code&gt; in private windows is isolated and cleared when the private window closes. &lt;code&gt;browser.storage.local&lt;/code&gt; persists regardless.&lt;/p&gt;

&lt;h2&gt;
  
  
  browser.storage.local API
&lt;/h2&gt;

&lt;p&gt;The API is promise-based and consistent across contexts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Save data&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;San Francisco&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NYC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Europe/London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Read data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clocks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'dark'&lt;/span&gt;

&lt;span class="c1"&gt;// Read all&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allPrefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Remove a key&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Clear everything&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  browser.storage.sync
&lt;/h2&gt;

&lt;p&gt;For data you want to sync across the user's Firefox installations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Save to sync storage&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Read from sync storage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100KB maximum total data&lt;/li&gt;
&lt;li&gt;8KB per key&lt;/li&gt;
&lt;li&gt;512 items maximum&lt;/li&gt;
&lt;li&gt;Firefox Sync must be enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Weather &amp;amp; Clock Dashboard, I chose &lt;code&gt;storage.local&lt;/code&gt; because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Settings (city, timezones) are personal to the device&lt;/li&gt;
&lt;li&gt;The user might have different setups on different machines&lt;/li&gt;
&lt;li&gt;No Firefox Sync dependency&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Listening for Changes
&lt;/h2&gt;

&lt;p&gt;A useful pattern for reactive UIs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;areaName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;areaName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;applyTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fetchWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets different parts of your extension react to storage changes without direct coupling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage Size Limits
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;browser.storage.local&lt;/code&gt; has no limit by default, but requesting the &lt;code&gt;unlimitedStorage&lt;/code&gt; permission removes any browser-imposed quota:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;manifest.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unlimitedStorage"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For most extensions, the default is sufficient. Only needed for extensions storing large amounts of data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern: Settings with Defaults
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DEFAULTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEFAULTS&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;DEFAULTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;stored&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// stored values override defaults&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you add new settings with defaults without breaking existing users who don't have the new keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; stores all user preferences this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load settings on new tab open&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clocks&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_CLOCKS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;applyTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;renderClocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clocks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;fetchWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, reliable, works everywhere the extension runs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Source code: &lt;a href="https://github.com/oren-sys/weather-clock-dashboard" rel="noopener noreferrer"&gt;github.com/oren-sys/weather-clock-dashboard&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>browserextensions</category>
      <category>firefox</category>
      <category>webdev</category>
    </item>
    <item>
      <title>World Clock vs. Timezone Calculator: Why Browser Extensions Win</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 22:33:55 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/world-clock-vs-timezone-calculator-why-browser-extensions-win-4i75</link>
      <guid>https://dev.to/weatherclockdash/world-clock-vs-timezone-calculator-why-browser-extensions-win-4i75</guid>
      <description>&lt;p&gt;Every developer who works with distributed teams has felt the pain: it's 9:15am and you need to schedule a meeting. You open a timezone converter, switch between tabs, do the math, close the calculator. You do this 5 times a day.&lt;/p&gt;

&lt;p&gt;There's a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Current Solutions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Dedicated timezone apps&lt;/strong&gt; (like World Time Buddy, Every Time Zone) are great for scheduling, but they require navigation to a separate website. That's a context switch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your OS clock&lt;/strong&gt; shows one timezone. You can add multiple clocks on macOS/Windows, but they're buried in menus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google's timezone tool&lt;/strong&gt; requires typing a search. Another context switch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser extension clock widgets&lt;/strong&gt; appear every time you open a new tab. Zero navigation required.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a New Tab Clock Should Show
&lt;/h2&gt;

&lt;p&gt;Here's what I found myself actually needing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;San Francisco:  9:15 AM  Wednesday
New York:      12:15 PM  Wednesday  
London:         5:15 PM  Wednesday
Tokyo:          2:15 AM  Thursday  ← crossed midnight!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That day-change indicator is critical. Seeing that it's &lt;em&gt;Thursday&lt;/em&gt; in Tokyo when it's &lt;em&gt;Wednesday&lt;/em&gt; here prevents the "oh no, they're asleep" mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;p&gt;The JavaScript Intl API makes this surprisingly clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatClock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeFormatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2-digit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hour12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dateFormatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timeFormatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dateFormatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;isNextDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;isNextDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isNextDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remoteDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timezone&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;remoteDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;localDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; API handles all the timezone conversion natively. No libraries needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking Timezones
&lt;/h2&gt;

&lt;p&gt;The IANA timezone database is the standard. Some common IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COMMON_ZONES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;US/Pacific&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/Los_Angeles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;US/Eastern&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Europe/London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Central EU&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Europe/Berlin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;India&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Asia/Kolkata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Singapore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Asia/Singapore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tokyo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Asia/Tokyo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sydney&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Australia/Sydney&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;Asia/Kolkata&lt;/code&gt; (India Standard Time) is UTC+5:30 — a non-whole-hour offset. The Intl API handles this correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Searchable Timezone List
&lt;/h2&gt;

&lt;p&gt;One UX challenge: letting users pick timezones. The full IANA list has 600+ entries. You need to show common ones and allow search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified searchable timezone picker&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;searchTimezones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;supportedValuesOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timeZone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// limit results&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Intl.supportedValuesOf('timeZone')&lt;/code&gt; returns all IANA timezones supported by the browser. In modern browsers this is comprehensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Tab Advantage
&lt;/h2&gt;

&lt;p&gt;The reason a clock belongs in your new tab specifically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You open a new tab naturally when starting a task&lt;/li&gt;
&lt;li&gt;The moment before you start doing something is when you're most likely to think "I should check if Tokyo is awake"&lt;/li&gt;
&lt;li&gt;Zero friction — the info is just there&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; implements exactly this. Multiple configurable world clocks with timezone labels, updating every minute, visible every time you open a tab.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The source code (MIT) is on &lt;a href="https://github.com/oren-sys/weather-clock-dashboard" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. The timezone implementation is in &lt;code&gt;dashboard.js&lt;/code&gt; if you want to see a working example.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>firefox</category>
    </item>
    <item>
      <title>The Perfect New Tab Page: A Developer's Perspective</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 22:31:14 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/the-perfect-new-tab-page-a-developers-perspective-3b5m</link>
      <guid>https://dev.to/weatherclockdash/the-perfect-new-tab-page-a-developers-perspective-3b5m</guid>
      <description>&lt;p&gt;Every developer has opinions about their development environment. Terminal themes, editor fonts, keyboard shortcuts. But there's one piece of the environment that gets strangely little attention: the new tab page.&lt;/p&gt;

&lt;p&gt;You open it hundreds of times a day. It flashes on screen for a moment every time you hit Ctrl+T. And yet most developers use the browser default — which shows either a search box with a grid of bookmarks, or (if you're on Firefox) the Pocket feed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Do You Actually Need From a New Tab?
&lt;/h2&gt;

&lt;p&gt;I spent time thinking about what information would actually make me more productive if it appeared the moment I opened a new tab. My criteria:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Loads instantly&lt;/strong&gt; — Any perceptible delay is worse than the default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shows useful information&lt;/strong&gt; — Not motivational quotes, not a beautiful photo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doesn't require interaction&lt;/strong&gt; — The info should be passively readable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doesn't distract&lt;/strong&gt; — No feed of content to get sucked into&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I settled on: weather and time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Weather + Time?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Weather&lt;/strong&gt; has a surprisingly practical use case. How many times have you gone to close your laptop for a lunch break and had no idea whether to bring a jacket? Looking at weather in a separate app is a context switch. Having it appear passively on every new tab means you absorb the info without trying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time&lt;/strong&gt; seems obvious — your OS shows it in the menu bar. But multiple time zones is different. If you work with a distributed team, knowing it's 3pm in your timezone but almost midnight for your colleague in Tokyo changes how you think about that Slack message you're about to send.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Constraints Shape the Design
&lt;/h2&gt;

&lt;p&gt;Building a new tab extension for Firefox taught me something about the relationship between technical constraints and design decisions.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;chrome_url_overrides.newtab&lt;/code&gt; API loads your HTML file on every new tab. There's no service worker, no background process — just your HTML/CSS/JS loading fresh each time.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No heavy frameworks&lt;/strong&gt; — React or Vue would be perceptible overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage is local&lt;/strong&gt; — &lt;code&gt;browser.storage.local&lt;/code&gt; for settings, no server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External calls are async and never blocking&lt;/strong&gt; — weather data loads after the tab visually appears&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The constraint forced simplicity. The extension is a single HTML file, a CSS file, and a JS file. No build step. No dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;weather-clock-dashboard/
├── dashboard.html      (the new tab page)
├── styles.css          (all styles)
├── dashboard.js        (all logic)
└── manifest.json       (extension config)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It loads &lt;strong&gt;instantly&lt;/strong&gt; (no framework parse time)&lt;/li&gt;
&lt;li&gt;It works &lt;strong&gt;offline&lt;/strong&gt; (clock works without network; weather shows cached data)&lt;/li&gt;
&lt;li&gt;The codebase is &lt;strong&gt;auditable&lt;/strong&gt; (a non-minified JS file you can read in 5 minutes)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; is what I built. After a few weeks of daily use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I've unconsciously internalized the weather for planning purposes&lt;/li&gt;
&lt;li&gt;I almost never miss a meeting because I'm accidentally surprised by timezones&lt;/li&gt;
&lt;li&gt;I've never once felt like the new tab page was slowing me down&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  For the Developer Who Wants to Build Their Own
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;chrome_url_overrides&lt;/code&gt; API in Firefox is genuinely simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"chrome_url_overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"newtab"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-page.html"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your HTML page gets full access to browser storage, the Extension API, and can make fetch requests. You could build a new tab that shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your calendar's next event&lt;/li&gt;
&lt;li&gt;Your current GitHub PRs&lt;/li&gt;
&lt;li&gt;Open Jira tickets&lt;/li&gt;
&lt;li&gt;A daily summary of your Slack messages&lt;/li&gt;
&lt;li&gt;Your code metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any of these would be more useful than a grid of bookmarks.&lt;/p&gt;

&lt;p&gt;The friction isn't technical. It's deciding what information genuinely improves your day versus what's just noise.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Interested in seeing the source? &lt;a href="https://github.com/oren-sys/weather-clock-dashboard" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard is on GitHub&lt;/a&gt;, MIT licensed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>firefox</category>
      <category>webdev</category>
      <category>browserextensions</category>
    </item>
    <item>
      <title>From Zero to AMO: How to Publish a Firefox Extension Without Losing Your Mind</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 22:28:18 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/from-zero-to-amo-how-to-publish-a-firefox-extension-without-losing-your-mind-48kj</link>
      <guid>https://dev.to/weatherclockdash/from-zero-to-amo-how-to-publish-a-firefox-extension-without-losing-your-mind-48kj</guid>
      <description>&lt;p&gt;Publishing to Mozilla's Add-ons site (AMO) is different from any other app store. Here's exactly what I learned going through the process with Weather &amp;amp; Clock Dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Manifest Version Situation
&lt;/h2&gt;

&lt;p&gt;Firefox currently supports both Manifest V2 and V3. Chrome is forcing V3 (which has significant limitations for ad blockers). Firefox's stance is more pragmatic — they support both and have promised to maintain V2 support longer.&lt;/p&gt;

&lt;p&gt;For a new extension in 2024, use MV3. Here's the basic &lt;code&gt;manifest.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Extension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"browser_specific_settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gecko"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yourextension@yourdomain.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;browser_specific_settings.gecko.id&lt;/code&gt; is &lt;strong&gt;required for AMO&lt;/strong&gt; and optional for Chrome. Include it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Tab Override API
&lt;/h2&gt;

&lt;p&gt;To replace the new tab page, use &lt;code&gt;chrome_url_overrides&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"chrome_url_overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"newtab"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"newtab.html"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Despite the &lt;code&gt;chrome_&lt;/code&gt; prefix, this works in Firefox. Your &lt;code&gt;newtab.html&lt;/code&gt; must be a file bundled within your extension — you can't point to an external URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building and Packing
&lt;/h2&gt;

&lt;p&gt;AMO requires a ZIP file (or use web-ext). I used web-ext:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; web-ext
web-ext build
&lt;span class="c"&gt;# Creates web-ext-artifacts/extension-name-1.0.0.zip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just zip manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zip &lt;span class="nt"&gt;-r&lt;/span&gt; extension.zip &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'*.git*'&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'node_modules/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The AMO Submission Process
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create an account at &lt;a href="https://addons.mozilla.org" rel="noopener noreferrer"&gt;addons.mozilla.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click "Submit a New Add-on"&lt;/li&gt;
&lt;li&gt;Choose: list on AMO (public) vs self-distribution&lt;/li&gt;
&lt;li&gt;Upload your ZIP&lt;/li&gt;
&lt;li&gt;Fill in details: name, summary (up to 250 chars), description, screenshots&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pro tip on the summary field&lt;/strong&gt;: This is what shows in search results. Make it specific and useful. "A new tab extension" is bad. "Replace Firefox new tab with live weather, world clocks, and search" is better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AMO Reviewers Check
&lt;/h2&gt;

&lt;p&gt;AMO does manual review for listed extensions. Based on my experience:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated checks:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dangerous permissions (any &lt;code&gt;&amp;lt;all_urls&amp;gt;&lt;/code&gt; permission gets scrutiny)&lt;/li&gt;
&lt;li&gt;Known malware patterns&lt;/li&gt;
&lt;li&gt;Minified/obfuscated code (requires source code upload)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Human review checks:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the extension do what it claims?&lt;/li&gt;
&lt;li&gt;Is the permission usage justified?&lt;/li&gt;
&lt;li&gt;Is there a clear privacy policy URL if you collect any data?&lt;/li&gt;
&lt;li&gt;Remote code execution risks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The source code requirement:&lt;/strong&gt;&lt;br&gt;
If your code is minified (webpack, etc.), AMO requires you to upload the unminified source separately. Since I used pure HTML/CSS/JS with no build step, I didn't need to do this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review Timeline
&lt;/h2&gt;

&lt;p&gt;My experience: initial review in ~48 hours. Subsequent updates are faster once the extension has a track record.&lt;/p&gt;

&lt;p&gt;The reviewer will email you with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Approval&lt;/li&gt;
&lt;li&gt;Request for clarification&lt;/li&gt;
&lt;li&gt;Specific issues to fix&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Rejection Reasons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Missing or inadequate privacy policy&lt;/strong&gt;: If you make ANY external requests, you need to disclose this. Even just calling a weather API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Over-broad permissions&lt;/strong&gt;: Requesting permissions you don't use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content Security Policy issues&lt;/strong&gt;: AMO is strict about CSP. Remote scripts aren't allowed (this is actually good practice).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unexpected remote code execution&lt;/strong&gt;: Loading scripts from CDNs in production code.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  After Publishing
&lt;/h2&gt;

&lt;p&gt;Once live, your extension gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AMO listing URL: &lt;code&gt;https://addons.mozilla.org/en-US/firefox/addon/your-extension-name/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install statistics (updated daily)&lt;/li&gt;
&lt;li&gt;Review capability&lt;/li&gt;
&lt;li&gt;A verified badge from Mozilla&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AMO install rate depends heavily on search discoverability and reviews. Getting your first 50 installs is hard — after that, organic discovery picks up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Out the Result
&lt;/h2&gt;

&lt;p&gt;I went through this entire process with &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt;. The source code is on &lt;a href="https://github.com/oren-sys/weather-clock-dashboard" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; if you want to see a concrete example of everything described here.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions about the AMO process? Drop them in the comments — happy to help.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>firefox</category>
      <category>webdev</category>
      <category>browserextensions</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building Privacy-First Browser Extensions: What I Learned</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 22:17:18 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/building-privacy-first-browser-extensions-what-i-learned-1lf9</link>
      <guid>https://dev.to/weatherclockdash/building-privacy-first-browser-extensions-what-i-learned-1lf9</guid>
      <description>&lt;p&gt;When I built Weather &amp;amp; Clock Dashboard for Firefox, I made a decision early on: &lt;strong&gt;no analytics, no accounts, no external calls except weather data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's what I learned about building browser extensions with privacy as a design principle — and why I think more extension developers should take this approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Default is Data Collection
&lt;/h2&gt;

&lt;p&gt;Most web development assumes you want to know who your users are. Analytics platforms are one npm install away. A/B testing, session recording, funnel analysis — the infrastructure for surveillance capitalism is baked into the default developer tooling.&lt;/p&gt;

&lt;p&gt;Browser extensions are in a privileged position. They run inside the browser, they can see your tabs (with permission), they load on page visits. An extension with analytics has more access to your behavior than a typical website.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Privacy-First Actually Means
&lt;/h2&gt;

&lt;p&gt;For my extension, I set these constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. No analytics scripts
2. No external requests except weather API (user-configured, optional)
3. No user accounts
4. No localStorage of personally identifiable information
5. MIT licensed — code is auditable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The localStorage Rule
&lt;/h3&gt;

&lt;p&gt;The extension stores settings: your preferred city, timezone list, theme preference. But these settings live only in your browser, via &lt;code&gt;browser.storage.local&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Storing user preference — this stays on-device&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NYC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This data never leaves the browser. It can't. There's no server to send it to.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Weather API Problem
&lt;/h3&gt;

&lt;p&gt;Weather is a hard case. To show current weather, you need to call an external API — there's no way around it. That external API will see your IP address.&lt;/p&gt;

&lt;p&gt;My solution: &lt;strong&gt;require users to bring their own API key&lt;/strong&gt;. Weather &amp;amp; Clock Dashboard uses OpenWeatherMap's free API tier. Users create their own account and enter their own API key.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I have zero knowledge of what city any user is in&lt;/li&gt;
&lt;li&gt;OpenWeatherMap has a direct relationship with the user (their own account)&lt;/li&gt;
&lt;li&gt;I have no API key to revoke if I need to shut down a server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tradeoff: higher setup friction. Some users bounce when they see "enter API key." That's a cost I'm willing to pay.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AMO Review Benefit
&lt;/h2&gt;

&lt;p&gt;Mozilla's Add-ons review process (for Firefox) actually rewards privacy-respecting extensions. Reviewers check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What permissions the extension requests&lt;/li&gt;
&lt;li&gt;What external requests it makes&lt;/li&gt;
&lt;li&gt;Whether the permissions are necessary for the stated functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An extension that requests &lt;code&gt;&amp;lt;all_urls&amp;gt;&lt;/code&gt; permission but only needs to show weather will get questions. An extension with no permissions beyond &lt;code&gt;storage&lt;/code&gt; sails through.&lt;/p&gt;

&lt;h2&gt;
  
  
  The User Trust Payoff
&lt;/h2&gt;

&lt;p&gt;Privacy-first design communicates trust before users read a single word of your privacy policy. When I see an extension that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires no account&lt;/li&gt;
&lt;li&gt;Requests minimal permissions&lt;/li&gt;
&lt;li&gt;Is open source&lt;/li&gt;
&lt;li&gt;Has been reviewed by Mozilla&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...I trust it immediately. That trust is the product.&lt;/p&gt;

&lt;p&gt;For extensions specifically, trust is the moat. Users install things that run inside their browser. The bar for trust should be high.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Implementation
&lt;/h2&gt;

&lt;p&gt;Here's the manifest for Weather &amp;amp; Clock Dashboard — notice what permissions are and aren't requested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chrome_url_overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"newtab"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dashboard.html"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;tabs&lt;/code&gt;, no &lt;code&gt;history&lt;/code&gt;, no &lt;code&gt;&amp;lt;all_urls&amp;gt;&lt;/code&gt;. Just &lt;code&gt;storage&lt;/code&gt; to save preferences. That's the entire permission surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics Without Surveillance
&lt;/h2&gt;

&lt;p&gt;I do want to know how many people are using the extension. AMO's install count is my only metric, and it's coarse. I can see total installs and a weekly active user count — nothing more granular.&lt;/p&gt;

&lt;p&gt;For a lot of developers, this is unacceptable. How do you optimize UX without knowing which features are used? How do you prioritize roadmap?&lt;/p&gt;

&lt;p&gt;My answer: user feedback channels (GitHub issues, AMO reviews) and intuition. It's less data-driven. I'm okay with that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Sourcing as a Privacy Signal
&lt;/h2&gt;

&lt;p&gt;MIT licensing and public source code is a form of privacy documentation that no privacy policy can match. Users can verify that the code does what it claims. Researchers can audit it. Security professionals can check for vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/oren-sys/weather-clock-dashboard" rel="noopener noreferrer"&gt;The source code is on GitHub&lt;/a&gt;. I'm comfortable with anyone reading it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building privacy-first isn't a technical constraint — it's a design decision made before you write the first line of code. The constraint is deciding what you won't collect, not figuring out how to anonymize what you do.&lt;/p&gt;

&lt;p&gt;For browser extensions especially, privacy-first design is also good product design. Users are rightfully suspicious of extensions. Being genuinely privacy-respecting — not just claiming to be — is a real differentiator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Install Weather &amp;amp; Clock Dashboard&lt;/a&gt; if you want to see this in practice.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>browserextensions</category>
      <category>firefox</category>
      <category>javascript</category>
    </item>
    <item>
      <title>5 Firefox New Tab Extensions Compared: Which One Actually Improves Your Workflow?</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 22:16:44 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/5-firefox-new-tab-extensions-compared-which-one-actually-improves-your-workflow-2eod</link>
      <guid>https://dev.to/weatherclockdash/5-firefox-new-tab-extensions-compared-which-one-actually-improves-your-workflow-2eod</guid>
      <description>&lt;p&gt;Your browser opens dozens of times per day. Each time, you see the new tab page for a split second — maybe less. That's hundreds of micro-moments that currently show you... nothing useful.&lt;/p&gt;

&lt;p&gt;Here's a comparison of the top Firefox new tab extensions, from a developer who builds them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes a New Tab Extension Good?
&lt;/h2&gt;

&lt;p&gt;Before comparing, let's define the criteria:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Information density&lt;/strong&gt; — does it show me something useful?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load time&lt;/strong&gt; — does it slow down new tab opens?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt; — does it track me?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customization&lt;/strong&gt; — can I make it mine?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free tier&lt;/strong&gt; — is the useful version free?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Contenders
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Momentum Dash
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Verdict: Beautiful but limited (free tier)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Momentum shows a beautiful photo, the time, and a daily focus prompt. Premium unlocks weather, todos, and more widgets. The free tier is minimal — just time and a pretty background.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load time: Fast&lt;/li&gt;
&lt;li&gt;Privacy: Account required (tracks usage)&lt;/li&gt;
&lt;li&gt;Free tier: Very limited&lt;/li&gt;
&lt;li&gt;Customization: Moderate (premium)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. New Tab Override
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Verdict: Developer-focused, total control&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;New Tab Override lets you set any URL as your new tab page. It's pure configuration — no widgets, no weather, just "open this URL when I press Ctrl+T."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load time: Depends on the target URL&lt;/li&gt;
&lt;li&gt;Privacy: Excellent (no data collection)&lt;/li&gt;
&lt;li&gt;Free tier: Full feature (open source)&lt;/li&gt;
&lt;li&gt;Customization: Unlimited (you control the URL)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Tabliss
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Verdict: Most customizable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tabliss is a widget-based new tab with backgrounds, weather, clock, to-do lists, and more. Each widget is modular and configurable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load time: Moderate (many active widgets can slow it)&lt;/li&gt;
&lt;li&gt;Privacy: Good (self-contained)&lt;/li&gt;
&lt;li&gt;Free tier: Full feature&lt;/li&gt;
&lt;li&gt;Customization: Excellent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. iTab New Tab
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Verdict: Feature-rich but heavy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;iTab packs in a lot: bookmarks, apps, wallpapers, weather, search. It's essentially a mini dashboard.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load time: Slower (many features)&lt;/li&gt;
&lt;li&gt;Privacy: Mixed (some features call external servers)&lt;/li&gt;
&lt;li&gt;Free tier: Full feature&lt;/li&gt;
&lt;li&gt;Customization: High&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Weather &amp;amp; Clock Dashboard
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Verdict: Focused on the essentials&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the extension I built (&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;install here&lt;/a&gt;). I built it because I wanted exactly three things on my new tab: weather, clocks, search. Nothing else.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load time: Instant (pure HTML/CSS/JS, no frameworks)&lt;/li&gt;
&lt;li&gt;Privacy: No data collection, no account, no external analytics&lt;/li&gt;
&lt;li&gt;Free tier: 100% free, MIT open source&lt;/li&gt;
&lt;li&gt;Customization: Weather city, multiple timezone clocks, theme toggle&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Performance Test
&lt;/h2&gt;

&lt;p&gt;I benchmarked new tab load times on the same machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Tab Override (blank):     ~10ms
Weather &amp;amp; Clock Dashboard:    ~150ms  
Tableau (3 widgets):          ~400ms  
iTab (default config):        ~800ms  
Momentum (premium):           ~250ms  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: External API calls (weather data) are loaded asynchronously and don't block the visual render.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Which One Should You Use?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose Momentum if:&lt;/strong&gt; You want beautiful backgrounds and don't mind a paid subscription for full features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose New Tab Override if:&lt;/strong&gt; You have a specific webpage you want to open (your company dashboard, Notion, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Tabliss if:&lt;/strong&gt; You want maximum widget customization and are willing to configure each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose iTab if:&lt;/strong&gt; You want a full browser home page with bookmarks and apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Weather &amp;amp; Clock Dashboard if:&lt;/strong&gt; You want live weather + world clocks without any account, subscription, or data collection. Just install and use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Open Source Angle
&lt;/h2&gt;

&lt;p&gt;One thing worth noting: Weather &amp;amp; Clock Dashboard and New Tab Override are both MIT-licensed open source projects. You can inspect the code, fork it, or contribute. For a piece of software that loads on literally every new browser tab, knowing exactly what it does matters.&lt;/p&gt;

&lt;p&gt;For closed-source new tab extensions, you're trusting that they aren't injecting ads, tracking your browsing habits, or monetizing your data in ways not disclosed in privacy policies that nobody reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The "best" new tab extension depends entirely on what you want your new tab to do. If the answer is "show me weather + current time in multiple cities + let me search quickly," then I'm biased but I'd recommend &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want complete flexibility, New Tab Override combined with a locally-hosted page is unbeatable. If you want maximum widgets, Tabliss has the most.&lt;/p&gt;

&lt;p&gt;The one I'd avoid: any extension with a required account on the free tier. Your new tab page shouldn't phone home every time you open a tab.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build Weather &amp;amp; Clock Dashboard. All benchmarks were performed on my own machine; YMMV.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>firefox</category>
      <category>productivity</category>
      <category>browserextensions</category>
      <category>opensource</category>
    </item>
    <item>
      <title>OpenWeatherMap API for Browser Extensions: A Practical Guide</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 21:46:33 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/openweathermap-api-for-browser-extensions-a-practical-guide-5m7</link>
      <guid>https://dev.to/weatherclockdash/openweathermap-api-for-browser-extensions-a-practical-guide-5m7</guid>
      <description>&lt;h1&gt;
  
  
  OpenWeatherMap API for Browser Extensions: A Practical Guide
&lt;/h1&gt;

&lt;p&gt;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).&lt;/p&gt;

&lt;p&gt;Here's what I learned building &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; for Firefox.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started: Free Tier Limits
&lt;/h2&gt;

&lt;p&gt;The OpenWeatherMap free tier gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;60 calls per minute&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1,000,000 calls per month&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Access to Current Weather Data API&lt;/li&gt;
&lt;li&gt;Access to 5 Day / 3 Hour Forecast API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;But here's the thing: &lt;strong&gt;every user needs their own API key&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Right Architecture: User-Provided API Keys
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Extension popup/settings page&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;owmApiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// In newtab.js&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;owmApiKey&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;owmApiKey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;owmApiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Show API key setup prompt&lt;/span&gt;
    &lt;span class="nf"&gt;showApiKeyPrompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.openweathermap.org/data/2.5/weather?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;appid=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owmApiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;units=metric`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;showApiKeyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid API key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keeps user data private (their key, their account)&lt;/li&gt;
&lt;li&gt;Keeps your distribution simple (no backend needed)&lt;/li&gt;
&lt;li&gt;Avoids API key extraction from your extension&lt;/li&gt;
&lt;li&gt;Puts clear quota responsibility on each user&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Smart Caching to Minimize API Calls
&lt;/h2&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CACHE_DURATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 10 minutes&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWeatherWithCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`weather_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;null&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;CACHE_DURATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Use cached data&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchWeatherFromAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With 10-minute caching, a user opening 100 tabs in an hour only triggers 6 API calls instead of 100.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling the 5-Day Forecast
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/data/2.5/forecast&lt;/code&gt; endpoint returns 40 data points (every 3 hours for 5 days). For a dashboard, you typically want one reading per day:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getForecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.openweathermap.org/data/2.5/forecast?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;appid=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;units=metric&amp;amp;cnt=40`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Group by day and take noon reading (or first available)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;byDay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dayKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Prefer readings around noon (12-15h)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;byDay&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dayKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;byDay&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dayKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Return next 3 days (excluding today)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;byDay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;day&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;temp_min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temp_min&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;temp_max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temp_max&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One Icon API Trick You'll Need
&lt;/h2&gt;

&lt;p&gt;OpenWeatherMap weather icons are served at &lt;code&gt;https://openweathermap.org/img/wn/{icon}@2x.png&lt;/code&gt;. But if you want nicer icons, you can map their weather condition codes to emoji or custom SVGs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;weatherCodeToEmoji&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iconCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;iconCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// '01', '02', etc.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;iconCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isDay&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;☀️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🌙&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;02&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isDay&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⛅&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;☁️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;03&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;☁️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;04&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;☁️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;09&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🌧️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isDay&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🌦️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🌧️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⛈️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;13&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❄️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;50&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🌫️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🌡️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Geolocation vs. Manual City Entry
&lt;/h2&gt;

&lt;p&gt;For an extension that shows your local weather, auto-detecting location is ideal — but requires user permission and can fail. Always build both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initWeather&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Try geolocation first&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;geolocation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geolocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`...?lat=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;lon=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;appid=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// ... fetch and display&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Geolocation denied or failed&lt;/span&gt;
        &lt;span class="nf"&gt;showCityInputForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maximumAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Cache for 1 hour&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showCityInputForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Full Stack for Weather &amp;amp; Clock Dashboard
&lt;/h2&gt;

&lt;p&gt;Here's what works in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API&lt;/strong&gt;: OpenWeatherMap free tier, user-provided key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: 10-minute localStorage cache&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Units&lt;/strong&gt;: Auto-detect metric/imperial by location, user-overridable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback&lt;/strong&gt;: Manual city entry if geolocation denied&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling&lt;/strong&gt;: Specific messages for 401 (bad key), 404 (city not found), network errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The extension is &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;open source&lt;/a&gt; — feel free to look at the implementation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions about the implementation? Drop them in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>firefox</category>
      <category>webextensions</category>
      <category>api</category>
    </item>
    <item>
      <title>r/startpages Is the Most Underrated Firefox Community You're Not Using</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 21:26:45 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/rstartpages-is-the-most-underrated-firefox-community-youre-not-using-2ked</link>
      <guid>https://dev.to/weatherclockdash/rstartpages-is-the-most-underrated-firefox-community-youre-not-using-2ked</guid>
      <description>&lt;h1&gt;
  
  
  r/startpages Is the Most Underrated Firefox Community You're Not Using
&lt;/h1&gt;

&lt;p&gt;If you care about browser customization, there's a subreddit you probably haven't found yet: &lt;a href="https://www.reddit.com/r/startpages/" rel="noopener noreferrer"&gt;r/startpages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With 35,000+ members, it's a community of people who genuinely care about what appears when they open a new browser tab. They share custom HTML/CSS homepages, new tab extensions, and browser startpage setups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who's In This Community?
&lt;/h2&gt;

&lt;p&gt;The r/startpages crowd is a mix of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Front-end developers&lt;/strong&gt; who build custom HTML/CSS new tab pages from scratch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy-focused users&lt;/strong&gt; who want their browser to feel like &lt;em&gt;theirs&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firefox and power browser users&lt;/strong&gt; who've invested in customizing their setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimalists&lt;/strong&gt; who want weather, clocks, or bookmarks without the noise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't the general Firefox subreddit. These people have strong opinions about whitespace, typography, and whether a clock should be 12-hour or 24-hour format.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes a Good r/startpages Post?
&lt;/h2&gt;

&lt;p&gt;Looking at top posts, a few patterns emerge:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Show, don't tell&lt;/strong&gt; — Screenshots or gifs of the actual new tab are essential. Text-only posts get buried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Share your approach&lt;/strong&gt; — People want to know: Is this a custom HTML file? An extension? A framework?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Open source gets upvotes&lt;/strong&gt; — The community strongly prefers shareable setups. GitHub links are valued.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Functionality over pure aesthetics&lt;/strong&gt; — Beautiful-but-useless pages do less well than actually-useful ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Kind of New Tab These Users Actually Want
&lt;/h2&gt;

&lt;p&gt;Based on community behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clocks&lt;/strong&gt; — Nearly universal. Time matters on a new tab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather&lt;/strong&gt; — Common, especially for users who commute or work outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal bookmarks&lt;/strong&gt; — Quick access without the visual clutter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No social feeds&lt;/strong&gt; — Reddit, Twitter widgets are generally disliked here&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dark mode&lt;/strong&gt; — A huge majority of the community prefers dark backgrounds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by default&lt;/strong&gt; — Suspicion of extensions that "phone home"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly why &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; fits the community's needs well — it's a Firefox extension that delivers the weather + clock combo that startpage enthusiasts actually want, with no accounts, no tracking, and clean code.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Engage Authentically
&lt;/h2&gt;

&lt;p&gt;If you're building a new tab extension or startpage tool, the community values genuine participation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Comment first, post later&lt;/strong&gt; — Engage with other people's setups before promoting your own&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be transparent&lt;/strong&gt; — Share what it's built with, what data it accesses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invite feedback&lt;/strong&gt; — "What would you change?" gets more engagement than pure announcements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respond to comments&lt;/strong&gt; — The community notices when creators engage&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Beyond r/startpages
&lt;/h2&gt;

&lt;p&gt;If you're in this niche, also check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;r/firefox&lt;/strong&gt; — 240k members, broader Firefox community&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;r/firefoxaddons&lt;/strong&gt; — Specifically for extension discovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;r/unixporn&lt;/strong&gt; — If you're on Linux, this community has massive crossover with custom browser setups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mozilla Discourse&lt;/strong&gt; — The official Mozilla community forum, including an add-ons category&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These communities exist because people genuinely want to own their browser experience. Extensions that respect that ethos — no dark patterns, no tracking, open source — tend to find warm receptions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; is available for Firefox. Open source, MIT licensed, no account required.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>firefox</category>
      <category>productivity</category>
      <category>community</category>
      <category>webextensions</category>
    </item>
    <item>
      <title>The New Tab Page Is Prime Real Estate. Are You Wasting It?</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 21:13:09 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/the-new-tab-page-is-prime-real-estate-are-you-wasting-it-2hek</link>
      <guid>https://dev.to/weatherclockdash/the-new-tab-page-is-prime-real-estate-are-you-wasting-it-2hek</guid>
      <description>&lt;h1&gt;
  
  
  The New Tab Page Is Prime Real Estate. Are You Wasting It?
&lt;/h1&gt;

&lt;p&gt;You open a new browser tab dozens of times a day. Most people see either a blank white page, or a corporate-designed "inspiration" page they never asked for.&lt;/p&gt;

&lt;p&gt;That's a waste.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math
&lt;/h2&gt;

&lt;p&gt;Let's be conservative: 20 new tabs per day × 300 working days per year = &lt;strong&gt;6,000 glimpses at your new tab page per year&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each glimpse lasts maybe 2-3 seconds before you type in a URL or start searching. That's over 3 hours per year spent on your new tab page — even with that conservative estimate.&lt;/p&gt;

&lt;p&gt;If your new tab page delivers zero value, that's 3 hours of pure wasted screen time annually. If it delivers &lt;em&gt;negative&lt;/em&gt; value (anxiety-inducing news feeds, intrusive ads), it might be costing you much more in lost focus.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Most New Tab Extensions Get Wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Inspiration Trap
&lt;/h3&gt;

&lt;p&gt;Extensions like Momentum made "inspiring quotes + beautiful photos" the dominant new tab paradigm. It worked at first. But there's a problem: inspiration is a finite resource, and your brain quickly learns to tune out the same stimulus.&lt;/p&gt;

&lt;p&gt;After a week of "Be the change you wish to see in the world" over the same mountain stock photo, your brain categorizes it as background noise. You stop seeing it. The "inspiration" provides zero value.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Feature Creep Problem
&lt;/h3&gt;

&lt;p&gt;Other extensions try to make the new tab page a productivity hub — tasks, notes, goals, bookmarks, Spotify, weather, news, stocks, habits...&lt;/p&gt;

&lt;p&gt;Result: cognitive overload. Every time you open a new tab, you're confronted with a dashboard that demands decisions. "Should I check my tasks? Did I do my habit today? What's the weather?" You came here to search for something, and now your brain is distracted by 15 widgets.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Account Wall
&lt;/h3&gt;

&lt;p&gt;Many extensions require accounts — even just for saving settings. Now your browser behavior is tied to a company's server. When they pivot or shut down, your carefully configured setup disappears overnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works: The Calm Information Glance
&lt;/h2&gt;

&lt;p&gt;After trying most popular options, I settled on a principle: &lt;strong&gt;the new tab page should answer ambient questions without demanding attention&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ambient questions are things you'd glance at naturally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What time is it?&lt;/li&gt;
&lt;li&gt;What's the weather like?&lt;/li&gt;
&lt;li&gt;What time is it in [city where I have a meeting]?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're not making decisions. You're not being inspired. You're just getting a passive update on your environment.&lt;/p&gt;

&lt;p&gt;This is the philosophy behind &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; for Firefox. When I built it, I stripped out everything except:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Current time&lt;/strong&gt; — a large, readable clock&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather&lt;/strong&gt; — current conditions + 3-day forecast&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;World clocks&lt;/strong&gt; — for distributed teams and timezone-aware people&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search bar&lt;/strong&gt; — because that's usually why you opened a new tab&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No tasks. No goals. No news feed. No social media integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dark Mode Effect
&lt;/h2&gt;

&lt;p&gt;One thing I didn't expect: &lt;strong&gt;dark mode matters more on the new tab page than anywhere else&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you open a new tab, the screen flashes. In a light-themed browser, that flash is bright white. At night, after hours of screen time, a white new tab page is a small but real irritant.&lt;/p&gt;

&lt;p&gt;Auto dark mode (matching system preference) should be mandatory for any serious new tab extension. It's a quality-of-life detail that compounds over 6,000 tab opens per year.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Open Source Advantage
&lt;/h2&gt;

&lt;p&gt;For a page you see this often, you should be able to trust it completely.&lt;/p&gt;

&lt;p&gt;Open source extensions let you inspect exactly what code runs every time you open a new tab. No analytics calls. No ad networks. No mystery functions. The &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;source code&lt;/a&gt; is readable by anyone.&lt;/p&gt;

&lt;p&gt;Closed-source new tab extensions have a long history of being sold to data brokers or having tracking code injected post-acquisition. This isn't paranoia — it's documented history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;The new tab page is the most-seen surface in your browser. Treat it with intention:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Remove what doesn't serve you&lt;/strong&gt; — no inspirational noise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add ambient information&lt;/strong&gt; — time, weather, timezone awareness&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimize decision demand&lt;/strong&gt; — no tasks, no goals, no feeds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify what runs&lt;/strong&gt; — open source, or nothing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your new tab page is real estate. Stop leaving it empty or cluttered.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; is available for Firefox. Open source, privacy-first, no account required.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>firefox</category>
      <category>webextensions</category>
      <category>ux</category>
    </item>
    <item>
      <title>Why Your Browser Extension Doesn't Need an Account (And Why That Matters)</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 20:53:35 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/why-your-browser-extension-doesnt-need-an-account-and-why-that-matters-16nl</link>
      <guid>https://dev.to/weatherclockdash/why-your-browser-extension-doesnt-need-an-account-and-why-that-matters-16nl</guid>
      <description>&lt;h1&gt;
  
  
  Why Your Browser Extension Doesn't Need an Account (And Why That Matters)
&lt;/h1&gt;

&lt;p&gt;When I built &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; for Firefox, I made a deliberate choice early on: &lt;strong&gt;no user accounts, ever&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This might seem like a limitation. In practice, it turned out to be the extension's best feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Account Trap
&lt;/h2&gt;

&lt;p&gt;Look at most popular new tab extensions and you'll find the same pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the extension&lt;/li&gt;
&lt;li&gt;Create an account to sync settings&lt;/li&gt;
&lt;li&gt;Extension features are gated by subscription tier&lt;/li&gt;
&lt;li&gt;Your browsing context is now tied to a company's server infrastructure&lt;/li&gt;
&lt;li&gt;The company pivots, gets acquired, or shuts down → your extension breaks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This happened with Momentum's aggressive upselling, with Speed Dial 2 locking basic features behind "SD2 Pro", and with various note-taking extensions that simply stopped working when the company discontinued their backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "No Account Required" Actually Means for Users
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Your data stays local
&lt;/h3&gt;

&lt;p&gt;When Weather &amp;amp; Clock Dashboard saves your preferred city or clock settings, it uses &lt;code&gt;chrome.storage.local&lt;/code&gt; (or &lt;code&gt;localStorage&lt;/code&gt; for the new tab page). That data never leaves your browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Settings save — purely local&lt;/span&gt;
&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectedCity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;temperatureUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;celsius&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clockFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;24h&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saved locally&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare this to an account-based extension where the same setting triggers an API call to &lt;code&gt;api.extension.com/users/123/preferences&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No tracking, no profiling
&lt;/h3&gt;

&lt;p&gt;With no accounts, there's no way to build a profile of what you search for, what times you open new tabs, or what cities you check weather for. I can't know this — and more importantly, I &lt;em&gt;can't sell this data&lt;/em&gt; even if someone offered me money for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Works forever, even if I disappear
&lt;/h3&gt;

&lt;p&gt;The extension is MIT licensed and open source. The weather data comes from OpenWeatherMap (a well-established provider with a generous free tier). Even if I abandon the project tomorrow, users continue using a working extension indefinitely. The only thing that would break it is if OpenWeatherMap changes their API — and even then, someone could fork the repo and update the API call.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Zero onboarding friction
&lt;/h3&gt;

&lt;p&gt;Install → open new tab → it works. No signup flow, no email verification, no "choose your username," no onboarding wizard.&lt;/p&gt;

&lt;p&gt;For an extension that should feel like a natural part of the browser, friction is the enemy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tradeoff: What You Actually Give Up
&lt;/h2&gt;

&lt;p&gt;I'm not saying account-free is always the right call. There are real tradeoffs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can't have&lt;/strong&gt;: Cross-device sync, cloud backup of settings, social features, or personalized recommendations.&lt;/p&gt;

&lt;p&gt;For a new tab extension, I made a judgment call: the overhead of syncing clock preferences across devices is not worth the privacy cost. If you reinstall Firefox or get a new laptop, you'll spend 30 seconds reconfiguring. That's fine.&lt;/p&gt;

&lt;p&gt;For a bookmark manager or reading list extension, the calculus is different — cross-device sync is core to the value proposition.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Privacy-First Approach in Practice
&lt;/h2&gt;

&lt;p&gt;Here's what the permissions section of the manifest looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"optional_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://api.openweathermap.org/*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One permission. One external host. That's the entire network surface area of the extension — and it's only for weather data that you explicitly request.&lt;/p&gt;

&lt;p&gt;No analytics. No error reporting. No "phone home" behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Good for Extension Distribution Too
&lt;/h2&gt;

&lt;p&gt;The Firefox Add-Ons (AMO) review process specifically rewards minimal permissions. Extensions with broad host permissions or tracking-adjacent behavior get extra scrutiny.&lt;/p&gt;

&lt;p&gt;With a clean, minimal permissions manifest, review goes faster and users see a reassuring "This extension requires these permissions" screen instead of an alarming one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;The browser extension ecosystem has a trust problem. Too many extensions have been caught harvesting browsing data, injecting ads, or (in extreme cases) acting as surveillance tools.&lt;/p&gt;

&lt;p&gt;Users are right to be suspicious. The solution isn't better privacy policies — it's building extensions that structurally &lt;em&gt;can't&lt;/em&gt; do these things.&lt;/p&gt;

&lt;p&gt;No accounts. No analytics. Open source. Minimal permissions.&lt;/p&gt;

&lt;p&gt;If you're building a browser extension and wondering whether you need user accounts: ask yourself what you &lt;em&gt;actually&lt;/em&gt; need them for. You might find the answer is "mostly for tracking users I want to convert to paid subscribers" — and that's a reason to reconsider the whole model.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; is available for Firefox. Open source, MIT licensed, no account required.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>firefox</category>
      <category>privacy</category>
      <category>webextensions</category>
      <category>opensource</category>
    </item>
    <item>
      <title>5 Lessons I Learned Building a Firefox New Tab Extension from Scratch</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 20:51:19 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/5-lessons-i-learned-building-a-firefox-new-tab-extension-from-scratch-1720</link>
      <guid>https://dev.to/weatherclockdash/5-lessons-i-learned-building-a-firefox-new-tab-extension-from-scratch-1720</guid>
      <description>&lt;h1&gt;
  
  
  5 Lessons I Learned Building a Firefox New Tab Extension from Scratch
&lt;/h1&gt;

&lt;p&gt;I spent the better part of a month building &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; — a Firefox new tab extension that shows live weather, world clocks, and a search bar. No frameworks, no bundlers, just pure HTML/CSS/JS.&lt;/p&gt;

&lt;p&gt;Here's what I wish someone had told me before I started.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The &lt;code&gt;manifest_version: 3&lt;/code&gt; Transition Is Mostly Smooth, But Watch for Service Worker Gotchas
&lt;/h2&gt;

&lt;p&gt;Firefox now supports Manifest V3, and I decided to go all-in. Most things just work — declarative content scripts, the new permissions model, cleaner background handling.&lt;/p&gt;

&lt;p&gt;But service workers have a critical limitation: &lt;strong&gt;they go inactive after 30 seconds&lt;/strong&gt;. For a new tab extension that needs to refresh weather data periodically, this matters.&lt;/p&gt;

&lt;p&gt;My solution: fetch weather data on each new tab open, not on a persistent background schedule. Yes, this means an API call every time you open a tab, but with proper caching (save to &lt;code&gt;localStorage&lt;/code&gt; with a timestamp, refresh only if &amp;gt; 10 minutes old), it works great and doesn't drain the battery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getWeather&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weatherCache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Use cached data&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.openweathermap.org/data/2.5/weather?...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weatherCache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. &lt;code&gt;chrome_url_overrides&lt;/code&gt; Is Still the Right Way to Override New Tab in Firefox
&lt;/h2&gt;

&lt;p&gt;Despite Firefox calling its extension API &lt;code&gt;browser.*&lt;/code&gt;, the &lt;code&gt;manifest.json&lt;/code&gt; key for overriding the new tab page is still &lt;code&gt;chrome_url_overrides&lt;/code&gt;. Not &lt;code&gt;browser_url_overrides&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chrome_url_overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"newtab"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"newtab.html"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tripped me up for an embarrassing amount of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Geolocation in an Extension Requires Extra Permission Handling
&lt;/h2&gt;

&lt;p&gt;My extension uses the user's location for weather. In a regular webpage, you'd call &lt;code&gt;navigator.geolocation.getCurrentPosition()&lt;/code&gt;. In an extension's new tab page, this works — BUT you need to handle the case where the user denies permission.&lt;/p&gt;

&lt;p&gt;More importantly, on first install, Firefox shows a permission prompt. If the user clicks "Block", you get exactly zero indication of what happened — the geolocation call silently fails.&lt;/p&gt;

&lt;p&gt;My fix: use a fallback to manual city entry if geolocation fails, with a clear UI prompt explaining what happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geolocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchWeatherByCoords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// err.code === 1: User denied&lt;/span&gt;
    &lt;span class="c1"&gt;// err.code === 2: Position unavailable  &lt;/span&gt;
    &lt;span class="c1"&gt;// err.code === 3: Timeout&lt;/span&gt;
    &lt;span class="nf"&gt;showManualCityInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; 
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Location blocked — enter your city manually&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; 
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not detect location&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maximumAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Dark Mode Detection in Extensions Is Actually Elegant
&lt;/h2&gt;

&lt;p&gt;Implementing dark/light mode switching is one place where browser extensions have an advantage over regular web apps. You get &lt;code&gt;prefers-color-scheme&lt;/code&gt; support out of the box, plus the option to persist user preference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Auto-detect system preference&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefersDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Apply theme&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dark&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dark&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Listen for system changes&lt;/span&gt;
&lt;span class="nx"&gt;prefersDark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;applyTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// On load: check user preference first, then system&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;savedTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;savedTheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;applyTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;savedTheme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;applyTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefersDark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users get the "just works" experience (matches their OS setting) with the option to override.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. AMO Review Is Actually Quite Fast (and Human)
&lt;/h2&gt;

&lt;p&gt;I was dreading the Firefox Add-On (AMO) review process, expecting a multi-week wait. My experience was surprisingly good:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Initial review&lt;/strong&gt;: ~3 days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer feedback&lt;/strong&gt;: Specific, actionable (they flagged one &lt;code&gt;eval()&lt;/code&gt; call I hadn't noticed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-up review&lt;/strong&gt;: ~1 day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reviewer actually looked at the code. They pointed out that I was using &lt;code&gt;innerHTML&lt;/code&gt; to set a clock time value (unnecessary DOM risk even though the data was internal), and asked me to switch to &lt;code&gt;textContent&lt;/code&gt;. Fair point.&lt;/p&gt;

&lt;p&gt;Things that help get through review faster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write clean, readable code&lt;/li&gt;
&lt;li&gt;Minimize permissions to exactly what you need&lt;/li&gt;
&lt;li&gt;No remote code execution&lt;/li&gt;
&lt;li&gt;No obfuscation&lt;/li&gt;
&lt;li&gt;Include source maps if you minify&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;After all this, I shipped &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Weather &amp;amp; Clock Dashboard&lt;/a&gt; — it's open source (MIT), no account required, and genuinely useful.&lt;/p&gt;

&lt;p&gt;If you're building a Firefox extension, the developer experience is actually really solid. The API documentation has improved dramatically in the past few years, and the Mozilla developer community is helpful.&lt;/p&gt;

&lt;p&gt;Have questions? Drop them in the comments — happy to share more specifics about the implementation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with: Pure HTML/CSS/JS · No build step · MIT Licensed&lt;/em&gt;&lt;/p&gt;

</description>
      <category>firefox</category>
      <category>webextensions</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Compared 8 Firefox New Tab Extensions So You Don't Have To</title>
      <dc:creator>Weather Clock Dash</dc:creator>
      <pubDate>Sun, 03 May 2026 20:38:32 +0000</pubDate>
      <link>https://dev.to/weatherclockdash/i-compared-8-firefox-new-tab-extensions-so-you-dont-have-to-44b6</link>
      <guid>https://dev.to/weatherclockdash/i-compared-8-firefox-new-tab-extensions-so-you-dont-have-to-44b6</guid>
      <description>&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;The new tab page is the most-opened page in your browser. If you open 20 tabs a day, that's 7,300 times per year you interact with it. Getting it right matters.&lt;/p&gt;

&lt;p&gt;I spent a week testing 8 popular Firefox new tab extensions. Here's what I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Evaluated
&lt;/h2&gt;

&lt;p&gt;For each extension, I checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: How fast does it load on a fresh tab?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: What data does it collect? What permissions does it need?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Features&lt;/strong&gt;: Does it do what it claims?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;: Does it break? Does weather update correctly?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle size&lt;/strong&gt;: How heavy is it?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Extensions Tested
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Momentum (proprietary, freemium)&lt;/li&gt;
&lt;li&gt;Infinity New Tab (proprietary, freemium)&lt;/li&gt;
&lt;li&gt;New Tab by Tabliss (open source)&lt;/li&gt;
&lt;li&gt;Nighttab (open source)&lt;/li&gt;
&lt;li&gt;New Tab Redirect (simple, open source)&lt;/li&gt;
&lt;li&gt;iChrome (discontinued)&lt;/li&gt;
&lt;li&gt;Speed Dial (commercial)&lt;/li&gt;
&lt;li&gt;Weather &amp;amp; Clock Dashboard (open source, MIT)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Results Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Privacy Score
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Extension&lt;/th&gt;
&lt;th&gt;Permissions Required&lt;/th&gt;
&lt;th&gt;Data Collection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Momentum&lt;/td&gt;
&lt;td&gt;tabs, history, bookmarks&lt;/td&gt;
&lt;td&gt;Yes, account-linked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infinity&lt;/td&gt;
&lt;td&gt;tabs, history&lt;/td&gt;
&lt;td&gt;Yes, account-linked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tabliss&lt;/td&gt;
&lt;td&gt;storage only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nighttab&lt;/td&gt;
&lt;td&gt;storage only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weather &amp;amp; Clock&lt;/td&gt;
&lt;td&gt;storage, optional geolocation&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed Dial&lt;/td&gt;
&lt;td&gt;bookmarks, sessions&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The clear winners on privacy: &lt;strong&gt;Tabliss&lt;/strong&gt;, &lt;strong&gt;Nighttab&lt;/strong&gt;, and &lt;strong&gt;Weather &amp;amp; Clock Dashboard&lt;/strong&gt; — no accounts required, no data sent to servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed (Time to First Meaningful Paint)
&lt;/h3&gt;

&lt;p&gt;Tested on a standard laptop, WiFi, 50 tabs already open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tabliss&lt;/strong&gt;: ~50ms (fastest — very minimal)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nighttab&lt;/strong&gt;: ~80ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather &amp;amp; Clock&lt;/strong&gt;: ~90ms (with cached weather)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed Dial&lt;/strong&gt;: ~150ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Momentum&lt;/strong&gt;: ~400ms (loads image from CDN)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infinity&lt;/strong&gt;: ~500ms (heavy JS bundle)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Extensions that load images from remote URLs are inherently slower because they depend on network requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Momentum&lt;/th&gt;
&lt;th&gt;Tabliss&lt;/th&gt;
&lt;th&gt;Weather &amp;amp; Clock&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clock&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓ (world clocks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weather&lt;/td&gt;
&lt;td&gt;Paid&lt;/td&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;✓ (free)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search bar&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background image&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dark mode&lt;/td&gt;
&lt;td&gt;Paid&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No account needed&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓ (MIT)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  My Recommendations By Use Case
&lt;/h2&gt;

&lt;h3&gt;
  
  
  If You Want Maximum Simplicity
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Tabliss&lt;/strong&gt;. Minimal, fast, open source. No weather, but if you don't need it, it's excellent.&lt;/p&gt;

&lt;h3&gt;
  
  
  If You Want Weather + Clock
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Weather &amp;amp; Clock Dashboard&lt;/strong&gt;. Free, no account, privacy-friendly. Has the features you need without the bloat. &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/weather-clock-dashboard/" rel="noopener noreferrer"&gt;Install it here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  If You Want Beautiful Backgrounds
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Momentum&lt;/strong&gt; (paid tier) or &lt;strong&gt;Infinity&lt;/strong&gt; — but be aware they both require accounts and collect data.&lt;/p&gt;

&lt;h3&gt;
  
  
  If You Just Want Bookmarks on the New Tab
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Speed Dial&lt;/strong&gt; — well-designed bookmark management.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Wish More Extensions Would Do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ship without requiring an account&lt;/strong&gt;. The new tab is local — there's no reason to force a sign-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache weather data properly&lt;/strong&gt;. Fetching on every single tab open is wasteful and slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List permissions and explain them&lt;/strong&gt;. Most extensions ask for permissions without explaining why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go MIT or Apache licensed&lt;/strong&gt;. Proprietary new tab extensions can be discontinued without warning (see: iChrome, which left thousands of users without their setup).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For most users, a good new tab extension should be fast, private, and do its job without fuss. You don't need a $3/month subscription for a clock and weather widget.&lt;/p&gt;

&lt;p&gt;The open source options — especially Tabliss, Nighttab, and Weather &amp;amp; Clock Dashboard — are better than the commercial alternatives for most people.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have a favorite new tab extension I missed? Drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>firefox</category>
      <category>productivity</category>
      <category>tools</category>
      <category>review</category>
    </item>
  </channel>
</rss>
