Surfaces
A popup, a side panel, both or neither — how to start each one, and what the buyer sees.
An extension can show itself in a popup (the small window under the toolbar button), a side panel (the column Chrome docks beside the page), both, or neither — a tool that works from a context menu, a keyboard shortcut or a content script has no page of its own at all.
The Wall works the same way on every one of them. Pick the case below that is yours.
The one rule that decides most of this
Chrome gives the toolbar click to a popup whenever the manifest names one, and never opens two surfaces at once. So:
| Your extension | What the toolbar button does |
|---|---|
| A popup | Opens the popup. |
| A side panel, no popup | Opens the side panel — the platform sets that up for you. |
| Both | Opens the popup. The side panel needs its own way in: a button in your popup, a keyboard command, or Chrome's side-panel menu. |
| Neither | Nothing. Your own code decides when to sell. |
1. I want an extension with no popup
Nothing of yours opens on a click; the work happens in the background, a content script, a context menu or a shortcut.
npx -y toolaby@latest create <tool-id> --surfaces noneIn an extension that already exists, npx -y toolaby@latest wire <tool-id> sees there is no popup and writes no page of the platform's. There is nothing to stand in front of.
The gate goes where the work is:
const permit = await toolaby.gate();
if (!permit.allowed) {
// Nothing is on screen to explain it, so say it yourself:
await toolaby.openPaymentPage(); // your buyer page, with the plans
return;
}permit.reason is sign_in, paid, limit or feature — Client has the shapes. A notification, a badge on the icon or a message in your own content script are all fair; the platform does not put anything on screen unless you give it a page.
A middle way: if you want a buyer to see the plans when they click the icon, but you have no popup of your own, let the platform's popup be the popup. Name it in the manifest and leave appPopup empty:
"action": { "default_popup": "toolaby-popup.html" }// toolaby.config.js
appPopup: '', // nothing of yours to open on to — the panel staysThe click then always shows the panel: signed out, the sign-in doors; metered, the meter; unpaid, the plans; unlocked, the plan held and the account.
2. I want an extension with only a popup
npx -y toolaby@latest create <tool-id> --surfaces popupOr, in an extension that already has one:
npx -y toolaby@latest wire <tool-id>The wiring makes toolaby-popup.html the action popup and remembers yours as appPopup. At every open the platform's popup asks the gate and then either opens your popup — the buyer never sees the platform's — or shows the panel: sign in, the meter, the plans. Your popup is not touched, and gets the account bar at its foot.
Or draw the panel inside your own popup and keep the platform's page out of the way — what create makes:
appPopup: '', // no page of the platform's in frontimport { toolaby, loadPaywall, applyBrand, ensureStyles, renderPaywall, renderUnlocked, refusalOf } from './toolaby.js';Frameworks has the whole page, written out.
3. I want an extension with a popup and a side panel
npx -y toolaby@latest create <tool-id>Both is the default. In an extension that already has both, npx -y toolaby@latest wire <tool-id> finds them — side_panel.default_path in a plain manifest, entrypoints/sidepanel.html in WXT, the defineManifest source in CRXJS — and stands one page of the platform's in front of each, remembering them as appPopup and appSidePanel.
The toolbar click stays the popup's, so give the side panel a door. From your popup:
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
await chrome.sidePanel.open({ tabId: tab.id }); // needs a click, which this is
window.close();create writes exactly that button into the popup it makes, and hides it when there is no side panel beside it.
4. I want an extension with only a side panel
npx -y toolaby@latest create <tool-id> --surfaces side-panelWith a side panel and no popup, the platform tells Chrome to open the panel on the toolbar click (sidePanel.setPanelBehavior), so the button does what your buyer expects. Nothing to wire by hand.
In an extension that already has one, npx -y toolaby@latest wire <tool-id> puts toolaby-sidepanel.html in front of yours and adds the sidePanel permission.
5. I already have one and want to add the other
Add the page as your framework wants it (entrypoints/sidepanel.html in WXT; a side_panel key and a file in a plain manifest), then:
npx -y toolaby@latest upgrade <tool-id>It re-reads the folder, writes the platform's page for the new surface and fills in appSidePanel (or appPopup). In an extension whose own pages draw the panel (appPopup: '', what create makes), the new page stays in front too, and the platform's is written beside it for showPaywall(). Rebuild and reload.
Dropping one is the other way round, and there is a trap: delete the page and the key that names it. 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.
What the panel looks like on each
The platform draws one panel, laid out for the surface it is on:
- In a popup — 336px wide, the header, then the state.
- In a side panel — the column fills the panel up to a readable width, centred, with the mark and what it has to say together in the middle of the column. A tall state fills it and scrolls.
Configure → Branding → Popup previews both, in your brand, one state at a time, and so does Preview on a tool's Pricing page.
A page of your own that draws the panel in a side panel must say so, or it will be sized as a 336px popup:
<html data-toolaby-surface="sidepanel">That is the whole difference; everything else — loadPaywall, the renderers, the account bar — is the same on both.
What is the same everywhere
Whatever you open, the platform is one thing behind it: toolaby.gate() answers by what the tool's Free plan holds and what this device holds, has() asks about a feature, and the account, licences, devices and the version check are the same. Surfaces decide where a buyer meets you, never what they may do — that is Access and features.
Firefox spells the side panel sidebar_action with default_panel; the wiring handles that key too, and the page itself is the same.