chrome.tabs calls like any other code. What it does change is the manifest that you run against during development, and that difference has a trap in it.
Development injects the tabs permission
The dev loop reloads content scripts by injecting them into tabs that are already open. That needs permissions your extension may not declare, so Extension.js adds them to the manifest that it writes intodist/.
For a manifest v3 project, development adds these to permissions:
host_permissions.
For a manifest v2 project, development adds tabs and storage, plus the match patterns, because manifest v2 has no host_permissions key.
None of this reaches production. extension build emits exactly the permissions that you declared.
You can see the difference yourself. Declare only storage in manifest.json, then compare the two builds:
dist/chromium/manifest.json
dist/chromium/manifest.json
The trap, and how to avoid it
Because development injectstabs, a call to chrome.tabs.query succeeds in extension dev even when manifest.json never asked for the permission. The same call fails after extension build.
Extension.js warns about this for some APIs. Use chrome.management without declaring it and the build says so:
storage, scripting, and management. It does not cover tabs. A project that calls chrome.tabs without declaring the permission compiles clean, runs clean in development, and breaks in the packaged extension.
Declare what you use:
manifest.json
dist/chromium as an unpacked extension and exercise the feature.
Do you need the tabs permission at all?
Many extensions do not. Thetabs permission exists to read privileged fields, not to call the API.
activeTab is the smaller request. It grants access to the tab that the user acted on, for as long as that visit lasts. Stores review it more kindly than tabs plus <all_urls>.
Read Permissions and host permissions for the full set.
Cross-browser naming
Firefox and Safari ship a promise-basedbrowser.tabs. Chromium ships a callback-based chrome.tabs. Extension.js provides the browser namespace on Chromium through webextension-polyfill, so one spelling works everywhere:
Targeting one tab from the terminal
Several CLI verbs act on a single tab. List the open tabs first:--tab takes a numeric tab id and nothing else. To match on address instead, use --url, which accepts a match pattern or a plain substring:
reload, --tab chooses what to act on. On logs, it filters events that were already recorded.
extension storage has no --tab option. Choose a surface with --context instead.
Failure messages
The last one is common on
chrome:// pages and on the Web Store, which no extension may touch.
Best practices
- Declare
tabswhen you read tab URLs. Development will not remind you. - Prefer
activeTabwhen a user gesture starts the work. - Verify permissions against a production build, not against the dev build.
- Never assume a tab id survives a browser restart. Query for it again.
Next steps
- Review permissions and host permissions.
- Drive the browser from the terminal with the eval command.
- Read about content scripts.

