| name | btst-registry |
| description | Patterns for maintaining the BTST shadcn v4 registry, including adding new plugins to build-registry.ts, EXTERNAL_REGISTRY_COMPONENTS for directory-based workspace/ui imports, design rules for what is and isn't ejectable, and the 4-step workflow for updating registry JSON files. Use when adding a plugin to the shadcn registry, updating build-registry.ts, handling EXTERNAL_REGISTRY_COMPONENTS, debugging hooks accidentally included in registry output, or running the registry E2E test. |
BTST Shadcn Registry
Plugin UI pages are distributed as a shadcn v4 registry so consumers can eject and customize the UI layer via npx shadcn@latest add.
Key files
| File | Purpose |
|---|
packages/stack/scripts/build-registry.ts | Build script — globs plugin components, rewrites imports, outputs JSON |
packages/stack/scripts/test-registry.sh | E2E test — packs @btst/stack, installs via shadcn, builds Next.js project |
packages/stack/registry/btst-{name}.json | Per-plugin registry item (committed, regenerated by build script) |
packages/stack/registry/registry.json | Combined registry collection (committed, regenerated) |
.github/workflows/registry.yml | CI — rebuilds registry and runs E2E on PRs touching plugin source |
Design rules
- Hooks are excluded — components import hooks from
@btst/stack/plugins/{name}/client/hooks. Only the view layer is ejectable.
- Routable pages — wire back via
pageComponents on the client plugin config when the plugin supports page overrides.
@workspace/ui imports are rewritten: standard shadcn components → registryDependencies; custom components (page-wrapper, empty, etc.) → embedded as registry:component files from packages/ui/src/.
- Directory structure is preserved — source files land at
src/components/btst/{name}/client/{relative} so all relative imports remain valid.
- Workspace component API compatibility — the workspace version must match the standard shadcn API. Never add custom props that standard shadcn doesn't have.
EXTERNAL_REGISTRY_COMPONENTS
Multi-file @workspace/ui components (directories, not single files) must be mapped to an external registry URL:
const EXTERNAL_REGISTRY_COMPONENTS: Record<string, string> = {
"auto-form": "https://shadcn-registry.example.com/auto-form",
"minimal-tiptap": "https://shadcn-registry.example.com/minimal-tiptap",
}
If a new component imports from @workspace/ui/components/my-dir/... (a directory), add it here.
Adding a plugin — 4 steps
- Add a
PluginConfig entry to PLUGINS in packages/stack/scripts/build-registry.ts
- Run
pnpm --filter @btst/stack build-registry
- Run
pnpm --filter @btst/stack test-registry locally
- Commit the updated
registry/*.json files
Gotchas
- Registry not rebuilt after plugin changes — always run
build-registry and commit the updated JSON. CI auto-commits if forgotten, but don't rely on it.
@workspace/ui sub-path components not found — if a new component imports from a directory, add it to EXTERNAL_REGISTRY_COMPONENTS.
- Hooks accidentally included — check that
hooks/ files show as skip in the build output. Update shouldExclude() in build-registry.ts if a plugin has hooks in a non-standard location.
- Custom prop on workspace component — remove the custom prop and use the standard shadcn API instead. The shadcn CLI will overwrite embedded components when installing other components that share the same dependency (e.g.
popover).