How the shadcn Build Process Works: Configuration, Output, and Registry Generation

The shadcn build process compiles the CLI and core utilities into a minified ES-module bundle using tsup, while a separate registry build command generates static JSON files that power the component installation workflow.

The shadcn-ui/ui repository is a monorepo that packages the shadcn CLI and its registry system. Understanding the shadcn build process is essential for contributors who need to modify the CLI or maintain a custom component registry. This article breaks down the bundler configuration, monorepo orchestration, and registry generation pipeline.

shadcn Build Output Configuration

The core build configuration lives in packages/shadcn/tsup.config.ts and uses tsup, a fast TypeScript bundler built on esbuild.

tsup Bundler Settings

The configuration defines a strict ES-module output with aggressive optimizations:

// packages/shadcn/tsup.config.ts
import { defineConfig } from "tsup"

export default defineConfig({
  clean: true,           // Removes dist/ before building
  dts: true,             // Generates .d.ts type declarations
  entry: [
    "src/index.ts",      // CLI entry point
    "src/registry/**/*.ts",
    "src/utils/**/*.ts",
    // ... other public APIs
  ],
  format: ["esm"],       // Only ES modules
  sourcemap: true,       // Debug symbols
  minify: true,          // Minified output
  target: "esnext",      // Latest JavaScript features
  outDir: "dist",        // Output directory
  treeshake: true,        // Remove unused code
  onSuccess: "cp src/tailwind.css dist/tailwind.css"
})

The onSuccess hook copies src/tailwind.css into the dist folder, ensuring the CLI can access the base Tailwind styles when initializing new projects.

Output Directory Structure

After running the build, packages/shadcn/dist/ contains:

  • index.js – The CLI entry point (shebang included for npx execution)
  • registry/, schema/, utils/, icons/ – Sub-paths for modular imports
  • tailwind.css – Static asset for project initialization
  • *.d.ts – Bundled type definitions for TypeScript consumers

Monorepo Build Orchestration with Turbo

The shadcn-ui/ui repository uses Turborepo to coordinate builds across packages.

Root-Level Build Scripts

The root package.json defines a Turbo pipeline:

{
  "scripts": {
    "build": "turbo run build"
  }
}

Running pnpm run build (or npm run build) triggers turbo run build, which walks the dependency graph and executes package builds in parallel where possible.

Package-Specific Build Commands

For the shadcn package specifically, the build is invoked via:

pnpm shadcn:build            # alias for pnpm --filter=shadcn build

This executes the build script defined in packages/shadcn/package.json:

{
  "scripts": {
    "build": "tsup"
  }
}

Other packages in the monorepo (such as the v4 app) have their own build steps, but the core CLI output always lands in packages/shadcn/dist.

shadcn Registry Build Process

Beyond the CLI bundle, shadcn provides a registry build command that generates static JSON files powering the component installation workflow.

CLI Command Structure

The registry build logic lives in packages/shadcn/src/commands/registry/build.ts. The command is exposed through the CLI as:

pnpm shadcn registry:build

Or using the production binary:

pnpm shadcn:prod registry:build

The command accepts several options:

  • -o, --output – Output directory (default: ./public/r)
  • -c, --cwd – Working directory
  • --verbose – Detailed logging

Registry Resolution and Validation

The build process follows a strict pipeline:

  1. Pre-flight validation – Ensures registry.json (or components.json) exists and loads project configuration.
  2. Schema validation – Loads and validates registry.json against a Zod schema.
  3. Item resolution – Recursively walks each registryItem.files, resolving import statements and pulling referenced files and dependencies.
  4. Deduplication – Removes duplicate file paths and dependencies per item.
  5. Content embedding – Reads the source code of every resolved file from disk and stores it under file.content.

JSON Generation and Output

After processing, the command:

  1. Attaches a $schema URL for editor validation.
  2. Validates each enriched item against registryItemSchema.
  3. Writes one JSON file per registry item to the output directory (default ./public/r).
  4. Copies the master registry.json into the output folder.
  5. Optionally prints a summary of generated items when --verbose is enabled.

The resulting ./public/r folder contains:

  • registry.json – The master index of all available components.
  • Individual JSON files (e.g., button.json, card.json) – Self-contained component definitions with embedded source code.

Practical Build Examples

Building the Component Library

To compile the shadcn CLI and utilities from the repository root:


# Build all packages using Turbo

pnpm run build

# Or build only the shadcn package

pnpm shadcn:build

The output lands in packages/shadcn/dist/ and includes the CLI entry point, type definitions, and the tailwind.css asset.

Building the Registry JSON

To generate the static registry files for component distribution:


# Default output to ./public/r

pnpm shadcn registry:build

# Custom working directory and output

pnpm shadcn registry:build -c ./my-app -o ./my-app/public/registry

# With detailed logging

pnpm shadcn registry:build --verbose

The production CLI runs with REGISTRY_URL=https://ui.shadcn.com/r, serving the generated JSON files as static assets.

Key Configuration Files

File Role
packages/shadcn/tsup.config.ts Defines how TypeScript source compiles into the dist folder using tsup.
packages/shadcn/package.json Contains the build script (tsup) and publish configuration.
packages/shadcn/src/commands/registry/build.ts Implements the CLI command that creates JSON registry output.
package.json (root) Turbo monorepo orchestration; top-level build script runs all package builds.
turbo.json Turbo configuration defining the task graph and build caching.

Summary

  • tsup configuration in packages/shadcn/tsup.config.ts controls the bundler settings, producing a minified ES-module output with type declarations in dist/.
  • Turborepo orchestration coordinates builds across the monorepo via turbo run build, with the shadcn package building independently through pnpm shadcn:build.
  • Registry build command (shadcn registry:build) generates static JSON files by resolving imports, deduplicating dependencies, and embedding source code into public/r/.
  • Output artifacts include the CLI bundle (index.js), sub-modules for registry utilities, TypeScript declarations, and the tailwind.css asset required for project initialization.

Frequently Asked Questions

What bundler does shadcn use for its build process?

The shadcn CLI and core utilities are bundled using tsup, a fast TypeScript bundler built on esbuild. The configuration in packages/shadcn/tsup.config.ts specifies ES-module output, type declaration generation, minification, and tree-shaking to produce the final dist/ artifacts.

How does the shadcn registry build command work?

The shadcn registry:build command, implemented in packages/shadcn/src/commands/registry/build.ts, reads the project's registry.json, validates it against a Zod schema, recursively resolves all file imports for each registry item, deduplicates dependencies, embeds the source code content, and writes individual JSON files to the output directory (default ./public/r).

Where does the shadcn build output go?

The main CLI build outputs to packages/shadcn/dist/ and includes index.js (the CLI entry point), modular sub-directories for registry and utility functions, bundled .d.ts type declarations, and a copied tailwind.css file. The registry build outputs to ./public/r/ (configurable via -o flag) and contains the master registry.json and individual component JSON files.

Can I customize the output directory for the registry build?

Yes, the shadcn registry:build command accepts an -o, --output flag to specify a custom directory. For example, pnpm shadcn registry:build -o ./my-app/public/registry directs the generated JSON files to that location instead of the default ./public/r.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →