Toolaby

Add payments to a WXT extension

Wire a WXT extension to Toolaby with one command, gate the paid action in the background, show the paywall, then build, check and pack it.

Toolaby4 min readUpdated

This guide adds payments to a Chrome extension built with WXT. At the end, the project carries Toolaby's client, its background starts Toolaby, and the paid action shows the paywall when it is refused. Buyers pay through Stripe Checkout, on your Stripe account. Nothing is pasted into wxt.config.ts: a WXT module does the wiring at build time.

Before you begin

  • A tool registered in the dashboard, and its tool id: Tool id on the tool's page, or npx -y toolaby@latest tools. See the Quickstart.
  • Node.js 20 or later, for the command line.
  • A plan under the tool's Pricing, and Access set to Free uses or Nothing free, to see the paywall.
  • Nothing is needed from Stripe on Test: Toolaby makes a Stripe test account for your workspace.

Starting from nothing? Toolaby's WXT starter is a finished React extension wired this way, with a Free plan, a daily allowance and a Pro feature. Its npm run setup makes your tool and wires it. To start it with your own name, colour, pages and prices, use the extension generator.

1. Wire the project

In the project's folder, run:

npx -y toolaby@latest wire <tool-id>

The first run shows a code to approve in your browser. On Test, the command writes the tk_test_… tool key. It writes these files at the project root:

  • toolaby.js: the client and the paywall, one ES module file. It is not an npm package.
  • toolaby.config.js, which holds the tool key, and toolaby.d.ts, its types.
  • The Toolaby popup, toolaby-popup.html and toolaby-popup.js, and its side panel when you have one.
  • toolaby.background.js, used when the project has no background of its own.
  • A WXT module, modules/toolaby.wxt.ts. WXT loads modules from modules/ by itself.

The command prints one row per change: ✓ done, ! worth a look, → a step for you.

2. What the module does at build time

wxt.config.ts and entrypoints/background.ts stay as you had them. When WXT builds, the module:

  • merges the extension's key, the storage permission and Toolaby's address under externally_connectable into your manifest;
  • puts the Toolaby popup in front of your popup, and Toolaby's side panel in front of your side panel;
  • starts Toolaby in your background, after your own code has run;
  • adds the account bar at the foot of your popup and side panel, unless toolaby.config.js says accountBar: false;
  • copies the Toolaby popup, its side panel, toolaby.js and toolaby.config.js into the build output.

No host permission is added for Toolaby, so Chrome shows your users no new warning at install.

3. Gate the paid action

To gate an action, call startBackground() with your handlers. A call of yours wins over the module's. From the example project, shortened:

entrypoints/background.ts
import { toolaby } from '../toolaby.js';

export default defineBackground(() => {
  toolaby.startBackground({
    handlers: {
      async countWords() {
        const permit = await toolaby.gate();
        if (!permit.allowed) return permit;
        const words = await countWordsOnPage(); // the work you charge for
        return { allowed: true, words };
      },
    },
  });
});

gate() with no argument counts one free use. gate({ feature: 'export' }) asks whether the buyer's plan holds a feature. See Gate your extension.

4. Show the paywall

The popup asks the background, and answers a refusal with the paywall:

entrypoints/popup/main.ts
import { toolaby } from '../../toolaby.js';

const out = document.getElementById('out')!;
document.getElementById('count')!.addEventListener('click', async () => {
  const r = await chrome.runtime.sendMessage({ action: 'countWords' });
  if (!r.allowed) return toolaby.showPaywall(r);
  out.textContent = `${r.words} words`;
});

The popup's index.html loads it as a module: <script type="module" src="./main.ts"></script>. A side panel page sets <html data-toolaby-surface="sidepanel">, so the paywall opens at the panel's width. Since your code calls gate(), wire writes countOpens: false: opening the popup spends no free use.

Content scripts are bundled too, so one can import toolaby.js. Its methods ask the background, and its showPaywall() opens the checkout page in a new tab.

5. Build, check and load

npm run build
npx -y toolaby@latest check --built .output/chrome-mv3

Load .output/chrome-mv3 at chrome://extensions (Developer mode, then Load unpacked). When it runs, step 2 of the tool's Set up page says Checked in. Add --browser to check to load the build in Chromium and ask its background.

WXT builds every entrypoint, whether or not the manifest names it, so your popup stays built and the Toolaby popup opens it. npx -y toolaby@latest upgrade <tool-id> replaces the module with the current one. The complete example, built in Toolaby's CI, is wxt-word-count.zip.

6. Pack for the Chrome Web Store

Put the Live key in, build, and pack:

npx -y toolaby@latest wire <tool-id> --live --force
npm run build
npx -y toolaby@latest pack

The module takes the Live address from the key at build time. pack finds .output/chrome-mv3 and writes <name>-<version>.zip, without the manifest's key. It refuses a build that carries a tk_test_ key, which would sell on Test with test cards. See Ship to the Chrome Web Store.

Next steps

All guides