connect.tabs
Read and control the profile's tabs — query, navigate, capture, message, and tab events.
Read and control the tabs in the extension's profile. Reading is gated by
tabs.read; anything that changes a tab needs tabs.write. Messaging your own
content script needs no capability.
- Capabilities:
tabs.read(query/inspect),tabs.write(open/close/navigate/…) - Namespace:
connect.tabs
A tab object is { id, url, title, active, … }. "active" means the last-activated
browsing tab in the profile — it survives the shell chrome taking focus.
Walkthrough
Find the active tab and act on it.
- Declare the capabilities you need —
tabs.readto see tabs,tabs.writeto change them:{ "capabilities": ["tabs.read", "tabs.write"] } - Find the active tab with
query:const [tab] = await connect.tabs.query({ active: true }); - Act on it — navigate, reload, or message your content script.
- Stay in sync by subscribing to
onActivated/onUpdatedand re-rendering.
Methods
tabs.query(query?)
List tabs in the profile, optionally filtered.
How to use it
- Pass a filter (e.g.
{ active: true }) or nothing for all tabs. awaitthe array; readtab.id/tab.urloff each.
Signature — connect.tabs.query(query?) · Capability: tabs.read
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
query | object | no | Filter, e.g. { active: true }. Omit for all tabs. |
Returns — Promise<Tab[]>.
Examples
const [active] = await connect.tabs.query({ active: true });
const all = await connect.tabs.query();tabs.get(tabId)
Fetch one tab by id.
How to use it
- Pass a known
tabId. awaitit — wrap in try/catch, since an unknown id throws.
Signature — connect.tabs.get(tabId) · Capability: tabs.read
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId | number | yes | The tab's id. |
Returns — Promise<Tab>. Throws if the id is unknown.
tabs.reload(tabId)
Reload a tab.
How to use it
- Pass the
tabIdto reload;awaitcompletion.
Signature — connect.tabs.reload(tabId) · Capability: tabs.write · Returns Promise<void>.
tabs.navigate(tabId, url)
Navigate a tab to a URL.
How to use it
- Pass the
tabIdand ahttp(s)/aboutURL (other schemes are refused). awaitthe navigation.
Signature — connect.tabs.navigate(tabId, url) · Capability: tabs.write
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId | number | yes | The tab to navigate. |
url | string | yes | Destination. Refused for non-http(s)/about schemes. |
Returns — Promise<void>.
tabs.activate(tabId)
Focus (select) a tab.
How to use it
- Pass the
tabId;await— it becomes the active tab.
Signature — connect.tabs.activate(tabId) · Capability: tabs.write · Returns Promise<void>.
tabs.create(url)
Open a new tab.
How to use it
- Call
create(url). - Don't rely on the return for the id —
awaitonCreated(or re-query) to get it.
Signature — connect.tabs.create(url) · Capability: tabs.write
Returns — Promise<{ requested: true }>. Resolves "requested": the new tab's id
doesn't exist until it mounts, so re-query (or listen for onCreated) to get it.
await connect.tabs.create('https://example.com');
const off = connect.tabs.onCreated(({ id }) => { off(); /* got the new id */ });tabs.close(tabId)
Close a tab.
How to use it
- Pass the
tabId;awaitit closed.
Signature — connect.tabs.close(tabId) · Capability: tabs.write · Returns Promise<void>.
tabs.move(tabId, index)
Reorder a tab in the strip.
How to use it
- Pass the
tabIdand a targetindex(clamped to the strip length).
Signature — connect.tabs.move(tabId, index) · Capability: tabs.write
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId | number | yes | The tab to move. |
index | number | yes | Target position; clamped to the strip length. |
Returns — Promise<void>.
tabs.duplicate(tabId)
Open a copy of a tab (same URL).
How to use it
- Pass the
tabId; likecreate, the new id arrives viaonCreated, not the return.
Signature — connect.tabs.duplicate(tabId) · Capability: tabs.write
Returns — Promise<{ requested: true }>. Like create, resolves "requested" — the
strip owns placement.
tabs.captureVisibleTab(tabId, options?)
Capture a tab's rendered page as an image.
How to use it
- Make sure you hold a host grant covering the tab's URL (plus
tabs.read). - Capture the visible tab; pass
{ format: 'jpeg', quality }for a smaller image. - Handle a
nullresult (unknown tab or empty frame).
Signature — connect.tabs.captureVisibleTab(tabId, { format?, quality? })
· Capability: tabs.read plus a host grant covering the tab's URL
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId | number | yes | The tab to capture. |
options.format | 'jpeg' | 'png' | no | 'jpeg' (with quality) or PNG (default). |
options.quality | number | no | 0–100, JPEG only. |
Returns — Promise<string | null> — a data URL, or null for an unknown tab or an
empty frame.
Errors & edge cases
- Needs a host grant matching the tab's URL — pixels are as sensitive as injection, so there is no ambient capture.
- Capture reflects what's composited, so capture the visible tab; a backgrounded tab
can come back empty (
null).
const dataUrl = await connect.tabs.captureVisibleTab(active.id, { format: 'jpeg', quality: 80 });tabs.sendMessage(tabId, data?)
Message your own content script in a tab.
How to use it
- In your content script, add
connect.runtime.onMessage(...). - From a surface, call
sendMessage(tabId, data)and read{ ok, reply }.
Signature — connect.tabs.sendMessage(tabId, data?) · Capability: none
Returns — Promise<{ ok, delivered, reply }>. No listener ⇒ ok: false.
const { ok, reply } = await connect.tabs.sendMessage(tab.id, { type: 'theme', value: 'dark' });tabs.connect(tabId, options?)
Open a long-lived Port into your own content script in a tab — the reverse of a
content script's runtime.connect. Use it when the background needs to push a stream
into a page (live progress, a toggleable picker, settings sync) rather than fire
one-shot messages.
How to use it
- In your content script, register
connect.runtime.onConnect((port, sender) => …). - From a surface (usually the background),
await connect.tabs.connect(tab.id, { name }). - Use the Port as usual; when the tab navigates or
closes, both ends get
onDisconnect.
Signature — connect.tabs.connect(tabId, { name? }) · Capability: none
(strictly intra-extension, like sendMessage — it only reaches your content script)
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId | number | yes | The tab hosting your content script. |
options.name | string | no | A label the CS reads as port.name. |
Returns — Promise<Port> (same shape as runtime.connect).
Errors & edge cases
- Rejects when you have no injected content script with a
runtime.onConnectlistener at that tab's current URL — parallel toruntime.connectrejecting when nobody listens. - The port dies with the document: navigating the tab or closing it disconnects, and
both ends hear
onDisconnect— that's the point of a port oversendMessage.
// content script
connect.runtime.onConnect((port) => {
if (port.name !== 'inspector') return;
port.onMessage(({ highlight }) => setHighlight(highlight));
port.onDisconnect(() => clearHighlight());
});
// background
const port = await connect.tabs.connect(tab.id, { name: 'inspector' });
port.postMessage({ highlight: true });Events
All tab events need tabs.read and return an unsubscribe function.
tabs.onActivated(cb)
Fires when a different tab becomes active.
How to use it
- Subscribe; the callback gets the newly-active tab.
- Keep the returned function to unsubscribe later.
Payload — the newly-active tab.
const off = connect.tabs.onActivated((tab) => refresh(tab));
// later: off();tabs.onUpdated(cb)
Fires on navigation / title changes (top frame).
How to use it
- Subscribe to re-read a tab whose URL/title changed.
Payload — the updated tab.
tabs.onCreated(cb)
Fires when a tab opens.
How to use it
- Use it to learn the id of a tab you just
created /duplicated.
Payload — { id }.
tabs.onRemoved(cb)
Fires when a tab closes.
How to use it
- Subscribe to drop any per-tab state you were holding.
Payload — { id }.
Notes
- Everything is profile-scoped — you only ever see and touch tabs in the profile the extension is installed into.
create/duplicateresolve before the tab's id exists; useonCreatedor re-query when you need the id.- Tab groups are deliberately not implemented (
tabs.group/tabGroups.*don't exist; feature-detect with'group' in connect.tabs). Deferred to a future version.