The extension currently has no backend — all settings, API keys, and profiles live in chrome.storage.local. We want to add user accounts so we can accept payments. Users choose one of two paths on install:
- BYOK (Bring Your Own Key) — free, works exactly as today
- Paid Plan — user pays a subscription, AI calls are proxied through Convex HTTP Actions using server-managed API keys
┌─────────────────────────────────────────────────┐
│ Chrome Extension (packages/extension/) │
│ │
│ Sidepanel UI ──→ Convex Auth (login/signup) │
│ ──→ chrome.storage.local (BYOK) │
│ ──→ Convex DB (account, sub) │
│ │
│ Background ──→ BYOK: direct AI provider calls │
│ ──→ Paid: Convex HTTP Action proxy │
└──────────────────────┬──────────────────────────┘
│
┌──────────────────────▼──────────────────────────┐
│ Convex Backend (packages/backend/convex/) │
│ │
│ Auth: Email/Password + Google + GitHub OAuth │
│ DB: users, subscriptions, usage │
│ HTTP Actions: /ai-proxy (streams AI responses) │
│ HTTP Actions: /stripe-webhook │
│ Actions: createCheckoutSession, manageSubscription│
└─────────────────────────────────────────────────┘
- AI Proxy: Convex HTTP Actions (all-in-one, no extra infra)
- Auth: Email/Password + Google OAuth + GitHub OAuth via
@convex-dev/auth - Location:
packages/backend/(new monorepo package) - Payment: Stripe Checkout + webhook handling
packages/backend/
├── package.json
├── convex/
│ ├── schema.ts # DB schema
│ ├── auth.ts # Auth config (providers)
│ ├── http.ts # HTTP router (auth routes, stripe webhook, AI proxy)
│ ├── users.ts # User queries/mutations
│ ├── subscriptions.ts # Subscription queries/mutations
│ ├── payments.ts # Stripe checkout action
│ └── aiProxy.ts # AI proxy HTTP action
└── .env.local # CONVEX_URL (gitignored)
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
import { authTables } from "@convex-dev/auth/server";
export default defineSchema({
...authTables,
// authTables already creates `users` with name, email, etc.
subscriptions: defineTable({
userId: v.id("users"),
plan: v.union(v.literal("free"), v.literal("pro")),
stripeCustomerId: v.optional(v.string()),
stripeSubscriptionId: v.optional(v.string()),
status: v.union(
v.literal("active"),
v.literal("canceled"),
v.literal("past_due"),
v.literal("inactive")
),
currentPeriodEnd: v.optional(v.number()),
})
.index("by_userId", ["userId"])
.index("by_stripeCustomerId", ["stripeCustomerId"])
.index("by_stripeSubscriptionId", ["stripeSubscriptionId"]),
usage: defineTable({
userId: v.id("users"),
month: v.string(), // "2026-02"
requestCount: v.number(),
tokensUsed: v.number(),
})
.index("by_userId_month", ["userId", "month"]),
});- Providers:
Password,GitHub,Google - Environment variables:
JWT_PRIVATE_KEY,JWKS,SITE_URL,AUTH_GITHUB_ID,AUTH_GITHUB_SECRET,AUTH_GOOGLE_ID,AUTH_GOOGLE_SECRET
- Receives POST requests from the extension with: model, messages, settings
- Validates the user's auth token (from Authorization header)
- Checks subscription is active
- Increments usage counters
- Forwards the request to the AI provider (OpenAI/Anthropic) with server-side API keys
- Streams the response back to the extension
- Environment variables:
OPENAI_API_KEY,ANTHROPIC_API_KEY
payments.ts:createCheckoutSessionaction — creates Stripe Checkout session, returns URLhttp.ts:/stripe-webhookroute — handlescheckout.session.completed,customer.subscription.updated,customer.subscription.deleted- Environment variables:
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET,STRIPE_PRO_PRICE_ID
packages/extension/
├── convex/
│ └── client.ts # ConvexClient setup, auth helpers, chromeTokenStorage
├── sidepanel/ui/
│ └── account/
│ ├── panel-account.ts # Account UI mixin (login/signup/manage)
│ └── account-styles.css # Account panel styles
- Creates
ConvexClientfrom"convex/browser"with deployment URL (build-time constant) - Custom
chromeTokenStorageusingchrome.storage.localfor auth tokens - Exports:
convexClient,signIn(),signOut(),getAuthState(),getSubscription() - Uses
anyApifrom"convex/server"since backend is in separate package
New mixin on SidePanelUI following existing pattern:
- Onboarding screen (shown on first install): "Use your own API keys" vs "Get a subscription"
- Login/signup form: email/password fields, Google/GitHub OAuth buttons
- Account section in settings: shows plan, usage, manage subscription button
- Attaches to existing settings panel as a new collapsible section
Modify the AI call path:
- On startup, initialize Convex client (if user has account)
- Before each AI call, check: does user have own API key set? → use it directly (BYOK)
- No API key + active subscription? → route through Convex HTTP Action proxy
- No API key + no subscription? → show "add API key or subscribe" message
- Add
CONVEX_URLas esbuilddefineconstant (from env or hardcoded for prod) - Ensure
convex/browsermodule is bundled correctly for extension - No changes to entry points needed — new code imports into existing entries
- Add Convex deployment domain to
host_permissions:https://*.convex.cloud/*,https://*.convex.site/*
- User clicks "Get a subscription" on onboarding (or "Upgrade" in settings)
- Extension calls
convexClient.action(anyApi.payments.createCheckoutSession, {}) - Action returns Stripe Checkout URL
- Extension opens URL in new tab (
chrome.tabs.create) - User completes payment on Stripe
- Stripe webhook fires → Convex creates/updates subscription record
- Extension polls or subscribes to subscription status → unlocks paid features
- AI calls now routed through Convex proxy
- User clicks "Use your own keys" on onboarding
- Extension shows existing settings panel (provider, API key, model)
- Works exactly as today — no Convex account required
- Optional: user can later create account for cloud sync / backup
| File | Purpose |
|---|---|
packages/backend/package.json |
Backend package config |
packages/backend/convex/schema.ts |
Database schema |
packages/backend/convex/auth.ts |
Auth providers config |
packages/backend/convex/http.ts |
HTTP router (auth, webhook, proxy) |
packages/backend/convex/users.ts |
User queries/mutations |
packages/backend/convex/subscriptions.ts |
Subscription CRUD |
packages/backend/convex/payments.ts |
Stripe checkout action |
packages/backend/convex/aiProxy.ts |
AI proxy HTTP action |
packages/extension/convex/client.ts |
Extension Convex client |
packages/extension/sidepanel/ui/account/panel-account.ts |
Account UI mixin |
packages/extension/sidepanel/styles/account.css |
Account styles |
| File | Change |
|---|---|
packages/extension/manifest.json |
Add Convex domains to host_permissions |
packages/extension/background/service.ts |
Add subscription check + proxy routing |
packages/extension/sidepanel/ui/core/panel-core.ts |
Initialize account UI, add account section |
packages/extension/sidepanel/ui/settings/panel-settings.ts |
Add account section to settings |
packages/extension/sidepanel/templates/main.html |
Add account UI containers |
packages/extension/sidepanel/panel.ts |
Import account mixin |
scripts/build.mjs |
Add CONVEX_URL define |
package.json (root) |
Add backend workspace, convex deps |
- Backend:
cd packages/backend && npx convex dev— schema deploys, functions sync - Auth: Sign up with email, verify login works, test Google/GitHub OAuth
- BYOK flow: Existing behavior unchanged — enter API key, chat works
- Paid flow: Create checkout → pay with Stripe test card → subscription activates → AI calls proxy through Convex
- Proxy: Send a message as paid user → response streams back via Convex HTTP action
- Cancellation: Cancel in Stripe → webhook fires → subscription deactivated → proxy stops working
- Build:
npm run buildfrom root succeeds, extension loads in Chrome
- Backend setup (schema, auth, basic functions) — can test independently via Convex dashboard
- Extension client + account UI (login/signup flow)
- Stripe integration (checkout, webhook, subscription management)
- AI proxy HTTP action (the most complex piece)
- Background service routing (BYOK vs proxy decision logic)
- Polish (onboarding flow, error handling, loading states)