Skip to content

How schema resolution works & how to wire it

A schema is only useful once manni meta knows which schema applies to which file. In a real repo, several signals compete to answer that question. A flag on the command line and a $schema key inside the document both have a claim. So do a directory rule in your config and a repo-wide default. manni meta settles the competition with a fixed precedence order, and once you know that order, wiring schemas to documents becomes predictable instead of mysterious.

This page documents the precedence chain and the three kinds of schema reference you can use anywhere a schema is named. It also covers how manni meta detects a schema’s JSON Schema dialect.

For every file, manni meta builds a schema set (the list of schemas that file will be validated against) by checking five sources in order. The first source that yields a schema wins, and the search stops there. Nothing lower in the list is consulted.

  1. CLI -s/--schema: if you pass one or more --schema flags, they apply to every file in the run and override everything else. Repeatable; the flags together form the set.

  2. The file’s own $schema key: a $schema field in the document’s metadata. May be a single string or a list of strings.

  3. The first matching overrides entry in config: config overrides are checked in order; the first whose glob or collection matches the file supplies the set. Its schemas replace everything below, unless the entry sets defaults: true, which puts the default set first.

  4. Config schemas: the repo-wide list in manni.config.yaml. It joins the default set, after it. meta.defaults: false makes it replace the defaults instead.

  5. The default set applies when there are no config schemas. Its eleven built-ins are google:okf:0.1, passo-uno:seven-action:1.0 and nine manni vocabularies, led by manni:core:1.0.0. Core requires title and description, so every file the defaults judge needs both.

Here is the same chain as a quick-reference table:

OrderSourceScopeNotes
1CLI -s/--schemaAll files in the runRepeatable; replaces everything below.
2File $schema keyThat one fileString or list of strings. A reserved key, stripped before validation.
3Config overridesFiles matching a glob or a collectionFirst matching entry wins. Replaces, unless the entry sets defaults: true.
4Config schemasAll files no override matchedThe default set, then these. meta.defaults: false drops the defaults.
5The default setAll files (last resort)Eleven built-ins. See the default set.

At levels 3 to 5, strict: true stacks each strict version right after its base. That covers each manni: default and each registered schema that has one.

Levels 3 and 4 come from your manni.config.yaml. Here is the config the worked example below assumes, with a repo-wide house schema plus one directory override:

manni.config.yaml
meta:
# Level 4: the default set, then the house schema, for every file...
schemas:
- ./schemas/house.schema.json
# Level 3: ...unless a file matches an override glob, checked top to bottom.
overrides:
- files: "api/**"
schemas:
- ./schemas/api.schema.json

Each overrides entry pairs a schemas list with the files it governs. The files are named in one of two ways. A files glob, as above, names them by pattern. collection: <name> names the members of a declared collection. An entry carries exactly one of the two. Entries are tried in order, and the first that matches the file supplies the set, whichever way it names its files.

Reach for collection: when the set is a real document set the repository already declares, so the globs are written once. Keep files: for a glob that only affects which schema applies. (For every config key, type, and default, see the Configuration reference.)

When a document names its own schema with a $schema key, that key is a directive to manni meta, not part of the document’s metadata. manni meta strips $schema out of the metadata before handing it to the validator.

This matters for one specific case. A schema with "additionalProperties": false rejects any field it doesn’t declare. Without special handling, a document carrying $schema: would fail against such a schema, because $schema isn’t one of the schema’s declared properties. Because manni meta removes $schema first, that never happens. You never need to add $schema to your schema’s properties, and a self-describing document validates cleanly against even the strictest schema.

The $schema value can be a single reference or a list:

---
$schema: ./schemas/metadata.schema.json
type: guide
title: My page
---

Level 2 beating config is what makes a document self-describing, and it is also the one level a repository can narrow. A repo taking pull requests from outside can narrow it with schemaTrust. It can say that documents may name a built-in id or a repository file but not a URL. It can also ignore their $schema entirely. Left unset, level 2 behaves exactly as described here.

Everywhere a schema is named, the value is a reference. That covers a --schema flag, a $schema key, and a config entry. manni meta classifies each reference into one of three kinds by its shape. You don’t declare the kind; manni meta infers it.

Built-in

A vendor:name:version id with no path separators and not ending in .json.

Example: google:okf:0.1

Resolved from the schemas bundled into manni meta, or from a schema your config registers under that id. A typo’d id (e.g. an unknown vendor) is reported as an unknown schema, not silently treated as a missing file.

File

A path that ends in .json or contains a path separator (/ or \).

Examples: ./schemas/api.schema.json, schemas/house.json

Read from the local filesystem. Relative paths resolve against the current working directory. A .flmeta file reads as a MadCap Flare meta tag set.

URL

Anything starting with http:// or https://.

Example: https://example.com/schemas/okf-1.0.json

Fetched over the network with a 10-second timeout and cached for the run. The same URL is downloaded only once, even across thousands of files.

A house schema is a file, so every config entry names it by path. meta.register gives it an id instead. The config lists the file or its directory, and the schema’s own $id becomes a name like house:page:1.0.0. That id then works in config, on --schema, in a document’s $schema, and in a $ref from another schema, exactly as a built-in id does. Name your schema and its strict version walks through it.

URL references are one way to govern a single schema across many repos: a central, versioned schema URL that every consumer points at. Vendoring that URL into each consumer is the other, and the more durable one. Both, with the fetch timeout and caching behavior in depth, are covered in Govern a shared schema across repos.

When a file’s schema set contains more than one reference, manni meta validates the file against every schema in the set. It reports the union of all violations, each tagged with the schema that produced it. A file passes only if it satisfies all of them.

A multi-schema set is what you get when you name several schemas at a single precedence level:

  • several --schema flags on the command line,
  • a list-valued $schema key in a document,
  • or multiple entries in one config overrides rule or in config schemas, which the default set joins.

This is useful for layering a shared organizational schema with a local one. For example, validate against a company-wide house-style.schema.json and a team-specific api.schema.json at once. Each schema is compiled and cached separately, so layering is cheap.

Because the file must satisfy every schema in the set, the strictest member governs. If any schema in the set sets "additionalProperties": false, an unknown key fails the file even when another schema in the set allows it. Keep that in mind when combining a lenient shared schema with a strict local one; the strict one wins on every rule it imposes.

JSON Schema has evolved through several versions, called dialects (draft-04, draft-06, draft-07, 2019-09, 2020-12). A schema written for one dialect can fail to compile under another. So manni meta reads each schema’s own $schema line and selects the matching validation engine. That line is the URI at the top of the schema file. It points at the dialect’s meta-schema, the schema that defines the dialect itself.

manni meta supports these dialects:

DialectDetected from a $schema URI containing
2020-122020-12 (also the fallback)
2019-092019-09
draft-07draft-07 or draft/7
draft-06draft-06 or draft/6 (shares the draft-07 engine)
draft-04draft-04 or draft/4

If a schema omits its $schema line or uses an unrecognized one, manni meta falls back to 2020-12, the dialect of the built-in schemas. Because of that auto-detection, a schema you fetch from a remote URL can target draft-07. It still validates correctly alongside a local 2020-12 schema in the same run.

For the full lookup table of precedence, reference kinds, and dialects in one place, see the Schema resolution reference.