Registry Format
The registry is the source of truth for every installable component, block, page, bundle, and theme.
Each item is an item.json manifest plus one or more source
files living under registry/open/ or
registry/pro/. Running
php artisan registry:build validates the tree, inlines
file content and SHA-256 hashes, and writes JSON endpoints under
public/r/<tier>/ that the
ui:add CLI fetches and writes into the consumer app.
Manifest
Every item declares itself through an item.json file
at the root of its source directory. Below is the real manifest for the
Button component.
{
"name": "button",
"type": "registry:component",
"title": "Button",
"description": "Displays a button or link with variants, sizes, loading and an optional animated arrow.",
"version": "1.2.1",
"license": "open",
"stability": "stable",
"knowledge": {
"keywords": ["action", "submit", "call to action"],
"useWhen": ["Triggering an immediate action such as saving or submitting."],
"avoidWhen": ["Representing a persistent on/off value; use switch or checkbox."],
"related": ["button-group", "toggle"]
},
"registryDependencies": [],
"dependencies": {
"composer": ["jml/brok:^1.0"],
"npm": []
},
"files": [
{
"path": "ui/button/button.blade.php",
"target": "resources/views/components/ui/button.blade.php",
"type": "blade"
}
],
"meta": {
"a11y": "wcag-2.2-aa-target",
"responsive": true,
"rtl": true,
"darkMode": true,
"localized": true
}
}
Field reference
| Field | Type | Description |
|---|---|---|
| name | string | Slug used by the CLI (ui:add button) and as the registry key. Lowercase, hyphenated. |
| type | string | Item kind. One of registry:component, registry:block, registry:page, registry:bundle, or registry:theme. Drives target and catalog validation. |
| title | string | Human-readable display name shown in docs and the CLI confirmation prompt. |
| description | string | One-sentence summary rendered on the docs index and in ui:status output. |
| version | string | Semver string. ui:diff and ui:update compare this against what is recorded in the consumer's ui-lock.json. |
| contractVersion | integer | Version of the normalized human/agent contract emitted by the builder. Authored v2 manifests are checked against their Blade prop names and defaults. |
| foundationVersion | semver | Minimum shared ui.css/_styles.php foundation required by the item. The CLI blocks incompatible installs. |
| license | string | "open" (MIT, no auth required) or "pro" (commercial, requires ui:auth). Must match the tier directory the item lives in; the build enforces parity. |
| stability | string | Maturity signal ("stable", "beta", "experimental", or "deprecated"). Change impact is recorded separately in migrations. |
| registryDependencies | string[] | Other registry item slugs this item requires. The CLI resolves them recursively (with a cycle guard) and installs them automatically. Open items may not depend on pro items. |
| knowledge | object | Guidance for humans and agents: keywords, use/avoid cases, anatomy, source-derived props/slots, behaviour, examples, related items, accessibility, and theming. Sparse authored data is completed by the builder. |
| composition | object | Machine-readable layout contract: kind, surface, frame, regions, responsive rules, states, and components used. Blocks and pages expose it through ui:show --skeleton. |
| migrations[] | object[] | Structured from/to migration guidance, summary, breaking flag, optional manual steps, and optional codemod identifier. |
| deprecation | object | Required when stability is deprecated: version introduced, replacement item, removal horizon, and optional message. |
| dependencies.composer | string[] | Composer package constraints the consumer must have in their composer.json. The CLI checks for these and warns if they are absent. |
| dependencies.npm | string[] | npm package constraints (e.g. "alpinejs:^3.0") wired into the consumer's package.json check. |
| files[].path | string | Path to the source file relative to the tier root (registry/<tier>/), i.e. <kind>/<name>/<file> such as ui/button/button.blade.php. This is the single edit point for that file. |
| files[].target | string | Where the file lands in the consumer's project. Must be under resources/views/components/ui, resources/views/blocks, resources/views/pages, resources/js/ui, or resources/css/themes. The build rejects any target outside these paths. |
| files[].type | string | File kind: "blade", "js", or "css". JS is wired into resources/js/ui/index.js; registry theme CSS receives a managed import in resources/css/ui.css. |
| meta | object | Quality signals: a11y, responsive, rtl, darkMode, localized. Displayed as capability badges in the docs UI. |
The build pipeline
php artisan registry:build (the BuildRegistry command in
apps/docs-registry) runs in four stages:
-
Scan — walks every
registry/<tier>/<kind>/<name>/item.jsonand loads the manifest. - Validate — checks that every declared source file exists, targets stay inside the allowed path set, license flags match the tier, and open items carry no pro dependencies.
-
Inline — reads each source file and
embeds its UTF-8 content plus a
sha256hash directly into the published JSON so the CLI can detect transport corruption after download. (The hash is co-shipped with the content, so it guards against accidental drift, not a tampered registry — see the integrity note below.) -
Write — emits
public/r/<tier>/<name>.jsonper item and a tier-levelpublic/r/<tier>/registry.jsonindex.
On the consumer side, ui:add <name> fetches the item
JSON from the registry endpoint, writes each files[].target
into the app, resolves registryDependencies recursively
(a cycle guard prevents infinite loops), wires any JS files into
resources/js/ui/index.js, and records the installed version
in ui-lock.json.
Three places must stay in sync
A registry item is only fully "done" when it exists in all three of the following
locations simultaneously. Guard tests (CatalogNavigationTest,
MirrorParityTest, BlockRenderTest)
enforce parity in CI and fail fast when any location is missing.
1. Registry source
registry/open/<kind>/<name>/ — the
item.json manifest plus all source files. This is the
single edit point. Never modify files anywhere else to "fix" the component.
2. Docs-app render + JS copy
The docs site renders each item's Blade in place from
registry/ via non-prefixed anonymous component paths;
there is no per-item Blade mirror. Each item's behaviour JS is copied into the docs app at its
files[].target because the lazy loader can only glob
resources/js/ui/**. Run
php artisan registry:mirror — never hand-copy files.
3. Catalog entry
The kind-specific file under apps/docs-registry/catalog-data/ drives
navigation, docs pages, and every catalog-driven guard test. An item that is installable but
not listed there is invisible. Block and page render providers derive from the catalog row; do not maintain a second test list.
Note
php artisan registry:mirror manually. Drift is gated by
php artisan registry:mirror --check, which runs inside
composer check and CI and exits non-zero if any shared
asset or per-item JS copy diverges.Validation rules
The build command rejects any manifest that violates the following rules. All checks
also run with --dry-run so you can validate without writing
output files.
-
—
Every declared file must exist.
Each
files[].pathis resolved against the item source directory; the build fails if the file is missing. -
—
Open items may not depend on pro items.
Any slug in
registryDependenciesis checked against the tier of the dependency; cross-tier references from open → pro are rejected. -
—
Targets must stay under allowed paths.
Valid prefixes are
resources/views/components/ui,resources/views/blocks,resources/views/pages, andresources/js/ui. Any other target path is rejected. -
—
Published files carry SHA-256 hashes and inlined content.
The build computes a
sha256of each source file and embeds both the hash and the full file content in the published JSON. The CLI recomputes the hash on write and aborts on a mismatch. Because the hash is co-shipped with the content, this catches transport corruption and accidental drift — by itself it is not a defence against a tampered or malicious registry (an attacker who can alter the response can recompute a matching hash), and unpinned consumers rely on HTTPS transport trust to the registry host. For provenance independent of the transport, pin the publisher's Ed25519 key asregistrySigningPublicKeyinui.json: bothui:addandui:updatethen verify the registry index against its detached signature before trusting anything, failing closed on a missing or invalid signature, reject a replayed older index, and bind every fetched item to the signed index's per-item content hash — a shipped guarantee, not a planned one. -
—
License flags must match the source tier.
An item under
registry/open/must declare"license": "open"; an item underregistry/pro/must declare"license": "pro". -
—
Name must match the directory slug.
The
namefield must equal the item's containing directory name to prevent mismatches between the CLI key and the file path.
Validate locally
Run the build in dry-run mode from the apps/docs-registry
directory to catch manifest errors without writing any output files:
php artisan registry:build --dry-run
To also check that shared assets and per-item JS copies are in sync with their canonical sources (useful before opening a PR):
php artisan registry:mirror --check
Warning
composer check shortcut in
apps/docs-registry runs
registry:mirror --check, Pint, and the full test suite
in one command. Run it before pushing to confirm nothing is out of sync.