React

Private registries: tokens, namespaces and installs in CI

How a private shadcn registry works: authentication, namespace, token scope and repeatable installs in CI, plus the errors you actually see in practice.

Key points

  • A private registry is a URL plus a token: the CLI has no concept of a team.
  • The namespace separates sources in `components.json` and makes the origin of every component readable.
  • In CI the token belongs in a secret, with the minimum scope and a rotation plan, never in the repository.

A private registry answers one simple question: how do I install a component that is not public, without copying it by hand? The answer is a protected JSON endpoint and a token with limited permissions.

The protocol, in short

The CLI asks a URL for an item, receives JSON with files, dependencies and metadata, then writes the files into the project. There is no magic: if the endpoint answers 401 or 403, the CLI stops.

{
  "registries": {
    "@eneui": {
      "url": "https://ui.eneawork.it/registry/{name}",
      "headers": {
        "Authorization": "Bearer ${ENEUI_TOKEN}"
      }
    }
  }
}

The `@eneui` namespace is what makes the command readable: `npx shadcn@latest add @eneui/hero`. Without a namespace, two registries with the same block name become ambiguous.

Where to put the token

ContextWhere the token livesWhat to avoid
LocalShell environment variableA committed `.env` file
CIProvider secretEchoing the token in logs
ProductionSecret managerThe token in the client bundle
TeamPassword manager or vaultSharing the token over chat

The errors you actually see

SymptomLikely causeCheck
401 UnauthorizedMissing header or expired token`curl` with the same header
403 ForbiddenValid token without access to the itemPlan or token scope
404 Not FoundWrong name or namespaceThe exact item URL
402 Payment RequiredPaid item without accessSubscription status
Files written but imports brokenMissing registry dependencyThe item `dependencies`

On EneUI the Pro block code is protected server-side: the preview is public, and the registry item answers with an explicit error until access is granted. The client never receives the code before payment, so there is nothing to get around in the browser.

Repeatable installs in CI

# .github/workflows/ui.yml
- name: Install UI blocks
  run: npx shadcn@latest add @eneui/hero @eneui/pricing --yes
  env:
    ENEUI_TOKEN: ${{ secrets.ENEUI_TOKEN }}
  • Pin the CLI version: do not use `latest` in CI.
  • Commit the generated files: the install is deterministic, the human review is not.
  • Fail the pipeline on 401/402/403: those are configuration errors, not noise.
  • Regenerate the token with an expiry and keep only the latest one.

Namespace and provenance

With 1,556 blocks from 28 registries (31 October 2026) the same name can exist in several sources. The namespace is not decoration: it is the part of the name that says where the code comes from and which licence applies.

The /blocks page shows source, licence, files and dependencies for every block before you install it.

Frequently asked questions

Does a private registry need a server?

No, you only need an endpoint that returns JSON in the registry format. It can be static when there is no sensitive data.

Can the token live in `components.json`?

Better not: the file ends up in the repository. Use an environment variable and keep the secret out.

Why do I need a namespace?

To tell apart registries with the same item name, and to make provenance explicit in the install command.

What does 402 Payment Required mean?

That the item exists but needs paid access. It differs from 403: the token is valid, the entitlement is missing.

How do I rotate a token without breaking CI?

Add the new token as a secret, verify the pipeline, then revoke the old one. Never revoke before the deploy.