Author a schema for your metadata standard
Your metadata standard already exists, even if it only lives in a style guide or in people’s heads. Every page needs a type. A timestamp should be an ISO date. API pages must declare a version. A JSON Schema turns those agreements into a file that manni meta can enforce on every commit. This page walks through writing that schema, from a minimal one-field example up to the fuller built-in OKF schema. It also shows how a document points itself at the schema you wrote.
A schema is just a JSON file
Section titled “A schema is just a JSON file”manni meta validates against standard JSON Schema. If you can write JSON, you can write a manni meta schema; there is no manni-specific schema format to learn. A schema describes the frontmatter object: which keys are required, what type each value should be, and whether unexpected keys are allowed.
Here is the smallest schema worth shipping. It says: the metadata must be an object, it must have a title, and that title must be a non-empty string.
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "required": ["title"], "properties": { "title": { "type": "string", "minLength": 1 } }}This is the extra.schema.json fixture that manni meta’s own test suite runs, so you can trust it compiles and validates exactly as shown. Four things are doing the work:
"$schema"at the top names the JSON Schema dialect this file is written in, which here is draft 2020-12. The line tells manni meta which validation engine to use. This is the schema’s own dialect declaration. It differs from the$schemakey you put in a document to choose a schema, covered below."type": "object"says the metadata is a set of key/value pairs, which frontmatter always is."required": ["title"]is the list of fields that must be present. A document missingtitlefails validation."properties"describes individual fields.titlemust be a string, andminLength: 1rejects an emptytitle:that a contributor left blank.
A document with this frontmatter passes:
---title: Getting started---A document with empty or missing title fails. That is the whole loop: declare the shape, and manni meta holds every file to it.
Required vs. recommended fields
Section titled “Required vs. recommended fields”The single most important decision in a metadata schema is which fields are required and which are merely recommended. JSON Schema models this with one list:
- Required. List the field in
"required". Its absence is a hard failure. - Recommended (optional). Describe the field in
"properties"but leave it out of"required". If the field is present, manni meta checks its type and format; if it’s absent, that’s fine.
This distinction is what lets you encode a standard that has a strict core and a softer edge. In the example below, type is mandatory, while title and tags are validated when present but never demanded:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "required": ["type"], "properties": { "type": { "type": "string", "minLength": 1 }, "title": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } } }}Constrain values with types and formats
Section titled “Constrain values with types and formats”Requiring a field guarantees it exists; constraining it guarantees it’s usable. JSON Schema gives you the building blocks, and manni meta supports the standard format checks through ajv-formats. Two formats come up constantly in document metadata:
Use "format": "date-time" to require an ISO 8601 timestamp. This catches the classic fat-fingered date: 2026-13-01, June 5th, or a bare 2026-06-25 where a full timestamp was expected.
"timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 datetime of last meaningful change."}Passes: 2026-06-25T14:30:00Z
Fails: June 5th, 2026-13-01
Use "format": "uri" for fields that must hold a well-formed URI, such as a canonical resource link. This checks the shape of the URI; it does not fetch the URL.
"resource": { "type": "string", "format": "uri", "description": "A URI uniquely identifying the underlying asset."}Passes: https://example.com/asset/42
Fails: not a url, example.com (no scheme)
Beyond formats, the everyday constraints carry most of the weight:
enum: restrict a field to a fixed set of values, ideal for a controlledtypeorstatus:"enum": ["guide", "reference", "tutorial"].minLength: reject empty strings ("minLength": 1).array+items: validate every element of a list, as with thetagsexample above.
Decide whether unknown fields are allowed
Section titled “Decide whether unknown fields are allowed”By default, JSON Schema lets a document carry fields your schema never mentions. Whether to permit that is a deliberate choice, controlled by additionalProperties:
"additionalProperties": true(or omitting it): extra keys are tolerated. Contributors can add experimental or tool-specific frontmatter without tripping the check. This is the lenient default and a good fit for a young or shared standard."additionalProperties": false: every key must be declared in"properties". An unexpected key is a failure. Use this to catch typos liketitel:or to keep frontmatter tightly governed.
The built-in OKF schema, a fuller example
Section titled “The built-in OKF schema, a fuller example”The schema anchoring manni meta’s default set is Open Knowledge Format (OKF) v0.1, addressed as google:okf:0.1. Run manni meta schemas for the full built-in list. OKF is a useful reference because it combines every technique on this page in a realistic standard. Reading it is the fastest way to see how the pieces fit together. When no other schema applies, manni meta falls back to a set rather than to OKF alone. That set is google:okf:0.1 together with passo-uno:seven-action:1.0, which constrains an optional action field and requires nothing of its own.
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "google:okf:0.1", "title": "Open Knowledge Format (OKF) v0.1", "description": "Frontmatter schema for OKF v0.1 concept files. OKF requires only `type`; all other fields are recommended, and unknown keys are explicitly tolerated.", "type": "object", "required": ["type"], "additionalProperties": true, "properties": { "type": { "type": "string", "minLength": 1, "description": "A short string identifying the kind of concept." }, "title": { "type": "string", "description": "Human-readable display name." }, "description": { "type": "string", "description": "Single-sentence summary used for indexing and previews." }, "resource": { "type": "string", "format": "uri", "description": "A URI uniquely identifying the underlying asset." }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Short strings for categorization." }, "timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 datetime of last meaningful change." } }}Notice the design choices OKF makes, each one a decision you’ll face in your own schema:
- Exactly one required field (
type). OKF demands the bare minimum, so adopting it rarely breaks existing files. additionalProperties: true, stated explicitly. Unknown keys are welcome by design, because OKF is meant to be extended.- Recommended fields with real constraints.
title,description, andtagsare optional, but if present they’re checked.resourceandtimestampcarryurianddate-timeformats. - A
$idand human-readabletitle/description. Validation does not need these, but they document the schema for the humans who read it.$idis what gives the built-in itsgoogle:okf:0.1address. Setting a$idin a schema file of your own does not register a new built-in id, because built-ins are bundled into manni meta. You reference your own schema by its file path or URL, never by its$id. Give it an id of your own, or leave it out; see the warning below before reusing one.
Borrow a schema instead of writing one
Section titled “Borrow a schema instead of writing one”Everything above assumes you’re defining the field set yourself. manni meta ships twenty-three schemas you can adopt without authoring anything. The built-in schema registry lists all of them in one table, with what each constrains and which two are on by default.
If your site is built with Docusaurus, start with docusaurus:docs:3.10 and its blog and pages counterparts. They encode the front matter contract of Docusaurus 3.10 and require nothing. Switching one on cannot fail a page that was already building. It only catches a sidebar_position: "2" that your site would have rejected later. They claim none of the type or action keys below, so they stack with any of the vocabularies. They do share tags with OKF, which allows only strings where Docusaurus also allows tag objects. See the overlap note before pairing those two.
If what you need is a closed list of document types, three of the built-ins are vocabularies:
diataxis:diataxis:1.0requirestypeand constrains it to the four Diátaxis forms.tgdp:templates:1.0requirestypeand constrains it to the 25 template slugs of The Good Docs Project.passo-uno:seven-action:1.0constrainsactionto the seven reader actions of the Seven-Action model.
The first two both claim type, so they are alternatives; either one pairs with the third.
The two type vocabularies each demand the key as well as constraining it, so neither needs a partner for presence-checking. Stack one with OKF for the other OKF fields. Both schemas apply in full, in either order:
manni meta validate docs/ -s diataxis:diataxis:1.0 -s google:okf:0.1Seven-Action is the exception: it rules that an action, if present, is
legitimate, without insisting on one. That is what makes it safe to layer onto
what you already have. Every value of all three, which combinations compose, and
the crosswalk from Seven-Action to Diátaxis are in the taxonomy schemas
reference.
Point a document at its schema
Section titled “Point a document at its schema”A schema only matters once documents are validated against it. The most direct way to connect the two is to let a document name its own schema with a $schema key in its frontmatter. manni meta reads that key and validates the file against whatever it points to.
Here is a document naming the built-in OKF schema, the schema-ref.md fixture:
---$schema: google:okf:0.1type: guidetitle: Self-Describing Document---
# Self-Describing Document
This file names its own schema via `$schema`.The $schema value can be a built-in id (as here), a path to a .json file you wrote, or a URL. To point at a local schema file instead:
---$schema: ./schemas/metadata.schema.jsontype: guidetitle: My page----
Write your schema as a
.jsonfile using the patterns above, keeping the"$schema"dialect line at the top. -
Add
$schema:to a document’s frontmatter, pointing at your file (or a built-in id, or a URL). -
Validate it:
Terminal window npx @hawkeyexl/manni meta validate path/to/document.mdmanni meta resolves the document’s
$schema, validates the frontmatter, and reports any missing or malformed fields.
Keep a value encrypted on the page
Section titled “Keep a value encrypted on the page”Some fields your standard requires are not for publishing. An owner, an
internal ticket, a service name. Mark the property x-manni-encrypt, and the
page has to hold that value encrypted:
"owner": { "type": "string", "enum": ["platform", "billing"], "x-manni-encrypt": true }The field stays required and your enum still holds, because validation checks
the decrypted value against the property’s whole schema. Require a field and
keep its value private follows one such
value through every command that touches it. Put the key where every tool
finds it settles where the key lives first.
Say where each field lives
Section titled “Say where each field lives”Some fields are for whoever fetches the page, and some only serve the people
and the CI that maintain it. Mark a property x-manni-location to say which:
"owner": { "type": "string", "x-manni-location": "external" }page keeps a value in the document, and external keeps it in the
collection’s external-metadata manifest. validate warns about a value on the
wrong side, and manni meta relocate moves it. Keep maintainer metadata out
of delivered pages follows the mark
through every command.