Toolaby

Add payments to a Plasmo extension

Wire a Plasmo extension to Toolaby, add the manifest lines to package.json, draw the paywall in your own popup, gate the paid action, then build and pack.

Toolaby3 min readUpdated

This guide adds payments to a Chrome extension built with Plasmo. At the end, the project carries Toolaby's client, its background starts Toolaby, and your popup shows the paywall when the paid action is refused. Buyers pay through Stripe Checkout, on your Stripe account. Plasmo keeps the manifest in package.json, so you add a few lines the command prints, once.

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.

Plasmo's last release on npm is 0.90.5, of 17 May 2025. An existing Plasmo extension sells with Toolaby as shown here. For a new one, see WXT, Plasmo or CRXJS.

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 toolaby.js (the client and the paywall, one ES module file), toolaby.config.js with the tool key, and toolaby.d.ts at the project root. With no background.ts, it creates one with the two lines that start Toolaby, and Plasmo picks it up. With one, it prints the lines to add at its top.

2. Add Toolaby to the manifest

Plasmo's manifest is JSON in package.json, which cannot import, so the printed lines carry the values:

package.json
"manifest": {
  "key": "<the extension's key, from the printed lines>",
  "permissions": ["storage"],
  "externally_connectable": { "matches": ["https://<workspace>.toolaby.app/*"] }
}

No host permission is needed for Toolaby, so Chrome shows your users no new warning at install.

3. Draw the paywall in your popup

Plasmo's manifest override replaces action whole, so the Toolaby popup cannot stand in front of yours. Your popup decides at the open, as the Toolaby popup does, and draws the paywall with functions from toolaby.js:

popup.tsx
import { useEffect, useRef, useState } from "react"
import { toolaby, loadPaywall, applyBrand, renderPaywall, refusalOf } from "./toolaby.js"

function IndexPopup() {
  const panel = useRef<HTMLDivElement>(null)
  const [open, setOpen] = useState(false)

  useEffect(() => {
    ;(async () => {
      const user = await toolaby.refresh()
      const mayNot = (!user.paid && user.access === "paid") || (user.signInRequired && !user.account?.linked)
      if (!mayNot) return setOpen(true)
      const config = await chrome.runtime.sendMessage({ action: "toolaby.config" })
      const paywall = await loadPaywall(config)
      applyBrand(paywall)
      renderPaywall(panel.current!, paywall, user, config, {
        buy: (plan) => toolaby.openPaymentPage(plan),
        signIn: (provider?: "google") => toolaby.openLoginPage(provider),
        account: () => toolaby.openAccountPage()
      }, refusalOf(user, null))
    })()
  }, [])

  const run = async () => {
    const permit = await toolaby.gate()
    if (!permit.allowed) return toolaby.showPaywall(permit)
    // your work
  }

  return open ? <main><button onClick={run}>Do the thing</button></main> : <div ref={panel} />
}

export default IndexPopup

At the open, mayNot is true when nothing is free and no plan is held. It is also true when an account is required and none is signed in. Only then does the paywall show.

4. Gate the paid action

Opening spends nothing: gate() counts the free use at the work, as in run above. gate({ feature: 'export' }) asks whether the buyer's plan holds a feature instead. See Gate your extension.

Plasmo builds no page of Toolaby's, so showPaywall() opens the checkout page in a new tab. Put <toolaby-account> at the foot of your popup for the free uses left and Upgrade. Content scripts are bundled too, so one can import toolaby.js and call the same methods.

5. Build, check and pack

npm run build
npx -y toolaby@latest check --built build/chrome-mv3-prod

Load build/chrome-mv3-prod at chrome://extensions (Developer mode, then Load unpacked). For the Chrome Web Store, put the Live key in, replace the manifest in package.json with the lines the command prints, build, and pack:

npx -y toolaby@latest wire <tool-id> --live --force
npm run build
npx -y toolaby@latest pack

pack finds build/chrome-mv3-prod and writes <name>-<version>.zip, without the manifest's key, and refuses a build that carries a tk_test_ key. See Ship to the Chrome Web Store.

Next steps

All guides