Adding a new export path
Two files must always be updated together:
1. packages/stack/build.config.ts — add to entries array
entries: [
// existing entries...
"./src/plugins/{name}/api/index.ts",
"./src/plugins/{name}/client/index.ts",
"./src/plugins/{name}/client/hooks/index.tsx",
"./src/plugins/{name}/client/components/index.tsx",
"./src/plugins/{name}/query-keys.ts",
]
2. packages/stack/package.json — add exports AND typesVersions
{
"exports": {
"./plugins/{name}/client/hooks": {
"import": "./dist/plugins/{name}/client/hooks/index.mjs",
"require": "./dist/plugins/{name}/client/hooks/index.cjs"
}
},
"typesVersions": {
"*": {
"plugins/{name}/client/hooks": ["./dist/plugins/{name}/client/hooks/index.d.ts"]
}
}
}
Both entries are required — missing typesVersions breaks TypeScript consumers even when runtime works.
CSS exports
Plugins with UI components need two CSS files:
src/plugins/{name}/client.css— client-side stylessrc/plugins/{name}/style.css— full styles with Tailwind source directives
The postbuild.cjs script auto-discovers and copies them — no manual registration needed. Only add the package.json export entry:
{
"exports": {
"./plugins/{name}/css": "./dist/plugins/{name}/client.css"
}
}
Updating all three codegen projects
When adding a plugin, changing plugin config, or changing a framework entry point, update ALL three generated projects:
Next.js (codegen-projects/nextjs/)
lib/stack.ts— backend plugin registrationlib/stack-client.tsx— client plugin registrationapp/pages/client-layout.tsx— shared client provider and overridesapp/(request)/pages/layout.tsx— trusted request-origin hydrationapp/(static)/pages/layout.tsx— header-free SSG/ISR provider wrapperapp/globals.css—@import "@btst/stack/plugins/{name}/css";
React Router (codegen-projects/react-router/)
app/lib/stack.tsapp/lib/stack-client.tsxapp/routes/pages/_layout.tsxapp/app.css
TanStack (codegen-projects/tanstack/)
src/lib/stack.tssrc/lib/stack-client.tsxsrc/routes/pages/route.tsxsrc/styles.css
Keep generated framework routes on the v3 entry factories:
| Framework | API route | Page route | Provider router |
|---|---|---|---|
| Next.js | toNextRouteHandlers |
createNextPage |
nextRouter() |
| React Router | toReactRouterHandlers |
createReactRouterPage |
reactRouter() |
| TanStack Start | toTanStackHandlers |
createTanStackPageOptions |
tanstackRouter() |
Do not generate hand-written route resolution, loader/meta ordering, dehydration, or 404 plumbing. The entry factories own that behavior.
Generated layouts pass the resolved client stack to StackProvider:
<StackProvider
stack={clientStack}
router={frameworkRouter()}
auth={authProvider}
overrides={{
blog: { uploadImage },
}}
>
{children}
</StackProvider>
Only plugin-specific values belong in overrides. Never generate Link,
Image, navigate, refresh, API paths, identity, or login values inside a
built-in plugin override. Configure api, site, and queryClient once in
createClientStack(). Resolved client definitions such as Blog and AI Chat
accept only plugin-specific options; their loaders and metadata receive shared
runtime through the stack resolver.
Override type inference
Resolved definitions contribute their public override type automatically under
their canonical programmatic ID (blog, aiChat). Register the definition in
createClientStack({ plugins }) and pass that resolved stack to
StackProvider; never recreate a manual application override map or provider
generic. Do not restore removed framework, API, guard, or identity fields in
local intersection types.
Adding shared UI components (@workspace/ui)
Components live in packages/ui/src/components/. Add via shadcn CLI:
cd packages/ui
pnpm dlx shadcn@latest add {component-name}
Import in plugins:
import { Button } from "@workspace/ui/button"
import { MarkdownContent } from "@workspace/ui/markdown-content"
After changes
pnpm build # rebuild all packages
# If turbo cache is stale:
pnpm turbo clean && pnpm build
Gotchas
- Missing
typesVersions— always add alongsideexports; TypeScript won't resolve the new path otherwise. - Build cache — run
pnpm turbo cleanif changes aren't reflected in codegen projects afterpnpm build. - CSS not loading — ensure
"./plugins/{name}/css"entry exists inpackage.jsonexports;postbuild.cjshandles the rest automatically. @workspace/uisub-path components — if a new component imports from a directory (not a single file), add it toEXTERNAL_REGISTRY_COMPONENTSinbuild-registry.ts.- Stale v2 templates — generated routes must use framework entry factories.
Generated layouts configure
api,site, andqueryClientonce increateClientStack(), then pass the resolved stack toStackProvider. Only provider-owned services such asrouterandauthremain alongside thestackprop.