connect.scripting
Static content scripts, the restricted content-script API, and programmatic injection.
Run your code inside web pages — either declared statically in the manifest, or
injected programmatically at runtime. Both are gated by a host grant covering the
target URL; programmatic injection additionally needs the scripting capability.
- Capabilities: a
{ host }grant (static content scripts);scripting+ host (programmatic injection) - Namespace:
connect.scripting
Walkthrough
Inject a content script into a site you're granted.
- Grant a host and (for programmatic injection)
scripting:{ "capabilities": ["scripting", { "host": "https://*.example.com/*" }] } - Simplest path — declare a static content script in the manifest (
contentScripts); the host grant is the consent, noscriptingneeded. - Or inject at runtime into a specific tab with
executeScript. - Talk back to your background from the content script via the restricted
runtime/storageAPI.
Static content scripts (manifest)
Declared in contentScripts, injected into pages matching your granted host
patterns — no scripting capability needed; the host grant is the consent.
"capabilities": [{ "host": "https://*.example.com/*" }],
"contentScripts": [{
"matches": ["https://*.example.com/*"],
"excludeMatches": ["https://admin.example.com/*"],
"js": ["cs.js"],
"css": ["cs.css"],
"runAt": "documentStart", // documentStart | documentEnd | documentIdle
"world": "isolated", // isolated (default) | main
"allFrames": false // inject into subframes too
}]Timing matches Chrome exactly: documentStart fires before any page script,
documentEnd at DOMContentLoaded, documentIdle ≈ min(load, DCL+200ms). The
isolated world is invisible to the page (and to other extensions); main runs in the
page's own context (CSP-immune).
The connect API inside a content script
Matched isolated-world content scripts get a restricted window.connect — enough
to talk to your background and keep small state:
// content script (isolated world)
connect.runtime.sendMessage({ hello: 'bg' }); // → your background / surfaces
connect.runtime.onMessage((data, sender) => { /* from tabs.sendMessage */ });
connect.storage.get('key'); // needs `storage`
connect.storage.set('key', value);
// Long-lived Port to your background (the CS is the opener).
const port = await connect.runtime.connect({ name: 'live' });
port.onDisconnect(() => { /* the background went away */ });Every CS call is re-checked: main verifies the caller is installed ∩ host-granted ∩
has a matching isolated entry at the tab's current URL. A revoked host stops outbound
calls immediately and closes any Port opened here. A CS Port dies with its document —
navigating or closing the tab disconnects it. The reverse also works: a surface opens
a Port into this tab's CS with tabs.connect, which the
CS accepts via connect.runtime.onConnect(cb).
Programmatic injection
Needs scripting and a host grant covering the tab's current URL.
scripting.executeScript(details)
Run code, a file, or a function in a tab.
How to use it
- Confirm you hold
scripting+ a host grant for the tab's URL. - Pass one of
code,files, orfunc(+args) with thetabId. - If using
func, keep it self-contained — it's stringified, so no closure capture.
Signature — connect.scripting.executeScript({ tabId, code?, files?, func?, args?, world? })
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId | number | yes | The tab to inject into. |
code | string | one of code/files/func | Source to run. Each call is wrapped so const/let don't collide across runs. |
files | string[] | one of code/files/func | Extension-relative files to run. |
func | function | one of code/files/func | A function to run (chrome.scripting parity). |
args | unknown[] | no | Arguments for func, JSON-cloned. |
world | 'isolated' | 'main' | no | Defaults to isolated. |
Returns — Promise<unknown> — the injected code's result.
Errors & edge cases
- Needs
scriptingand a host grant matching the tab's current URL. funcis stringified and called with JSON-clonedargs, so it must be self-contained (no closure capture) andargsmust be JSON-serialisable.
await connect.scripting.executeScript({
tabId: tab.id,
func: (name) => { document.title = name; },
args: ['Renamed'],
});scripting.insertCSS(details) · scripting.removeCSS(details)
Add or remove styles.
How to use it
- Pass
css(text) orfiles, with thetabId. - Remove later with
removeCSSusing the same key.
Signature — connect.scripting.insertCSS({ tabId, css?, files? }) /
removeCSS({ tabId, css?, files? })
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId | number | yes | The tab. |
css | string | one of css/files | The stylesheet text. |
files | string[] | one of css/files | Extension-relative CSS files. |
Returns — Promise<void>. removeCSS removes styles inserted with the same key.
scripting.registerContentScripts(scripts)
Add runtime content scripts alongside the static ones.
How to use it
- Build an array of scripts, each with a unique
idandmatches. - Register them; update or remove later by
id.
Signature — connect.scripting.registerContentScripts(scripts)
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
scripts | object[] | yes | Each: { id, matches, js?, css?, runAt?, world?, allFrames? }. |
Returns — Promise<void>.
scripting.updateContentScripts(scripts)
Update registered scripts by id.
How to use it
- Pass the same
idwith the fields to change.
Returns — Promise<void>.
scripting.unregisterContentScripts(filter?)
Remove registered scripts.
How to use it
- Pass
{ ids }to remove specific ones, or omit to remove all.
Signature — connect.scripting.unregisterContentScripts({ ids? }) · Returns
Promise<void>.
scripting.getRegisteredContentScripts(filter?)
List the dynamic scripts.
How to use it
- Call it (optionally with
{ ids }) to see what's currently registered.
Signature — connect.scripting.getRegisteredContentScripts({ ids? }) · Returns
Promise<object[]>.
Notes
- Static content scripts are the simplest path (no
scriptingcapability) — reach for programmatic injection when you need to inject conditionally or into a specific tab. main-world injection runs in the page's context and bypasses the page CSP; useisolatedunless you specifically need the page's own globals.