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
| Context | Where the token lives | What to avoid |
|---|---|---|
| Local | Shell environment variable | A committed `.env` file |
| CI | Provider secret | Echoing the token in logs |
| Production | Secret manager | The token in the client bundle |
| Team | Password manager or vault | Sharing the token over chat |
The errors you actually see
| Symptom | Likely cause | Check |
|---|---|---|
| 401 Unauthorized | Missing header or expired token | `curl` with the same header |
| 403 Forbidden | Valid token without access to the item | Plan or token scope |
| 404 Not Found | Wrong name or namespace | The exact item URL |
| 402 Payment Required | Paid item without access | Subscription status |
| Files written but imports broken | Missing registry dependency | The 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.