# shadcn/ui Registries Explained: How They Work (and How to Run Your Own)

> How shadcn/ui registries actually work: registry.json, URL resolution, styles, and namespaces — explained by a team that runs a production registry.

Published: 2026-07-31 · Author: Matt Wierzbicki · Canonical: https://www.shadcndesign.com/blog/shadcn-ui-registry-explained

You have probably run `npx shadcn@latest add button` more times than you can count. Have you ever looked at where that code actually comes from?

The answer is a registry: an HTTP endpoint that serves JSON files matching a published schema. The shadcn CLI fetches that JSON, resolves dependencies, and writes real source files into your project. No npm package, no `node_modules` import — the code lands in your repo and you own it. The official docs describe the registry as ["a distribution system for code"](https://ui.shadcn.com/docs/registry), and that is exactly what it is: components, hooks, pages, config, and rules delivered as copyable files over HTTP.

We run one of these in production — the registry behind our Pro Blocks — so this guide covers both the official mechanics and what they look like once real customers, paid content, and multiple styles get involved.

> **TL;DR:** A shadcn/ui registry is any HTTP endpoint that serves component JSON matching the registry schema. `npx shadcn add button` resolves against the official registry; `npx shadcn add https://your.site/r/thing.json` installs from yours. You define items in `registry.json`, build them with `shadcn build`, and serve static JSON — or generate responses dynamically, like we do for Pro Blocks.

## What a shadcn/ui registry actually is

Strip away the tooling and a registry is three things:

1. **A schema.** Every installable item is a JSON document that follows the [registry-item schema](https://ui.shadcn.com/docs/registry/registry-item-json): a `name`, a `type`, a `files` array with inlined source code, plus optional `dependencies` (npm packages), `registryDependencies` (other registry items), and `cssVars`.
2. **An endpoint.** Any URL that returns that JSON. A static file on a CDN works. A Next.js route handler works. A public GitHub repo works.
3. **A client.** The `shadcn` CLI fetches the JSON, installs npm dependencies, recursively resolves registry dependencies, and writes the files to the paths your `components.json` aliases dictate.

The important contrast is with a traditional component library. When you `npm install` a library, you import compiled code you do not control, and upgrading means taking whatever the next version ships. When you install from a registry, the source is copied into your project once. There is no runtime dependency on the registry at all — it is a delivery mechanism, not a package manager.

That ownership model is the whole reason shadcn/ui works the way it does, and the registry system generalizes it: anyone can distribute code the same way the official components are distributed.

## How the CLI resolves what you type

When you run `shadcn add <something>`, the CLI supports three input shapes:

- **A bare name** — `shadcn add button` resolves against the official registry at ui.shadcn.com, using your project's configured style.
- **A full URL** — `shadcn add https://example.com/r/fancy-card.json` fetches that exact JSON from any registry, no configuration needed.
- **A namespaced name** — `shadcn add @acme/button` looks up the `@acme` namespace in your `components.json` and expands a URL template.

Namespaces are configured under the `registries` key in `components.json`, per the [namespace docs](https://ui.shadcn.com/docs/registry/namespace):

```json
{
  "registries": {
    "@acme": "https://registry.acme.com/r/{name}.json"
  }
}
```

The CLI replaces the `{name}` placeholder with the item you asked for. There is also an optional `{style}` placeholder, which the CLI substitutes with your project's configured style — that is how one registry can serve different code per style. If you have ever wondered why `components.json` has a `style` field at all, our [guide to choosing a shadcn/ui style](/blog/how-to-choose-shadcn-ui-style) covers what actually changes between them.

### What the official registry's URLs actually return

Because URLs are the ground truth here, we probed the official registry directly rather than trusting docs or old blog posts. Observed on 2026-07-31:

| URL | HTTP status |
|-----|-------------|
| `https://ui.shadcn.com/r/styles/default/button.json` | 200 |
| `https://ui.shadcn.com/r/styles/new-york/button.json` | 200 |
| `https://ui.shadcn.com/r/styles/new-york-v4/button.json` | 200 |
| `https://ui.shadcn.com/r/button.json` | 404 |

Two things stand out. First, the style-scoped paths for `default` and `new-york` still return 200 even though those styles are deprecated — existing projects keep working, which is exactly the backward-compatibility behavior you want from a registry you depend on. The `new-york-v4` path is the current Tailwind v4 delivery path. Second, the bare `/r/button.json` path returns 404: the official registry does not resolve items without a style segment, which is worth knowing if you ever hardcode registry URLs in scripts or CI.

The resolution pipeline, end to end: name or URL → `components.json` lookup (style, namespaces, aliases) → final URL → fetch JSON → resolve `registryDependencies` recursively → install npm `dependencies` → write `files` to their target paths.

## registry.json: the entry point to your registry

If the item JSON is the payload, [`registry.json`](https://ui.shadcn.com/docs/registry/registry-json) is the manifest. It lives at the root of your registry project and declares what you publish:

```json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "acme",
  "homepage": "https://acme.com",
  "items": [
    {
      "name": "fancy-card",
      "type": "registry:component",
      "title": "Fancy Card",
      "description": "A card with a gradient border.",
      "files": [
        {
          "path": "registry/fancy-card/fancy-card.tsx",
          "type": "registry:component"
        }
      ]
    }
  ]
}
```

Item `type` matters because it controls default install targets. The [registry-item schema](https://ui.shadcn.com/docs/registry/registry-item-json) defines the full set — the ones you will use most are `registry:ui` (primitives), `registry:component` (simple components), `registry:block` (multi-file components), `registry:hook`, `registry:lib`, `registry:page` (route files), `registry:style`, and `registry:theme`.

### The May 2026 update: include and validate

Until recently, one `registry.json` had to list every item, which got unwieldy for large registries. The [May 2026 "Registry Include and Validate" changelog](https://ui.shadcn.com/docs/changelog/2026-05-registry-include) added two pieces:

**`include`** lets the root `registry.json` compose other `registry.json` files:

```json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "acme",
  "homepage": "https://acme.com",
  "include": [
    "components/ui/registry.json",
    "hooks/registry.json"
  ]
}
```

Each include path must point to an explicit `registry.json` file (no folder shorthand), included files may omit `name` and `homepage`, and item names must stay unique across everything that gets resolved. Running `shadcn build` flattens the whole tree into built output with no `include` field remaining.

**`registry validate`** checks your source files before you publish:

```bash
npx shadcn@latest registry validate
```

It validates the root manifest, included files, item schema errors, duplicate names, and local file paths — without requiring a build first. If you maintain a registry of any size, put this in CI. We learned the manual version of this lesson the slow way: a malformed item JSON fails at *install time*, on a customer's machine, which is the worst possible place to discover it.

## Build your own registry, step by step

Here is the minimal path from nothing to a working registry, following the [official getting-started guide](https://ui.shadcn.com/docs/registry/getting-started).

**1. Start from the template.** Clone [shadcn-ui/registry-template](https://github.com/shadcn-ui/registry-template) — a Next.js project preconfigured with the build step and file layout.

**2. Add your component** under a registry directory, for example `registry/fancy-card/fancy-card.tsx`, and reference it from `registry.json` as shown above.

**3. Build.**

```bash
npx shadcn@latest build
```

This generates one static JSON file per item in `public/r/` — for example `public/r/fancy-card.json` — with your source code inlined into the `files[].content` field.

**4. Serve it.** Run `next dev` locally or deploy anywhere that serves `public/`. Your registry is now the set of URLs under `/r/`.

**5. Install from it.**

```bash
npx shadcn@latest add http://localhost:3000/r/fancy-card.json
```

That is genuinely all a registry is. Optionally, register a namespace so consumers get the short syntax:

```bash
npx shadcn@latest registry add @acme=https://acme.com/r/{name}.json
npx shadcn@latest add @acme/fancy-card
```

Note what is absent from this list: you do not need Next.js, a database, or a server at all. Static JSON on any host satisfies the contract. The template just makes the build-and-serve loop convenient.

## What a production registry looks like (our setup)

The template covers the happy path. Our registry — the one that serves [Pro Blocks](/blog/shadcn-pro-blocks-guide), several hundred sections and page templates built on shadcn/ui — has to handle a few things the template does not, and the gap between the two is where the interesting decisions live.

**Static items, dynamic delivery.** Every block is pre-built into a static JSON file checked into the repo, one file per item — the same output shape `shadcn build` produces. But we serve them through Next.js route handlers instead of `public/`, because the route is where authentication, rate limiting, and cache headers happen. Conceptually:

```ts
// app/api/registry/styles/[style]/[name]/route.ts
export async function GET(request, { params }) {
  const { style, name } = await params;
  return serveRegistryItem(request, name, dirsForStyle(style));
}
```

**Per-style delivery via the `{style}` placeholder.** Consumers configure one URL template ending in `/styles/{style}/{name}`, and the CLI substitutes their project's style. On our side, that segment picks which registry directory serves the request — blocks built for Radix UI versus blocks built for Base UI are separate item sets, with CSS-only items (themes, style presets) shared between them so we do not maintain duplicates. One lesson from running this: treat the style segment as *routing input, not trust input* — validate it and fall back sensibly, because you will receive values you did not anticipate, including styles from very old `components.json` files.

**Auth for paid content.** Free-tier items are served to anyone with IP-based rate limiting. Paid items require a license key sent as a request header; the route validates it against the payment provider, caches the validation result, and returns 401 or 403 with a helpful JSON error when it fails. This maps directly onto the official [authentication docs](https://ui.shadcn.com/docs/registry/authentication) pattern — the CLI can attach headers per namespace, with env-var expansion so tokens never live in `components.json`:

```json
{
  "registries": {
    "@private": {
      "url": "https://api.company.com/registry/{name}.json",
      "headers": {
        "Authorization": "Bearer ${REGISTRY_TOKEN}"
      }
    }
  }
}
```

**Legacy URLs never die.** Our original endpoint predates per-style delivery and is still hit daily by existing customers, so it lives on as a permanent alias for the default item set. The official registry's own 200s on deprecated style paths tell the same story: registry URLs are public API, and consumers hardcode them in scripts, CI, and docs. Version by adding paths, not changing them.

**What we would do differently.** Start with the `{style}` placeholder in the URL scheme from day one, even with a single style — retrofitting a path segment is an API migration. And make error responses rich immediately: an install failure inside a CLI is a terrible debugging surface, so a JSON body that says *why* (wrong library variant, missing license, item does not exist) saves real support volume.

## Registries and AI coding agents

The consumer of a registry is increasingly not a human typing `npx shadcn add`. Since the [August 2025 CLI 3.0 release](https://ui.shadcn.com/docs/changelog), shadcn has shipped an MCP server that lets AI agents browse, search, and install registry items directly. An agent asked to "build a pricing page" can query a registry for pricing sections and install one, instead of hallucinating a component from training data.

This changes what a registry is *for*. It stops being a convenience for developers and becomes the distribution layer for agent workflows: a machine-readable catalog of code your organization has already approved. Our own agent skills consume our registry API the same way the CLI does — the JSON contract does not care who is fetching. If you are building AI-assisted design-to-code pipelines, this is the same principle we wrote about in [using a Figma kit as a design system for Claude](/blog/use-shadcn-ui-figma-kit-as-design-system-for-claude-design): agents produce dramatically better output when they pull from a structured source of truth instead of improvising.

If you maintain internal components and your team uses coding agents, publishing a registry — even a private, static one — is one of the highest-leverage moves available right now.

## FAQ

**Is a registry the same as a fork of shadcn/ui?**
No. A fork is a copy of the component source. A registry is a distribution format — a schema plus an HTTP endpoint. Registries commonly serve components that have nothing to do with the official set: hooks, config files, full pages, entire themes.

**Can a registry be private?**
Yes. The [authentication docs](https://ui.shadcn.com/docs/registry/authentication) cover token-based access: your endpoint checks a header, and consumers configure that header (with env-var expansion) on the namespace in `components.json`. Our paid Pro Blocks work exactly this way, with license keys validated server-side.

**Do I need Next.js to run one?**
No. The contract is JSON over HTTP. Static hosting, any framework's route handlers, or a public GitHub repo all work. The registry-template uses Next.js purely for convenience.

**What is the difference between `dependencies` and `registryDependencies`?**
`dependencies` are npm packages installed via your package manager. `registryDependencies` are other registry items — names or URLs — that the CLI resolves recursively before installing, so a block can pull in the `button` and `card` it is composed from.

## Takeaways

- A registry is just schema-conformant JSON over HTTP; the CLI does the rest. There is no server-side magic to build unless you want auth.
- Resolution is mechanical: bare names hit the official registry, URLs hit anything, `@namespace/name` expands templates from `components.json` — with `{name}` and `{style}` placeholders.
- Since [May 2026](https://ui.shadcn.com/docs/changelog/2026-05-registry-include), `include` composes multi-file registries and `registry validate` catches broken items before your users do.
- The template-to-production gap is real but small: style-aware routing, honest error responses, auth for private content, and never breaking a published URL.
- Registries are becoming agent infrastructure, not just developer convenience.

If you want to see a production registry from the consumer side, [Pro Blocks](/pricing) installs through everything described in this post — the same CLI, the same schema, the same URL resolution you would use to run your own.
