The $0/month stack for paid browser extensions.
A complete, production-tested launch kit: MV3 extension with offline-verifiable licensing, a Cloudflare Worker that turns Paddle payments into signed license keys, and a static checkout site. No servers. No database bill. No sales-tax filings.
One-time purchase · lifetime updates · unlimited products · 14-day refund
no license stored
- Basic mode
- Advanced mode
- Export data
- Unlimited items
Edge cases: tampering & outages
Live demo: real ES256 keys signed and verified in your browser with a keypair generated for this page. Nothing leaves the tab.
Your browser doesn't support WebCrypto, so the live demo is off.
Three things no other extension kit gives you together
Every other boilerplate is "connect Stripe, add a Firebase project, host a backend". You are then on the hook for hosting bills, uptime and VAT.
$0/month, honestly
GitHub Pages hosts the site. Cloudflare Workers + KV run licensing on the free tier (100k requests/day — tens of thousands of users). Paddle only takes a cut of sales. Your fixed cost is a domain.
Merchant of record built in
Paddle sells to your customer, collects VAT/GST/sales tax worldwide, issues invoices and handles refunds and chargebacks. The kit turns Paddle webhooks into license keys — you never touch a tax form.
Pro works when your server doesn't
License keys are ES256-signed JWTs verified inside the extension with an embedded public key. No "ask the server if the user is Pro" — and no one-star reviews when a backend hiccups.
What's inside
One private repository, five packages, one config file. Everything reads from paidextension.config.json. Real code, straight from the repo:
TypeScript MV3 extension
- Popup, settings, upgrade flow, sample content script
licensing/module: offline key verification, device seats, trials, checkout polling, offline grace- Config-driven feature gates —
hasFeature('exportData') - Firefox manifest variant, Edge-ready packaging
- Vitest suite, ESLint, strict TypeScript, webpack
// Entitlement derives from the signed license, never a stored flag.
export async function isPro(): Promise<boolean> {
return (await getEntitlementStatus()).tier !== 'free';
}
export async function hasFeature(feature: string): Promise<boolean> {
const { tier } = await getEntitlementStatus();
return featuresForTier(tier).has(feature);
}
// verify.ts — offline ES256 check with the embedded PUBLIC key
const pubKey = await crypto.subtle.importKey('jwk', LICENSE_PUBLIC_JWK,
{ name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' },
pubKey, signature, data);
Cloudflare Worker licensing backend
- Verifies Paddle webhooks, issues signed keys for monthly, yearly and lifetime plans
- Seat registry in KV, short-lived install-bound entitlement tokens
- Revokes on refund, chargeback and cancellation
- Abuse-limited free trials, per-route rate limits
- License-key emails via Resend, idempotent on retries
- 65 tests, no framework, deploys with
wrangler deploy
const claims = await verifyLicenseKey(body.key, env.LICENSE_PRIVATE_JWK);
if (!claims) return json({ error: 'invalid_key' }, 400, cors);
// 'unknown' = couldn't confirm; never lock a payer out over our outage.
const status = await licenseStatus(claims.sub, env);
if (status === 'cancelled' || status === 'expired') {
return json({ error: 'subscription_inactive', status }, 403, cors);
}
const seats = await readSeats(claims.sub, env);
const known = seats.installs.some((i) => i.id === installId);
if (!known && seats.installs.length >= MAX_SEATS) {
return json({ error: 'seat_limit',
seats: { used: seats.installs.length, max: MAX_SEATS } }, 409, cors);
}
const ttl = now + ENTITLEMENT_TTL_SECONDS;
const token = await signEntitlement(claims, installId, ttl, env);
Static checkout & customer site
- Landing, Paddle overlay checkout, license-key page, device management
- Privacy, terms, support pages with refund policy
- Strict per-page CSP, HTML validation, link and CSP checkers
- Deploys to GitHub Pages on push
// The extension creates the Paddle transaction server-side (Worker
// /checkout) and opens this page with ?txn=<id>&env=<sandbox|production>
// This page only renders Paddle's overlay for that transaction.
var config = window.SITE_CONFIG || {};
var PINNED_TOKENS = config.paddleTokens || {};
var TXN_PATTERN = /^txn_[a-z0-9]{1,64}$/;
var params = new URLSearchParams(window.location.search);
var txnId = params.get('txn');
var env = params.get('env') || 'production';
var token = Object.prototype.hasOwnProperty.call(PINNED_TOKENS, env)
? PINNED_TOKENS[env] : '';
Agent-native
- Step-by-step skills for Devin, Claude Code, Cursor and Codex: setup, publish, test
AGENTS.mdwith the invariants an agent must not break- Hand the repo to your agent: "rename to X, set prices, deploy, submit"
---
name: setup
description: Take a fresh clone of the kit to a working paid extension —
rebrand, signing keypair, Paddle sandbox, Worker deploy, GitHub Pages,
and a verified trial → purchase → activate loop.
---
# Setup
Ask the user for the product facts once: name, description, company,
support email, site origin, seats per license, trial days.
Never paste a secret into a tracked file — `wrangler secret put` only.
## 1. Rebrand · 2. Keypair · 3. Paddle sandbox
## 4. Worker · 5. Site + domain · 6. Prove the loop
Rebrand, validate, release
npm run initrewrites the entire repo for your product from a few answersvalidate:configkeeps manifests, Worker and site consistent;generate:listingdrafts store copy and a privacy policy- CI on every PR; tag-triggered release builds store zips with production config
- Pages deploy for the site, Wrangler deploy for the Worker
# Rebrand the kit from the sample "Acme Extension" to your product.
npm run init # interactive
npm run init -- --from answers.json # non-interactive (agents, CI)
npm run validate:config && npm test # manifests, Worker, site agree
# release.yml — push tag v1.2.3 → store zips + GitHub release
on: { push: { tags: ['v*'] } }
steps:
- run: npm run validate:config
- run: npm run lint && npm run typecheck && npm test
- name: Build with production payment config
run: npm run build && npm run check:manifest && npm run package
Configure it in sixty seconds
Answer a few questions and npm run init rewrites the whole repo. Try it: the config, manifest and popup update as you type.
More answers
Pro unlocks advanced mode, export and unlimited items.
Extracted from a real product, not a demo
PaidExtension is the exact stack behind Manila Mail Manager, a paid Gmail extension live on the Chrome Web Store — Paddle checkout, Cloudflare licensing, seat limits, trials and refunds have all been exercised by paying customers. The kit is the generic version, with lifetime plans, Edge/Firefox packaging and the agent skills added.
Want to poke at one before buying? Marker is a highlighter extension built from the kit: free tier, 7-day trial, Pro unlocked by a Paddle sandbox checkout (card 4242 4242 4242 4242), server-verified sharing, and revocation when you cancel. Its product code is public at github.com/house-of-arqam/marker — about 40 lines of it touch licensing.
How it compares
Against the popular extension boilerplates and the hosted "payments SDK" services — including what each one leaves you per month at your numbers.
| PaidExtension | Typical boilerplate (ExtensionFast, extFast, TurboStarter…) |
Hosted payment SDKs (ExtensionPay, crxpay…) |
|
|---|---|---|---|
| You keep, per month on $1,160 of sales | $0 | $0 | $0 |
| Fees, infrastructure & tax, per month | |||
| Price of the kit | $99 once (Solo) | $99–$499 once | 3–5% of every sale, forever |
| Sales tax / VAT handled | Yes — Paddle MoR | No — Stripe, you file | No — Stripe, you file |
| Works when backend is down | Yes — offline ES256 keys | No | No |
| Device seat limits | Yes, self-serve release | No | No |
| Lifetime + subscription plans | Both | Usually subscription | Both |
| User accounts required | No — key-based | Google / email auth | No |
| Refund / chargeback revocation | Automatic | DIY | Yes |
| Own the code, no vendor | Yes | Yes | No — hosted dependency |
| AI-agent setup/publish/test skills | Yes | No | No |
| Firefox + Edge packaging | Yes | Sometimes | Chrome-first |
Assumptions behind the monthly numbers
Illustrative, from published rates at the time of writing: Paddle 5% + 50¢ per sale (tax included); Stripe 2.9% + 30¢; Stripe Tax 0.5% per transaction; Cloudflare Workers free tier 100k requests/day, then $5/month. Competitor details as published on their sites; check current terms before deciding.
Pricing
Pay once. Build as many extensions as you like. Same repo, same updates — the tier sets how many developers may use it.
Solo
$149$99
one-time, one developer
$50 off for the first 100 customers — 100 left
- One developer, any number of products
- Personal or company projects
Team
$249
one-time, up to 5 developers
- Up to 5 developers at one company
- Share the repo internally
Agency
$499
one-time, unlimited developers
- Unlimited developers at one company
- Build and hand over products for clients
- Private GitHub repo access, instantly
- Unlimited end products, commercial use
- Lifetime updates
- Setup, licensing and publishing guides
- Agent skills: setup, publish, test
- Support via GitHub Issues and email
14-day refund on every tier if it's not what you expected. Payments handled by Polar (merchant of record); you'll get a proper invoice.
Questions
What exactly do I receive?
Access to a private GitHub repository containing the full source: extension/, worker/, site/, scripts/, docs/, .agents/skills/ and the GitHub Actions workflows. Polar grants the repo access to the GitHub account you connect at checkout right after purchase; on Team and Agency, share it with your developers as the license allows. Updates arrive as commits.
Do I need to know Cloudflare or Paddle already?
No. The setup guide (and the setup agent skill) walks through creating the Paddle prices and webhook, deploying the Worker and pointing your domain at GitHub Pages. Budget about an hour for a first run.
Why Paddle rather than Stripe?
Paddle is a merchant of record: it is legally the seller, so it collects and remits sales tax in every jurisdiction and handles invoices, refunds and chargebacks. With Stripe those obligations are yours. For a solo developer selling globally that difference is the whole ballgame.
Is it really $0/month?
Until you outgrow the free tiers, yes: GitHub Pages is free, Cloudflare Workers gives 100,000 requests/day and KV 1,000 writes/day, Resend gives 3,000 emails/month. Paddle charges 5% + 50¢ per sale and nothing otherwise. You pay for a domain (~$12/year).
Can I use React / WXT / Plasmo?
The extension scaffold is plain TypeScript + webpack so there's nothing to fight. The licensing/ module has no UI dependencies and drops into any MV3 project; the Worker and site are independent of the extension framework.
What does the license allow?
Unlimited commercial end products, forever, including all updates. The tier sets who may work with the kit's files: Solo is one developer, Team is up to 5 developers at one company, Agency is unlimited developers at one company including work delivered to clients. You may not redistribute the kit itself or use it to build a competing kit. Full text on the license page.
How is this "agent-native"?
The repo ships an AGENTS.md plus three skills in .agents/skills/ — setup, publish and test — written as checklists an AI coding agent can execute. They encode the things that usually go wrong: service-worker constraints, CSP rules, Paddle sandbox quirks, store-review requirements.
Refunds?
14 days, no questions asked — email hello@paidextension.dev. We'll revoke repo access and refund through Polar.