Toolaby Wall

Wall popup

The popup — or side panel — the wiring puts in front of yours, and the paywall it shows.

Which surfaces to give your extension, and how to start each one, is Surfaces. This page is what the platform's own page does once it is there.

The wiring sets toolaby-popup.html as the extension's action popup. At every open it reads the tool's policy and the device's state, then either opens your own popup (appPopup in toolaby.config.js) or shows the paywall. Your popup is unchanged. An extension with a side panel gets the same in front of that — see The side panel.

When it opens your popup

When gate() would allow: the Free plan holds everything, the person is signed in where sign-in is required, free uses remain, or the device holds a plan, trial or grant. Opening counts as one use when the Free plan is metered.

To count at the action instead, put your popup first: your popup is the action popup, appPopup: '', and your code calls gate() at the action and showPaywall() when it refuses. The paywall then opens in this same page, on request. See Gate your extension.

The paywall

When the device may not continue, the popup shows one of:

RefusalShown
sign_inThe tool needs an account (Pricing → Access) and there is none, and there is something free to use once they have one: the title, one line, and the doors — Continue with Google where your workspace has a Google client (it opens your sign-in page already going to Google), a hairline or, Continue with email. Nothing else: the header above it already carries your mark and the tool's name, and who sells it belongs where money is asked for.
limitThe device's free-use meter — Free uses · 7 / 10 and a bar — then the plan to buy.
paidNothing is free: the plan to buy — name, price, the Recommended badge and the first three lines of what it includes, as on the checkout page — one button that opens its checkout, the other plans as rows below, and Already bought it? Sign in at the foot. This is what a tool that also needs an account shows a stranger: the price first, the account at the checkout.
featureExport is in Team, and only the plans that hold the feature.
blockedYour message from Versions.

Under it: Already bought it? Sign in, and who sells the tool. Once the device is unlocked, the same panel shows the plan held and how it stands — Renews 22 Oct · €3 / month, Lifetime licence · since 4 Mar, Trial until 29 Sep, or Payment failed — update your card in amber — the account it is on, and the way to the account page. Bought while the popup is open, it turns without a reload.

The header carries the extension's own icon, from its manifest, and the tool's name. The panel is drawn in your brand — accent, corners, colour mode from Configure → Branding — in system type, no font shipped, and in the buyer's language. Its facts come from the tool's public endpoint, GET /api/tools/<tool-id>?lang=<code>, and are cached on the device for a day, so it opens offline and at once. The signed manifest the client checks at every open carries a stamp of the brand and the pricing; when it moves, the popup fetches its facts again at that open and redraws. So a change under Branding or Pricing is in every popup at its next open, not a day later.

Configure → Branding has a Popup tab: the panel drawn by the very files the extension ships, in the brand as you edit it, one state at a time — Free, Limit reached, Feature, Unlocked, Trial, Payment failed, Update needed — only the states your tool's buyers can meet. What you see there is what they get.

The panel lives inside toolaby.js, the one file the wiring writes beside your code — the client and the popup UI together, minified, with its stylesheet inside. Do not edit it; style your own popup in your own files. npx -y toolaby@latest upgrade <tool-id> replaces it.

The account bar

Once a device is through, the Wall popup hands over to yours and the buyer never sees it again — so your popup is where they look for who is signed in, their plan, their account and sign out. (The Branding page's preview shows this state as your popup's stand-in with the bar at its foot, since the Wall popup is not what they see.) Sign out in the bar signs the device out on the account too: it leaves the account page's Devices at once. toolaby.js defines it as one tag:

<script type="module" src="toolaby.js"></script>
<toolaby-account></toolaby-account>

One strip: the buyer's initial and email, the plan they hold, and two icon buttons — their account page, sign out — or, signed out, Not signed in and a Sign in button in your accent. It takes the colour scheme of the page it sits in (from your popup's background), so it belongs to your popup; only the accent is the brand's. It never wraps. It talks to the background over messages, so it works in any popup however it is built, and reads the same cached facts the panel does. A WXT project gets it docked flush at the foot of its popup from the module without adding the tag (data-dock); place the tag yourself to put it elsewhere, or set accountBar: false in toolaby.config.js to keep it out.

Attribute or method
schemelight or dark: the bar's colours. Without it, they follow your page's background, read again at each redraw. Set it when your page switches themes by a class.
data-dockFlush at the foot of its container: no border but the top one, no corners.
refresh()Draws the bar again now. It already redraws the moment the device's account changes (signed in or out, bought, refreshed), so this is for a change it cannot see.

The popup's Sign in opens the workspace's sign-in page in a tab, titled Sign in to connect <tool> — the one-time link by email and, when the workspace has it, Continue with Google — and the page hands the extension its credential when it is done.

The side panel

An extension with a side panel gets the Wall there too. The wiring sets toolaby-sidepanel.html as side_panel.default_path (and adds the sidePanel permission), and at every open it gates exactly as the popup does, then hands over to yours — appSidePanel in toolaby.config.js — or shows the panel at the side panel's width. It is the same page and the same files: a two-line shell that calls mountSidePanel() from toolaby.js. The account bar docks at the foot of your side panel as it does in your popup.

One warning worth having: Chrome refuses to load an extension whose side_panel.default_path names a file that is not there — the whole extension, not just the panel. Remove a side panel and you remove the key and the sidePanel permission with it.

Chrome opens one surface from the toolbar button, never both, and a popup, when the manifest names one, wins. With a side panel and no popup, the client sets the panel to open on the click (sidePanel.setPanelBehavior), so the button does what it did before you wired it. With both, the popup keeps the click and your side panel opens from wherever you open it — a button in the popup, a command, a context menu, the browser's side panel menu — and the Wall's stands in front of it there too.

The panel is drawn for the surface it is on: 336px wide in a popup; in a side panel it fills the width up to a readable column, centred, with what it has to say — the sign-in doors, the meter, the plans — sitting in the middle of the column rather than stranded at the top of a tall empty one. A state taller than the panel simply fills it and scrolls.

A side panel of your own — one that draws the panel itself with loadPaywall and the renderers, instead of putting the Wall's in front — needs one attribute:

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

ensureStyles() sizes a page as a 336px popup unless that says otherwise, so without it your side panel would sit in a 336px column. Nothing else differs between the two surfaces.

Firefox spells the side panel sidebar_action with default_panel and has no sidePanel permission; the wiring handles that key too when a manifest carries it, and the page is the same.

Branding → Popup, and Preview on a tool's Pricing page, show the panel at either width.

Without the Wall popup

A framework that cannot put a page in front of yours (Plasmo), or a tool that wants its own paywall, calls gate() in its popup and draws the paywall with loadPaywall, applyBrand, renderMark, renderPaywall, renderUnlocked, renderNotice and refusalOf from paywall.js. See Frameworks for the code.

On this page