Lint rules
leadtype lint validates source MDX before generation. Run it in CI to fail PRs on schema and link issues before they reach a build. CLI flags are documented in the CLI reference — this page covers the rules, schemas, and library API.
Rules
Grouped by what they catch.
Frontmatter rules
| Rule | Severity | Catches |
|---|---|---|
schema | error | Required field missing or wrong type. |
unknown-field | warn (default) | Top-level field not in the active schema. |
parse-error | error | YAML frontmatter or meta.json won't parse. |
unknown-field upgrades to error with --error-unknown. Treat parse-error as the highest priority — it blocks every other check.
Content / link rules
| Rule | Severity | Catches |
|---|---|---|
invalid-link | error | A /docs/... link points to a route that doesn't exist. |
unresolved-placeholder | error | A docs URL still contains {framework} or similar. |
cross-framework-link | error | A framework-scoped page links to another framework's docs. |
unflattened-component | warn | A rendered JSX component has no built-in or custom flattener — agents would see raw JSX in the generated markdown. |
jsonld | warn | The page's frontmatter would emit invalid JSON-LD — most often a lastModified/last_updated value that isn't a valid date, producing a broken dateModified. Broken schema is worse than none. |
geo:heading-skip | warn | A heading jumps a level (e.g. H2 → H4). Keep the hierarchy sequential so answer engines can parse the topic tree. |
geo:code-language | warn | A fenced code block has no language label (a bare fence). Tag it with the language (e.g. ts) so engines surface it for the right stack. |
geo:image-alt | warn | An image has no alt text. Answer engines can't see images — describe what it conveys. |
GEO rules
The geo:* rules are the mechanical half of Write for agents & GEO — the structure signals a linter can check. The editorial ones (lead-with-the-answer, question-form headings) can't be linted; that's what the guide is for. All three are warn-level: legitimate exceptions exist, so they never fail the build by default (gate with --max-warnings if you want them to). The same checks feed the Structure portion of leadtype score.
Linting renders MDX through the default remark stack before walking links — same flattening the converter performs, so link checks see the final URLs.
The unflattened-component rule walks the MDX AST, so components inside code fences or inline code are examples — not rendered JSX — and never warn. It recognizes the built-in tag contract plus any custom defineComponentFlattener plugins declared in your config.
Library API
| Option | Description |
|---|---|
srcDir | Required. Root of .md, .mdx, and meta.json files. |
changelogDir | Subdirectory under srcDir validated against the changelog schema. |
ignore | Glob patterns relative to srcDir. Defaults shown below. |
unknownFieldSeverity | "warn" or "error" for fields outside the schema. Default: "warn". |
schemas.frontmatter | Custom Valibot schema for docs page frontmatter. |
schemas.changelogFrontmatter | Custom Valibot schema for changelog frontmatter. |
schemas.meta | Custom Valibot schema for meta.json. |
Default ignore patterns: **/shared/**, **/_shared/**, **/_partials/**, **/node_modules/**.
Result shape
Required-field failures are reported as schema violations — that's the public rule for missing or invalid required fields.
Default schemas
Docs frontmatter
| Field | Required | Type |
|---|---|---|
title | Yes | non-empty string |
description | No | string |
icon | No | string |
status | No | new, updated, or experimental |
deprecated | No | non-empty string |
date | No | ISO-8601 or parseable date |
tags | No | string array |
group | No | string or string array |
variants | No | array of { value, label?, href, description? } |
related | No | array of { title, href, description? } |
full | No | boolean |
lastModified and lastAuthor are produced by leadtype generate when git metadata is available. Don't author them.
Page status is editorial metadata. Release channels such as canary, RC,
preview, and stable should be modeled in build config or transformers, not in
source-authored page status.
Changelog frontmatter
| Field | Required | Type |
|---|---|---|
title | Yes | non-empty string |
version | Yes | SemVer string |
date | Yes | ISO-8601 or parseable date |
description | No | string |
icon | No | string |
type | No | release, improvement, retired, or deprecation |
tags | No | string array |
canary | No | boolean |
authors | No | string or string array |
draft | No | boolean |
meta.json
| Field | Required | Type |
|---|---|---|
pages | Yes | string array |
title | No | non-empty string |
root | No | boolean |
icon | No | string |
defaultOpen | No | boolean |
nav.sidebar | No | section or combined |
nav.label | No | string |
nav.mode | No | string |
Custom schemas
Pass a Valibot schema to extend or replace the defaults:
Once you provide a custom schema, unknown-field warnings apply to that schema. Add --error-unknown in CI to keep your contract strict.
Practical guidance
- Run lint before
leadtype generateso content errors fail fast. - Use
--format githubin GitHub Actions and--format jsonin any other CI. - Treat
unresolved-placeholderas a content bug first — usually a missing entry invariantsor a stale URL template. - After a docs move, lint and run
meta.jsonupdates together; they drift at the same time.
For wiring lint into pipelines, see Validate in CI.