For the complete documentation index, see llms.txt. Prefer markdown by appending .md to documentation URLs or sending Accept: text/markdown.

Integrate AI Kit

Expose Core-hosted AI features in the browser extension with authenticated streaming, compact UI, and no provider secrets in the extension.

AI Kit does not ship a browser extension. Integrate its server packages and routes into Core Kit first, then build the extension as a thin client of that API.

The web app remains the backend

Complete the web AI Kit integration before adding an extension surface. Model providers, storage writes, database access, rate limits, and billing checks belong in the web API.

An extension is best for short, contextual actions: summarize the current page, rewrite selected text, ask about a tab, save a prompt, or continue a recent conversation. Keep model administration, history management, uploads, and billing in the web dashboard unless they are essential to the extension workflow.

Choose the execution context

WorkExtension context
Form state and streamed outputPopup, side panel, new tab, or options UI
Read page text or selectionContent script
Coordinate long-lived workBackground entry
Invoke a model or write the databaseCore web API only

Content scripts should collect the smallest required page context and send it to an extension UI or background handler. They should never receive provider credentials.

Confirm the shared API

The Core extension client already points to appConfig.url and tags requests with Platform.EXTENSION:

apps/extension/src/lib/api/index.tsx
export const { api } = hc<AppRouter>(getBaseUrl(), {
  headers: {
    "x-client-platform": Platform.EXTENSION,
  },
});

After the web recipe, the typed client exposes the selected endpoints below api.ai. For AI Kit chat, the streaming endpoint is available from api.ai.chat.chats.$url().

Verify the production web origin is present in extension host permissions and allowed by the API's CORS and CSRF configuration. Keep Core's extension origin in Better Auth's trustedOrigins.

Add an extension-specific AI module

Do not copy AI Kit's Next.js screen into WXT. Create a small extension module that uses Core's API URL, auth session, and @workspace/ui-web components:

apps/extension/src/modules/ai/
  chat/
    composer.tsx
    conversation.tsx
    use-chat.ts

Add @ai-sdk/react and ai to apps/extension/package.json if you want to consume AI SDK UI streams directly. Keep model-provider packages in packages/ai, not in the extension.

A transport should use the Hono-generated URL and include extension credentials:

apps/extension/src/modules/ai/chat/use-chat.ts
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";

import { Platform } from "@workspace/shared/constants";

import { api } from "~/lib/api";

export const useExtensionChat = () =>
  useChat({
    transport: new DefaultChatTransport({
      api: api.ai.chat.chats.$url().toString(),
      credentials: "include",
      headers: {
        "x-client-platform": Platform.EXTENSION,
      },
    }),
  });

If your auth setup stores the session outside the browser cookie jar, use the same authenticated fetch or cookie forwarding strategy as the rest of your extension instead of relying on credentials: "include".

Mount it in the right surface

Use the popup for a single quick action. Prefer the side panel or new-tab page for a multi-turn conversation because closing the popup unmounts its React tree and interrupts local UI state.

Wrap the chosen entry with Core's existing Layout so auth, TanStack Query, error handling, theme, analytics, and monitoring remain consistent:

apps/extension/src/app/sidepanel/main.tsx
import { Layout } from "~/modules/common/layout/layout";
import { Chat } from "~/modules/ai/chat/conversation";

export default function Sidepanel() {
  return (
    <Layout>
      <Chat />
    </Layout>
  );
}

Use the background entry when work must survive closing a popup. Persist only identifiers and resumable state in extension storage, not full provider responses or secrets.

Pass page context deliberately

For page-aware tools, define a typed WXT message between the content script and the extension UI. Send only what the server-side prompt needs, such as:

  • Canonical URL and page title
  • Selected text
  • A bounded excerpt of readable page content
  • User-confirmed metadata

Validate and size-limit that payload again in the Hono route. Web page content is untrusted input, so do not concatenate it into system instructions and do not let it override tool or authorization rules.

Test browser lifecycle and access

pnpm --filter extension dev

Check the cases that differ from a normal browser tab:

  1. Signed-in and signed-out requests across the extension origin.
  2. A stream interrupted by closing the popup.
  3. Side-panel state after switching tabs.
  4. Content from a hostile page that contains prompt-injection text.
  5. Requests above the plan, credit, or rate limit.
  6. Chrome and Firefox production builds with the final API origin.

Build both targets before shipping:

pnpm --filter extension build:chrome
pnpm --filter extension build:firefox

How is this guide?

Last updated on

On this page

Ship your startup everywhere. In minutes.Try TurboStarter