GuidesPacks

Create a Browser Extension

Create a WXT and React browser extension next to a web app and a Hono tRPC API on Bun with Drizzle and SQLite in one Better Fullstack workspace.

Updated October 9, 2026browser-extensionwxthonotrpc

Use the Browser Extension pack when an extension and a web app should share one backend: the extension lives in its own workspace app, and the API and database sit beside it.

Generate the extension starter

npm create better-fullstack@latest my-extension -- \  --ecosystem typescript \  --frontend tanstack-router \  --backend hono \  --runtime bun \  --database sqlite \  --orm drizzle \  --auth none \  --api trpc \  --addons wxt turborepo \  --css-framework tailwind \  --ui-library shadcn-ui

What the pack includes

  • A WXT extension in apps/extension built from WXT's React starter shape, with a popup, a background script, and a content script.
  • A TanStack Router web app in apps/web with a tRPC client.
  • A Hono server on Bun in apps/server that mounts tRPC at /trpc and returns OK from GET /.
  • A tRPC router with healthCheck and public tasks procedures for listing, creating, completing, and removing tasks.
  • Drizzle on SQLite through libSQL, with DATABASE_URL pointing at local.db in the project root.
  • A Turborepo workspace, so one command starts the server, the web app, and the extension.

Generated shape

txt
my-extension/
  apps/
    extension/
      wxt.config.ts
      entrypoints/
        background.ts
        content.ts
        popup/
          App.tsx
          main.tsx
          index.html
    server/
      .env
      src/index.ts
    web/
  packages/
    api/src/routers/index.ts
    db/src/
      schema/task.ts
      tasks.ts

The content script matches *://*.google.com/* and logs a message, and the background script logs the extension ID. Both are placeholders to replace.

Run it locally

npm installnpm run db:pushnpm run dev

npm run dev runs turbo dev, which starts the server on port 3000, the web app on port 5173, and wxt --port 5555 for the extension in Chrome development mode. Installing dependencies also runs wxt prepare in the extension app.

The command lists turborepo next to wxt on purpose. Passing --addons replaces the default addon set, and without Turborepo the npm root dev script runs npm run dev --workspaces, which starts workspaces one after another.

From apps/extension, npm run dev:firefox targets Firefox, npm run build writes a production build, and npm run zip packages it for a store upload.

Call the API from the extension

The extension does not call the API yet. Grant host permission for the API origin in apps/extension/wxt.config.ts:

ts
import { defineConfig } from "wxt";

export default defineConfig({
  modules: ["@wxt-dev/module-react"],
  manifest: {
    host_permissions: ["http://localhost:3000/*"],
  },
});

Then fetch from the popup or the background script:

ts
const response = await fetch("http://localhost:3000/");
const status = await response.text();

For typed calls, add @trpc/client and the @my-extension/api workspace package to the extension's dependencies and create a client with httpBatchLink pointed at http://localhost:3000/trpc, the same way apps/web/src/utils/trpc.ts does.

First things to customize

  1. Replace the content script's matches pattern with the sites the extension should run on.
  2. Add auth before the tasks procedures hold user data. They are public procedures in this pack because the command uses --auth none.
  3. Move the API URL into WXT config or an environment value per build, and list the production API origin in host_permissions.
  4. Keep secrets on the server. Anything bundled into the extension can be read by its users.

When this is the right pack

Choose this pack when the extension is one client of a product that also has a web app and an API. If the extension needs no backend, run the same command with --backend none --runtime none --database none --orm none --api none and keep only the extension and web apps. For the API side, read Hono with tRPC and Drizzle.

GitHub Sponsors