Content layer for Markdown-driven websites.

Markdown from anywhere, served as a typed API: get, list, navigation with a plugin ecosystem: schema validation, SQL queries, full-text search. No build step.
content.ts
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'

export const content = comarkContent({
  source: fs('./content'),
})

// parsed & typed version of content/hello.md
const page = await content.get('/hello')
// Generated navigation
const nav = await content.navigation()

// Works with your stack //

// Sources //

Bring your own source

Content can live next to your app for the fastest dev loop, in a separate repo so editors publish without a redeploy, or in any storage driver. Same API either way.

content.ts
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'

const content = comarkContent({
  source: fs('./content'),
})

const page = await content.get('/getting-started')

// Typed API //

Query it like a database

Types are generated from your actual frontmatter and narrow get, list, and query by source and by path. Add SQLite when you want SQL queries and full-text search.

const content = comarkContent({
  source: fs('./content/posts'),
})

// Known path -> data auto-narrowed, no type parameter
const blogPost = await content.get('/posts/hello')

blogPost?.data.title // typed from your frontmatter
blogPost?.data.tags  // autocompletes every field

const pages = await content.list()
// List all documents from all sources

// Capabilities //

A small core. Everything else is a plugin.

Works with your stack
Nuxt, Next.js, Vite, SvelteKit, Nitro, Node, or Hono: mount one handler, render with Vue, React, or Svelte.
Documents and assets, together
Images and files live next to your Markdown: query them like content, serve them where they are referenced.
Type-safe from your frontmatter
Generated types narrow get() and query() by source and by path. One CLI command, or automatic with Vite.
Lightweight core
Three methods: get, list, navigation. Plugins cover the rest.
Opt-in SQL and search
Skip the database for small sites. Add SQLite through a plugin when you want SQL queries and BM25 full-text search.
Navigation for free
content.navigation() builds an ordered, nested tree from your files: numeric prefixes, .navigation.yml, frontmatter overrides.

// Compare //

Know what you're replacing

vs Contentlayer
Runtime parsing from any source vs an unmaintained build-time compiler coupled to your bundler.
vs Astro Content Collections
The same typed collections, without the two constraints: any framework, no rebuild on content changes.
vs Headless CMS
Markdown in storage you own, turned into the same queryable API — no hosted database, no vendor API.

// FAQ //

Common questions

Add a content layer in five lines.

One source, one get(). Add a database when you need queries and search, and write in Comark syntax when you need components.