From Chrome 148, all Chrome Extension APIs are available under the
browser namespace in addition to the existing chrome namespace. For
example, browser.tabs.create({}) and chrome.tabs.create({}) are
equivalent.
The namespace is available wherever you can call extension APIs, including
content scripts, service workers, and offscreen documents. It points to the
same API objects as chrome, so chrome.tabs === browser.tabs.
The browser namespace comes out of work in the
WebExtensions Community Group (WECG),
a W3C community group where browser vendors collaborate on shared extension
standards. The chrome namespace is not going away; both namespaces will
continue to work.
Decide whether to adopt the browser namespace
If you're using webextension-polyfill, skip ahead to A note for polyfill users before changing anything else - the answer is different for you.
If you're building a new extension, set
minimum_chrome_version
to "148" and use browser unconditionally; you can stop reading here. The
rest of this section is for existing extensions deciding how to adopt.
Check which Chrome versions your users are on
If you have an existing extension, check what versions of Chrome your users are running before switching. Chrome auto-updates, but some users disable updates and others are on older devices that can't run the latest version. Confirm with your own analytics data. If you don't have analytics set up yet, see Monitor your extension's performance with Google Analytics 4 to get started.
From there, pick a path:
- If your users are on Chrome 148 or later, adopt unconditionally.
- If a meaningful portion of your users are on Chrome 147 or earlier, use the runtime guard.
Adopt unconditionally
Set minimum_chrome_version
in your manifest and use browser unconditionally - no runtime guard needed:
{
"minimum_chrome_version": "148"
}
Use a staged rollout when raising minimum_chrome_version. If something
goes wrong, you can roll back your extension in
the Chrome Web Store.
Use the runtime guard
Add the following snippet early in your extension's startup code before
referencing browser anywhere else:
if (!globalThis.browser) {
globalThis.browser = chrome;
// Consider firing an analytics event here to measure how often
// your users hit this fallback path.
}
This makes browser an alias for chrome on earlier versions, so the rest of
your code can use browser unconditionally.
A note for polyfill users
If your extension uses
webextension-polyfill, it
becomes a no-op on Chrome 148 and later. The polyfill skipped wrapping when
browser was already defined, assuming the host browser had already provided
the API.
An earlier attempt to ship the namespace in Chrome 136 was rolled back for
this reason: with browser newly defined, the polyfill stopped wrapping, but
Chrome's browser.runtime.onMessage did not yet support promise-returning
listeners, which the polyfill had been providing. Extensions relying on that
pattern broke. Chrome 148 ships the namespace and native promise-returning
onMessage listeners together to avoid that gap.
You can remove the polyfill dependency once your user base has moved to Chrome 148.
Other features
Async responses in runtime.sendMessage
In Chrome 148, runtime.onMessage listeners can return a Promise directly
to send an async response. This works whether you call it using chrome.* or
browser.*.
Previously the only way to respond asynchronously was to return a literal
true from the listener and call sendResponse later:
// Old pattern - requires returning true to keep the channel open
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
fetch('https://example.com')
.then(response => sendResponse({ statusCode: response.status }));
return true; // keeps the message channel open for the async response
});
You can now return a Promise (or use an async function) directly:
// New pattern - return a promise or use async/await
browser.runtime.onMessage.addListener(async (message, sender) => {
const response = await fetch('https://example.com');
return { statusCode: response.status };
});
The return true pattern continues to work, so existing code doesn't need to
change.