# Frameworks

> Wire a WXT, CRXJS or Plasmo project, or an extension with a bundler of its own.

Which surfaces your extension has — a popup, a side panel, both or neither — is [Surfaces](https://toolaby.app/docs/surfaces.md); this page is where the files go in each kind of project.

An extension built with a framework or a bundler uses the same files as a plain one: the client, `toolaby.js`, which carries the paywall; its config and types; and the Toolaby popup. The wiring puts them where that kind of project keeps them. The client is not an npm package: `toolaby.js` is one ES module file, written into your project.

**Choose your extension's folder** on the tool's Set up page and `npx -y toolaby@latest wire <tool-id>` do the same thing:

* **Plain extension** (`manifest.json` at the root): the manifest is edited and every file goes beside it.
* **WXT**: the files and a WXT module, `modules/toolaby.wxt.ts`, which WXT loads by itself. Nothing to paste.
* **CRXJS** with `manifest.config.ts`, and **Plasmo**: these generate the manifest at build time, so the files are written and the lines for the framework's config are printed. They are never written into your TypeScript.

The printed lines carry no values for CRXJS. `toolaby.manifest.js`, written beside the client, derives the extension's id, the `storage` permission and Toolaby's address from `toolaby.config.js`. Your config spreads it once; swap in the live key and the manifest follows. Plasmo's manifest is JSON in `package.json`, which cannot import, so its lines carry the values.

## WXT

Nothing to paste. `wire` writes the client with its config and types, the Toolaby popup and `toolaby.background.js&#x60; at the project root, and &#x2A;*`modules/toolaby.wxt.ts`** — a WXT module, which WXT loads from `modules/` by itself. At build time it:

* merges what the manifest needs — the extension's `key`, the `storage` permission, Toolaby's address under `externally_connectable` — into yours, takes out a host permission for Toolaby's address, which it no longer needs, and puts the Toolaby popup in front of your popup — and, with an `entrypoints/sidepanel.html`, Toolaby's side panel in front of that, the same way;
* starts the platform in your background after your own code has run. A `toolaby.startBackground({ handlers })` of yours wins; a background that never mentions the platform still starts it; a project with no background at all gets the platform's own (`toolaby.background.js`);
* puts the [account bar](https://toolaby.app/docs/toolaby-popup.md#the-account-bar) at the foot of your popup and your side panel — who is signed in, their plan, their account, sign out — unless `toolaby.config.js` says `accountBar: false` or the tag is already in your page;
* copies the Toolaby popup, its side panel, `toolaby.js` and `toolaby.config.js` into the build output as they are, so each page finds the client beside it.

`wxt.config.ts` and `entrypoints/background.ts` stay exactly as you had them. `npx wxt build` and the popup is there.

To gate your own actions, your background calls the platform as anywhere else:

```ts
import { toolaby } from '../toolaby.js';

export default defineBackground(() => {
  toolaby.startBackground({ handlers: { countWords: async () => { const permit = await toolaby.gate(); if (!permit.allowed) return permit; /* … */ } } });
});
```

WXT builds every entrypoint whether or not the manifest names it, so your popup and side panel stay built and Toolaby's open them. `npx -y toolaby@latest upgrade <tool-id>` replaces the module with the current one along with the other platform files.

A complete example, built in Toolaby's CI: [wxt-word-count.zip](/examples/wxt-word-count.zip). Unzip, `npm install`, `npx -y toolaby@latest wire <tool-id> --force`, `npm run build`. For a new extension, [the WXT starter](https://github.com/Toolaby/chrome-extension-starter) is a finished React one wired the same way; its `npm run setup` makes your tool and wires it. The [extension generator](/create) makes it with your own name, colour, pages and prices.

## CRXJS

With `manifest.json` at the root, a CRXJS project is wired as a plain one; Vite processes the Toolaby popup as a page. With `manifest.config.ts`, the command prints what to add inside `defineManifest({ … })`:

```ts
import { manifest as toolaby } from './toolaby.manifest.js';

...toolaby,
action: { default_popup: 'toolaby-popup.html' },
```

CRXJS builds only the pages the manifest names. Keep your popup built as a Vite input in `vite.config.ts` — and a side panel the same way: `side_panel: { default_path: 'toolaby-sidepanel.html' }` in the manifest and `sidepanel: 'src/sidepanel/index.html'` beside `popup` in the inputs; the wiring prints both when it finds one:

```ts
export default defineConfig({
  plugins: [crx({ manifest })],
  build: { rollupOptions: { input: { popup: 'src/popup/index.html' } } },
});
```

The background: when `src/background.ts` exists, the command prints the two lines to add at its top, the import climbing out of `src/`: `import { toolaby } from '../toolaby.js'; toolaby.startBackground();`. When there is none, it writes one with those two lines: at the name your config gives, or `src/background.ts`, which the printed lines then add to the manifest.

## Plasmo

Files at the root. The background: with no `background.ts`, the command creates one with the two lines, and Plasmo picks it up; with one, it prints the two lines to add at its top. `package.json` gets, from the printed lines:

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

Plasmo builds only the pages its manifest names, and its manifest override replaces `action` whole, so the Toolaby popup cannot stand in front of yours. Your popup keeps the door itself, with the functions `toolaby.js` exports, and counts uses at the work:

```tsx title="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 () => {
      // The door, as the Toolaby popup keeps it: nothing is spent at an open. The sign-in or the plans come first only
      // when the person may not use the extension yet; free uses are counted at the work, below.
      const user = await toolaby.refresh()
      const mayNot = (!user.paid && user.access === "paid") || (user.signInRequired && !user.account?.linked)
      if (!mayNot) return setOpen(true)
      // The tool as the background resolved it from the key: its name, Toolaby's address, the free limit.
      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() // one free use, or a plan's
    if (!permit.allowed) return toolaby.showPaywall(permit) // Plasmo builds no page of Toolaby's: the checkout page, in a new tab
    // your work
  }

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

export default IndexPopup
```

The panel shows only when the person may not use the extension yet; while they may, the popup is your extension. Put `<toolaby-account>` at its foot for the [account line](https://toolaby.app/docs/toolaby-popup.md#the-account-bar): the free uses left and **Upgrade**. The panel's **Continue with Google** calls `signIn('google')`; the optional `provider` passes it on to `openLoginPage()`. See [Panel](https://toolaby.app/docs/reference/panel.md).

## Other bundlers

**Wire it by hand**, under step 1 of the tool's **Set up** page, gives the files (**Download the files**), what to add to `manifest.json`, `toolaby.config.js`, and the two lines for the top of your background, with your values. The rule for any layout:

* The platform (`toolaby.js` — the client and the popup UI as one file — with `toolaby.config.js` and `toolaby.d.ts`) goes where your build imports from.
* The Toolaby popup (`toolaby-popup.html` and its two-line `toolaby-popup.js`) — and, with a side panel, `toolaby-sidepanel.html` and `toolaby-sidepanel.js` — reaches the output with `toolaby.js` and `toolaby.config.js` beside it: copied verbatim, or built as a page of the bundler's.
* The manifest gets the `key` that gives every copy the same id, the `storage` permission, your workspace's address under `externally_connectable` (no host permission: Toolaby needs none), a module background (`"type": "module"`) that starts Toolaby, and the Toolaby popup as `action.default_popup` — Toolaby's side panel as `side_panel.default_path`, with the `sidePanel` permission.
* A page of your own that draws the panel in a **side panel** sets `data-toolaby-surface="sidepanel"` on `<html>`, so `ensureStyles()` does not size it as a 336px popup.

Firefox is not supported yet. It has no `externally_connectable`, so the sign-in and purchase handoffs need the client's bridge content script (`toolaby-bridge.js`) on your workspace's pages, and the wiring does not add it.
