Container Vertical Tabs is a Firefox extension that replaces tab scanning with a sidebar-first workflow. It lists tabs vertically, groups them by Firefox Container, and lets each Container group collapse independently.
The project is intentionally simple for AMO review: no bundler, no minifier, no remote code, and no build-time source transformation. The package submitted to Firefox is generated directly from src/.
Just a project built with Codex to solve a small problem of mine.
- Shows tabs from the current Firefox window in a vertical sidebar.
- Groups tabs by Firefox Container.
- Collapses and expands each Container group, with state saved locally.
- Reorders tabs by dragging them inside the same Container group.
- Shows Firefox split view tabs as a merged unit inside their Container group.
- Creates a new tab without a Container or in a selected Container.
- Verifies every Container tab creation by checking the created tab's
cookieStoreId. - Closes an unverified tab immediately if Container assignment cannot be proven.
- Activates tabs, closes tabs, supports middle-click close, and filters tabs by title or URL.
- Firefox Desktop 140 or newer.
- Node.js 24 or newer.
- npm 11 or newer.
The Node and npm versions match Mozilla's documented reviewer build environment as of this project setup.
Install dependencies:
npm installRun checks:
npm run checkLaunch a temporary Firefox profile with the extension loaded:
npm run runBuild an unsigned package:
npm run buildThe generated package is written to dist/.
- Open
about:debugging#/runtime/this-firefox. - Click
Load Temporary Add-on. - Select
src/manifest.json. - Open the sidebar from the Firefox sidebar picker or click the extension toolbar button.
Container isolation matters. When creating a tab in a Container, the extension:
- Confirms the requested Container still exists through
browser.contextualIdentities.get. - Creates the tab with
tabs.create({ cookieStoreId }). - Reads the created tab back with
tabs.get. - Compares the actual
cookieStoreIdwith the requested one. - Closes the created tab and reports an error if the value is missing or mismatched.
The extension validates Firefox's extension-layer Container identity. It does not inspect website cookies or transfer data between Containers.
WebExtensions cannot hide Firefox's built-in horizontal tab strip. Use userChrome.css for that part. See docs/userChrome.css.
GitHub and AMO release notes live in docs/RELEASE.md and docs/AMO_SUBMISSION.md.
Install dependencies and run checks before building:
npm ci
npm run checkBuild an unsigned package for local testing or manual review upload:
npm run buildThe unsigned package is written to dist/container_vertical_tabs-<version>.zip.
Build a signed self-distributed .xpi through AMO:
export WEB_EXT_API_KEY="user:..."
export WEB_EXT_API_SECRET="..."
npm run sign:unlistedThe signed package is written to dist/container_vertical_tabs-<version>.xpi. The <version> comes from src/manifest.json; AMO requires each submitted version to be unique, so bump src/manifest.json, package.json, and package-lock.json before signing a new build if that version was already submitted.
For listed AMO submission:
export WEB_EXT_API_KEY="user:..."
export WEB_EXT_API_SECRET="..."
npm run sign:listednpm run sign:listed submits listing metadata from amo-metadata.json; use npm run sign:unlisted when you only need a signed .xpi for self-distribution or GitHub Releases.
Push a v* tag to let GitHub Actions build and publish the release assets automatically:
git tag v0.2.0
git push origin v0.2.0Configure AMO_JWT_ISSUER and AMO_JWT_SECRET as repository secrets so the release workflow uploads a signed unlisted .xpi. The workflow fails without those secrets to avoid publishing unsigned release artifacts.
This extension does not collect, transmit, sell, or share personal data. It stores only local UI preferences, such as collapsed Container groups, in browser.storage.local. See PRIVACY.md.
The manifest explicitly declares Mozilla's no-data-collection permission with data_collection_permissions.required=["none"].
MIT