How Chrome extension updates actually work
Chrome checks for extension updates roughly every 5 hours. When it finds a new version in the Chrome Web Store, it downloads and installs it silently in the background. The user never sees a prompt — unless you changed permissions.
That "unless" is where most problems start. If your new version requests permissions or host access that the previous version did not have, Chrome will:
- Disable the extension immediately
- Show a badge on the puzzle icon in the toolbar
- Wait for the user to manually click "Re-enable" and accept the new permissions
Until the user acts, they are stuck on the old version with the extension disabled. For a business-critical tool like an ads management extension, this is unacceptable.
1. Scope your host_permissions tightly
The host_permissions field in manifest.json is the number one cause of update friction. Every new origin you add triggers the re-consent flow.
Bad — too broad
"host_permissions": [
"<all_urls>"
]This requests access to every website. It also means any future narrowing of scope will never trigger a re-consent (you started at the maximum), but Google's review will be slower and users may not install at all.
Good — specific origins
"host_permissions": [
"https://ads.allegro.pl/*",
"https://api.example.com/*"
]Only the origins you actually need. If you later need https://analytics.allegro.pl/*, that will trigger a re-consent — but you can plan for that.
Best — plan ahead
"host_permissions": [
"https://*.allegro.pl/*",
"https://api.example.com/*"
]Use a wildcard subdomain for platforms where you know you will need multiple subdomains. Adding analytics.allegro.pl later will not trigger re-consent because it is already covered by *.allegro.pl.
2. Content scripts and match patterns
Content scripts have their own matches field that determines which pages they run on. Changing these patterns does not trigger a permission re-consent — but getting them wrong still delays your updates in other ways.
Match patterns that cause problems
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"]
}]Running on every page triggers a slower review from Google. It also increases the chance of conflicts with other extensions and makes your extension look suspicious to users.
Match patterns done right
"content_scripts": [{
"matches": [
"https://ads.allegro.pl/*",
"https://allegro.pl/ads/*"
],
"js": ["content.js"],
"run_at": "document_idle"
}]Specific match patterns. Google reviews these faster because they can verify your extension only touches the pages it claims to need.
Use run_at wisely
The run_at field does not affect update speed directly, but it matters for user experience after an update:
document_idle(default) — safest, runs after the page is loadeddocument_end— runs after DOM is ready but before subresourcesdocument_start— runs before anything else, can slow down the page
Use document_idle unless you have a specific reason not to. Extensions that slow down page loads get lower ratings and more uninstalls.
3. The permissions field — plan your API surface upfront
The permissions array (separate from host_permissions) controls access to Chrome APIs like storage, tabs,alarms, and notifications. Adding a new permission here also triggers re-consent.
Practical advice
- Request everything you will need in v1. If you know you will add notifications in a month, add
notificationsto permissions now. An unused permission is better than a re-consent that disables your extension. - Use
optional_permissionsfor features you might not ship. These are requested at runtime viachrome.permissions.request()and never trigger a re-consent on update. - Never remove and re-add a permission. Removing
tabsin v2 and adding it back in v3 will trigger re-consent in v3.
optional_permissions example
{
"permissions": ["storage", "alarms"],
"optional_permissions": ["notifications", "downloads"],
"optional_host_permissions": ["https://analytics.example.com/*"]
}Users grant optional permissions when they first use a feature that needs them. No re-consent on update, no disabled extension, no lost users.
4. Notify users in real time with version checking
Even if you do everything right with permissions and match patterns, Chrome's ~5-hour update cycle means users can be running stale code for hours. For critical bugfixes or features, that is too slow.
The solution: check the published version from inside your extension and prompt the user to update when a new version is available.
Why not a GitHub Action?
You might think CI/CD is the right place for version checking. We have a GitHub Action for permission escalation detection and it works great for catching problems before they ship. But for notifying end users about available updates, CI/CD does not help:
- A GitHub Action runs in your pipeline, not in the user's browser
- It burns CI minutes on every check
- It cannot tell the user "hey, click Update now"
What you need is a lightweight API that your extension can call at runtime to compare its own version against the version published in the Chrome Web Store.
Chrome Extension Version API
We built and open-sourced chrome-extension-version-api — a single-endpoint .NET service that queries the Chrome Web Store for the published version of any extension. It runs as a Docker container with built-in response caching.
API usage
GET /check-published-extension-version/{extensionId}
// Response (200 OK)
{
"version": "1.23.4",
"cached": false,
"checkedAt": "2026-03-16T12:00:00Z"
}Deploy with Docker
docker run -p 8080:8080 ghcr.io/toumash/chrome-extension-version-api:latestDeploy it behind your existing ingress or reverse proxy. It needs no database, no configuration, and barely any resources. Set CacheTtlMinutes to control how often it actually hits the Chrome Web Store (default: 5 minutes).
Extension-side implementation
In your content script — the code that runs on the page your users are already working on — check the published version and show an update notification directly in the UI:
// content.js — runs on the page the user is actively using
const EXTENSION_ID = chrome.runtime.id;
const VERSION_API = 'https://your-api.example.com';
async function checkForUpdate() {
const localVersion = chrome.runtime.getManifest().version;
const res = await fetch(
`${VERSION_API}/check-published-extension-version/${EXTENSION_ID}`
);
const { version: storeVersion } = await res.json();
if (storeVersion && storeVersion !== localVersion) {
// Show an update banner on the page the user is working on
const banner = document.createElement('div');
banner.textContent = `Update available (v${storeVersion}). Please reload.`;
banner.style.cssText =
'position:fixed;bottom:16px;right:16px;padding:12px 20px;' +
'background:#f97316;color:#fff;border-radius:8px;z-index:99999;' +
'font-family:sans-serif;cursor:pointer;';
banner.onclick = () => location.reload();
document.body.appendChild(banner);
}
}
// Check on page load
checkForUpdate();The user sees a "NEW" badge on your extension icon and knows to update. You can also show a banner inside your popup or content script UI.
Why this beats waiting for Chrome
~5h
Chrome's default update check interval
30min
Your version API check interval
0
CI minutes burned
FAQ
How often does Chrome check for extension updates?
Approximately every 5 hours, but the actual interval varies. Chrome may batch update checks and network conditions can add delay. You cannot control this interval from your extension code, but you can build your own version-checking mechanism (see section 4 above) to notify users faster.
Can I force a Chrome extension to update immediately?
Users can go to chrome://extensions, enable Developer Mode, and click "Update". For a better approach, use a version-checking API to detect new versions and notify users with a badge or in-app prompt.
Do permission changes slow down updates?
Yes. If your new version adds permissions or host_permissions that the previous version did not have, Chrome disables the extension and shows a re-consent dialog. The extension stays disabled until the user manually approves.
Why not just use <all_urls> to avoid future re-consent?
While <all_urls> avoids future re-consent for host permissions, it triggers a slower Chrome Web Store review, scares users during installation, and may get your extension flagged. Use wildcard subdomains (e.g. *.example.com) for the platforms you target instead.
Related articles
How to Build Chrome Extensions with React, Vite & CRXJS
A practical guide to modern extension development
Chrome Extension Permission Check
GitHub Action to detect MV3 permission escalations in CI
CI/CD for Browser Extensions
How CI/CD in CRXJS unlocked Windows development
CRXJS vs Plasmo vs WXT
Choosing the right Chrome extension framework in 2026
Need help shipping updates faster?
We have shipped 100+ extension versions and built the tooling to make updates frictionless. Whether you need help with manifest configuration, CI/CD, or real-time version checking, we can help.
