How Long Does a Chrome Extension Update Take to Reach Users?
6 min readModerok team
Why a published Chrome extension update takes hours or days to reach users, covering Web Store review, percentage rollout, update checks, and the idle rule.
There is no single number for how long a Chrome extension update takes to reach users, because the version you published passes through four independent gates before anyone runs it: Chrome Web Store review, your rollout percentage, Chrome's periodic update check, and the rule that an update is only installed when the extension is idle. Review is the step with the widest variance, and the idle rule is the one that creates the long tail. Expect most active profiles to pick up a new version within a day or two of it going live, and expect a stubborn minority to stay on the old version until they restart the browser.
Gate 1: Chrome Web Store review
Nothing happens until the item is approved. Google's published figures are that in early 2021 most submissions completed review in less than 24 hours, with over 90% completed within three days, and the same doc tells you to contact developer support if an item has been pending for more than three weeks. Plan for the median, but do not schedule a launch around it.
All submissions go through the same review system regardless of developer tenure or user count, but some signals cause a reviewer to examine an extension more closely, including dangerous permission requests. Adding a broad permission to an otherwise routine release is a good way to turn a one day review into a longer one.
One dashboard option is worth knowing: from the three dot menu on the submission you can Defer publish, having the item reviewed now and published later. Once approved, you have up to 30 days to publish it.
Gate 2: Percentage rollout
If your item has more than 10,000 seven day active users, the Distribution tab exposes a Percentage rollout option that limits a new version to a fraction of your user base. After the version is live you can raise that percentage without resubmitting for review, up to 100%.
This is the one gate you actually control, and it is worth using for anything touching the service worker or storage: shipping to 10% first means a crash in the new worker costs you a tenth of your users instead of all of them. Below 10,000 seven day active users the option is not available, so a small extension always ships to everyone at once.
If a release does go wrong, Roll back to previous version (three dot menu on the listing, or Build then Package) republishes the previous version under a new, higher version number you supply. That code already passed review, so the rollback skips the queue and goes live within a minute. It only goes back one version, and it only fixes what the store serves: users who already took the bad version still have to clear the gates below.
Gate 3: Chrome's update check
Once a version is live for a user, Chrome still has to notice. Per Chrome's documentation, the browser checks for extension updates on startup and every few hours. The docs do not promise an exact interval, so treat "hours, not minutes" as the contract.
To force a check on your own machine, open chrome://extensions, turn on Developer mode, and click Update. That immediately fetches the latest version of every installed extension, which is the fastest way to confirm a release actually shipped.
Inside your extension you can trigger the same check with chrome.runtime.requestUpdateCheck():
const result = await chrome.runtime.requestUpdateCheck();
// result.status is "throttled" | "no_update" | "update_available"
// result.version is the available version when status is "update_available"
if (result.status === "update_available") {
console.log(`Update ${result.version} is ready`);
}
Frequent calls get a "throttled" status back, and Chrome's own docs say most extensions should not use this method at all, since the browser already checks periodically and you can listen for chrome.runtime.onUpdateAvailable instead. The circumstance the reference carves out is narrow: your extension talks to a backend, the backend has determined this client is very far out of date, and you want to prompt the user. Call it only when you already know an update exists, not to poll speculatively.
Gate 4: The idle rule (the real long tail)
An update is only installed when the extension is considered idle. Open extension surfaces block that: a side panel, an open popup, or an options page all keep the extension in active use. And in Manifest V3 there is a sharper version of the problem. If your service worker is constantly being woken by events, it may never reach an idle state at all, in which case the update is deferred until the browser is restarted.
Extensions that poll on a short chrome.alarms interval, listen to chrome.tabs events on every navigation, or hold a long lived message port open are exactly the ones that stay busy. The design that keeps a worker responsive also keeps it from being idle when an update is waiting.
The practical takeaway: a user who never quits Chrome may sit on an old version indefinitely. That is not a bug in your release, and no amount of republishing will fix it.
Deferring an update on purpose
Sometimes you want the opposite: to control when an update lands. chrome.runtime.onUpdateAvailable fires when an update has been downloaded and is ready to install. The details argument carries the manifest of the pending update, so details.version tells you which version is waiting.
chrome.runtime.onUpdateAvailable.addListener((details) => {
console.log(`Version ${details.version} downloaded and waiting`);
});
Chrome's reference for the event is explicit about what happens next: if you do nothing, the update is installed the next time the background context is unloaded, and if you want it installed sooner you can call chrome.runtime.reload() yourself. Under MV3 that first case is the idle teardown from Gate 4, which is another way of saying a permanently busy worker delays its own update. Use the event when you need the swap to happen at a moment you choose:
chrome.runtime.onUpdateAvailable.addListener(async () => {
if (await workInProgress()) return; // let it apply on the next idle teardown
chrome.runtime.reload(); // safe to swap versions now
});
When the update does land
Applying an update tears down the old extension context. The service worker is stopped and restarted on the new version, and chrome.runtime.onInstalled fires with reason: "update" plus a previousVersion. Content scripts already injected into open tabs are orphaned: they keep running against a dead context and throw "Extension context invalidated" on the next API call. If your extension injects content scripts, handle that explicitly, as covered in Extension context invalidated.
Firefox runs on a different clock
Firefox checks for add-on updates on its own schedule, controlled by the extensions.update.interval preference, which defaults to 86400 seconds (24 hours). Ship the same codebase to both stores and your Firefox rollout curve will look flatter and slower than Chrome's for that reason alone, before AMO review times are counted.
Measuring how long your update takes to reach users
The Web Store dashboard gives you part of this: the Weekly Users page breaks weekly user retention out by country, language, operating system, and item version, so you can see roughly how installs are distributed across versions. What it cannot tell you is who is actually using each version, because those stats capture installations rather than activity. A profile that installed six months ago and has not opened the extension since still counts, a distinction we covered in Weekly Users vs. Installs.
For a rollout curve measured in active users, the extension has to report it. The mechanics are in our post on detecting install vs. update with chrome.runtime.onInstalled.
If you use Moderok, that part is automatic. The SDK registers chrome.runtime.onInstalled synchronously inside init(), sends an __update event carrying previousVersion when Chrome provides it, and stamps every event with the extension version it came from, read from chrome.runtime.getManifest().version. It also sends a __daily_ping at most once per UTC day per user, so "active users on each version, day by day" becomes a query rather than a guess.
import { Moderok } from "@moderok/sdk";
Moderok.init({ appKey: "mk_your_app_key" });
The SDK is 5.7 kB gzipped with zero dependencies and no host_permissions. If you want to watch a release actually propagate instead of estimating it, read the automatic events guide and drop init() into your service worker before your next submission.