- Releases
- Competitors
- Chrome Web Store
Chrome extension version history: how to see past versions, compare updates and get alerted
The Chrome Web Store shows only the current version. How to build a Chrome extension version history, diff each update's permissions and get alerted.
- Author
- Extenify Team
- Published
- Reading time
- 14 min read

On this page
Looking for a Chrome extension version history usually starts with a practical question. A tool you rely on changed overnight and you want to know what changed. A competitor shipped something and you want to know when. Or your own users say "it broke after the update" and you need to know which update they mean. The Chrome Web Store does not answer any of these well: a listing shows the current version and the date of the last update, and nothing before that.
This guide explains where version information does exist, why you cannot simply install an older version from the store, and how to keep your own version history for any extension with about 80 lines of Node.js: a daily check, a download only when the version changes, and a diff of the permissions and site access each update adds. It also covers Firefox, which publishes the full history through an official API, and the version adoption view for your own extension.
Key takeaways
- The Chrome Web Store shows only the current version and its "Updated" date. There is no public archive of older versions, and no official API for version history.
- Chrome's update service tells you which version it serves today and can return the package itself. Checked once a day, that is enough to build your own history going forward.
- The useful part of a version history is the diff: new permissions, new host access and new content script matches are the changes worth an alert.
- Firefox is different. The addons.mozilla.org API lists every public version with its release notes, permissions and a download link.
- For your own extension, the Developer Dashboard groups users by item version, and the dashboard can roll back to the previously published version within a minute.
Does the Chrome Web Store keep a Chrome extension version history?
Not publicly. Each listing has a details section with Version and Updated, and that is the whole history the store shows. Release notes appear only if the developer writes them into the description. People have asked for an archive for years: the Stack Overflow question "Is there an archive for older versions of Chrome Extensions?" has more than 64,000 views, and the answer has not changed.
Here is where version information does exist, and who can see it:
| Source | Whose extensions | What it shows | History? |
|---|---|---|---|
| Chrome Web Store listing | Any | Current version and the date of the last update | No, today only |
chrome://extensions |
What you have installed | The installed version | No |
| Chrome's update service | Any public item | The version served today, its package size and a download | No, today only |
| Developer Dashboard, Package page | Yours | The published package and pending drafts | Partial, previous version via rollback |
| Developer Dashboard, Users report | Yours | Users grouped by item version | Yes, as adoption over time |
| Developer's changelog or GitHub releases | Depends on the developer | Release notes | Yes, if they keep one |
| addons.mozilla.org API | Any Firefox add-on | Every public version, notes, permissions, download | Yes, complete |
The pattern is clear. For Chrome, nobody keeps a public history for you, so if you want one, you have to record it as it happens. That is the same lesson as with users and ratings: our guide to the Chrome Web Store stats API shows that every number on a listing is a snapshot, and history only exists if someone stores it.
Check which version you have installed
Before tracking anything, confirm what is on your own machine. Open chrome://extensions, find the extension, and click Details: the version is listed there. To pull updates right away instead of waiting for Chrome's periodic check, turn on Developer mode in the top right corner and click Update. That asks for updates for every installed extension at once.
Chrome checks for extension updates on its own every few hours, so most users get a new version within a day of it going live. If the developer used a percentage rollout, you might not get it at all for a while, even after clicking Update. Our post on Chrome Web Store review time explains the gap between "approved" and "installed" in more detail.
Can you install an older version of a Chrome extension?
Not from the store. The store serves one version per item, and the update service only ever hands out that version. The options that remain each come with a catch:
- Third-party CRX mirrors. Sites that host old packages exist, but you cannot verify what you download. A modified package with the same name is exactly how malware spreads, so do not install old builds from mirrors on a machine that matters.
- The developer's own releases. Open source extensions often publish every build on GitHub. You can load an older build with Load unpacked in developer mode. It will not auto-update, and Chrome shows it as a developer-mode extension, so treat it as a temporary fix.
- Version pinning for managed devices. Google Workspace and Chrome Enterprise admins can pin a force-installed app or extension to a version. The help page lists ChromeOS 89 or later for users and browsers, and you can only pin a version that is currently available in an organizational unit or the latest store version, so it holds a version back rather than bringing an old one back.
- Rollback, if it is your extension. The Developer Dashboard can roll back to the previously published version. You enter a new, higher version number and a reason, no review is needed, and "the previous version becomes live on the Web Store within a minute." A rollback always switches to the previously published version, and it discards pending submissions and partial rollouts.
So for anyone other than the developer, the realistic goal is not to go back. It is to know what changed and when, which is what a version history gives you.
Build your own Chrome extension version history
The plan is simple: once a day, ask Chrome which version it serves for each extension you care about. When the version changes, download the new package, read its manifest and compare it with the last one you stored. The code below runs on Node.js 22 or later with no dependencies.
Two rules before you start. Google's terms forbid automated access that ignores machine-readable instructions such as robots.txt, so keep this to a single request per extension per day, identify your client, and space the requests out. And treat the endpoint as what it is: the service Chrome uses for its own updates, which works but is not documented as a public API.
Step 1: Ask the update service for the current version
The update service answers with a few hundred bytes of XML. If it serves the item, the updatecheck element has status="ok" and a version attribute:
// latest-version.mjs: ask Chrome's update service which version it serves today.
export async function latestVersion(id) {
const x = encodeURIComponent(`id=${id}&uc`);
const url =
'https://clients2.google.com/service/update2/crx' +
`?response=updatecheck&acceptformat=crx3&prodversion=141.0.0.0&x=${x}`;
const res = await fetch(url, { headers: { 'user-agent': 'acme-version-log/1.0' } });
const check = (await res.text()).match(/<updatecheck\b[^>]*>/)?.[0] ?? '';
if (!check.includes('status="ok"')) return null; // removed, private or unknown id
return check.match(/version="([^"]+)"/)?.[1] ?? null;
}
For Dark Reader (eimadpbcbfnmbkopoojfekhnkhdbieeh) this returned 4.9.133 on October 7, 2026. For the original uBlock Origin, one of the Manifest V2 extensions that is no longer served, it returned null. That null is a signal in itself: an extension you track that stops being served was removed, unpublished or taken down.
Step 2: Download the package and read its manifest
With response=redirect instead of response=updatecheck, the same URL redirects to the CRX file. A CRX3 file is a small binary header followed by an ordinary ZIP archive. Skip the header and you can read manifest.json with Node's built-in zlib:
// crx-manifest.mjs: download the CRX Chrome serves today and read its manifest.json.
import { inflateRawSync } from 'node:zlib';
export async function downloadCrx(id) {
const x = encodeURIComponent(`id=${id}&uc`);
const url =
'https://clients2.google.com/service/update2/crx' +
`?response=redirect&acceptformat=crx3&prodversion=141.0.0.0&x=${x}`;
const res = await fetch(url, { headers: { 'user-agent': 'acme-version-log/1.0' } });
if (!res.ok) throw new Error(`HTTP ${res.status} for ${id}`);
return Buffer.from(await res.arrayBuffer());
}
// A CRX3 file is "Cr24", a format version, a header length, the signed header, then a ZIP.
export function crxToZip(crx) {
if (crx.toString('latin1', 0, 4) !== 'Cr24') throw new Error('not a CRX file');
const headerSize = crx.readUInt32LE(8);
return crx.subarray(12 + headerSize);
}
// Find one file in the ZIP's central directory and inflate it.
export function readZipEntry(zip, name) {
const eocd = zip.lastIndexOf(Buffer.from([0x50, 0x4b, 0x05, 0x06]));
if (eocd < 0) throw new Error('no ZIP directory');
let p = zip.readUInt32LE(eocd + 16);
const count = zip.readUInt16LE(eocd + 10);
for (let i = 0; i < count; i++) {
const method = zip.readUInt16LE(p + 10);
const size = zip.readUInt32LE(p + 20);
const nameLen = zip.readUInt16LE(p + 28);
const extraLen = zip.readUInt16LE(p + 30);
const commentLen = zip.readUInt16LE(p + 32);
const local = zip.readUInt32LE(p + 42);
if (zip.toString('utf8', p + 46, p + 46 + nameLen) === name) {
const start = local + 30 + zip.readUInt16LE(local + 26) + zip.readUInt16LE(local + 28);
const data = zip.subarray(start, start + size);
return method === 8 ? inflateRawSync(data) : data;
}
p += 46 + nameLen + extraLen + commentLen;
}
throw new Error(`${name} not found`);
}
export async function fetchManifest(id) {
const zip = crxToZip(await downloadCrx(id));
const text = readZipEntry(zip, 'manifest.json').toString('utf8');
return JSON.parse(text.replace(/^/, '')); // some packages start with a BOM
}
If you prefer the command line, unzip also opens a CRX file directly. It prints a warning about extra bytes at the beginning of the file (the CRX header) and exits with status 1, but it extracts everything. Keep that in mind if a script checks exit codes. The Stack Overflow thread on CRX_HEADER_INVALID is a good reminder of the other direction: a CRX you repackaged yourself will not install from outside the store.
Step 3: Diff what each update can access
A version number alone tells you that something changed. The manifest tells you whether it matters. Three fields decide what an extension can do, and an update that widens any of them deserves a closer look:
permissions: API access such astabs,cookies,historyorscripting.host_permissions: which sites it can read and change, from one domain to<all_urls>.content_scripts[].matches: pages where its scripts run automatically.
This script ties the steps together. It stores one entry per new version in a JSON file and prints what each update added or dropped:
// version-log.mjs: run once a day. Keeps a version history per extension in history.json.
import { readFile, writeFile } from 'node:fs/promises';
import { fetchManifest } from './crx-manifest.mjs';
import { latestVersion } from './latest-version.mjs';
const ITEMS = ['eimadpbcbfnmbkopoojfekhnkhdbieeh', 'ddkjiahejlhfcafbddmgiahcphecmpfh'];
const FILE = 'history.json';
const history = JSON.parse(await readFile(FILE, 'utf8').catch(() => '{}'));
// Everything an update can widen: API permissions, site access and content script reach.
function access(m) {
return new Set([
...(m.permissions ?? []),
...(m.host_permissions ?? []),
...(m.content_scripts ?? []).flatMap((s) => s.matches ?? []),
]);
}
for (const id of ITEMS) {
try {
const entries = (history[id] ??= []);
const last = entries.at(-1);
const version = await latestVersion(id);
if (!version) {
console.log(`${id}: not served any more (removed or unpublished?)`);
continue;
}
if (version === last?.version) continue; // nothing new, no download
const manifest = await fetchManifest(id);
const now = [...access(manifest)].sort();
const before = new Set(last?.access ?? []);
const added = last ? now.filter((a) => !before.has(a)) : [];
const removed = last ? [...before].filter((a) => !now.includes(a)) : [];
entries.push({ version: manifest.version, seenAt: new Date().toISOString(), access: now });
if (last) {
console.log(`${id}: ${last.version} -> ${manifest.version}`);
if (added.length) console.log(` new access: ${added.join(', ')}`);
if (removed.length) console.log(` dropped: ${removed.join(', ')}`);
}
} catch (error) {
console.error(`${id}: ${error.message}`); // try again tomorrow
}
await new Promise((resolve) => setTimeout(resolve, 5_000));
}
await writeFile(FILE, JSON.stringify(history, null, 2));
The first run records a baseline and prints nothing. After that, a run prints output only when a version changed, for example:
eimadpbcbfnmbkopoojfekhnkhdbieeh: 4.9.131 -> 4.9.133
new access: fontSettings
Because the cheap version check runs first, the package is downloaded only on the day a new version appears. That keeps the daily cost to one small request per extension.
Step 4: Decide what deserves an alert
Not every new version is news. A sensible split:
- Alert the same day when an update adds a permission, adds host access (especially
<all_urls>or*://*/*), adds content script matches, or when an extension stops being served at all. - Put in a weekly digest any other version change, with the package size so a large jump stands out.
- Ignore a version that flips back and forth over a few days. During a percentage rollout, the update service can serve either the old or the new version depending on the client, so confirm a change on two consecutive days before reporting it.
Our guide to store change alerts your team will actually read covers how to route same-day alerts and digests without training people to ignore them. If you track competitors, the same history also answers "how often do they ship?", which is a useful number to set next to your own release rhythm.
To keep the history in a database rather than a JSON file, store one document per extension and version with a unique index on both, so a rerun never duplicates an entry. The snapshot pattern in the stats API guide works the same way.
Firefox publishes the full version history
If an extension is also on Firefox, you get far more for free. The addons.mozilla.org API has a documented version list endpoint, GET /api/v5/addons/addon/<id or slug>/versions/, that returns every public version with its review date, release notes, permission lists and a download URL:
curl -s "https://addons.mozilla.org/api/v5/addons/addon/darkreader/versions/?page_size=3" \
| jq '.count, (.results[] | {version, reviewed, permissions: .file.permissions, notes: .release_notes["en-US"]})'
On October 7, 2026, that reported 181 public versions of Dark Reader, with 4.9.133 reviewed on September 24, 2026 and its release notes starting with "Fixed HTTP 431 error on Salesforce." No API key is needed for public add-ons. The same data appears on each add-on's version history page on the AMO website. Our guide to Firefox add-on stats covers the rest of what AMO publishes.
This makes Firefox a useful backfill: if an extension ships the same code to both stores, its Firefox history often tells you what each Chrome version contained. Version numbers and timing can still differ between stores, which is why tracking one extension across Chrome, Edge and Firefox shows each store's release line separately.
For your own extension: version adoption
When you are the developer, the question shifts from "what changed?" to "who is on which version?". Three tools answer it:
- Users by item version. In the Developer Dashboard, the Users report can be grouped by "country, language, operating system, and item version", according to Chrome's store listing metrics guide, and every report exports as CSV. After a release, watch the new version's share climb. A version that stays stuck at a small share points at a percentage rollout you forgot, or at a permission prompt users are not accepting.
runtime.onInstalled. When the reason is"update", the event includespreviousVersion, so your extension can record "updated from 4.9.131 to 4.9.133" in its own telemetry and you can tie error rates and support tickets to a version.runtime.onUpdateAvailable. An update waits until the service worker is idle and unloads. If your extension keeps it alive, callchrome.runtime.reload()at a safe moment so the update applies. The runtime API reference also documentsrequestUpdateCheck(), with a clear warning that most extensions should not use it.
Write release notes somewhere users can find them, too. A short "What's new" section at the top of your store description, or a changelog page on your site, turns "it broke after the update" reviews into "it broke in 4.9.133" reports. The review analysis guide shows how to group reviews by version so you can see which release started the complaints.
Or let Extenify keep the history
If you would rather not run a script for every extension you care about, Extenify's extension version tracker records every published version with its size and date on the Chrome Web Store, Edge Add-ons and Firefox Add-ons. It shows them on one timeline next to users, ratings and category rank, flags a store that is left behind on an older build, and includes new versions in the daily alert to email, Slack, Telegram, Discord or Teams. It works for your own extensions and for competitors, because it reads public listing data only. Tracking one extension is free, and the Extenify pricing page lists the plans for more extensions and longer history. If you are still deciding how to measure your extension overall, our comparison of Chrome extension analytics tools shows where version tracking fits next to in-extension analytics.
Frequently asked questions
Is there an archive of older Chrome extension versions?
Not an official one. The Chrome Web Store serves only the current version, and Google does not publish older packages. Some developers publish every build on GitHub, and Firefox keeps a public version history on addons.mozilla.org. Avoid third-party CRX mirrors, because you cannot verify that a package was not modified.
How do I see when a Chrome extension was last updated?
Open its Chrome Web Store listing and look for Updated in the details section, next to Version. That date belongs to the current version only. To know when earlier versions shipped, you need a history recorded over time, either your own or a tracking tool's.
How do I force a Chrome extension to update?
Open chrome://extensions, turn on Developer mode and click Update. Chrome then checks for updates to all installed extensions. If the developer is rolling the update out to a percentage of users, you may still get the old version until the rollout reaches you.
Can I roll back my own extension to a previous version?
Yes. In the Developer Dashboard, use Roll back to previous version on the package page, enter a new version number and a reason. The previous version goes live within about a minute without review, and any pending submission is discarded, so resubmit it after you fix the problem.
How do I get notified when a Chrome extension updates?
Check the version once a day and compare it with the last one you saw, as in the script above, or use a tracker that sends version change alerts. Inside an extension you develop, runtime.onInstalled with reason "update" tells you when your own extension updated on a user's machine.
Summary
The Chrome Web Store keeps no public version history, so a Chrome extension version history is something you build. Ask Chrome's update service once a day which version it serves, download the package only when that changes, and diff its permissions, host access and content script matches against the last version you stored. Alert on wider access and on removals, digest the rest, and confirm rollouts over two days. Use the addons.mozilla.org API to fill in history for extensions that also ship on Firefox, and, for your own extension, follow version adoption in the Developer Dashboard's Users report.
Image credits
- Cover photo: Freshly cut tree trunk with growth rings by Bigul Malayi on the WordPress Photo Directory, CC0.


