How do you build a Chrome extension that shares your SaaS backend?
It depends on not building a second backend. MV3 extensions can't rely on HttpOnly cookies, so authenticate with workspace API keys instead: the extension calls your app's versioned REST API (/api/v1) with a scoped key bound to one workspace, reusing the same auth model, tenant scoping, and Postgres database as the web app. supastack ships an MV3 scaffold — popup, options page, background worker, least-privilege manifest — wired exactly this way.
The Trap: Accidentally Building a Second Backend
The usual story: your SaaS needs a companion extension to capture pages, clip content or annotate the web. Six months later you maintain two auth systems (cookies for web, something hand-rolled for the extension), a parallel API the extension can actually call, and a sync layer reconciling two data stores that were supposed to be one.
None of that is necessary. An extension is a client of your existing product — it needs a credential it can hold, an API meant for programmatic access, and nothing else. The mistake is reaching for a new backend instead of the workspace API keys and versioned REST surface a serious SaaS already has.
What Manifest V3 Actually Lets You Do
MV3 replaced background pages with ephemeral service workers and pushed extensions toward least-privilege permissions. Practically: you cannot lean on persistent in-memory state, you should not scrape cookies for session reuse (HttpOnly cookies are unreachable anyway), and every host permission is a Web Store review question.
The clean answer is the one the platform already gives you: a scoped credential in chrome.storage.local and HTTPS calls to your app's origin, requested as an optional host permission at setup time — not install time.
The supastack MV3 Scaffold: What's Already Wired
supastack's apps/extension is a deliberately small, framework-free Manifest V3 companion — 'Save page to workspace' — that demonstrates the whole pattern end to end. It captures the active tab's title, URL and selected text into a workspace content entry through the versioned REST API, POST /api/v1/content-entries.
Authentication is a workspace API key (sk_…) created under Settings → Developers: the key is bound to exactly one workspace, so the binding is the workspace selection — the key can never write outside it. The options page stores the app URL and key in chrome.storage.local, requests access to that origin only, and verifies the connection through GET /api/v1/workspaces/current.
apps/extension/
manifest.json MV3: service worker, least-privilege permissions
src/
background.js context-menu save + badge feedback
popup.html/.js capture UI — title, URL, selection, note
options.html/.js app URL + workspace API key setup
lib/
api.mjs versioned /api/v1 client (key auth)
capture.mjs active-tab + selection capture
config.mjs chrome.storage.local persistenceLeast-Privilege by Default
The scaffold ships with the smallest permission set the job needs — which matters for Web Store review and for user trust:
- No
tabs, no<all_urls>, no cookies/webRequest/declarativeNetRequest — the scaffold is honest about what it doesn't need.
| Permission | Why it's there |
|---|---|
| activeTab | Read the current page's title/URL only when invoked |
| scripting | Read the text selection inside the invoked tab |
| contextMenus | Register the right-click save action |
| storage | Persist app URL, API key, cached workspace |
| optional_host_permissions | App origin requested at setup, never at install — zero site access on install |
From Scaffold to Your Product's Companion
Rebranding is renaming manifest.json; extending is the interesting part, and it stays inside the same pattern — new capture types call new /api/v1 endpoints with the same key, and the typed @supastack/api-sdk generated from the app's OpenAPI document keeps request shapes honest.
Because the extension reads and writes through the same tenant-scoped API as every other client, a saved page lands in the same Postgres rows the web dashboard lists — one database, one auth model, one place where workspaceId scoping is enforced. No second backend, no sync, no drift. Compare tiers on the pricing page.
| DIY second-backend approach | supastack MV3 scaffold | |
|---|---|---|
| Auth | Hand-rolled token system beside cookies | Workspace API keys — same auth model as web |
| API | New endpoints invented for the extension | Versioned /api/v1 + OpenAPI-typed SDK |
| Data | Second store + sync logic | Same Postgres rows, workspace-scoped |
| Permissions | Broad access for convenience | Least-privilege manifest, review-friendly |
FAQ: SaaS + Chrome Extension on One Backend
What developers ask before shipping an extension companion:
- Why API keys instead of the user's session cookie?, HttpOnly cookies are invisible to extension JS by design, and MV3 service workers are ephemeral — a scoped
sk_…key inchrome.storage.localis durable, revocable, and bound to one workspace so it can never cross tenants. - Does the extension work in Firefox too?, MV3 supports the same surface; the code uses a
browser ?? chromenamespace shim and is developed Chromium-first, so Firefox is a port, not a rewrite. - Is it store-ready out of the box?, It's a production-pattern scaffold, not a store submission — you'll rebrand the manifest, add your own capture logic, and write the listing. The hard part (auth, API, tenant scoping) is already done.
- Which license includes the extension?, The Full Bundle at $299 ships all five surfaces — two web engines, Expo, Tauri and the extension scaffold — in one repo.