Plugins

Extend the Content instance with opt-in, independent plugins.

A plugin is an object with a setup(content) function that runs at construction time. It can register parsers, hook into watch events, and expose new methods on the Content instance: with full TypeScript inference for the consumer.

Built-in plugins

sqlQuery

Typed fluent query builder over flattened frontmatter columns.

sqliteFullTextSearch

BM25 ranking, section-level results, snippet highlighting.

media

Treats binary files (.png, .svg, .mp4…) as media entries served at their path.

markdown

Parses Markdown by default, with explicit options for Comark and trimmed listing fields.

json

Parses .json files into manifest entries with the parsed JSON as their data.

yaml

Parses .yaml / .yml files into manifest entries.

markdownFields

Parses Markdown strings inside JSON/YAML data fields via x-content schema metadata.

schemaValidation

Validates Markdown, JSON, and YAML documents against a source schema.

references

Cross-document reference fields with lazy populate on list / get.

Custom plugins

Build your own with defineContentPlugin().

Usage

Plugins are passed in the plugins array. The Content instance calls setup(content) synchronously at construction, so any new methods are available immediately:

content.ts
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'
import sqlite from 'comark-content/database/sqlite-node'
import sqlQuery from 'comark-content/plugins/sql-query'
import sqliteFullTextSearch from 'comark-content/plugins/sqlite-full-text-search'

const database = sqlite()

export const content = comarkContent({
  source: fs('./content'),
  plugins: [
    sqlQuery({ database }),
    sqliteFullTextSearch({ database }),
  ],
})

// `content.query` and `content.search` are added by the plugins above.
const rows = await content.query('default').where('data.title', 'LIKE', '%intro%').all()
const hits = await content.search(['default'], 'hello world')

Plugin order

Plugins run in array order. If a plugin's setup reads another plugin's contribution, put it later:

plugins: [
  sqlQuery({ database }),                // adds content.query, content.prepareQueryIndex
  customAnalytics({ content }),              // can use content.query inside setup
]

The Content instance doesn't enforce ordering: it's your responsibility when plugins depend on each other.

How plugins are typed

comarkContent infers methods added by plugins through the defineContentPlugin helper. You always get correct types on the returned Content: no manual augmentation needed.

types.ts
const content = comarkContent({
  plugins: [sqlQuery({ database }), sqliteFullTextSearch({ database })],
})

content.query   // (source) => SourceQueryBuilder<...>
content.search  // (sources, query, opts?) => Promise<SearchResult[]>
content.get     // unchanged, with all base methods

See Custom plugins for the full pattern.