runtime.onEnabled shipped in Chrome 155, onExtensionLoaded is next: how to wire both
Chrome 155 added runtime.onEnabled; runtime.onExtensionLoaded follows in 157. Which event fires when, one listener pattern for both, and the edge cases.

Chrome 155 reached the stable channel on October 6 with a new extension event, runtime.onEnabled, that fires when your own extension is switched back on. It never made Chrome's "What's new in Chrome extensions" page; on Chrome's own docs it appears only in the chrome.runtime API reference, tagged "Chrome 155+".
It slipped past me too. My first pass over Chrome 155 concluded that no extension-API changes rode the milestone, and it took a second scan two weeks later to find this one. A broader sibling, runtime.onExtensionLoaded, is already queued behind it. Here's what each one does, how to listen for both without double-initializing, and the edge cases.
The gap these events close
Before 155, an extension had two lifecycle hooks of its own. runtime.onInstalled fires on install and update. runtime.onStartup fires when a browser profile that has the extension starts. The third case — the user switches the extension off and back on while the browser keeps running — sent the extension nothing.
The nearest hook pointed the wrong way. management.onEnabled reports other extensions being enabled, and it needs the management permission, a broad ask most extensions can't justify. The W3C WebExtensions issue #353, open since 2023, listed three fixes: a new runtime.onEnabled, a new "enable" reason on onInstalled, or letting management.onEnabled work without the permission when an extension listens for itself.
So a re-enabled extension came back silently. Anything it set up once — a native-messaging handshake, a license check, a sync session — stayed down until the next browser start, and it had no way to tell "the user turned me back on" from "the machine rebooted."
What landed in Chrome 155
The commit is 61c52ec, "[Extensions] Implement runtime.onEnabled lifecycle event", committed September 2, 2026. Chrome 155 branched on September 14 per the ChromiumDash schedule, so it made the cut with twelve days to spare. The commit message defines the behavior:
"With this change, extensions can listen to runtime.onEnabled, which fires whenever an extension transitions from a disabled state to an enabled state while the browser is running."
The listener takes no arguments. What counts as enabling was settled in the issue thread: "flipping the switch that toggles the extension either manually or through sync/policies/setEnabled API." So the chrome://extensions toggle, an enterprise policy change, a sync from another device and another extension calling management.setEnabled() all reach your handler. Chrome 156 followed on October 20 and carries it too.
The next one: onExtensionLoaded
On September 30, two days after Chrome 156 branched, Chromium landed 4f1b414, "Implement runtime.onExtensionLoaded lifecycle event". Its commit message names the problem plainly: extensions have "multiple disjoint lifecycle events (onInstalled, onStartup, onEnabled)" and no single one that fires whenever the extension becomes active.
onExtensionLoaded passes details.reason — install, update, browser_update, enable, startup or reload — plus details.previousVersion on updates. Since it landed after the 156 branch point, the first stable release it can reach is Chrome 157, scheduled for November 3. The API reference lists it as "Pending" today.
The name held up under one challenge. At the October 8 WECG meeting, a request to rename it runtime.onLoaded came up. Oliver Dunk (Chrome) preferred to keep the current name and Rob Wu (Mozilla) agreed; Timothy Hatcher (Apple) said he didn't like the redundancy. The name stays onExtensionLoaded, and there is no onLoaded to code against.
Which event fires when
| Event | Fires when | Available |
|---|---|---|
runtime.onInstalled |
install, update, browser update | long-standing |
runtime.onStartup |
a profile with the extension starts | long-standing |
runtime.onEnabled |
disabled → enabled while the browser runs | Chrome 155+ |
runtime.onExtensionLoaded |
any of the above, plus reload, with a reason |
landed for Chrome 157 |
management.onEnabled |
another extension is enabled (needs management) |
long-standing |
Both new events are Chrome-only for now. Firefox and Safari carry "supportive" labels on issue #353 but haven't shipped either one.
One listener pattern for every Chrome version
Register at the top level of the service worker, prefer onExtensionLoaded where it exists, and fall back to the older trio. Don't register both paths: onInstalled keeps firing in Chrome 157, so an extension listening to both would set itself up twice on every install.
// background.js — top level, so the listener survives service worker restarts
async function bringUp(reason) {
// Must be idempotent: see the 157 edge case below.
await connectNativeHost();
await refreshLicense();
}
if (chrome.runtime.onExtensionLoaded) {
// Chrome 157+: one event, with a reason.
chrome.runtime.onExtensionLoaded.addListener(({ reason }) => bringUp(reason));
} else {
chrome.runtime.onInstalled.addListener(({ reason }) => bringUp(reason));
chrome.runtime.onStartup.addListener(() => bringUp("startup"));
// Chrome 155–156 only; undefined on older Chrome and on Firefox/Safari.
chrome.runtime.onEnabled?.addListener(() => bringUp("enable"));
}connectNativeHost() and refreshLicense() stand in for your own setup code.
Edge cases worth knowing
Updated while disabled. If the extension updates while it's switched off and the user then turns it on, Chrome 157 can deliver both an update and an enable reason. A follow-up commit, 958dc38, makes only the higher-precedence reason fire (update beats install, reload, enable, browser_update and startup) and adds previousVersion to enable. It landed on October 20, after Chrome 157 branched, so expect it in 158 (stable November 17) unless it gets merged back. Until then, keep your setup safe to run twice.
Only while the browser runs. The commit scopes onEnabled to a switch flipped during a session. A browser launch is still onStartup's job.
Not every user has 155 yet. Chrome rolls stable out gradually, and managed fleets often pin older versions. The ?. above is what keeps those users working.
What to do now
- Move your re-initialization into one idempotent function. Native-host reconnects, license checks and sync restarts all belong there, whichever event calls it.
- Wire
onEnabledtoday, behind the feature check. It's live on Chrome 155 and 156 stable. - Switch to
onExtensionLoadedwhen 157 lands, and keep the fallback branch for older Chrome, Firefox and Safari. - Use the
reason. Tellingenablefrominstalllets you greet a returning user differently from a new one.
If you're touching background code anyway, the free Manifest V3 generator builds a cross-browser manifest with live store-policy validation, and npx @extenshi/cli scan checks your own build before you upload it — 3 scans + 10 reads free, one-time, with a free account; prepaid packs past that.
See where your extension stands in the catalog → — manifest version, permissions and store status on Chrome, Firefox and Edge.
Sources
- Chromium commit 61c52ec — "[Extensions] Implement runtime.onEnabled lifecycle event" — Chromium (Google)
- Chromium commit 4f1b414 — "[Extensions] Implement runtime.onExtensionLoaded lifecycle event" — Chromium (Google)
- Chromium commit 958dc38 — "[Extensions] Populate previousVersion for onExtensionLoaded enable" — Chromium (Google)
- chrome.runtime API reference — Chrome for Developers
- W3C WebExtensions issue #353 — onEnabled event — W3C WebExtensions Community Group
- WECG meeting notes, October 8, 2026 — W3C WebExtensions Community Group
- Chrome milestone schedules: 155, 156, 157, 158 — ChromiumDash (Google)
Technical claims come from the cited Chromium commits, the Chrome API reference, the W3C issue and meeting notes, and ChromiumDash milestone schedules, checked on October 21, 2026. Release dates are scheduled dates and can move; a change can also be merged back into an earlier milestone. If you think something here is inaccurate, tell us at [email protected].
Related Articles

Extension Pulse, week of October 12: Chrome 157's break list is already written
Chrome 157 beta started October 14 with five extension changes merged. Chrome 156 hits early stable. Plus runtime.onEnabled and a rejected W3C proposal.

Chrome 154 landed MV3's lost-event fix — for up to 276,820 Chrome extensions that can't use it yet
Chrome 154 shipped the async_listener_registration manifest key for MV3 service workers. Here's what landed, who it covers, and why you shouldn't switch yet.

Can a disabled extension skip its own uninstall URL? In Chrome, no — here's why
A disabled extension can't clear its uninstall URL (setUninstallURL) before removal — the service worker is already dead. Here's why, and the server-side fix.

chrome.management in browser extensions: build a permission audit popup
A hands-on Manifest V3 tutorial: use the chrome.management API and Chrome's own permission warnings to audit every extension installed. Full runnable code.