Add a paywall to a Chrome extension
Choose what is free, gate the paid action, show the paywall, sell features and add the account bar to a Chrome extension.
This guide adds a paywall to a Chrome extension with Toolaby. At the end, a Free plan says what people get before paying, and the Toolaby popup stands in front of your popup. Your paid action asks Toolaby first and answers a refusal with the paywall. Paid features unlock by plan, and the account bar sits at the foot of your popup.
Before you begin
- An extension wired to a tool:
toolaby.jsandtoolaby.config.jsbeside your code. See the Quickstart. - A plan under the tool's Pricing, added with New plan: once (a lifetime licence), monthly or yearly.
- The tool on Test, where nothing is charged.
1. Choose what the Free plan holds
The Access card at the top of the tool's Pricing page sets what a person gets before paying:
| Before paying | access | The extension |
|---|---|---|
| Everything free | open | Nothing is counted. A feature a plan sells is still refused until bought. |
| Free uses | free_uses | Works until the uses you set are spent, then the paywall offers a plan. |
| Nothing free | paid | Shows the paywall until the person holds a plan, a trial or a grant. |
Free uses with no account required is the classic freemium extension: try it, then pay. The uses are counted in total, or start again each day, week or month, in UTC.
The card's Require an account switch decides who the uses are counted for. Off, they are counted per device, and removing the extension and adding it again makes a new device. On, nothing works until the person signs in, and every device they sign in on spends the account's count. To keep a reinstall from giving the uses again, require an account.
Select Save, or set it from the command line. --sign-in-required requires an account:
npx -y toolaby@latest access <tool-id> --access free_uses --free-uses 3 --free-uses-per day2. Keep the Toolaby popup in front
npx -y toolaby@latest wire <tool-id> makes the Toolaby popup the extension's action popup, in front of yours. At every open, it opens your popup at once, unless the person may not use the extension yet. Then it shows the sign-in or the plans.
countOpens in toolaby.config.js says whether an open spends a free use. wire and upgrade write it false once your code calls toolaby.gate(): opening spends nothing, and your gate() spends the uses at the paid action. Where your code calls no gate(), it is true, and each open spends one.
Keep the Toolaby popup in front: a change under Access then shows at the next open, with no new build. Putting your popup first suits a popup that draws the paywall itself, or code that calls chrome.action.setPopup(). npx -y toolaby@latest upgrade <tool-id> --own-popup puts it first; --toolaby-popup puts the Toolaby popup back.
3. Gate the paid action
Where your extension does the work it charges for, ask first. Return the permit when gate() refuses, so the page that asked can show the paywall.
import { toolaby } from './toolaby.js';
toolaby.startBackground({
handlers: {
async exportAll({ tableIds }) {
const permit = await toolaby.gate();
if (!permit.allowed) return { permit };
return { ok: true, file: await buildExport(tableIds) };
},
},
});Call startBackground() once, synchronously, at the top level of the background. This one is a module ("type": "module" in the manifest). In a classic background, toolaby is already a global: leave out the import line.
Call gate() before the action, not when the popup opens. It answers on the device when it can, and makes one request to count a metered use. To spend several uses, pass a whole number from 1 to 1000, such as toolaby.gate(invoices.length). It spends them only when it allows.
4. Show the paywall
Answer the refused permit in your popup:
import { toolaby } from './toolaby.js';
document.getElementById('export').addEventListener('click', async () => {
const r = await chrome.runtime.sendMessage({ action: 'exportAll', tableIds });
if (r.permit) return toolaby.showPaywall(r.permit);
save(r.file);
});The popup loads this script as a module: <script type="module" src="popup.js"></script>. Your popup then gives way to the paywall for the permit's reason:
reason | Why | The paywall shows |
|---|---|---|
limit | The free uses are spent. | The free-use meter, then the plans. |
paid | Nothing is free, and no plan is held. | The plans. |
feature | The device does not hold the feature. | Export is in Pro, and only the plans that hold it. |
sign_in | An account is required, and none is signed in. | Continue with email, and Continue with Google where it is on (Configure → Sign-in). |
The paywall has a Back button, and returns to your popup by itself once the device may continue. Showing it spends nothing. It is drawn in your brand from Configure → Branding, in the buyer's language.
showPaywall() throws in the background. From a content script or an extension page in a tab, it opens the checkout page in a new tab. For sign_in, it opens the sign-in page. In a side panel, set data-toolaby-surface="sidepanel" on the page's <html>, or the paywall opens at popup width.
5. Sell features
Without features, a paid plan unlocks the whole tool. To sell part of it, use the Features card under Pricing, which takes entries once the tool has a paid plan. Add feature asks for a Name, which buyers read, and a Key, which your code asks for. It makes the key from the name: Export becomes export. Tick the plans that hold it, then select Save.
Gate the feature at the action. A refusal carries needs, the ids of the plans that hold the feature, and the paywall offers only those:
const permit = await toolaby.gate({ feature: 'export' });
if (!permit.allowed) return { permit }; // reason 'feature', and needsDraw a lock with has():
const locked = !(await toolaby.has('export'));
exportButton.classList.toggle('locked', locked);
exportButton.addEventListener('click', () => {
if (locked) return toolaby.showPaywall({ feature: 'export' });
runExport();
});has() makes no request and spends nothing. It does not stop anything by itself: the action still calls gate(), since code on the buyer's machine can be changed. Call toolaby.refresh() once when your popup opens, so a purchase made elsewhere is known before you draw.
npx -y toolaby@latest check reports FEATURE_KEY_UNKNOWN when your code gates on a key the tool does not have.
6. Add the account bar
Once a device is through, the buyer sees your popup, not the Toolaby popup. The account bar shows them who is signed in, their plan, their account and sign out:
<script type="module" src="toolaby.js"></script>
<toolaby-account></toolaby-account>It shows the free uses left or the signed-in buyer's plan, with Upgrade while there is a plan to buy. Upgrade opens the plans, as showPaywall() does. In a plain extension, wire and upgrade write the tag at the foot of your popup and side panel. In a WXT project, the module adds it as it builds. A CRXJS or Plasmo popup takes the tag by hand.
Place the tag yourself to put it elsewhere, or set accountBar: false in toolaby.config.js to keep it out. Its scheme attribute, light or dark, sets its colours. Sign out in the bar signs the device out on the account too.
7. Change the paywall from the dashboard
Installed copies follow the dashboard without a new build:
| Change | When installed copies see it |
|---|---|
| Access, on Test | Within seconds. |
| Access, on Live, with the Toolaby popup in front | Within about six minutes: the popup asks Toolaby at most once in five minutes when it opens. |
| Access, on Live, where the Toolaby popup does not open | Within an hour. |
| A refund, a cancellation that has run out, or a revoke | At the next check: the client asks for a fresh entitlement token every 15 minutes. |
The paywall's plans and brand are cached on the device for a day. They are fetched again at the open after you change Pricing or Branding.
The tool key also carries a copy of the Free plan, which a new copy shows until Toolaby first answers. After changing what the Free plan holds, run npx -y toolaby@latest upgrade <tool-id>, then rebuild.
8. Try it on Test
Produce each refusal:
limit: Access → Free uses, then spend them.paid: Access → Nothing free.sign_in: Access → Require an account, then sign out from the account bar.feature: a feature on a paid plan only, and a device that holds no plan.
Then buy: select a plan's button in the paywall, then its button on the checkout page. A subscription asks you to sign in first. Pay on Stripe's page with 4242 4242 4242 4242, any future date and any CVC. The popup opens your popup, and you appear on the tool's Customers page.
Next steps
Add a free trial to a Chrome extension
Let people try your Chrome extension before they pay, with free uses, a free trial on a subscription plan, or a trial with no card.
Payments for WXT, Plasmo and CRXJS extensions
Wire a WXT, Plasmo or CRXJS extension to Toolaby, gate the paid action, show the paywall, then build, check and pack it.