# How to Publish Your Own shadcn Registry (One-Command Installs for Your Components)

> List your components in registry.json (name and type are required). Run npx shadcn@latest build to get one JSON file per item in public/r and host it anywhere static, or put registry.json at the root of a public GitHub repo and install as owner/repo/item. For v0, link to https://v0.dev/chat/api/open?url= plus the item URL and skip cssVars, css, envVars and namespaces.

- URL: https://qodesign.dev/blog/publish-your-own-shadcn-registry
- Author: Teodor Avadani (https://qodesign.dev/blog/author/teodor-avadani)
- Published: 2026-10-21
- Updated: 2026-10-21
- Category: AI-built UI
- Publisher: qodesign (https://qodesign.dev), a shop of animated React + Tailwind CSS components for Next.js, each sold with its full source code and an AI prompt.

A shadcn registry is a `registry.json` file that lists your components, plus one JSON file per component, served over HTTP. Once it's online, anyone (future you included) installs a component with `npx shadcn@latest add <url>`, and the CLI writes the source files into their project and installs the npm packages they need. Static hosting like GitHub Pages is enough. v0 can open the same URL through its Open in v0 endpoint, and a public GitHub repo with `registry.json` at its root now works as a registry with no hosting step at all.

I make [qodesign](/), a set of React + TypeScript components with Tailwind (most also use Framer Motion). We publish six free pieces as a registry from our MIT repo [qodesign-free](https://github.com/teodoravadani/qodesign-free), including the [Day / Night Toggle](/components/day-night-toggle). Everything below comes from that setup and from the shadcn registry docs as I read them on October 21, 2026. If you want the other side, installing from someone else's registry, [this post covers it](/blog/shadcn-registry-install-third-party-components).

## Why ship your own components as a registry?

If you build with Cursor, v0 or Lovable, you probably have a handful of components you've tuned by hand and now paste from project to project. Copy-paste drops three things on the way: the npm packages the file imports, where the file is supposed to live, and every fix you make later. A registry item carries all three.

The person installing still owns the code. The CLI writes plain `.tsx` files into their repo, so there's no package version to keep in sync and nothing to eject from. That's the same deal shadcn/ui itself offers, which is why it fits AI editors well: the agent can read and edit the installed file like any other file in the project.

## What goes in registry.json?

The root file names the registry and lists its items. Each item has to match the [registry-item schema](https://ui.shadcn.com/docs/registry/registry-item-json), where only `name` and `type` are required. Here is our real entry for the toggle, with the homepage shortened:

```json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "qodesign-free",
  "homepage": "https://qodesign.dev",
  "items": [
    {
      "name": "day-night-toggle",
      "type": "registry:component",
      "title": "Day / Night Toggle",
      "description": "Theme switch where the sun rolls into a moon, clouds fall away and stars drop in. Free, MIT, by qodesign.dev.",
      "dependencies": [
        "framer-motion"
      ],
      "files": [
        {
          "path": "components/day-night-toggle/react/src/components/DayNightToggle.tsx",
          "type": "registry:component",
          "target": "components/day-night-toggle/DayNightToggle.tsx"
        }
      ]
    }
  ]
}
```

What each field does:

- `path` is where the file lives in your repo. `target` is where it lands in theirs.
- Without `target`, the CLI reads the person's `components.json` to decide where the file goes. The docs say `target` is only required for `registry:page` and `registry:file`. We set it anyway so each piece lands in its own folder.
- A `target` can start with `@ui/`, `@components/`, `@lib/` or `@hooks/`. Those resolve to the aliases in the person's `components.json`, so the file respects a `src/` layout or a monorepo package without you hardcoding paths.
- `dependencies` are npm packages the CLI installs. List every import that isn't React. Ours is just `framer-motion`.
- `registryDependencies` are other registry items, like `button` from shadcn/ui or a full URL to another item of yours.

For `type`, the docs list twelve values. The ones you'll use most: `registry:ui` for single-file primitives, `registry:component` for simple components, `registry:block` for multi-file pieces, plus `registry:hook`, `registry:lib`, and `registry:page` or `registry:file` for routes and config (both need a `target`). Our two dashboards are blocks and the four small pieces are components.

## What does the build step produce?

`npx shadcn@latest build` reads `registry.json` and writes one JSON file per item to `public/r` by default (`--output` changes the folder). Each generated file repeats the item's fields and adds a `content` string holding the full source of every file.

You can see ours at [/r/day-night-toggle.json](https://teodoravadani.github.io/qodesign-free/r/day-night-toggle.json). The toggle's source is inlined as 4,919 characters, starting with `'use client';`. That generated file is what the CLI downloads when someone runs `add`.

One thing to check before you build: the CLI copies your file as it is. If the component uses state, effects or Framer Motion, the `'use client'` line has to be in your source, or it will break the first time someone imports it into a Server Component page.

## Where should you host it?

You have three options, and you can mix them.

Static JSON files. Put the built `r/` folder on any static host: GitHub Pages, Vercel, Netlify or the `public` folder of a Next.js app. Ours lives on GitHub Pages, and the README documents this install:

```bash
npx shadcn@latest add https://teodoravadani.github.io/qodesign-free/r/day-night-toggle.json
```

A GitHub registry. Since shadcn added [GitHub registries](https://ui.shadcn.com/docs/registry/github), a public repo with `registry.json` at the root installs as `owner/repo/item`, with no generated JSON to publish:

```bash
npx shadcn@latest add teodoravadani/qodesign-free/day-night-toggle
```

The docs list four requirements: a github.com repository, `registry.json` at the root, valid schemas, and source files that exist at the paths you declare. Our repo meets all four on paper, but I haven't run this short address against it yet, so the Pages URL above is the one we document.

A namespace. Add your registry to `registries` in `components.json` with a `{name}` placeholder, then install by short name:

```json
{
  "registries": {
    "@qodesign-free": "https://teodoravadani.github.io/qodesign-free/r/{name}.json"
  }
}
```

```bash
npx shadcn@latest add @qodesign-free/day-night-toggle
```

Namespace names start with `@` and use only letters, numbers, hyphens and underscores. The docs point to namespaces when you need a stable alias, custom hosting, request headers or a private registry. For a solo builder sharing free pieces, a GitHub registry or static files are enough to start.

## How do you add an Open in v0 button?

If an item is public, v0 opens it from `https://v0.dev/chat/api/open?url=` followed by the item's URL. The docs' example wraps a shadcn `Button`. This version is a plain link, so it works without any other component (it compiles under strict TypeScript with React 19 types):

```tsx
type OpenInV0LinkProps = { itemUrl: string };

export function OpenInV0Link({ itemUrl }: OpenInV0LinkProps) {
  return (
    <a
      href={`https://v0.dev/chat/api/open?url=${itemUrl}`}
      target="_blank"
      rel="noreferrer"
      aria-label="Open in v0"
      className="inline-flex h-8 items-center rounded-md bg-black px-3 text-xs font-medium text-white dark:bg-white dark:text-black"
    >
      Open in v0
    </a>
  );
}
```

The docs list what Open in v0 doesn't support: `cssVars`, `css`, `envVars`, namespaced registries and advanced authentication. Items you want people to open in v0 should stick to files and `dependencies`, and the button should use the full item URL, not a `@namespace/item` name.

## What usually goes wrong?

- An item without `name` or `type` fails the schema. Those are the only two required fields, so check them first.
- A `path` that doesn't match a real file. GitHub registries require every declared file to exist in the repo.
- A missing npm package in `dependencies`. The install works, then the import fails on the first dev server start.
- A missing `'use client'` in an interactive component.
- A `registry:page` or `registry:file` item without a `target`.
- `cssVars` or `css` in an item you also send to v0.

## FAQ

### Do I need the shadcn registry template?

No. The docs say any framework that serves JSON over HTTP works, and so does a public GitHub repo with `registry.json` at the root. The template is a starting point if you want one.

### Can a registry ship things that aren't components?

Yes. The GitHub registry docs list config files, docs, templates, `AGENTS.md` and `.cursor/rules/*` as examples. Use `registry:file` with a `target` that starts with `~/` to place a file relative to the project root, which is a tidy way to share your Cursor rules across projects.

### How do people update a component they already installed?

They own the files once they're installed, so an update means running `add` again and reviewing the diff. Keep your item names stable so the same command keeps working.
