---
title: "Plugins"
description: "Extend the Content instance with opt-in, independent plugins."
canonical_url: "https://content.comark.dev/plugins"
---
# 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

::card-group{cols="2"}
  :::card{icon="i-lucide-table" title="sqlQuery" to="https://content.comark.dev/plugins/built-in/sql-query"}
  Typed fluent query builder over flattened frontmatter columns.
  :::

  :::card{icon="i-lucide-search" title="sqliteFullTextSearch" to="https://content.comark.dev/plugins/built-in/full-text-search"}
  BM25 ranking, section-level results, snippet highlighting.
  :::

  :::card{icon="i-lucide-image" title="media" to="https://content.comark.dev/plugins/built-in/media"}
  Treats binary files (`.png`, `.svg`, `.mp4`…) as media entries served at their path.
  :::

  :::card{icon="i-lucide-file-text" title="markdown" to="https://content.comark.dev/plugins/built-in/markdown"}
  Parses Markdown by default, with explicit options for Comark and trimmed listing fields.
  :::

  :::card{icon="i-lucide-braces" title="json" to="https://content.comark.dev/plugins/built-in/json"}
  Parses `.json` files into manifest entries with the parsed JSON as their data.
  :::

  :::card{icon="i-lucide-file-text" title="yaml" to="https://content.comark.dev/plugins/built-in/yaml"}
  Parses `.yaml` / `.yml` files into manifest entries.
  :::

  :::card{icon="i-lucide-text" title="markdownFields" to="https://content.comark.dev/plugins/built-in/markdown-fields"}
  Parses Markdown strings inside JSON/YAML data fields via `x-content` schema metadata.
  :::

  :::card{icon="i-lucide-shield-check" title="schemaValidation" to="https://content.comark.dev/plugins/built-in/schema-validation"}
  Validates Markdown, JSON, and YAML documents against a source schema.
  :::

  :::card{icon="i-lucide-link" title="references" to="https://content.comark.dev/plugins/built-in/references"}
  Cross-document reference fields with lazy populate on `list` / `get`.
  :::

  :::card{icon="i-lucide-activity" title="tracing" to="https://content.comark.dev/plugins/built-in/tracing"}
  Debug timelines in the console, or OpenTelemetry span forwarding.
  :::

  :::card{icon="i-lucide-wrench" title="Custom plugins" to="https://content.comark.dev/plugins/custom/plugin-api"}
  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. Falsy entries are skipped, so a plugin can be enabled conditionally inline (for example `plugins: [isDev && tracingDebug()]`):

```ts [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().where('data.title', 'LIKE', '%intro%').all()
const hits = await content.search('hello world')
```

## Plugin order

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

```ts
plugins: [
  sqlQuery({ database }), // adds content.query, content.prepareQueryIndex
  customAnalytics(),      // its setup(content) can call content.query
]
```

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

## How plugins are typed

[`comarkContent`](https://content.comark.dev/reference/content/comark-content) infers methods added by plugins through the [`defineContentPlugin`](https://content.comark.dev/reference/plugins/define-content-plugin) helper. You always get correct types on the returned Content: no manual augmentation needed.

```ts [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](https://content.comark.dev/plugins/custom/plugin-api) for the full pattern.


## Sitemap

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