Connect+Documentation
ExtensionsAPI reference

connect.runtime & messaging

Introspection, one-shot messaging, long-lived Ports, and lifecycle events between your surfaces.

Introspect your extension and talk between its own surfaces — popup, background, options, side panel, and content scripts. runtime needs no capability: an extension can always reach itself.

  • Capability: none (strictly intra-extension)
  • Namespace: connect.runtime

Cross-extension messaging is a different namespace — connect.channel.

Walkthrough

Wire a popup to its background.

  1. In the background, register a receiver:
    connect.runtime.onMessage((msg) => msg.type === 'ping' ? { pong: true } : undefined);
  2. In the popup, send and read the reply:
    const { reply } = await connect.runtime.sendMessage({ type: 'ping' }); // { pong: true }
  3. Need a stream or disconnect signal? Use a Port instead of one-shot sendMessage.
  4. Re-arm gated subscriptions from onGrantsChanged (see below).

Introspection

runtime.getManifest()

How to use it

  1. Call it to read your own { id, name, version, apiVersion } — handy for version checks.

Signatureconnect.runtime.getManifest() · ReturnsPromise<{ id, name, version, apiVersion }>.

One-shot messaging

runtime.sendMessage(data)

Send a message to your instance's other surfaces.

How to use it

  1. Make sure another surface has an onMessage listener.
  2. await sendMessage(data) and read { ok, reply }ok:false means nobody listened.

Signatureconnect.runtime.sendMessage(data)

Parameters

ParameterTypeRequiredDescription
dataunknownyesJSON-serialisable message.

ReturnsPromise<{ ok, delivered, reply }>. The first listener returning a non-undefined value answers; no listeners ⇒ ok: false.

runtime.onMessage(cb)

Receive messages. cb(data, sender) — return a value (or a promise) to reply. Register once per surface; a throwing listener never answers.

How to use it

  1. Register once per surface (e.g. in the background).
  2. Inspect data; return a value to reply, or nothing to stay silent.
// background.js
connect.runtime.onMessage((msg) => {
  if (msg.type === 'ping') return { pong: true };
});
// popup.js
const { reply } = await connect.runtime.sendMessage({ type: 'ping' }); // { pong: true }

Long-lived Ports

sendMessage is one-shot. A Port is a persistent, two-way channel — reach for it when you need a stream of messages, per-connection state, or (the main reason) to know when the other end goes away.

runtime.connect(options?)

Open a Port to your other surfaces.

How to use it

  1. Ensure the other end has an onConnect listener.
  2. await connect({ name }); it rejects if nobody is listening.
  3. Use port.postMessage / port.onMessage and clean up on onDisconnect.

Signatureconnect.runtime.connect({ name? })

Parameters

ParameterTypeRequiredDescription
namestringnoA label the receiver reads as port.name.

ReturnsPromise<Port>. Rejects when nothing is listening (like sendMessage's ok:false).

runtime.onConnect(cb)

cb(port, sender) fires in your other surfaces when one calls connect().

How to use it

  1. In the background, register onConnect.
  2. Filter by port.name, then wire postMessage / onMessage and clean up on onDisconnect.

The Port object

MemberDescription
port.nameThe name the opener passed.
port.postMessage(data)Send to the other end. A no-op once disconnected (never throws).
port.onMessage(cb)cb(data). Returns an unsubscribe fn.
port.onDisconnect(cb)Fires when the other end closes or its surface dies. Subscribing after the fact still fires. Returns an unsubscribe fn.
port.disconnect()Close it; the other end gets onDisconnect.
// popup.js — a live connection
const port = await connect.runtime.connect({ name: 'progress' });
port.onMessage(({ percent }) => render(percent));
port.postMessage({ start: true });

// background.js — one conversation per popup, cleaned up automatically
connect.runtime.onConnect((port) => {
  if (port.name !== 'progress') return;
  const timer = setInterval(() => port.postMessage({ percent: next() }), 100);
  port.onDisconnect(() => clearInterval(timer)); // popup closed → stop working
});

Rules & limits

  • Intra-extension only — a port never leaves your instance. Cross-extension uses connect.channel.
  • Content scripts can open a port out to their background, and a surface can open one into a CS with tabs.connect(tabId) (the CS accepts via its runtime.onConnect). A CS port disconnects when its document navigates or the tab closes.
  • A suspended background is not woken to accept: with no live listener, connect() rejects. Port users should declare a persistent background.
  • If several surfaces listen to onConnect, they share one port. In the normal 1:1 case the behaviour matches Chrome.
  • Ports die with their surface, on uninstall/unload, and on dev-mode teardown — both ends get onDisconnect.

Lifecycle events

runtime.onInstalled(cb)

Setup hook — fires once after install. Sticky: a background subscribing on first boot still receives it.

How to use it

  1. Subscribe in the background to run one-time setup (seed storage, create menus).
  2. Branch on reason ('install' vs 'update').

Payload{ reason: 'install' | 'update', version }.

runtime.onGrantsChanged(cb)

Fires when the user grants or revokes capabilities.

How to use it

  1. Put your gated on* subscriptions in one arm() function.
  2. Call arm() at boot and from onGrantsChanged, so a grant that lands after boot re-arms them (subscribing is idempotent).

Payload{ granted }.

function arm() { connect.tabs.onUpdated(handle); } // safe to call repeatedly
arm();
connect.runtime.onGrantsChanged(arm); // re-arm once the user grants tabs.read

runtime.onSuspend(cb) · runtime.onResume(cb)

Background-lifecycle signals for suspendable / ephemeral backgrounds.

How to use it

  1. On onSuspend, persist any in-memory state to storage.
  2. On onResume (or next boot), rebuild it — there's no event replay.

Notes

  • Register onMessage / onConnect once per surface.
  • For anything periodic that must survive suspension, drive it from connect.alarms, not a setInterval in a suspendable background.

On this page