---
title: "Content types"
description: "Core Content, content, cache, source, database, parser and registry types exported by comark-content."
canonical_url: "https://content.comark.dev/reference/types/content"
---
# Content types

> Core Content, content, cache, source, database, parser and registry types exported by comark-content.

The core types describing content, the manifest, sources, the cache, the database, parsers, and the Content instance.

Every export is re-exported from the main `comark-content` entry point:

```ts
import type {
  ComarkContent,
  ContentOptions,
  ContentHooks,
  ContentFile,
  ContentListFile,
  ContentFileMeta,
  Manifest,
  NavigationItem,
  ContentHub,
  ContentHubOptions,
  ContentSource,
  Source,
  ParsedSource,
  ParsedSnapshot,
  Database,
  RelationalDatabase,
  CacheArtifact,
  CacheOptions,
  CacheArtifactOptions,
  Cache,
  SearchOptions,
  SearchResult,
  Logger,
  OnErrorMode,
} from 'comark-content'
```

Every read API is built on **two types**: `ContentFile` (the full file, with parsed `nodes`) and `ContentListFile` (the same shape minus `nodes`).

## `ContentFile<TData, TMeta>`

The full parsed file: the shape [`content.get()`](https://content.comark.dev/reference/content/get) returns. Built around three fields: `path`, `data` (the frontmatter / parsed data), and `meta` (Content- and plugin-provided metadata), plus the parsed `nodes`.

```ts
interface ContentFile<TData = Record<string, any>, TMeta = Record<string, unknown>> {
  path: string                      // '/posts/hello'
  data: TData                       // the file's frontmatter / parsed data
  meta: TMeta & ContentFileMeta         // kind, type, key, source, extension, stem, partial, hash (+ plugin fields)
  nodes: MarkdownDocument['nodes']      // the parsed AST
}
```

## `ContentFileMeta`

The metadata every Content file carries, regardless of which API returned it. Plugins and parsers extend it through `ContentFile`'s `TMeta` parameter.

```ts
interface ContentFileMeta {
  kind: 'document' | 'media'        // whether it's a document or a media file
  type: string                      // mime type, e.g. 'text/markdown'
  key: string                       // 'default/posts/hello.md'
  source: string                    // the source the file belongs to
  extension: string                 // '.md' (with the leading dot)
  stem: string                      // 'posts/hello' (path without extension)
  partial: boolean                 // true when only frontmatter was parsed (body skipped)
  hash?: string                     // identity of the source text (16 hex chars); absent for media
}
```

`hash` is the first 16 hex characters of the SHA-256 of the file's source text. A partial parse and a full parse of the same text share it, which is how Comark matches an index entry to a cached body. Files you pass to [`update()`](https://content.comark.dev/reference/content/update) or [`ingest()`](https://content.comark.dev/reference/content/ingest) get a `hash` computed from their `data` and `nodes`, replacing any they carry.

## `ContentListFile<TData>`

The lightweight representation: `ContentFile` without `nodes`. This is what [`content.list()`](https://content.comark.dev/reference/content/list), [`content.query()`](https://content.comark.dev/plugins/built-in/sql-query) and `content.stat()` return, and what the manifest stores. Fetch the full file (including parsed `nodes`) with `content.get()`.

```ts
type ContentListFile<TData = Record<string, any>> = Omit<ContentFile<TData>, 'nodes'>
```

## `Manifest`

The index of every entry by path. Built from frontmatter on `content.init()`, it backs [`list()`](https://content.comark.dev/reference/content/list) and [`navigation()`](https://content.comark.dev/reference/content/navigation), and is returned as saveable data by [`content.manifest()`](https://content.comark.dev/reference/content/manifest).

```ts
interface Manifest {
  sources: string[]                          // instance names loaded into the index
  items: Record<string, ContentListFile>         // every entry, keyed by path
  time: number                               // build timestamp (ms)
  schemas?: Record<string, JsonSchema>       // per-instance `data` JSON Schema
}
```

## `NavigationItem`

The recursive shape produced by [`content.navigation()`](https://content.comark.dev/reference/content/navigation).

```ts
interface NavigationItem {
  title: string
  description?: string
  path: string
  stem?: string
  children?: NavigationItem[]
  page?: false
  [key: string]: unknown
}
```

## `ContentSource`

What an instance's [`source`](https://content.comark.dev/reference/content/comark-content#options-source) accepts: a raw [`Source`](#source) that hands over file text, or a [`ParsedSource`](#parsedsource) that hands over content Comark already parsed.

```ts
type ContentSource = Source | ParsedSource
```

## `Source`

The boundary that loads **raw** content. Implement this to build a [custom source](https://content.comark.dev/sources/custom).

```ts
interface Source {
  prefix?: string
  withRef?: (ref: string) => Source  // an equivalent source pinned to a branch, tag, or commit
  expensiveReads?: boolean  // cache raw bodies from partial init for reuse by get()
  keys: () => Promise<string[]> | string[]
  getItem: (key: string) => Promise<string> | string
  getItemRaw: (key: string) => Promise<unknown> | unknown
  watch?: (cb: (event: 'update' | 'remove', key: string) => void) => () => void
  schema?: JsonSchema
}
```

## `ParsedSource`

A source of **already-parsed** content: a stored [snapshot](https://content.comark.dev/sources/snapshot), a CMS API holding Comark output, another instance's exports. Parsed reads skip the parser and the `file:parsed` transforms, since the content was transformed when it was produced.

```ts
interface ParsedSource {
  parsed: true
  snapshot?: () => MaybePromise<ParsedSnapshot | null | undefined>   // heavy tier: every body
  manifest?: () => MaybePromise<Manifest | null | undefined>         // light tier: index only
  get?: (key: string) => MaybePromise<ContentFile | null | undefined>
  keys?: () => MaybePromise<string[]>
  origin?: Source                                                    // raw origin behind it
  schema?: JsonSchema
  withRef?: (ref: string) => ParsedSource                            // pinned origin, loaders told the ref
}
```

Every provider is optional, but the instance needs at least one way to read content: `snapshot()`, or `get()` with `keys()`, or an `origin`. The `origin` is what dev walks when no parsed data exists, what [`watch()`](https://content.comark.dev/reference/content/watch) observes, and what the [`snapshot` CLI](https://content.comark.dev/reference/cli#comark-content-snapshot) rebuilds from.

## `FSSourceOptions`

The optional second argument of the [`fs`](https://content.comark.dev/sources/filesystem) source. It extends unstorage's `fs` driver options (everything except `base`, which `fs` takes as its first argument) and adds the source-level fields below.

```ts
interface FSSourceOptions extends Omit<FSStorageOptions, 'base'> {
  prefix?: string
  exclude?: string[]
  schema?: JsonSchema
}
```

See [Filesystem options](https://content.comark.dev/sources/filesystem#options) for what each field does.

## `GithubSource`

The argument of the [`github`](https://content.comark.dev/sources/github) source. It extends unstorage's `github` driver options (`repo`, `branch`, `ttl`, `token`, `apiURL`, `cdnURL`) and adds `path` plus the source-level fields below.

```ts
interface GithubSource extends Omit<GithubOptions, 'dir'> {
  path?: string
  prefix?: string
  exclude?: string[]
  schema?: JsonSchema
}
```

See [GitHub options](https://content.comark.dev/sources/github#options) for what each field does.

## `Database`

The storage contract for parsed files. It is passed to the SQL-aware plugins ([`sqlQuery`](https://content.comark.dev/plugins/built-in/sql-query), [`sqliteFullTextSearch`](https://content.comark.dev/plugins/built-in/full-text-search)).

```ts
interface Database {
  upsert: (source: string, data: ContentFile) => void | Promise<void>
  delete: (source: string, key: string) => void | Promise<void>
  get: (source: string, key: string) => ContentFile | Promise<ContentFile>
  list: (source: string) => ContentFile[] | Promise<ContentFile[]>
  clear: (source: string) => void | Promise<void>
}
```

## `RelationalDatabase`

Extends [`Database`](#database) with the raw SQL methods the query and full-text search plugins actually call.

```ts
interface RelationalDatabase extends Database {
  first: <T>(query: string, params?: unknown[]) => Promise<T | undefined>
  all: <T>(query: string, params?: unknown[]) => Promise<T[]>
  execute: <T>(query: string, params?: unknown[]) => Promise<T>
}
```

## `ParsedSnapshot`

Every parsed file of one instance plus the time it was produced — what [`content.snapshot()`](https://content.comark.dev/reference/content/snapshot) returns and what a [`ParsedSource`](#parsedsource) hands over.

```ts
interface ParsedSnapshot {
  items: ContentFile[]              // full files, bodies included
  time?: number                     // epoch ms it was produced
}
```

## `CacheArtifact`

A checksummed envelope wrapping content data for **transport**, such as an HTTP response, prerendered file, or storage value. The [`handler()`](https://content.comark.dev/reference/content/handler) artifact routes produce this envelope, and the [`snapshot()` source](https://content.comark.dev/sources/snapshot) decodes it. The snapshot CLI writes plain JSON instead.

```ts
interface CacheArtifact {
  name: string                      // 'manifest' or an instance name
  data: string                      // JSON, optionally gzipped + base64
  checksum: string                  // SHA-256 of `data`
  size: number                      // bytes
  time?: number                     // epoch ms it was built (informational; the payload's own time drives freshness)
}
```

::note
Artifacts are a wire format, not the data model. [`manifest()`](https://content.comark.dev/reference/content/manifest) and [`snapshot()`](https://content.comark.dev/reference/content/snapshot) return plain data; wrapping it is the transport layer's job.
::

## `CacheOptions`

The `cache` object passed to [`comarkContent()`](https://content.comark.dev/reference/content/comark-content) — pure storage configuration. See [Caching](https://content.comark.dev/advanced/caching#options).

```ts
interface CacheOptions {
  driver?: Driver                   // unstorage driver; defaults to in-memory
  ttl?: number                      // ms before an entry is stale; omit to never expire
  swr?: boolean                     // serve stale while revalidating; defaults to true
}
```

## `CacheArtifactOptions`

Options for producing a [`CacheArtifact`](#cacheartifact).

```ts
interface CacheArtifactOptions {
  compress?: boolean                // gzip + base64 the payload; defaults to true
}
```

## `Cache`

The resolved cache exposed as `content.cache`: storage, nothing more. Backed by an unstorage driver, it stores [`ContentFile`](#contentfiletdata-tmeta) entries keyed `<name>:<path-in-source>`, plus the index and artifacts. See [Caching](https://content.comark.dev/advanced/caching).

```ts
interface Cache {
  get<T = ContentFile>(key: string): Promise<T | null>
  set<T = ContentFile>(key: string, value: T): Promise<void>
  keys(): Promise<string[]>                         // derived from the index, so key-less drivers work
  invalidate(key: string): Promise<void>            // drop one entry
  expire(key: string): Promise<void>                // mark one entry stale (swr refreshes on next read)
}
```

::note
Reading and producing content data lives on the instance ([`refresh()`](https://content.comark.dev/reference/content/refresh), [`manifest()`](https://content.comark.dev/reference/content/manifest), [`snapshot()`](https://content.comark.dev/reference/content/snapshot)), not here.
::

## `ContentHooks`

Events you can subscribe to via `content.hooks.hook(name, callback)`.

```ts
interface ContentHooks {
  'read:after': (ctx: ContentReadContext) => void | Promise<void>
  clean: (ctx: { ref?: string; key: string }) => void | Promise<void>
  'watch:file:update': (sourceName: string, key: string, file: ContentFile) => void
  'watch:file:remove': (sourceName: string, key: string) => void
  'watch:error': (sourceName: string, key: string, error: unknown) => void
  'file:parsed': (ctx: FileParsedContext) => void | Promise<void>
  'typegen:field': (ctx: TypegenFieldContext) => void | Promise<void>
  'file:upsert': (sourceName: string, key: string, file: ContentFile) => void
  'file:remove': (sourceName: string, key: string) => void
  'file:bulk:load': (sourceName: string, items: ContentFile[]) => void
}
```

See [`FileParsedContext`](https://content.comark.dev/reference/types/file-parsed#fileparsedcontext) for the `file:parsed` payload and [`TypegenFieldContext`](https://content.comark.dev/reference/types/typegen#typegenfieldcontext) for `typegen:field`. The [`markdown-fields`](https://content.comark.dev/plugins/built-in/markdown-fields), [`schema-validation`](https://content.comark.dev/plugins/built-in/schema-validation), and [`references`](https://content.comark.dev/plugins/built-in/references) plugins are reference implementations.

## `ContentReadContext`

The payload of [`read:after`](https://content.comark.dev/reference/content/hooks#readafter), emitted by the server instance and by the [HTTP client](https://content.comark.dev/reference/client/create-content-client#options-hooks) with identical tags.

```ts
type ContentReadOperation = 'get' | 'list' | 'navigation' | 'query' | 'search' | (string & {})

interface ContentReadInput {
  key?: string        // get(): the path or file key
  sources?: string[]  // instance names the read was scoped to
  query?: string      // search(): the term; informational, not a tag
}

interface ContentReadContext<TResult = ContentFile | ContentListFile[] | NavigationItem[] | null | undefined> {
  operation: ContentReadOperation
  input: ContentReadInput
  result: TResult
  ref?: string        // the instance's ref when pinned with withRef()
  tags: string[]      // 'content', then 'content:path:<path>' or 'content:<operation>', then 'content:source:<name>'…
}
```

## `SearchOptions`

Options accepted by [`content.search()`](https://content.comark.dev/plugins/built-in/full-text-search), added by the [`sqliteFullTextSearch`](https://content.comark.dev/plugins/built-in/full-text-search) plugin.

```ts
interface SearchOptions {
  limit?: number
  fields?: ('title' | 'content')[]
  minTermLength?: number
  weights?: { title?: number; content?: number; heading?: boolean }
  snippet?: { columns?: ('title' | 'content')[]; around?: number; tag?: string }
}
```

## `SearchResult`

A single result row returned by [`content.search()`](https://content.comark.dev/plugins/built-in/full-text-search).

```ts
interface SearchResult {
  source: string
  id: string
  title: string
  titles: string[]
  level: number
  content: string
  rank: number
  snippets?: { title?: string; content?: string }
}
```

## `Parser`

Registered via [`addParser`](https://content.comark.dev/reference/content/comark-content) to turn a raw file into a parsed result. `ParserContext` describes the file; `ParserResult` is what the parser returns.

```ts
interface ParserContext {
  filepath: string
  extension: string
  partial: boolean
  read: () => string | Promise<string>
}

type ParserResult = {
  kind?: 'media' | 'document'
  type?: string
  data?: ContentFile['data']
  meta?: Record<string, any>
  nodes?: ContentFile['nodes']
  partial?: boolean
}

type Parser = (ctx: ParserContext) => ParserResult | null | Promise<ParserResult | null>
```

## `ContentOptions`

The options object passed to [`comarkContent()`](https://content.comark.dev/reference/content/comark-content). See [Options](https://content.comark.dev/reference/content/comark-content#options).

```ts
interface ContentOptions {
  source?: ContentSource
  cache?: CacheOptions | false
  plugins?: ReadonlyArray<ContentPlugin<any>>
  hooks?: NestedHooks<ContentHooks>   // registered on the instance and its withRef() siblings
  markdown?: ParserOptions
  basePath?: string          // default '/api/content'
  baseURL?: string
  logger?: Logger | false    // default console-backed; false silences output
  onError?: OnErrorMode      // default 'warn'
}
```

## `Logger`

The minimal logger interface used across the Content instance pipeline. Provide one via [`ContentOptions.logger`](https://content.comark.dev/reference/content/comark-content#options-logger) to redirect output, or pass `false` to silence it. The active logger is exposed as `content.logger` and in the plugin and parser contexts.

```ts
interface Logger {
  debug: (...args: unknown[]) => void
  info: (...args: unknown[]) => void
  warn: (...args: unknown[]) => void
  error: (...args: unknown[]) => void
}
```

## `OnErrorMode`

How the pipeline reacts when a file fails to parse. Set the default via [`ContentOptions.onError`](https://content.comark.dev/reference/content/comark-content#options-onerror); the built-in `json`, `yaml`, and `markdown-fields` plugins accept a per-plugin override.

```ts
type OnErrorMode =
  | 'throw'   // surface the error and abort
  | 'warn'    // log via the logger and drop the file (default)
  | 'ignore'  // silently drop the file
```

## `Perf`

The lifecycle timing recorder exposed as [`content.perf`](https://content.comark.dev/reference/content/perf), active when a [tracing plugin](https://content.comark.dev/plugins/built-in/tracing) is installed — `tracingDebug()` for debug timelines, `tracingOtel({ tracer })` for OpenTelemetry forwarding. Every method is a no-op otherwise.

```ts
interface PerfEntry {
  id: number
  parentId?: number               // parent span id from the active stack
  name: string                    // e.g. 'parse:.md', 'cache:get', 'hook:file:parsed'
  start: number                   // milliseconds, same clock as performance.now()
  duration: number                // milliseconds
  meta?: Record<string, unknown>  // file key, source name, …
}

interface PerfScope {
  entries(): PerfEntry[]
  end(): void
  serverTiming(totalMs?: number): string
}

interface Perf {
  // ComarkTracer / OTel Tracer — pass content.perf to createMarkdownParser({ tracer })
  startSpan(name: string, options?: { attributes?: Record<string, unknown> }): { end(): void }
  startActiveSpan<T>(name: string, fn: (span: { end(): void }) => T): T
  startActiveSpan<T>(name: string, options: { attributes?: Record<string, unknown> }, fn: (span: { end(): void }) => T): T
  run<T>(name: string, fn: () => T, options?: { attributes?: Record<string, unknown> }): T
  entries(): PerfEntry[]
  reset(): void
  report(): void
  timeline(options?: { width?: number }): void
  startScope(): PerfScope | undefined  // undefined unless the debug recorder is active
}
```

## `ComarkContent`

The Content instance returned by [`comarkContent()`](https://content.comark.dev/reference/content/comark-content); plugin methods are mixed in on top. Every member is documented on the [comarkContent](https://content.comark.dev/reference/content/comark-content) page.

```ts
interface ComarkContent<TMeta = Record<string, unknown>, Name extends string = string> {
  readonly name: Name
  options: ContentOptions
  readonly ref?: string      // the ref this instance is pinned to (withRef())
  readonly key: string       // opaque per-instance key ('' on the default instance); scopes plugin state
  status: 'created' | 'initializing' | 'initialized-partial' | 'initialized-full' | 'disposed'
  cache: Cache
  logger: Logger
  perf: Perf
  hooks: Hookable<ContentHooks>
  init(opts?: { partial?: boolean; ignoreCache?: boolean }): Promise<void>
  manifest(): Promise<Manifest>
  snapshot(): Promise<ParsedSnapshot>
  refresh(): Promise<ContentFile[]>
  list: ContentList<TMeta, Name>
  stat(key: string): ContentListFile | undefined
  getSource(name?: string): Source | undefined
  get: ComarkGet
  navigation(): Promise<NavigationItem[]>
  handler(request: Request): Promise<Response>
  update(file: ContentFile): Promise<void>
  ingest(file: ContentFile): Promise<void>
  remove(key: string): Promise<void>
  watch(): Promise<() => Promise<void>>
  withRef(ref: string): this   // a sibling instance pinned to ref, with its own cache namespace
  dispose(): Promise<void>     // release runtime resources; data is kept
  clean(opts?: { force?: boolean }): Promise<void>  // delete this instance's persisted data, then dispose
}
```

## `ContentHub<T>`

The surface [`contentHub()`](https://content.comark.dev/reference/content-hub) returns, typed against the composed instances.
Every member is documented on that page.

```ts
interface ContentHub<T extends readonly AnyComarkContent[]> {
  readonly instances: T
  readonly names: HubNames<T>[]
  readonly basePath: string
  from<N extends HubNames<T>>(name: N): HubInstance<T, N>
  withRefs(refs: Partial<Record<HubNames<T>, string>>): ContentHub<T>  // named instances pinned, others reused
  dispose(): Promise<void>                            // dispose every composed instance
  init(opts?: { partial?: boolean; ignoreCache?: boolean }): Promise<void>
  manifest(): Promise<Manifest>                       // merged across instances
  stat(key: string): ContentListFile | undefined
  get: HubGet
  list: HubList<T>
  navigation(names?: string[]): Promise<NavigationItem[]>
  search(query: string, opts?: SearchOptions & { instances?: string[] }): Promise<SearchResult[]>
  query: HubQuery<T>
  handler(request: Request): Promise<Response>
  watch(): Promise<() => Promise<void>>
  media?: HubMedia                                      // when any instance has the media plugin
}
```

## `ContentHubOptions`

The second argument of [`contentHub()`](https://content.comark.dev/reference/content-hub#options).

```ts
interface ContentHubOptions {
  basePath?: string          // default '/api/content'
  logger?: Logger | false    // default console-backed; false silences output
}
```

## Hub helper types

| Type                | Description                                                                                  |
| ------------------- | -------------------------------------------------------------------------------------------- |
| `AnyComarkContent`  | Any instance a hub can compose, plugin methods included.                                     |
| `HubNames<T>`       | Union of the composed instance names.                                                        |
| `HubInstance<T, N>` | The composed instance type for name `N`.                                                     |
| `HubGet`            | `hub.get()`: a known path narrows to that path's data across every registered instance.      |
| `HubList<T>`        | `hub.list()`: known names narrow each item's `data`; no names gives the registry-wide union. |
| `HubQuery<T>`       | `hub.query(name)`: the named instance's query builder.                                       |

## `ContentList`

The typed signature of [`content.list()`](https://content.comark.dev/reference/content/list): a registered instance name narrows each item's `data`.

```ts
interface ContentList<TMeta, Name extends string> {
  <Data extends Record<string, any> = NameData<Name>>(): Promise<ContentListFile<Data, TMeta>[]>
}
```

## Registry and query types

Generated and advanced types powering path autocompletion and the [`sqlQuery`](https://content.comark.dev/plugins/built-in/sql-query) builder. `ContentRegistry` and `ContentPathsRegistry` are empty by default and augmented by [`generateSourceTypes()`](https://content.comark.dev/guide/typescript).

| Type                             | Description                                                                                                                                       |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ContentRegistry`                | Map of instance name to `{ data, row }`; augmented by generated types. Empty by default.                                                          |
| `ContentPathsRegistry`           | Map of instance name to that instance's `path -> data` map; augmented by generated types. Empty by default.                                       |
| `RegistryRow<K>`                 | The query-row type for instance `K`.                                                                                                              |
| `NameData<Name>`                 | The `data` type for instance `Name`, falling back to the registry-wide union.                                                                     |
| `SourceData<K>`                  | The `data` (frontmatter) type for instance `K`.                                                                                                   |
| `RegistryData`                   | Union of every instance's `data` type (the fallback for `get`/`list`).                                                                            |
| `PathData<Name, P>`              | The `data` type for path `P` in instance `Name`.                                                                                                  |
| `Ref<K>`                         | Branded path string pointing at instance `K` — emitted by typegen for [reference](https://content.comark.dev/plugins/built-in/references) fields. |
| `QueryRowBase`                   | Base columns present on every query row.                                                                                                          |
| `QueryRow`                       | A flattened query row (`path`, `meta.*`, `data.*` columns).                                                                                       |
| `SourceQueryBuilder`             | The chainable builder returned by [`content.query()`](https://content.comark.dev/plugins/built-in/sql-query).                                     |
| `SqlQueryMethods`, `ComarkQuery` | The typed `query()` method the `sqlQuery` plugin adds.                                                                                            |
| `ComarkGet`                      | The typed signature of [`content.get()`](https://content.comark.dev/reference/content/get).                                                       |

## Option and method types

Per-feature option and method bags, each documented in full on its feature page.

| Type                 | Used by                                                                     |
| -------------------- | --------------------------------------------------------------------------- |
| `SqliteOptions`      | [`sqlite()`](https://content.comark.dev/reference/database#sqlite-for-node) |
| `SqliteWasmOptions`  | [`sqliteWasm()`](https://content.comark.dev/reference/database#sqlite-wasm) |
| `ContentRow`         | the SQLite `__contents` table row shape                                     |
| `IndexSearchOptions` | the full-text search index builder                                          |
| `JsonSchema`         | a source's `data` schema (the source `schema` option)                       |


## Sitemap

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