Connect+Documentation
ExtensionsAPI reference

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.

  1. Grant a host and (for programmatic injection) scripting:
    { "capabilities": ["scripting", { "host": "https://*.example.com/*" }] }
  2. Simplest path — declare a static content script in the manifest (contentScripts); the host grant is the consent, no scripting needed.
  3. Or inject at runtime into a specific tab with executeScript.
  4. Talk back to your background from the content script via the restricted runtime / storage API.

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, documentIdlemin(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

  1. Confirm you hold scripting + a host grant for the tab's URL.
  2. Pass one of code, files, or func (+ args) with the tabId.
  3. If using func, keep it self-contained — it's stringified, so no closure capture.

Signatureconnect.scripting.executeScript({ tabId, code?, files?, func?, args?, world? })

Parameters

ParameterTypeRequiredDescription
tabIdnumberyesThe tab to inject into.
codestringone of code/files/funcSource to run. Each call is wrapped so const/let don't collide across runs.
filesstring[]one of code/files/funcExtension-relative files to run.
funcfunctionone of code/files/funcA function to run (chrome.scripting parity).
argsunknown[]noArguments for func, JSON-cloned.
world'isolated' | 'main'noDefaults to isolated.

ReturnsPromise<unknown> — the injected code's result.

Errors & edge cases

  • Needs scripting and a host grant matching the tab's current URL.
  • func is stringified and called with JSON-cloned args, so it must be self-contained (no closure capture) and args must 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

  1. Pass css (text) or files, with the tabId.
  2. Remove later with removeCSS using the same key.

Signatureconnect.scripting.insertCSS({ tabId, css?, files? }) / removeCSS({ tabId, css?, files? })

Parameters

ParameterTypeRequiredDescription
tabIdnumberyesThe tab.
cssstringone of css/filesThe stylesheet text.
filesstring[]one of css/filesExtension-relative CSS files.

ReturnsPromise<void>. removeCSS removes styles inserted with the same key.

scripting.registerContentScripts(scripts)

Add runtime content scripts alongside the static ones.

How to use it

  1. Build an array of scripts, each with a unique id and matches.
  2. Register them; update or remove later by id.

Signatureconnect.scripting.registerContentScripts(scripts)

Parameters

ParameterTypeRequiredDescription
scriptsobject[]yesEach: { id, matches, js?, css?, runAt?, world?, allFrames? }.

ReturnsPromise<void>.

scripting.updateContentScripts(scripts)

Update registered scripts by id.

How to use it

  1. Pass the same id with the fields to change.

ReturnsPromise<void>.

scripting.unregisterContentScripts(filter?)

Remove registered scripts.

How to use it

  1. Pass { ids } to remove specific ones, or omit to remove all.

Signatureconnect.scripting.unregisterContentScripts({ ids? }) · Returns Promise<void>.

scripting.getRegisteredContentScripts(filter?)

List the dynamic scripts.

How to use it

  1. Call it (optionally with { ids }) to see what's currently registered.

Signatureconnect.scripting.getRegisteredContentScripts({ ids? }) · Returns Promise<object[]>.

Notes

  • Static content scripts are the simplest path (no scripting capability) — 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; use isolated unless you specifically need the page's own globals.

On this page