Schema Validation Plugin

Validate Markdown, JSON, and YAML documents against a source JSON Schema.

When a source declares a schema, the schema-validation plugin checks every Markdown, JSON, and YAML document on the file:parsed hook — including content.update(). How failures are handled depends on mode.

Register the plugin explicitly. Skip validation for whole sources or individual paths with the ignore option.

Usage

content.ts
import { comarkContent, type JsonSchema } from 'comark-content'
import fs from 'comark-content/sources/fs'
import json from 'comark-content/plugins/json'
import yaml from 'comark-content/plugins/yaml'
import schemaValidation from 'comark-content/plugins/schema-validation'

const schema: JsonSchema = {
  type: 'object',
  properties: {
    title: { type: 'string' },
    author: { type: 'string' },
    tags: { type: 'array', items: { type: 'string' } },
    seo: {
      type: 'object',
      properties: { title: { type: 'string' } },
      required: ['title'],
    },
  },
  required: ['title', 'author'],
}

const content = comarkContent({
  source: fs('./content', { schema }),
  plugins: [json(), yaml(), schemaValidation()],
})

Given a content folder with valid and invalid files:

valid-post.md
---
title: Valid post
author: Ada Lovelace
---
# Valid post
valid-settings.json
{
  "title": "Site settings",
  "author": "Ada Lovelace"
}
missing-author.md
---
title: Missing author
---
# This file is excluded — `author` is required
bad-types.json
{
  "title": 42,
  "author": "Ada Lovelace"
}
incomplete-seo.yaml
title: Incomplete SEO
author: Ada Lovelace
seo: {}
load.ts
await content.init()

content.stat('/valid-post') // loaded
content.stat('/valid-settings') // loaded
content.stat('/missing-author') // undefined — rejected
content.stat('/bad-types') // undefined — rejected
content.stat('/incomplete-seo') // undefined — rejected

Invalid documents never enter the manifest when mode is ignore. With mode: 'error', load stops on the first invalid file.

See the Vite + Vue schema validation example for a runnable demo.


API

schemaValidation(options?)

Returns a ContentPlugin that hooks file:parsed.

Parameters:

NameTypeDefaultDescription
mode'warn' | 'error' | 'ignore''ignore'How validation failures are handled on load, watch, and update().
ignoreIterable<string>undefinedSource names or document paths to skip validation for. Accepts an array or Set.

Returns: ContentPlugin

import schemaValidation from 'comark-content/plugins/schema-validation'

const content = comarkContent({
  plugins: [
    schemaValidation({
      mode: 'error',
      ignore: new Set(['drafts', 'default/bad-types.json']),
    }),
  ],
})

mode

ValueBehavior
errorThrow — stops load, watch handling, or update() on the first invalid file.
warnLog a warning and keep the file (including on update()).
ignoreLog a warning and exclude the file (ctx.file = null). On update(), the write is rejected.

ignore

Each entry is one of:

FormExampleSkips
Source name'drafts'Every document in the drafts source
Warning id'default/bad-types.json'One file — same source/path as validation logs
Pasted log line'├ default/bad-types.json: Expected …'Normalized to the warning id above
Public path'/about'The document at that path
Glob'default/legacy/*.json', '**/drafts/**'Picomatch against warning ids and keys

Copy straight from a validation warning — the source/file.ext before the colon:

├ default/bad-types.json: Expected string, received integer for property "title".
  ^^^^^^^^^^^^^^^^^^^^^^ paste this
content.ts
const content = comarkContent({
  source: fs('./content', { schema }),
  plugins: [
    schemaValidation({
      ignore: [
        'default/bad-types.json',
        'drafts',
        'default/legacy/*.json',
      ],
    }),
  ],
})

The source schema is still used for types and query columns on ignored entries.

content.ts
const content = comarkContent({
  sources: {
    default: fs('./content', { schema }),
    drafts: fs('./drafts', { schema }),
  },
  plugins: [schemaValidation({ ignore: ['drafts'] })],
})

How it works

On file:parsed:

  1. Collect validation issues against source.schema (skipped when the file matches ignore).
  2. Apply mode — warn and keep, warn and exclude, or throw.

Invalid files log one line per issue:

├ default/bad-types.json: Expected string, received integer for property "title".

Snapshot hydration and manifest hydration skip file:parsed entirely — artifacts are checksum-verified exports loaded as-is.

Register markdown-fields before this plugin so markdown field transforms run before validation.


Validated extensions

.md, .markdown, .json, .yaml, .yml

Media and other binary entries are skipped. Parsed MarkdownDocument values are accepted for x-content: { type: 'markdown' } fields.