Back to blog

How to add payments to a Chrome extension

ExtensionBill

This is the complete, code-first path to charging money inside a Chrome extension: install an SDK, check whether the user has paid, open a checkout when they haven’t, and let a Merchant of Record handle sales tax. No backend, no webhook plumbing, no license server.

If you’re still choosing a monetization model (one-time license vs subscription vs freemium), read How to Monetize a Chrome Extension in 2026 first, then come back here for the implementation.

What you’ll build

By the end of this guide your extension will:

  1. Know whether the current user has paid (user.paid).
  2. Open a hosted checkout page when they haven’t.
  3. Unlock features the moment a payment completes.
  4. Collect VAT, GST, and sales tax correctly worldwide, without you registering anywhere.

Total integration is a few dozen lines. Everything runs from your extension’s existing code; there is no server to deploy.

Prerequisites

  • A Chrome extension on Manifest V3 (a background service worker, not a persistent background page).
  • Node and npm if you bundle your extension; a plain <script> include works too.
  • An ExtensionBill account (free tier) with an extension registered, which gives you the extension ID used below.

Step 1: Install the SDK

npm install extensionbill

If you don’t use a bundler, download the standalone build and include it in your extension package instead. Either way, the SDK ships as a single file with no runtime dependencies.

Step 2: Initialize in your background service worker

The SDK needs to run in your background context so it can keep paid status fresh and react to checkout completions:

// background.js
import ExtensionBill from 'extensionbill'

const extpay = ExtensionBill('your-extension-id')
extpay.start()

start() wires up the internal message listeners. Call it once, at the top level of the service worker, so it re-registers every time the worker wakes up.

Step 3: Check paid status before unlocking features

Anywhere in your extension (popup, options page, content script relay), fetch the user and branch on paid:

const user = await extpay.getUser()

if (user.paid) {
  unlockProFeatures()
} else {
  showUpgradePrompt()
}

The status check is resolved by the hosted verification layer, so it works even though your extension has no server of its own. Treat user.paid as the single source of truth; don’t cache it in chrome.storage where it can go stale or be edited.

Step 4: Open checkout

When a free user clicks your upgrade button, send them to the hosted checkout:

upgradeBtn.onclick = () => extpay.openCheckout()

Checkout runs on your Merchant of Record’s hosted payment page: card fields, wallets, receipts, and tax calculation are all handled there. Your extension never sees card data, which also keeps you clear of PCI scope.

Step 5: React to successful payment

Unlock immediately after purchase instead of waiting for the next status check:

extpay.onPaid.addListener((user) => {
  unlockProFeatures()
})

This fires in the background context when the checkout completes, so the paid experience starts without the user reloading anything.

Step 6: Let the Merchant of Record handle tax

This step is configuration, not code, and it’s the one most tutorials skip.

When you sell software to consumers worldwide, VAT (EU, UK), GST (Australia, India, Canada), and US sales tax become your problem the moment you cross each jurisdiction’s threshold. Registering and filing in dozens of places is not realistic for an indie developer.

A Merchant of Record (MoR) solves this by being the legal seller of record: it collects the right tax at each buyer’s location and remits it. In your ExtensionBill dashboard, connect the MoR you prefer (Polar, Paddle, Lemon Squeezy, Creem, or Dodo Payments). Checkout and tax compliance then run through that provider, while you keep your pricing, your payouts, and your customer relationships.

Step 7: Test the full flow

  1. Load your unpacked extension and confirm getUser() resolves with paid: false for a fresh profile.
  2. Click your upgrade button and complete a test-mode checkout (your connected MoR provides test cards).
  3. Confirm the onPaid listener fires and features unlock without a reload.
  4. Reload the extension and confirm paid: true persists.

Troubleshooting

  • getUser() never resolves in a content script. Content scripts should ask the background worker for status via messaging rather than calling the SDK directly; keep SDK calls in extension pages and the service worker.
  • Checkout opens but paid status doesn’t update. Make sure extpay.start() runs at the top level of the service worker; if it’s inside an event callback it won’t re-register when the worker restarts.
  • Manifest V2 extension. Migrate first; Chrome is done with MV2. See the Manifest V3 migration guide.

Where this fits in the bigger picture

Adding the payment integration is the mechanical part. Picking the model (one-time vs subscription vs freemium), pricing it, and launching it well are covered in the companion guide: How to Monetize a Chrome Extension in 2026. If you’re comparing tools first, see ExtPay alternatives, compared.