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

5 min read

TL;DR 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.

On this page
  1. Why ship your own components as a registry?
  2. What goes in registry.json?
  3. What does the build step produce?
  4. Where should you host it?
  5. How do you add an Open in v0 button?
  6. What usually goes wrong?
  7. FAQ
  8. Do I need the shadcn registry template?
  9. Can a registry ship things that aren't components?
  10. How do people update a component they already installed?

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, including the 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.

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.

Day / Night Toggle running live. A theme switch with a sun that sets into a starry night.

What goes in registry.json?

The root file names the registry and lists its items. Each item has to match the registry-item schema, 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. 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, 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.