Chrome Extension Pay: The Complete 2026 Guide to Taking Payments in a Chrome Extension (Stripe, ExtPay, CRXPay, DIY — Fees, Setup Time, and the MV3 Stuff That Breaks Everything)
"Chrome extension pay" is a phrase that means different things to different people. If you're a user, it usually means "how do I pay for this Chrome extension I just installed." If you're a developer, it means "how do I take payments inside a Chrome extension I'm building." If you're searching for a brand, you probably mean ExtensionPay (ExtPay).
This post answers all three, but the heart of it is the developer question — because that's where the real work lives. Google killed the native Chrome Web Store payment system on February 1, 2021, and every paid extension shipping today is on a third-party stack. The decisions you make about which stack determine whether you spend an hour shipping or eight weeks debugging webhooks.
I've built payment systems in two Chrome extensions in the last few months and pulled apart every reasonable approach. Long post. Save it.
Quick disambiguation: what "Chrome extension pay" actually means
If you're a user trying to pay for an extension: You won't pay through the Chrome Web Store anymore. Modern paid Chrome extensions install for free, then prompt you to upgrade inside the extension itself. Click "Upgrade" or "Get Pro" in the extension's popup. It opens a checkout page in a new tab (usually Stripe's hosted checkout). You pay there. Come back to the extension, your Pro features unlock.
If you're a developer: You need a third-party payment stack. The options are ExtensionPay/ExtPay (the indie default), CRXPay (the modern MV3-native option), DIY with Stripe, or a Merchant-of-Record platform like Lemon Squeezy or Paddle. The right choice depends on your stage and engineering capacity.
If you're searching for the brand "ExtensionPay" (ExtPay): It's the open-source JS library + service Glen Chiacchieri built in 2021 after Google shut down Chrome Web Store payments. It handles Stripe under the hood and charges a 5% platform fee. It's still the most popular indie option, but it predates Manifest V3 and is missing features modern extensions need.
The rest of this post is the developer version — how to actually take payments in a Chrome extension in 2026.
Why taking payments in a Chrome extension is weirdly hard
Stripe is well-documented. Chrome extensions are well-documented. Combining them is where everything quietly falls over. Five structural reasons:
1. There is no native payments API in Chrome anymore. Google deprecated Chrome Web Store payments on February 1, 2021. Whatever you ship, you bring yourself.
2. Chrome extensions can't run a backend. The extension is JavaScript that runs in the user's browser. Stripe webhooks need a server. So you're already building two things: an extension AND a backend.
3. Manifest V3 service workers die. Background scripts now terminate after ~30 seconds idle. Anything in memory is gone. Half the "add payments to your Chrome extension" tutorials on Google are written for the persistent-background MV2 model and silently break on a real production install today.
4. Manifest V3 bans remote code execution. You cannot lazy-load Stripe.js from a CDN inside the extension. Hosted Stripe Checkout in a new tab is the only correct UI pattern.
5. Your extension has no user accounts by default. A Chrome extension is just JavaScript running in the browser. There's no login, no user ID, no email — until you build one. Most payment platforms assume you already have these. You don't.
If you skip those five realities, you'll spend three weekends on what should be a one-week build.
The architecture every Chrome extension payment system needs
Regardless of which path you pick below, here are the seven components you're building or buying:
┌──────────────────────┐ ┌──────────────────┐
│ Chrome Extension │ │ Your Backend │
│ │ fetch via SW │ │
│ ┌────────────────┐ │ (CORS-safe) │ ┌────────────┐ │
│ │ Service Worker │◄─┼────────────────┼─►│ /api │ │
│ │ + signed cache │ │ │ │ entitlement│ │
│ └────────┬───────┘ │ │ └────────────┘ │
│ │ │ │ ┌────────────┐ │
│ ┌────────▼───────┐ │ open new tab │ │ /webhook │ │
│ │ Popup / pages │──┼────────────────┼──┤ Stripe │◄─┼─── Stripe
│ │ (read cache) │ │ │ └────────────┘ │
│ └────────────────┘ │ │ ┌────────────┐ │
└──────────────────────┘ │ │ User DB │ │
▲ │ │ + entitle- │ │
│ │ │ ments │ │
└──── hosted Stripe Checkout ──┴──┴────────────┘ │
opens in a new tab │
The seven components, none optional:
- Chrome extension with popup + MV3 service worker + (optionally) content scripts
- Backend with HTTPS endpoint (Cloudflare Workers, Vercel, Railway, Fly — anything serverless)
- Stripe Checkout hosted page (never collect cards inside the extension — PCI violation + Chrome Web Store policy violation)
- Webhook listener at your backend with signature verification + idempotency
- Database for user → Stripe customer → entitlement mapping
- Signed entitlement token (HMAC) the extension caches and verifies offline
- MV3 service worker with
chrome.alarms-based hourly refresh
Build all seven yourself = 3–8 weeks. Use a platform = 5 minutes to 1 hour.
The 4 ways to take payments in a Chrome extension
Option 1: DIY with Stripe + your own backend
The maximum-control path.
Stack: Stripe Checkout + Stripe Webhooks + Stripe Billing Portal + your backend (Cloudflare Workers + D1 / Vercel + Supabase / Railway + Postgres) + your own SDK inside the extension.
Fees: Stripe ~2.9% + 30¢ only. No platform fee.
Honest time estimate:
- First time wiring Stripe webhooks for subscriptions: 3–8 weeks
- Done it before: 1–2 weeks
- Plus ongoing maintenance for webhook schema changes and MV3 lifecycle quirks
The "no-server" variation (Stripe Payment Links + Cloudflare Workers + D1) that floats around as a weekend project: works fine for one-time purchases, quietly breaks on subscriptions. You'll need to extend the logic, and you're now building a full subscription state machine on Cloudflare Workers.
Best fit: Backend teams, $100K+ ARR target, custom pricing models, want zero platform lock-in.
Option 2: ExtensionPay / ExtPay — the indie default since 2021
How it works: Drop in the ExtPay.js library, sign up at extensionpay.com, connect Stripe via Stripe Connect (Standard accounts), call extpay.getUser() to check user.paid.
Fees: 5% platform fee on top of Stripe's ~3%. ~8% total of your revenue.
Pros:
- Fastest path to first payment (~1 hour)
- Established platform — has helped indie devs earn $500K+ combined since 2021
- Open-source client-side code
- Cross-browser (Chrome, Edge, Firefox, Opera, Brave)
- Multi-device user login via email magic link
Cons:
- Predates Manifest V3. Docs literally instruct you to redeclare
ExtPayinsidechrome.storage.local.getcallbacks because service workers don't preserve state. Workable, but it's a tell. - Single
paidboolean — no native multi-tier entitlements (Pro / Team / Lifetime requires hacking on top viauser.plan.nicknamestring parsing) - No paywall editor — you build the UI yourself in your popup, every paywall iteration is a Chrome Web Store re-publish (days of review queue)
- No A/B testing
- No real analytics dashboard — you cross-reference between ExtensionPay's minimal interface and your Stripe dashboard
- GitHub issue #145 documents
user.subscriptionStatusdisappearing across extension versions - ExtensionPay's own creator publicly acknowledged in a 2021 Indie Hackers post that the platform's economics are tight ("with 5% transaction fees I'm usually not making much more than 50 cents per sale") — which limits how fast feature development moves
Best fit: Validating whether anyone will pay at all. $0 → ~$2K MRR phase. Get out before fees compound.
Option 3: CRXPay — the modern MV3-native option
The newer entrant rebuilt from scratch around Manifest V3 and the things ExtensionPay never added.
How it works: CRXPay gives you u/crxpay/sdk with three entry points (background, popup, content) mirroring MV3 context separation. You call configure() once in the service worker, call hasEntitlement('pro') anywhere.
Fees: First $2,500 in tracked revenue is free, then 2.5% — half of ExtensionPay's 5%. On a $10K MRR extension, that's $250/month back vs $500/month.
Pros:
- MV3-native by design. Background script owns SDK state,
chrome.alarms-based refresh (notsetInterval), top-level event listener registration, message router withreturn truedocumented inline, content scripts route through background to dodge CORS. - Multi-tier entitlements.
hasEntitlement('pro' | 'team' | 'lifetime')instead of one boolean. Multi-tier pricing works natively. - HMAC-signed offline entitlement cache. Pro access survives flaky networks and service-worker registration bugs.
- Stripe Connect (Express)-native. Your Stripe account, they never touch your money. One-click Stripe onboarding, no paste-the-key forms.
- No-code paywall editor with 8 templates (games, AI credits, lifetime, trial-first, productivity, etc.). Drop in components, edit copy, theme, layout.
- Remote-deploy paywalls. Change a price, every installed extension fetches the new version on next popup open. No Chrome Web Store re-review for copy changes.
- Bayesian A/B testing built in. Split traffic between variants, the test picks the winner.
- Native MRR / trial→paid / churn / LTV dashboard. No Stripe data export jobs.
- Self-serve Stripe Billing Portal pre-authed via one link. Users update cards, cancel, get receipts.
- Webhooks to your stack (Customer.io, Braze, warehouse).
onPaidlistener pushes from server to extension — no polling. The listener fires in the service worker while the user is still on Stripe's success page.
Migration from ExtPay if you're already on it: API-compatible adapter — u/crxpay/compat-extpay keeps your existing extpay.getUser(), onPaid, and openPaymentPage calls working. One import swap, npx crxpay migrate, users stay subscribed. Typical migration is 2–4 hours of dev work, zero user downtime.
// Was:
import ExtPay from 'extpay';
// Now:
import { ExtPay } from '@crxpay/compat-extpay';
// Everything else keeps working
Time to first payment: ~5 minutes via the quickstart. I timed it.
Best fit: Anyone past the "do users pay" validation phase. Anyone shipping multi-tier pricing. Anyone who wants A/B testing without rebuilding the infrastructure. Anyone who wants the MV3 lifecycle work handled correctly without writing it themselves.
Watch out: Newer product than ExtensionPay. The mitigation is the compat adapter — if it doesn't work out, you leave the way you came in. Stripe Connect means your customer data and revenue history are portable regardless.
Option 4: Merchant of Record (Lemon Squeezy, Paddle, Dodo Payments)
The "they handle global tax compliance" path.
Fees: Lemon Squeezy 5% + 50¢. Paddle 5% + 50¢. Dodo varies.
Pros: EU VAT, UK VAT, US sales tax nexus, Indian GST — they're the seller of record. Significant if you sell internationally.
Cons: No Chrome extension SDK. You're still building the entitlement check, the MV3 service-worker plumbing, the message routing, the signed offline cache yourself. They give you Stripe-like checkout + tax compliance; they don't give you extension primitives.
Best fit: Extensions with serious EU/UK/global revenue where tax compliance is the actual headache, and the team can build the extension-side layer.
Head-to-head comparison
| Approach | Setup time | Platform fee | MV3-native | Paywall editor | Multi-tier entitlements | A/B testing |
|---|---|---|---|---|---|---|
| DIY Stripe | 3–8 weeks | 0% | You build it | You build it | You build it | You build it |
| ExtensionPay (ExtPay) | ~1 hour | 5% | Workarounds | No | One bool | No |
| CRXPay | ~5 min | 2.5% (first $2.5K free) | Yes | Yes (8 templates) | Yes | Yes (Bayesian) |
| Lemon Squeezy / Paddle | ~1 day | 5% + 50¢ | You build it | No | Limited | No |
The 8 MV3 footguns that break Chrome extension payments
This is the part nobody documents and the reason 80% of "my payment isn't unlocking" support tickets exist. If you're building yourself or evaluating a tool, test for these.
1. Service workers die after ~30 seconds idle. Anything in a global variable is gone. Persist payment state to chrome.storage.local:
// ❌ BREAKS in MV3
let userIsPaid = false;
// ✅ WORKS
async function isPaid() {
const { sub } = await chrome.storage.local.get('sub');
return sub?.paid === true;
}
2. setInterval and setTimeout don't survive a service worker restart. Hourly entitlement refresh with setInterval will fire once, then never. Use chrome.alarms:
// ❌ BREAKS in MV3
setInterval(refreshSub, 60 * 60 * 1000);
// ✅ WORKS
chrome.alarms.create('refreshSub', { periodInMinutes: 60 });
chrome.alarms.onAlarm.addListener((a) => {
if (a.name === 'refreshSub') refreshSub();
});
3. return true in your async message listener. Without it, Chrome closes the message channel before your async response fires. Popup spinner hangs forever after the user pays:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'CHECK_SUB') {
checkSub().then(sendResponse);
return true; // ← THIS LINE OR EVERYTHING BREAKS
}
});
4. Content scripts cannot fetch your payment API directly. CORS blocks them. Route through the service worker via chrome.runtime.sendMessage.
5. host_permissions must include your payment API domain. Otherwise fetch() silently fails in production but works in dev. Discovered exactly once by every developer.
6. Remote code execution is banned in MV3. Don't try to load Stripe.js from a CDN. Bundle it or — much better — redirect to hosted Stripe Checkout in a new tab.
7. Service workers occasionally fail to register. Long-running Chromium bug — the SW stops registering at random for users. Your payment flow needs to survive this. A signed offline entitlement cache means Pro access keeps working when the SW is broken.
8. The popup window dies when the user clicks away. Never put payment state in the popup. The popup is a thin view; the background is the source of truth. The user clicks "Upgrade," your extension opens hosted Checkout in a new tab, the popup dies, the user pays, webhook fires, service worker receives the update — and the user reopens the popup to see Pro features unlocked. This sequence is non-negotiable.
The "can users hack my extension to bypass payment?" question
Short answer: yes, partially, and you should build for the 99% who don't.
Long answer: Chrome extension source code is technically open — users can unpack any extension and modify the JavaScript. A determined user can edit the entitlement check to always return true. There's no perfect DRM.
What you can do:
- Server-side feature gating for expensive operations. If your extension calls an LLM that costs you real money, gate that call server-side (your backend checks the entitlement before responding). Even if the user fakes
paid: trueclient-side, your backend refuses to do the expensive work. - HMAC-signed entitlement tokens. Backend signs the entitlement payload; extension verifies the signature before unlocking. The user can edit
chrome.storage.localto put any value, but your verification function rejects it. - Server-side validation on critical actions. Anything that affects another user, sends an email, or hits a paid API should validate the entitlement on your backend, not in the extension.
- Don't worry about UI gates for cheap features. If a feature is "show this checkbox in the popup," gating it in the extension is fine. The 1% who would unpack and modify the extension to access it wouldn't have paid anyway.
Mozilla's official guidance: "It's better to focus on making a good product for the majority of users that won't hack your extension."
How to actually pick
Stripped of marketing:
- First-ever payment integration, validating market? ExtensionPay. Ship today. Don't optimize for fees on $0 revenue.
- Past validation, want a real paywall + A/B testing + analytics? CRXPay. The fee math pays for the migration in the first month and the ExtPay-compatible adapter is one import swap.
- Backend team, $200K+ ARR target, zero lock-in priority? DIY Stripe. Plan 4–6 weeks honestly.
- Global revenue, tax compliance is the real headache? Lemon Squeezy / Paddle. Build the extension-side plumbing yourself.
The pre-launch checklist
The 12-item list that prevents 90% of post-launch payment failures:
- [ ] Stripe test card
4242 4242 4242 4242works end to end - [ ] Failed payment test card
4000 0000 0000 0341triggers dunning correctly - [ ] 3DS card
4000 0027 6000 3184works (EU traffic) - [ ] Kill the service worker in
chrome://serviceworker-internals— does Pro state recover? - [ ] Reinstall test — does Pro come back via email after uninstall?
- [ ] Cancel test — keeps access until period end?
- [ ] Refund test — entitlement actually revoked, when?
- [ ] Offline mode — does Pro still work (signed cache)?
- [ ] Edge browser test (Chromium, doubles TAM with zero code change)
- [ ] Clean Chrome profile test (no stale
chrome.storage.localcache) - [ ] Privacy policy linked in Chrome Web Store dashboard
- [ ] "Cancel anytime" visible somewhere users can find it
TL;DR
- "Chrome extension pay" splits three ways: how users pay (click Upgrade inside the extension), how developers take payments (third-party stack since Chrome Web Store payments died in 2021), and the brand ExtensionPay.
- Every Chrome extension payment system needs 7 components: extension + service worker + backend + webhook listener + DB + signed entitlement + hosted Stripe Checkout. None are optional.
- DIY Stripe = 3–8 weeks of work, 0% platform fee. Best for backend teams.
- ExtensionPay (ExtPay) = ~1 hour to ship, 5% fee, predates MV3, no paywall editor / A/B / analytics. Best for validation phase.
- CRXPay = ~5 minutes to ship, 2.5% fee with first $2,500 free, MV3-native by design, paywall editor + A/B testing + analytics + multi-tier entitlements included, drop-in ExtPay-compatible migration adapter. Best for anyone past validation.
- Lemon Squeezy / Paddle = ~1 day, 5% + 50¢, handles global tax. Best for international revenue.
- 8 MV3 footguns will silently break your payment system:
setInterval, missingreturn true, CORS on content scripts, missinghost_permissions, remote code bans, service worker death, registration failures, popup-held state. - Stripe Connect across all third-party options means your customer data and revenue history are portable — you're never locked in.
If you've shipped payments in a Chrome extension and have a different read on any of the time estimates above — especially the "DIY Stripe in 1–2 weeks the second time around" claim — drop it in the comments. The public data on this is thin and mostly self-reported. The more real numbers we share, the easier it gets for the next wave of paid extensions to plan their stacks.
Posted May 10, 2026, 10:03 PM UTC