Private registries
The CLI has always installed from any registry: --registry takes a URL or a path on disk. @dowel-ui/registry builds one, so an organisation can publish its own components and have them installed exactly the way these are — with the same dependency resolution, the same content hashes, and the same update that never overwrites an edited file.
This is the producer half of what the CLI has always consumed. Nothing here needs a licence.
Build one
Declare your items against the files that implement them. The build reads the real imports and refuses anything that would fail in a consumer’s project.
import { buildCustomRegistry, defineRegistryConfig } from "@dowel-ui/registry";
const result = await buildCustomRegistry(
defineRegistryConfig({
root: "src",
generatedFrom: "@acme/ui@1.0.0",
// One URL that serves both your components and everything upstream.
extends: "https://dowel-eight.vercel.app/r",
items: [
{
name: "acme-callout",
title: "Acme Callout",
description: "Acme's house callout, built on the upstream Badge.",
category: "display",
registryDependencies: ["badge"],
files: ["acme-callout.tsx"],
},
],
}),
);extends is the part that makes it one registry rather than two. Point a project at the result and add acme-callout installs your component, pulling badge from upstream on the way. Your developers learn one URL.
A local item replaces an upstream one of the same name, and the build tells you which. Overriding upstream’s Button is a legitimate thing to want and a catastrophic thing to do by accident, and the difference is whether anyone was told.
What it refuses
- A file it cannot read.
- An import written against the installed path (
@/components/ui/badge) rather than the authored one (@/components/badge). The leading group is rewritten to wherever the project keeps its components, so naming it twice produces a path that resolves nowhere. - A component that imports something it never declared, which would not be installed alongside it.
Host it
The output is a directory of JSON: an index and one file per item. Any static host serves it — an S3 bucket, a Pages site, a folder behind your VPN. Serve /r/*.json with Access-Control-Allow-Origin: * if a browser will ever read it, and cache it as hard as you like: a registry is immutable per release.
Then set it once per project, so nobody has to remember the flag:
{
"registry": "https://ui.acme.internal/r"
}Your agents see it too
dowel agents and the MCP server generate from whichever registry the project points at, so a coding agent in your repository learns your components alongside these, and is told when it types a name that exists in neither.
What Teams adds
Everything on this page is self-hosted and free. Teams is for organisations that would rather not run it: a hosted private registry, Pro licences for every developer in one agreement, and — on the roadmap — SSO and version governance across several products consuming one design system. It is priced by conversation, not by a table; see pricing.