Your Content,
as a typed API.

Point Comark Content at a folder, a GitHub repository, or a storage bucket. Read pages, list them, build navigation. Add types, search, and queries when you need them. No build step.
content.ts
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'

export const content = comarkContent({
  source: fs('./content'), // a folder of .md files
})

// reads and parse content/about.md
const page = await content.get('/about')
page.data.title // 'About', from the frontmatter
page.nodes      // the body, ready to render

// built from your folders
const nav = await content.navigation()

// Works with your stack //

// Start small //

One file today. Everything else when you need it.

Docs sites, blogs, changelogs, knowledge bases, content for LLM pipelines. The first result takes a few minutes, and nothing you add later changes how you read a page.

content/about.md
---
title: About
---

# About us

We write documentation for a living.
index.ts
const page = await content.get('/about')
console.log(page.data.title) // About

// Sources //

Bring your own source

Content can live next to your app, in a separate repository, or in any storage driver. Same API either way. This site is the proof: its pages live in a GitHub repository and go live on push, with a preview per branch.

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 //

Typed, searchable, queryable

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

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

// 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()
// Every document in the instance

// 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 instance and by path. Run one CLI command, or let the Vite plugin generate them.
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(), a printed title in a few minutes. Add search and queries when you need them, and write in Comark syntax when you need components.
© 2026 Vercel, Inc.