# Gate your extension

> Check what a device may do before the action, and show the paywall when it may not.

Gating is three calls. `gate()` decides at the action and enforces. `has()` answers for display: a lock, a badge, a greyed button. `showPaywall()` answers a refusal with the paywall, in your brand and the buyer's language.

This page shows where each call goes: in the background, in your popup or side panel, in a content script, and in a page open in a tab. What the Free plan holds and which plan holds which feature are set in the dashboard; see [Access and features](https://toolaby.app/docs/free-plan.md).

## Before you begin

* An extension wired to a tool: `toolaby.js` and `toolaby.config.js` beside your code. See the [Quickstart](https://toolaby.app/docs/quickstart.md).
* Under the tool's **Pricing**: the Free plan (**Access**), your plans, and the features each plan holds.

## 1. Choose what opens first

`npx -y toolaby@latest wire` puts the Toolaby popup in front of your popup. It opens yours at once, unless the person may not use the extension yet: then it shows the sign-in or the plans. Keep it in front: whatever you choose under **Access** in the dashboard shows at the next open, with no new build (on Live within about six minutes: installed copies ask Toolaby at most every five).

`npx -y toolaby@latest create` makes a new extension this way too. The alternative is your popup first, with every check in your code, for a popup that draws Toolaby's panel itself: Plasmo, or a design of your own. Such a popup draws the panel only when the person may not use the extension yet, or after a refusal, never under its own controls while they may.

|                              | The Toolaby popup in front                                                                                                                 | Your popup first                                                                                     |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| At every open                | Your popup, at once; the sign-in or the plans first when nothing is free and no plan is held, or an account is required and none signed in | Your popup, at once                                                                                  |
| A Free plan that counts uses | Your `gate()` spends them at the paid action. Code that calls no `gate()`: each open spends one                                            | Only the action spends                                                                               |
| Where you call `gate()`      | At each action you sell — or nowhere, and each open is the use                                                                             | At each action you sell                                                                              |
| A change under Access        | Shows at the next open                                                                                                                     | Shows where your code calls `gate()`                                                                 |
| Suits                        | Every extension                                                                                                                            | A popup that draws the panel itself, or code that switches its popup with `chrome.action.setPopup()` |

`countOpens` in `toolaby.config.js` says whether an open is a use, and `wire` and `upgrade` write it from your code: `false` once it calls `toolaby.gate()`. To put the Toolaby popup in front of an extension whose popup is first, run `npx -y toolaby@latest upgrade <tool-id> --toolaby-popup`.

To put your popup first, run `npx -y toolaby@latest upgrade <tool-id> --own-popup`, which does steps 1 to 3, and step 4 too in a plain extension or a WXT project, or do them by hand:

1. In `manifest.json`, set `action.default_popup` to your own page, and `side_panel.default_path` to yours if you have one.
2. In `toolaby.config.js`, set `appPopup: ''` and `appSidePanel: ''`.
3. Keep `toolaby-popup.html`, `toolaby-sidepanel.html` and their scripts in the extension: `showPaywall()` opens them.
4. Put the account bar in your popup and side panel: `<toolaby-account></toolaby-account>`. See [The account bar](https://toolaby.app/docs/toolaby-popup.md#the-account-bar).

From then on, `upgrade` keeps your popup first and writes Toolaby's pages beside it. To put the Toolaby popup back in front, run `upgrade <tool-id> --toolaby-popup`.

The rest of this page applies to both.

## 2. Gate an action in the background

Where your extension does the work it charges for, ask first. Return the permit when it refuses, so the page that asked can show the paywall.

```js title="background.js"
import { toolaby } from './toolaby.js';

toolaby.startBackground({
  handlers: {
    async exportAll({ tableIds }) {
      const permit = await toolaby.gate({ feature: 'export' });
      if (!permit.allowed) return { permit };
      return { ok: true, file: await buildExport(tableIds) };
    },
  },
});
```

This background is a module (`"type": "module"` in the manifest), which imports the client. In a classic background, which the Toolaby background loads with `importScripts()`, `toolaby` is already a global: leave out the `import` line.

`gate()` answers on the device when it can. It makes one request to count a metered use, or to ask about a feature the device's last token does not list. See [`gate()`](https://toolaby.app/docs/reference/client.md#gate) for every answer.

## 3. Show the paywall from your popup

```js title="popup.js"
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>`.

The popup gives way to the paywall for that reason: only the plans that hold `export`, the free-use meter, or the sign-in doors. It has a **Back** button. Once the device may continue (the buyer bought, signed in, or holds the feature), it returns to your popup by itself.

In a side panel, the paywall opens at the panel's width when your page says it is a side panel:

```html
<html data-toolaby-surface="sidepanel">
```

## 4. Draw locks with `has()`

```js
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.

Open the paywall from a lock with `showPaywall()`, rather than a screen of your own or `openPaymentPage()`. Toolaby counts the paywall it shows, and what opened it, for [Analytics → Features](https://toolaby.app/docs/analytics.md#what-is-counted); a screen of your own is not counted.

Call `toolaby.refresh()` once when your popup opens, so a purchase made elsewhere is known before you draw.

## 5. Count uses at the action

With **Access → Free uses**, pass the number of uses the action spends:

```js
const permit = await toolaby.gate(invoices.length);
if (!permit.allowed) return { permit }; // reason 'limit', remaining, limit
```

It spends them only when it allows. Show what is left from the device's record, which needs no request:

```js
const { usage } = await toolaby.getUser();
if (!usage.unlimited) meter.textContent = `${usage.remaining} of ${usage.limit} free`;
```

## 6. From a content script

A content script bundled with `toolaby.js` (WXT, CRXJS and Plasmo bundle them) calls the same methods; each asks the background. `showPaywall()` there opens a new tab, since the page is not your extension's: the checkout page, or the sign-in page for a `sign_in` refusal. For a refused build it opens nothing.

A content script that is not bundled sends the same messages the client sends:

```js
const permit = await chrome.runtime.sendMessage({ action: 'toolaby.gate', count: 1, feature: 'export' });
if (!permit.allowed) chrome.runtime.sendMessage({ action: 'toolaby.open', page: 'payment', plan: permit.needs?.[0] });
```

## 7. From a page open in a tab

An editor or a report page of your extension, open in a tab, would lose its work if it navigated away. There `showPaywall()` opens the checkout page in a new tab, or the sign-in page for a `sign_in` refusal, and leaves the page as it is. After the purchase the extension is told, `onPaid` fires in the background, and the page's next `has()` answers yes.

## 8. Refused builds

When the tool's **Versions** refuse this build, every `gate()` answers `{ allowed: false, blocked: true, message }`. `showPaywall()` shows your message in the popup or side panel.

## 9. Try every refusal on Test

| Refusal   | How to produce it                                                                                                                                                          |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `feature` | A feature on a paid plan only, and a device that holds no plan.                                                                                                            |
| `limit`   | **Access → Free uses**, then spend them.                                                                                                                                   |
| `paid`    | **Access → Nothing free**.                                                                                                                                                 |
| `sign_in` | **Pricing → Access → Require an account**, and sign out from the account bar.                                                                                              |
| `blocked` | **Versions**: a minimum above the build's version.                                                                                                                         |
| Unlocked  | Buy with `4242 4242 4242 4242`, or **Grant access** on your customer's page under **Customers**. Sign in in the extension first, or add yourself with **Create customer**. |

See [Testing](https://toolaby.app/docs/testing.md) for purchases that fail, renew or are refunded.

## See also

* [Access and features](https://toolaby.app/docs/free-plan.md)
* [Client](https://toolaby.app/docs/reference/client.md)
* [Toolaby popup](https://toolaby.app/docs/toolaby-popup.md)
* [Ship to the Chrome Web Store](https://toolaby.app/docs/ship.md)
