How to Configure Custom Component Registries for Internal Company Components
Add a namespaced URL to the registries field in your components.json file, ensure your server exposes the required JSON endpoints, and install internal components using npx shadcn add @company/component.
Shadcn UI discovers UI components through a registry system implemented in the shadcn-ui/ui repository. While the CLI ships with a built-in @shadcn registry, enterprises can configure custom component registries to distribute internal design systems across teams. By editing components.json and exposing a simple HTTP contract, you can consume private components exactly like public ones.
Understanding the Registry Architecture
Registries are namespaced collections that map to URL endpoints. The CLI maintains the default registry map in packages/shadcn/src/registry/constants.ts as the constant BUILTIN_REGISTRIES.
At runtime, the configuration loader in packages/shadcn/src/utils/get-config.ts merges built-in and user-defined registries inside the resolveConfigPaths function:
config.registries = {
...BUILTIN_REGISTRIES,
...(config.registries || {})
};
This merge operation ensures your internal entries coexist with the public index while taking precedence in resolution order.
Configuring Your Internal Registry
Define the Registry Endpoints
Your registry server must implement a minimal HTTP contract. The CLI expects two specific endpoints:
GET /registry– Returns metadata includingname,homepage, and anitemsarray describing available componentsGET /registry/<item>.json– Returns the full component definition including files, dependencies, and registry type
The resolver substitutes the {name} placeholder in your configured URL with the specific component identifier when fetching definitions. For example, a URL template https://registry.mycompany.com/registry/{name} becomes https://registry.mycompany.com/registry/button.json when installing a component named button.
Register in components.json
Edit your project’s components.json to include the registries object:
{
"registries": {
"@mycompany": "https://registry.mycompany.com/registry/{name}"
}
}
The namespace (@mycompany) becomes the prefix for all CLI commands targeting this registry. The {name} placeholder is required and is dynamically replaced by the resolver logic in packages/shadcn/src/registry/resolver.ts.
Verify Registry Discovery
Before executing installation commands, the CLI invokes ensureRegistriesInConfig() from packages/shadcn/src/utils/registries.ts. This utility performs three critical operations:
- Calls
resolveRegistryNamespaces()to identify all required registries (including transitive dependencies) - Filters out existing entries present in
BUILTIN_REGISTRIESor the current config - Fetches missing registry URLs from the public index and automatically persists them to
components.json(unless thewriteFileoption is disabled)
This automatic persistence ensures new team members receive registry configurations without manual file edits.
Authentication Support
Enterprise registries can require authentication without additional CLI configuration. The resolver in packages/shadcn/src/registry/resolver.ts automatically detects and forwards auth headers when your registry URL contains specific path segments:
/bearer//api-key//client-secret/
When these patterns are present, the CLI forwards the appropriate authentication headers from environment variables or CLI flags, enabling secure access to private component registries.
Minimal Registry Implementation
The test suite in packages/tests/src/utils/registry.ts provides a complete reference implementation. Below is the essential structure for a Node.js HTTP server that satisfies the CLI requirements:
import { createServer } from "http";
export async function createRegistryServer(items: Array<any>) {
const server = createServer((request, response) => {
const urlRaw = request.url?.split("?")[0];
// Serve registry index
if (urlRaw?.endsWith("/registry")) {
response.writeHead(200, { "Content-Type": "application/json" });
response.end(
JSON.stringify({
name: "Internal Registry",
homepage: "https://registry.mycompany.com",
items,
})
);
return;
}
// Serve individual component definition
if (urlRaw?.match(/\/registry\/.*\.json$/)) {
const componentName = urlRaw.match(/\/registry\/(.*)\.json$/)?.[1];
const item = items.find((i) => i.name === componentName);
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify(item));
return;
}
response.writeHead(404);
response.end(JSON.stringify({ error: "Not found" }));
});
return {
start: () => new Promise<void>((res) => server.listen(4444, res)),
stop: () => new Promise<void>((res) => server.close(() => res())),
};
}
This implementation handles the {name} substitution pattern and supports the JSON schema expected by packages/shadcn/src/registry/resolver.ts.
Using Internal Components
Once your registry is configured, install components using the namespace prefix:
# Install a component from your internal registry
npx shadcn add @mycompany/button
# View component details
npx shadcn view @mycompany/card
# Search your internal catalog
npx shadcn search @mycompany/
The CLI resolves the @mycompany namespace to your configured URL, replaces {name} with the component identifier, fetches the definition, and writes the files to your project's components alias directory. The system automatically updates your import map and respects your existing tailwind.config and TypeScript paths.
Summary
- Custom component registries are configured in
components.jsonusing namespaced URLs with a required{name}placeholder - The CLI merges user registries with
BUILTIN_REGISTRIESinpackages/shadcn/src/utils/get-config.tsat configuration load time - Required endpoints are
GET /registryfor metadata andGET /registry/<item>.jsonfor component definitions - Authentication is automatically handled for URLs containing
/bearer/,/api-key/, or/client-secret/patterns - The
ensureRegistriesInConfig()function inpackages/shadcn/src/utils/registries.tsautomatically persists missing registry entries tocomponents.json - Install internal components using namespace prefixes:
npx shadcn add @company/component
Frequently Asked Questions
How do I secure my internal registry behind corporate authentication?
Include authentication segments directly in the registry URL stored in components.json. The resolver in packages/shadcn/src/registry/resolver.ts automatically detects URLs containing /bearer/, /api-key/, or /client-secret/ and forwards the appropriate headers. Store sensitive tokens in environment variables and reference them in the URL configuration.
Can I use a static JSON file instead of an HTTP server for my registry?
Yes, you can host a static registry.json file on any CDN or internal file server. Ensure the JSON structure includes the required name, homepage, and items fields. Configure your components.json to point directly to the file pattern: "@mycompany": "https://cdn.mycompany.com/static/registry/{name}.json".
What happens if an internal component depends on another registry?
The CLI resolves transitive dependencies automatically. When you invoke ensureRegistriesInConfig(), it calls resolveRegistryNamespaces() to detect all required registries across the entire dependency tree. Missing registries are fetched from the public index and added to your components.json before installation proceeds.
Do developers need to manually edit components.json to add the registry?
No. The ensureRegistriesInConfig() function in packages/shadcn/src/utils/registries.ts automatically writes missing registry entries to components.json during the first CLI operation that references the namespace. Developers simply run npx shadcn add @mycompany/component, and the system persists the registry configuration automatically unless the writeFile option is explicitly disabled.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →