For the complete documentation index, see llms.txt. Prefer markdown by appending.mdto documentation URLs or sendingAccept: 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
| Work | Extension context |
|---|---|
| Form state and streamed output | Popup, side panel, new tab, or options UI |
| Read page text or selection | Content script |
| Coordinate long-lived work | Background entry |
| Invoke a model or write the database | Core 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:
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.tsAdd @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:
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:
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 devCheck the cases that differ from a normal browser tab:
- Signed-in and signed-out requests across the extension origin.
- A stream interrupted by closing the popup.
- Side-panel state after switching tabs.
- Content from a hostile page that contains prompt-injection text.
- Requests above the plan, credit, or rate limit.
- Chrome and Firefox production builds with the final API origin.
Build both targets before shipping:
pnpm --filter extension build:chrome
pnpm --filter extension build:firefoxHow is this guide?
Last updated on