JB logo

Command Palette

Search for a command to run...

yOUTUBE
Blog
PreviousNext

How to Create and Register a Shadcn Component Registry

A step-by-step guide to building your own custom shadcn/ui component registry. Learn how to package reusable components, blocks, and full-page templates that others can install with a single CLI command.

The shadcn/ui registry system lets you package and distribute React components, blocks, hooks, and even full-page templates. Anyone can install your components with a single CLI command — no npm publishing required.

In this guide, you'll learn how to create your own registry from scratch.

What is a Component Registry?

A component registry is a collection of JSON files served over HTTP. Each JSON file describes a component: its source code, dependencies, target file paths, CSS variables, and more.

When someone runs:

pnpm dlx shadcn@latest add https://yoursite.com/r/my-component.json

The CLI downloads the JSON, extracts the files, installs dependencies, and places everything in the right directories — automatically.

Prerequisites

  • A Next.js project (or any framework that serves static files)
  • Node.js 18+
  • pnpm, npm, or yarn

Step 1: Initialize Your Project

Start with an existing Next.js project or create a new one:

pnpm create next-app@latest my-registry
cd my-registry

Install the shadcn CLI as a dev dependency:

pnpm add -D shadcn@latest

Step 2: Create components.json

The components.json file configures how shadcn resolves paths in your project:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "zinc",
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  }
}

Step 3: Create Your First Component

Create a directory structure following the convention registry/[STYLE]/[NAME]/:

registry/
└── new-york/
    └── hello-world/
        └── hello-world.tsx

Write your component:

// registry/new-york/hello-world/hello-world.tsx
export function HelloWorld({ name }: { name: string }) {
  return (
    <div className="rounded-lg border p-4">
      <h2 className="text-lg font-semibold">Hello, {name}!</h2>
      <p className="text-muted-foreground">
        Welcome to the component registry.
      </p>
    </div>
  );
}

Step 4: Create registry.json

The registry.json at your project root defines all registry items:

{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "my-registry",
  "homepage": "https://my-registry.vercel.app",
  "items": [
    {
      "name": "hello-world",
      "type": "registry:block",
      "title": "Hello World",
      "description": "A simple greeting component with customizable name prop.",
      "files": [
        {
          "path": "registry/new-york/hello-world/hello-world.tsx",
          "type": "registry:component",
          "target": "components/hello-world.tsx"
        }
      ]
    }
  ]
}

Key Fields Explained

FieldPurpose
nameUnique identifier (kebab-case)
typeComponent type: registry:block, registry:ui, registry:hook, registry:lib
files[].pathSource file path in YOUR repo
files[].typeFile category: registry:component, registry:page, registry:lib, registry:file
files[].targetWhere the file goes in the USER's project

Step 5: Build the Registry

Add a build script to your package.json:

{
  "scripts": {
    "registry:build": "shadcn build"
  }
}

Run it:

pnpm registry:build

This generates JSON files in public/r/:

public/r/
├── registry.json        # Index of all items
└── hello-world.json     # Component with embedded source code

Step 6: Test Locally

Start your dev server:

pnpm dev

In another terminal, test the installation in a separate project:

pnpm dlx shadcn@latest add http://localhost:3000/r/hello-world.json

The CLI will download the component, install any dependencies, and create the file at the target path.

Adding Dependencies

Components often need npm packages. Declare them in registry.json:

{
  "name": "fancy-button",
  "type": "registry:block",
  "dependencies": ["class-variance-authority", "clsx"],
  "registryDependencies": ["button"],
  "files": [...]
}
  • dependencies — npm packages to install (e.g., "zod@^3.20.0")
  • registryDependencies — other registry items this component needs
  • devDependencies — dev-only npm packages

Multi-File Components (Blocks)

Blocks can contain multiple files — pages, components, libs, and API routes:

{
  "name": "auth-page",
  "type": "registry:block",
  "title": "Authentication Page",
  "description": "Login page with form validation and social providers.",
  "dependencies": ["zod", "react-hook-form"],
  "registryDependencies": ["button", "input", "card"],
  "files": [
    {
      "path": "registry/new-york/auth-page/login-page.tsx",
      "type": "registry:page",
      "target": "app/login/page.tsx"
    },
    {
      "path": "registry/new-york/auth-page/login-form.tsx",
      "type": "registry:component",
      "target": "components/login-form.tsx"
    },
    {
      "path": "registry/new-york/auth-page/auth-utils.ts",
      "type": "registry:lib",
      "target": "lib/auth-utils.ts"
    }
  ]
}

Adding CSS Variables

You can inject CSS variables that merge with the user's theme:

{
  "name": "my-theme",
  "type": "registry:style",
  "cssVars": {
    "light": {
      "brand": "oklch(0.6 0.2 260)",
      "brand-foreground": "oklch(1 0 0)"
    },
    "dark": {
      "brand": "oklch(0.7 0.15 255)",
      "brand-foreground": "oklch(0.1 0 0)"
    }
  }
}

Adding Environment Variables

For components that need API keys or config:

{
  "name": "payment-gateway",
  "type": "registry:block",
  "envVars": {
    "PAYMENT_API_URL": "http://localhost:8080",
    "PAYMENT_API_KEY": ""
  },
  "files": [...]
}

The CLI will add these to the user's .env.local file.

Component Types Reference

TypeUse CaseExample
registry:uiSingle reusable UI componentButton, Input, Card
registry:blockMulti-file component/templateLogin form, Dashboard
registry:hookReact hookuseDebounce, useMediaQuery
registry:libUtility/helper libraryAPI client, formatters
registry:pagePage templateLanding page, settings
registry:fileGeneric fileConfig, MDX content
registry:styleTheme/style configColor schemes

File Types Reference

Each file in the files array needs a type:

File TypeResolves To
registry:componentcomponents/ directory
registry:uicomponents/ui/ directory
registry:hookhooks/ directory
registry:liblib/ directory
registry:pageUses target path directly
registry:fileUses target path directly

Real-World Example: MDX Blog Component

Here's how we register a complete MDX blog system as a single installable block:

{
  "name": "mdx-blog",
  "type": "registry:block",
  "title": "MDX Blog",
  "description": "File-based MDX blog with syntax highlighting and SEO.",
  "dependencies": ["next-mdx-remote"],
  "devDependencies": [
    "gray-matter",
    "remark-gfm",
    "rehype-pretty-code",
    "rehype-slug",
    "shiki"
  ],
  "files": [
    {
      "path": "registry/new-york/mdx-blog/blog-types.ts",
      "type": "registry:lib",
      "target": "types/blog.ts"
    },
    {
      "path": "registry/new-york/mdx-blog/blog-data.ts",
      "type": "registry:lib",
      "target": "data/blog.ts"
    },
    {
      "path": "registry/new-york/mdx-blog/mdx.tsx",
      "type": "registry:component",
      "target": "components/mdx.tsx"
    },
    {
      "path": "registry/new-york/mdx-blog/blog-page.tsx",
      "type": "registry:page",
      "target": "app/blog/page.tsx"
    },
    {
      "path": "registry/new-york/mdx-blog/blog-slug-page.tsx",
      "type": "registry:page",
      "target": "app/blog/[slug]/page.tsx"
    },
    {
      "path": "registry/new-york/mdx-blog/sample-post.mdx",
      "type": "registry:file",
      "target": "content/blog/sample-post.mdx"
    }
  ]
}

Users install the entire blog with:

pnpm dlx shadcn@latest add https://yoursite.com/r/mdx-blog.json

This creates all the files, installs next-mdx-remote, gray-matter, rehype-pretty-code, etc., and they have a working blog immediately.

Publishing Your Registry

Deploy your project to any hosting platform (Vercel, Netlify, Cloudflare Pages). The public/r/ files will be served as static JSON.

Users install components using your domain:

pnpm dlx shadcn@latest add https://yoursite.com/r/hello-world.json

Namespaced Registries

Users can add your registry as a namespace in their components.json:

{
  "registries": {
    "@myorg": "https://yoursite.com/r/{name}.json"
  }
}

Then install with:

pnpm dlx shadcn@latest add @myorg/hello-world

Best Practices

  1. Use clear descriptions — They help AI assistants and developers discover your components
  2. List all dependencies — Don't assume the user has anything installed
  3. Use target paths — Always specify where files should go in the user's project
  4. Keep components self-contained — Minimize cross-component dependencies
  5. Include sample data — For data-driven components, include dummy data so they work immediately
  6. Add environment variables — Use envVars for API keys and config
  7. Test the install flow — Always test npx shadcn@latest add in a fresh project
  8. Version your dependencies — Use semver: "zod@^3.20.0"

Automating the Build

If your registry files live alongside your app (like ours), create a copy script that syncs source files into the registry/ directory before building:

{
  "scripts": {
    "registry:build": "node scripts/copy-sources.mjs && shadcn build"
  }
}

This ensures the registry always reflects your latest code.

Summary

The shadcn registry system is a powerful way to distribute components:

  1. Create components in registry/[style]/[name]/
  2. Define them in registry.json with metadata, files, and dependencies
  3. Build with shadcn build to generate public/r/ JSON files
  4. Deploy to any static host
  5. Share the URL — users install with npx shadcn@latest add [URL]

No npm publishing, no package maintenance, no version conflicts. Just JSON over HTTP.

Happy building!