Registry System
How the Star Forge registry works, how blocks resolve dependencies, and how to add new components.
Registry System
Star Forge uses a custom registry that powers the CLI installation flow. The registry is the single source of truth for every component, primitive, and block that can be installed.
Never edit registry.json manually. It is generated by scripts/build-registry.js from TypeScript source files.
Source Files
The registry is built from two TypeScript files:
| File | Purpose |
|---|---|
src/registry/registry-ui.ts | UI blocks (42 original + 15 base variants) |
src/registry/registry-primitives.ts | Primitives (18 dual + 9 aliases + 4 UI helpers = 31) |
Both export an array named ui and primitives respectively, typed against Registry from src/registry/schema.ts.
Registry Item Shape
interface RegistryItem {
name: string; // e.g. "select-1", "base-select"
type: 'registry:ui';
dependencies?: string[]; // npm packages (e.g. ["lucide-react"])
registryDependencies?: string[]; // Other registry items to auto-install
files: Array<{ path: string; type: 'registry:ui' }>;
example?: string; // Preview component path
component?: React.LazyExoticComponent<any>;
}Dependency Resolution
When a user runs npx shadcn@latest add <url>, the CLI:
- Downloads the requested item from
registry.json - Reads
registryDependencies - Recursively resolves and installs each dependency
- Reads
dependenciesand installs npm packages
Example: select-1-base
{
"name": "select-1-base",
"registryDependencies": ["base-select"],
"dependencies": []
}{
"name": "base-select",
"registryDependencies": [],
"dependencies": ["@base-ui/react"]
}Result: select-1-base + base-select primitive + @base-ui/react package are all installed automatically.
Registry Aliases
Star Forge uses three naming patterns for primitives:
| Pattern | Example | Purpose |
|---|---|---|
| Prefix | radix-select | Explicit Radix UI variant |
| Prefix | base-select | Explicit Base UI variant |
| No prefix | select | Backward-compatible alias (defaults to Radix) |
Blocks reference the alias (select) in their registryDependencies. This ensures backward compatibility while allowing the user to override with the -base block variant.
Adding a New Block
Create the component
Create the block source in src/components/star-forge/<category>/<name>.tsx.
Create the preview
Create the demo in src/components/star-forge-preview/<category>/<name>.tsx.
Register the block
Add an entry to src/registry/registry-ui.ts:
{
name: 'my-block',
type: 'registry:ui',
registryDependencies: ['button', 'select'],
dependencies: ['lucide-react'],
files: [{ path: 'src/components/star-forge/my-block.tsx', type: 'registry:ui' }],
example: 'src/components/star-forge-preview/my-block.tsx',
component: React.lazy(() =>
import('@/components/star-forge-preview/my-block').then((mod) => ({
default: mod.default
}))
)
}Build the registry
npm run build:componentsVerify
npm run audit:registryAdding a Base UI Variant for an Existing Block
If a block depends on dual primitives and you want a Base UI install path:
- Create a new entry in
src/registry/registry-ui.tswith-basesuffix - Change
registryDependenciesto usebase-*primitives - Keep the same
filesandexamplepaths (the block code does not change)
Example:
{
name: 'my-block-base',
registryDependencies: ['base-button', 'base-select'],
// ...same files and example as my-block
}- Add
hasEngineChoice={true}to theComponentPrevieworDrawerCodePreviewin the MDX docs.
Build Pipeline
registry-ui.ts
│
▼
parseRegistryItemsFromTsSource() ──► registry.json
▲
registry-primitives.tsscripts/build-registry.js uses the TypeScript compiler API to parse the AST of both registry files and extract all item metadata. It then writes a single registry.json.
Validation
Run the audit script after every registry change:
npm run audit:registryThis checks that every registryDependency points to an item that actually exists in registry.json.