Type safety
Every source can carry a JSON Schema for its frontmatter, inferred automatically or declared explicitly. comark-content turns that schema into real TypeScript types with one interface per source, plus a per-path map.
How it works
ContentRegistrymaps each source name to its{ data, row }shape.ContentPathsmaps every known document path to its data type, socontent.get('/posts/hello')narrows automatically without a type parameter.
Both interfaces are empty by default — the module augmentation written to comark-content.d.ts is what fills them in.
Generating types
Type generation is the same operation, writeSourceTypes(), run three different ways depending on your setup.
CLI
Run comark-content prepare as a standalone script or CI step:
npx comark-content prepareUse this when you're not using Vite, or want types regenerated on demand (e.g. after editing content schemas) rather than tied to a dev server or build.
Vite plugin
If you're using the Vite plugin, types are generated for you — no extra step:
import { defineConfig } from 'vite'
import comark from 'comark-content/vite'
import { content } from './content'
export default defineConfig({
plugins: [comark({ content })],
})It runs on dev-server start and at build (the types option, on by default). Pass types: false to disable it, or types: { outDir } to change where it writes.
writeSourceTypes()
Call it directly from any script for full control over when and how types are written:
import { writeSourceTypes } from 'comark-content'
import { content } from '../content'
await writeSourceTypes(content, {
outFile: 'types/comark-content.d.ts',
moduleName: 'comark-content',
paths: true,
})Parameters:
content: theComarkContentinstance to read schemas from.options?: an object withoutDir/fileName/outFile(default./comark-content.d.ts),moduleName(defaultcomark-content), andpaths(defaulttrue).
Returns: Promise<string>, the generated file contents (also written to disk).
Under the hood it calls generateSourceTypes(content, options) — the same function, without the file write — if you'd rather handle the output yourself (e.g. print it, or write it somewhere non-filesystem).
Wiring it into your project
Whichever method you use, make sure your tsconfig.json picks up the generated file:
{
"include": ["comark-content.d.ts", "**/*.ts"]
}comark-content.d.ts to .gitignore and regenerate it in CI/dev instead of committing it.Using generated types
Once generated, get(), list(), and query() all pick up the types automatically:
// Known path -> data auto-narrowed, no type parameter needed
const page = await content.get('/posts/hello')
page?.data.title // typed from the `posts` source's schema
// Unknown path -> pass a type parameter explicitly
const other = await content.get<PostData>('/dynamic/path')
// Known source name -> each item's data narrowed the same way
const posts = await content.list(['posts'])
posts[0].data.title // typed from the `posts` source's schemaSee Registry and query types for every generated/derived type, and the sqlQuery plugin for how query() uses the same schemas.
Plugin metadata
Metadata declared by Comark plugins is inferred directly from the Content instance configuration; it does not need generated declarations. For example, the headings plugin makes meta.title and meta.description available as string | undefined on get(), list(), and stat() results:
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'
import headings from 'comark/plugins/headings'
export const content = comarkContent({
source: fs('./content'),
markdown: {
plugins: [headings()],
},
})The same inference works when configuring the markdown plugin explicitly:
import { comarkContent } from 'comark-content'
import markdown from 'comark-content/plugins/markdown'
import headings from 'comark/plugins/headings'
export const content = comarkContent({
plugins: [
markdown({
comark: {
plugins: [headings()],
},
}),
],
})Semantic field types
Source schemas support vendor extensions on JsonSchema via x-content. The markdown-fields plugin parses string fields marked { type: 'markdown' } into MarkdownDocument at runtime and adjusts generated data types when registered. Import markdownField() from comark-content/plugins/markdown-fields, not the main package entry.