DEV Community

James
James

Posted on

How I monetized my Chrome extension in ~5 minutes with Stripe (no backend, no content scripts)

I'm in full vibe-coding mode right now - shipping features fast, iterating in public, trying not to accidentally build a whole SaaS when all I wanted was one thing:
Let people pay me for my browser extension.
I'd been putting it off because "payments" usually means:
setting up a backend
wiring webhooks
dealing with auth
or worst case… shipping a content script and getting those lovely "This extension can read and change data on all websites" warnings 😭

Then I tried BillingExtensions and had a genuine holy shit moment. I fully expected to spend at least an hour fighting with the importScripts syntax or debugging why my service worker wouldn't register the SDK. Instead, I went from "free extension" to "Stripe checkout → paid user" in about five minutes.
Not "five minutes if you squint." 
Like… actually five minutes, because most of the scaffolding is automated.
This is exactly what I did.

  • - One thing I didn't expect (and why this is different) Even though this post is "no backend required"… BillingExtensions still gives you a real backend option when you need it:
  • a server-side API to verify paid status without trusting the extension
  • webhooks to keep your database in sync with Stripe/subscriptions That matters if you ever: gate LLM calls or expensive APIs need a server to check access (instead of passing "paid=true" from the client) want clean subscription state in your own DB

So you can vibe-code the first version client-only… and still have a path to do it properly later.
(That's the part I couldn't find in most extension payment setups without building it all myself.)

  • - The only "setup" I did (before code) I went to billingextensions.com and did the basic order-of-operations: 1) Connect Stripe  2) Create an "App" (my extension)  3) Create a plan (monthly / yearly / whatever) That gives you two values:
  • appId
  • publicKey And now you're ready for the fun part.
  • - Vibe-coder path: copy/paste a Cursor prompt I stumbled across this copy/paste prompt for Cursor on the BillingExtensions website that handles the full setup. I was skeptical at first - I didn't think a prompt would correctly identify my specific manifest permissions or service worker type - but This is the most "vibe coding" thing about it. Because instead of me thinking about: manifests permissions service worker type (classic vs module) where to put the SDK file

…I just pasted the prompt, hit enter, and it applied the changes.
It actually worked. It detected whether I was using a classic vs. module worker and applied the changes for me. If you're a dev who refuses to manually edit a manifest file in 2026, this is the route.

  • - The "I like terminals" path: one init command If you want the CLI version, it's one command: npx -y -p @billingextensions/sdk bext init "appId" "publicKey" I had to try this, even though I was very happy with the above, and as with the cursor prompt, this was also seamless. It had the same detection qualities (it knew I was using a module worker for example) - but this allowed me to control more of the process. This is why it's so fast. It scaffolds the boring stuff for you: adds the required Chrome permissions (storage) adds the right host_permissions detects whether your service worker is classic (importScripts) or module (ESM) injects a working service-worker snippet and if you don't have a build step, it can drop in the prebuilt SDK file next to your service worker

So you don't lose 45 minutes to "why isn't Chrome importing this."

  • - The moment it clicked: the background listener The part that made it feel real wasn't even checkout - it was seeing the extension react when the user becomes paid. In my MV3 service worker: `


// background.js (service worker)
const client = BillingExtensionsSDK.createBillingExtensionsClient({
appId: "my-new-app",
publicKey: "app_publicKey",
});
// keeps status warm + synced across contexts
client.enableBackgroundStatusTracking();
// react to upgrades/downgrades
client.onStatusChanged((next, prev, diff) => {
if (!prev?.paid && next.paid) {
console.log("User upgraded ✅", next);
// unlock pro features, enable menus, etc.
}
if (prev?.paid && !next.paid) {
console.log("User downgraded ❌", next);
}
console.log("diff", diff);
});

`
That's the "oh… I'm actually running a business now" moment.

  • - The button that starts the money In my popup/options UI, I added a "Go Pro" button: await client.openManageBilling(); Click → Stripe opens in a new tab → checkout → done. No backend. No content script.
  • - The paywall gate (two lines) Before letting someone use the paid feature: const status = await client.getUser(); if (!status.paid) return; That's it.
  • - What the user flow feels like This is what I wanted: 1) user clicks "Go Pro" 2) Stripe checkout opens 3) user pays 4) they come back / reopen extension 5) it's paid - features unlocked And it works without content scripts (which is the whole point, for me).
  • - Why I didn't touch a content script I'm not against content scripts, but I didn't want them to be required. Most users don't keep your extension UI open while they pay - they click checkout, pay, and come back later. By avoiding content scripts, I kept my permissions footprint tiny. This is a massive win for reducing review friction: because I'm not asking for permission to "read and change data" on every website, the Chrome Web Store review process is usually much faster and less stressful. BillingExtensions keeps the default path clean.
  • -
    What's next
    This post is the "vibe coding" version: get paid fast. It works for the vast majority of extensions where you just need to gate a few UI features.
    But if you're like me, eventually you'll want to move past the client-only "trust the user" phase and add some real security. The good news is that the same setup scales into a proper backend flow without needing a total rewrite.
    Next, I'm writing the "protect my LLM tokens" version, covering:
    Server-side verification: How to verify paid status without trusting the extension.
    Webhook sync: Keeping your database in sync with Stripe events.
    Secure gating: How to actually protect expensive LLM calls or APIs so people can't just spoof your "paid" status.

  • -
    Resources for fellow vibe-coders
    If you want to try this setup yourself, here are the tools I used to get it running in five minutes:
    The Platform: BillingExtensions
    The Documentation: Setup & API Guides
    The SDK: @billingextensions/sdk on NPM
    The Source: GitHub Repo

  • -

Top comments (0)