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, andtoolaby.d.ts, its types.- The Toolaby popup,
toolaby-popup.htmlandtoolaby-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 frommodules/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, thestoragepermission and Toolaby's address underexternally_connectableinto 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.jssaysaccountBar: false; - copies the Toolaby popup, its side panel,
toolaby.jsandtoolaby.config.jsinto 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:
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:
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-mv3Load .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 packThe 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.