The tour in one paragraph
A schema is a small JSON file. A publisher hashes it to its DAG-CBOR CID and records the CID in a lockfile. A sync uploads the blob and publishes a companion document at an hm:// URL in the space of the account that signs the push. That document's metadata points at the blob. Apps bundle the library, resolve any other reference over the network, and run one validation engine to drive explorers, editors, forms and warnings. The reference validator uses the same engine. A generator turns every schema into a TypeScript type. The sections below take each layer in turn.
hypermedia/<name>.schema.json ──publish.mjs──▶ schemas.lock.json (name → CID)
│ + <name>.md │
│ ▼
└──────hypermedia:push──▶ DAG-CBOR blob (ipfs://<cid>)
+ document hm://<key>/<name>
metadata.schemaDefinition = ipfs://<cid>
│
┌─────────────────────────────┼──────────────────────────┐
▼ ▼ ▼
bundled registry in the app resolved over the network typegen.mjs
(tour, editors, inspector) (attributesSchema / childAttributesSchema) (TS types)Layer 1: values and the codec
Everything a schema types is an IPLD value. Each value is one of nine kinds: null, boolean, integer, float, string, bytes, list, map, link. The canonical form is DAG-CBOR, a deterministic binary encoding with a native link type for CIDs. The human form is dag-json, a lossless JSON projection. It spells a link as {"/": "bafy…"} and bytes as {"/": {"bytes": "…"}}. The repository holds dag-json, and the network holds DAG-CBOR. Both are projections of one graph, and converting between them is mechanical. See the data model and encoding.
Layer 2: schemas and the meta-schema
A schema is a map value that constrains other values. It takes one of nine shapes:
A bare string, integer, boolean or null is also a literal and accepts exactly that value. The meta-schema is the schema of schemas. It is the discriminated union of those shapes, and it validates as an instance of itself. The reference validator checks this self-description on every run. See the schema language.
Layer 3: the library
The library is a folder of pairs: <name>.schema.json holds the schema in dag-json, and <name>.md explains it. Three families live side by side, told apart by path prefix:
prefix | family | examples |
|---|---|---|
root and | the Hypermedia Network's real blobs, one canonical primitive per kind and the meta-schema at the root, the block model in |
|
| teaching schemas covering every feature, plus live instances |
|
Inside a schema, every reference is an hm:// URL with the authority hyper.media, and the URL path is the file's path: hm://hyper.media/string, hm://hyper.media/metadata, hm://hyper.media/example/person. hyper.media is a name the SDK and the sync understand. The network does not resolve domains in hm:// URLs yet (that is planned), so the app resolves these references from its bundled library. Each name also has a published page at the same path in the docs space, and that page's URL carries the space's key. See references and naming.
Layer 4: publishing
Two scripts publish the folder to the network.
publish.mjs encodes each schema to canonical DAG-CBOR, hashes it, and writes schemas.lock.json. The lockfile maps every hm:// URL to its content CID. It is the contract between the repository and the network: a schema cannot change without the lockfile changing too.
hypermedia:push signs with the main key, or with the key in the environment in CI, and publishes into that key's space in two steps. First it recomputes every schema CID, stops if any differs from the lockfile, and uploads the schema blobs. Then it imports the whole hypermedia/ folder as documents, and each page publishes at its path. index.md becomes the home document. A type page carries schemaDefinition = ipfs://<cid>, which says this document defines a type. An instance page, such as example/bob, holds its data in frontmatter and carries attributesSchema = hm://<type>, which says this document conforms to a type. The files write these URLs as hm://hyper.media/…. The push swaps hyper.media for the signing key in page links and frontmatter, and pull swaps it back, so git never holds a key. Schema blobs are uploaded unchanged. The narrative pages you are reading publish the same way. See publishing a folder.
So the type system lives on the network it types: browsing the account is browsing the library.
Layer 5: resolution
A schema reference comes in three forms, and the app resolves each one differently:
reference | example | how it resolves |
|---|---|---|
library URL |
| locally, from the registry compiled into the app, with no network |
IPFS CID |
| fetch the blob directly (bundled if known, otherwise from the daemon) |
any Hypermedia document URL |
| fetch the document, read its |
The third form lets anyone add types: a schema published under any account resolves the same way as one from the library. Network resolution is asynchronous, so the app exposes it through two hooks. One resolves a single reference. The other computes a document's effective attributes schema: its own attributesSchema, or else its parent's childAttributesSchema. See typed documents.
Layer 6: the engine and the app
There is one validation engine. The reference validator has no dependencies. It proves the meta-schema describes itself, validates every schema in the library against it, checks positive and negative data cases for the examples, and confirms the union rejects malformed schemas. The app runs a line-for-line port of the same engine, so the app cannot disagree with the reference validator. The app builds these on top of it:
The schema tour and explorer render every schema as a page. The page shows fields, variants, inherited and added properties, generic parameters, URL and CID, dependencies and dependents, and a live editor.
The schema editor is a form driven by the meta-schema, so it can only produce valid schemas.
The value editor is a form that builds data matching a schema. It has dropdowns for unions of literals, pickers for union variants, the right controls for link and bytes, title pills for document references and file pickers for IPFS references.
The document integration shows required attributes as fixed rows, validation problems in red without blocking, and header actions on a schema-definition document.
The inspector recognizes the signed blob types, detects when a blob is a schema, and validates a blob against its attached schema.
These tools sit behind Developer Mode in the Seed app. Developer Mode is off by default in the desktop app and on by default in the web app. Any schema blob, bundled or published, has a full page at /hm/schema/<cid>. On that page every reference is a link: a library type, an hm:// type document or an ipfs:// schema. You browse a schema graph by clicking. A schema that extends Signed blob gets a signing form instead of a plain editor. At publish time the form fills in the envelope and signs it with the selected account.
Layer 7: generated code
typegen.mjs walks the library and emits one TypeScript type per schema. A map becomes an object type, a literal becomes a literal type, anyOf becomes a union, extension becomes an intersection, and an open map becomes an index signature. params, var and args become real generics, so Change<Block> in the schema is Change<Block> in TypeScript. A self-referential schema, like a recursive JSON value, comes out as a legal recursive type. A --check mode fails when the generated file is stale. The schemas are the source of truth for the app's data types, and nobody writes those type declarations by hand.
The invariants
The system holds together because of a few properties that tooling checks:
Same bytes, same CID. Canonical DAG-CBOR encoding means any implementation that hashes a schema gets the lockfile's CID. The sync refuses to publish otherwise.
The meta-schema validates itself and rejects malformed schemas. Every validator run checks this.
One engine. The app's validation is a port of the reference validator, covered by the same cases.
Every reference is a document. There are no placeholder names. Each hm:// URL in a schema resolves to a published page.
Generated code matches the library. typegen --check and the bundled-registry generator fail the build when out of date.
Warnings never block writes. A document with out-of-spec data still saves. The app shows the mismatch and does not enforce it.
See also
Hypermedia Schemas in one page: the model, the library and the tools in brief.
The schema language: every schema shape and constraint.
Typed documents: the three binding keys and what the editor does with them.
References and naming: include, link and hm:// names.
Encoding: DAG-CBOR, dag-json and canonical encoding.
Publishing a folder: how a folder of pages becomes a site.
Blobs: signed blobs and content addressing on the network.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime