Markdown Fields Plugin
Document data — frontmatter on .md files, records in .json / .yaml, and any other parsed shape — can include string fields that hold Markdown. The markdown-fields plugin lets you mark those fields in a source schema with x-content: { type: 'markdown' } so they are parsed by the same Comark engine as document bodies.
The plugin is opt-in — register it explicitly on your Content. Without it, marked fields stay raw strings in file.data, including frontmatter on Markdown documents.
Usage
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 markdownFields, { markdownField } from 'comark-content/plugins/markdown-fields'
const faqSchema: JsonSchema = {
type: 'object',
properties: {
items: {
type: 'array',
items: {
type: 'object',
properties: {
question: { type: 'string' },
answer: markdownField(),
},
required: ['question', 'answer'],
},
},
},
required: ['items'],
}
const content = comarkContent({
source: fs('./content', { schema: faqSchema }),
plugins: [yaml(), json(), markdownFields()],
})Given content/faq/faq.yaml:
items:
- question: How do I return a product?
answer: |
You have **14 days** to request a return.
> Refunds are processed within 5 business days.const faq = await content.get('/faq/faq')
faq?.data.items[0].answer
// ^? MarkdownDocument — parsed nodes, frontmatter, metaRender with <MarkdownDocument> (Angular/React/Svelte/Vue) or render to HTML directly.
The same schema annotation works on Markdown frontmatter, only the document body is parsed automatically; frontmatter fields need this plugin when they contain Markdown:
---
title: Release notes
summary: |
We shipped **v2** with a new API.
> See the migration guide for details.
---
# Release notesWith summary: markdownField() in the source schema and markdownFields() registered, content.get('/post') returns a parsed MarkdownDocument at data.summary while file.nodes still holds the body.
API
markdownFields(options?)
Returns a ContentPlugin that:
- Hooks
file:parsedto replace marked string fields infile.datawithMarkdownDocumentobjects after the file parser runs. - Hooks
typegen:fieldso generated types emitMarkdownDocumentfor marked fields on data interfaces (query row types keepstring).
Parameters:
options?: see Options.
Returns: ContentPlugin
Requires a source schema and a registered .md (or .markdown) file parser — typically the built-in markdown parser that comarkContent installs by default.
Options
| Option | Type | Default | Description |
|---|---|---|---|
onError | 'throw' | 'warn' | 'ignore' | Content onError | How to react when a marked field fails to parse. |
onError
How the plugin reacts when a marked Markdown field can't be parsed: 'warn' logs and leaves the original string in place, 'ignore' leaves it silently, 'throw' aborts. Defaults to the Content-level onError (which defaults to 'warn').
markdownFields({ onError: 'throw' })Default: the Content instance onError option
Schema helpers
Import helpers from the plugin entry, not the main package:
import markdownFields, {
markdownField,
schemaHasMarkdownFields,
isMarkdownDocument,
} from 'comark-content/plugins/markdown-fields'| Export | Description |
|---|---|
markdownField() | Returns { type: 'string', 'x-content': { type: 'markdown' } } for use in a source schema. |
schemaHasMarkdownFields(schema) | Returns true when a schema (recursively) declares markdown fields. |
isMarkdownDocument(value) | Runtime type guard for a parsed field value. |
How parsing works
- Runs on every parsed document — Markdown frontmatter, JSON, YAML, and any other format whose parser fills
file.data. - Field parsing reuses the registered
.mdparser via a syntheticParserContext— no duplicate parse pipeline. - Nested objects and array items are walked recursively; any field matching the schema annotation is transformed.
- Parse failures are handled per
onError— by default a warning is logged and the original string is left in place.
Type generation
When markdown-fields is registered, writeSourceTypes() emits MarkdownDocument for marked fields on <Source>Data interfaces and adds import type { MarkdownDocument } from 'comark' when needed.
Query row types (<Source>Row) always use string for markdown columns at the type level. At runtime, content.query() indexes the parsed MarkdownDocument values written during load.
See the Vite + Vue markdown fields example to compare list(), get(), and query() side by side.
Without the plugin, marked fields type as string even if the schema carries x-content metadata.
Query and search
content.query()indexes the flattened parseddata.*values from the manifest.content.search()only indexes document nodes, not inline field markdown — use fullget()and render field trees in the UI, or add a custom index if you need field-level search.
file.data fields (frontmatter, JSON, YAML, etc.). The document body of a .md file is always parsed into file.nodes by the built-in Markdown parser — that path does not use x-content annotations.Trim listing payloads
Combine with json({ listingFields }) or yaml({ listingFields }) when FAQ-style records are large but listings only need titles:
plugins: [
yaml({ listingFields: ['title'] }),
json(),
markdownFields(),
]