Skip to main content
Send the browser’s requests through a proxy server that your extension controls. Extension.js has no proxy-specific behavior. The proxy API belongs to the browser, and your manifest.json permissions travel into the build as you wrote them. What changes between targets is which permissions survive, so start there.

What each target does with the permission

A manifest that asks for "proxy" builds like this: The Safari build prints the reason:
Plan for that before you promise proxy behavior on Safari. See Browser-specific manifest fields when you want a manifest key on one target only.

Set a fixed proxy

Configure the proxy from the background service worker, where the API is available:
The proxy permission is required, and a Chromium build also wants host permissions for the sites that you intend to cover.

Ship a PAC script

A PAC script is not a manifest field, so the bundler never sees it as an entry. It does not get hashed, rewritten, or added to the output on its own. Two routes work. Pass the script inline as data:
Or keep the script as a file. Anything inside the public/ folder is copied into the build without processing, so a PAC file placed there arrives in dist/<browser>/ under the same name. Read it with fetch from your own extension origin, then pass the text as data. See Special folders for what public/ does.

Handle proxy authentication

A proxy that asks for credentials triggers chrome.webRequest.onAuthRequired. Manifest V3 removed blocking webRequest, so answering that challenge needs the webRequestAuthProvider permission alongside webRequest. Intercept network requests covers the request APIs that Manifest V3 keeps, and where declarativeNetRequest replaces the blocking ones.
Credentials that are compiled into the extension are readable by anyone who installs it. Environment variables do not change that, because their values are inlined at build time. See Environment variables.

Firefox works differently

Firefox has a proxy API under the same permission name, and a different model behind it. Firefox asks the extension to decide per request through browser.proxy.onRequest, rather than storing a settings object. Extension.js does not warn about this difference. Its Gecko compatibility warnings cover other APIs, so a Chromium-shaped chrome.proxy.settings.set call reaches a Firefox build with no message at all. Confirm the API on MDN and test the Firefox target before you ship it.

Proxy the dev browser instead

Sometimes you want the browser proxied during development, and not the extension. Pass a browser flag rather than an extension API:
Browser flags covers that variable and the browserFlags config key.

Next steps