---
title: "Markdown Fields Plugin"
description: "Parse Markdown strings in document data — frontmatter, JSON, YAML — into Comark trees via x-content schema metadata."
canonical_url: "https://content.comark.dev/plugins/built-in/markdown-fields"
---
# Markdown Fields Plugin

> Parse Markdown strings in document data — frontmatter, JSON, YAML — into Comark trees via x-content schema metadata.

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](https://content.comark.dev/reference/types/content#option-and-method-types) 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

```ts [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 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`:

```yaml [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.
```

```ts [query.ts]
const faq = await content.get('/faq/faq')
faq?.data.items[0].answer
//    ^? MarkdownDocument — parsed nodes, frontmatter, meta
```

Render with [`<MarkdownDocument>`](https://comark.dev/rendering/vue) (Angular/React/Svelte/Vue) or [render to HTML](https://comark.dev/rendering/html#code-render-usage) 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:

```yaml [post.md]
---
title: Release notes
summary: |
  We shipped **v2** with a new API.

  > See the migration guide for details.
---
# Release notes
```

With `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

Everything is wired up by one factory:

### `markdownFields(options?)`{lang="ts"}

Returns a [`ContentPlugin`](https://content.comark.dev/reference/types/plugins#contentplugin) that:

1. Hooks [`file:parsed`](https://content.comark.dev/reference/content/hooks#fileparsed) to replace marked string fields in `file.data` with [`MarkdownDocument`](https://comark.dev/syntax/comark-ast) objects after the file parser runs.
2. Hooks [`typegen:field`](https://content.comark.dev/reference/content/hooks#typegenfield) so [generated types](https://content.comark.dev/guide/typescript) emit `MarkdownDocument` for marked fields on **data** interfaces (query row types keep `string`).

**Parameters:**

- `options?`: see [Options](#options).

**Returns:** [`ContentPlugin`](https://content.comark.dev/reference/types/plugins#contentplugin)

Requires a source [`schema`](https://content.comark.dev/reference/types/content#source) and a registered `.md` (or `.markdown`) file parser — typically the built-in markdown parser that `comarkContent` installs by default.

---

## Options

There is a single option, controlling error handling:

| Option                        | Type                            | Default           | Description                                      |
| ----------------------------- | ------------------------------- | ----------------- | ------------------------------------------------ |
| [`onError`](#options-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`](https://content.comark.dev/reference/content/comark-content#options-onerror) (which defaults to `'warn'`).

```ts
markdownFields({ onError: 'throw' })
```

**Default:** the Content instance `onError` option

---

## Schema helpers

Import helpers from the plugin entry, not the main package:

```ts
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

```mermaid {height="280px" theme="zinc-light" theme-dark="zinc-dark"}
sequenceDiagram
  participant P as file parser (.md / .json / .yaml)
  participant H as file:parsed hook
  participant M as markdown-fields
  participant MD as .md parser
  P->>H: ContentFile with raw strings in data
  H->>M: walk data + schema
  M->>MD: parse each x-content markdown string
  MD-->>M: ParserResult (nodes, data, meta)
  M->>M: replace string with MarkdownDocument in data
```

- Runs on every parsed document — Markdown frontmatter, JSON, YAML, and any other format whose parser fills `file.data`.
- Field parsing reuses the registered `.md` parser via a synthetic [`ParserContext`](https://content.comark.dev/reference/types/content#parser) — 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`](#options-onerror) — by default a warning is logged and the original string is left in place.

---

## Type generation

When `markdown-fields` is registered, [`writeSourceTypes()`](https://content.comark.dev/guide/typescript#writesourcetypes) emits `MarkdownDocument` for marked fields on the generated `<Name>Data` interfaces (`DefaultData`, `BlogData`, one per [instance name](https://content.comark.dev/guide/typescript#what-you-get)) and adds `import type { MarkdownDocument } from 'comark'` when needed.

Query row types (`<Name>Row`) always use `string` for markdown columns at the type level. At runtime, [`content.query()`](https://content.comark.dev/plugins/built-in/sql-query) indexes the parsed `MarkdownDocument` values written during load.

See the [Vite + Vue markdown fields example](https://content.comark.dev/examples/vite/vue-markdown-fields) 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()`](https://content.comark.dev/plugins/built-in/sql-query) indexes the flattened parsed `data.*` values from the manifest.
- [`content.search()`](https://content.comark.dev/plugins/built-in/full-text-search) only indexes document **nodes**, not inline field markdown — use full `get()` and render field trees in the UI, or add a custom index if you need field-level search.

::note
This plugin parses **Markdown inside `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 })`](https://content.comark.dev/plugins/built-in/json) or [`yaml({ listingFields })`](https://content.comark.dev/plugins/built-in/yaml) when FAQ-style records are large but listings only need titles:

```ts [content.ts]
plugins: [
  yaml({ listingFields: ['title'] }),
  json(),
  markdownFields(),
]
```


## Sitemap

See the full [sitemap](https://content.comark.dev/sitemap.md) for all pages.
