---
title: "CLI"
description: "The comark-content binary — locate your Content config, generate source types, and write snapshots."
canonical_url: "https://content.comark.dev/reference/cli"
---
# CLI

> The comark-content binary — locate your Content config, generate source types, and write snapshots.

`comark-content` ships a small CLI binary, `comark-content`, installed alongside the package. Run `comark-content --help` to list the available subcommands.

```bash [Terminal]
npx comark-content --help
```

## How your config is loaded

Both commands import your `content.ts` and read the instances it exports. That import happens **outside your framework**, with [`jiti`](https://github.com/unjs/jiti), and the differences from your dev server or your build matter:

| In the CLI                                   | Consequence                                                                                                                                                                                                                        |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `import.meta.dev` is `undefined`             | A ternary on it picks the *production* branch. With a bare `snapshot()` there, you would snapshot a snapshot — which is why [`withSnapshot()`](https://content.comark.dev/sources/snapshot#withsnapshot) keeps both tiers instead. |
| `NODE_ENV` is not `production`               | A watcher guarded only on `NODE_ENV !== 'production'` **starts**.                                                                                                                                                                  |
| Every module-scope side effect runs          | A database connection, a telemetry call, a `watch()` at the top level all execute, once per command.                                                                                                                               |
| The `server-only` marker is ignored          | `import 'server-only'` does not throw.                                                                                                                                                                                             |
| The process exits when the command completes | Handles your config opened (watchers, sockets) do not keep it alive.                                                                                                                                                               |

Keep dev-only side effects behind a guard that is false in the CLI, and idempotent for the dev server that re-evaluates the module. The Nuxt and Next examples use a `globalThis` flag:

```ts [server/content.ts]
if (process.env.NODE_ENV !== 'production') {
  const scope = globalThis as typeof globalThis & { __comarkWatching__?: boolean }
  if (!scope.__comarkWatching__) {
    scope.__comarkWatching__ = true
    void content.watch()
  }
}
```

The watcher still starts under the CLI here — `NODE_ENV` is not `production` — but it does no harm: the command finishes and exits. Anything with a real cost, such as a connection to a production database, belongs behind a check the CLI fails, for example an environment variable your framework sets.

## `comark-content prepare`

Locates your [`comarkContent()`](https://content.comark.dev/reference/content/comark-content) instance and generates [source types](https://content.comark.dev/guide/typescript) (`comark-content.d.ts` by default). This is the same [`writeSourceTypes()`](https://content.comark.dev/guide/typescript) the [Vite plugin](https://content.comark.dev/guide/typescript#let-vite-do-it) runs automatically — reach for the CLI when you're not using Vite, or want to (re)generate types as a standalone script or CI step.

Generated path entries use the same normalized lowercase public paths stored in the manifest. Calls with different casing still narrow to the matching generated type.

```bash [Terminal]
npx comark-content prepare
```

**Config discovery:**

Without `--config`, it searches for `content.{ts,mts,js,mjs}` in, relative to `--cwd`:

- `.`
- `./server`
- `./lib`
- `./server/utils`
- `./server/lib`
- `./src/lib`

The first match is loaded (via [`jiti`](https://github.com/unjs/jiti)) and its exported instances resolved by checking, in order: the `default` export, a `content` export, then any other exported value that looks like a `ComarkContent`. Every instance found is used, so a file exporting several instances (or a [`contentHub()`](https://content.comark.dev/reference/content-hub)) is declared in one pass. Export them explicitly if discovery doesn't find them:

```ts [content.ts]
export const blog = comarkContent('blog', { /* ... */ })
export const docs = comarkContent('docs', { /* ... */ })
```

**Flags:**

| Flag                            | Type      | Default               | Description                                                                       |
| ------------------------------- | --------- | --------------------- | --------------------------------------------------------------------------------- |
| `--cwd <dir>`                   | `string`  | `process.cwd()`       | Project directory to search and resolve paths from.                               |
| `--config <path/to/content.ts>` | `string`  | —                     | Path to the Content instance file, bypassing directory search.                    |
| `--output <file>`, `-o`         | `string`  | `comark-content.d.ts` | Where to write the generated types.                                               |
| `--module-name <name>`          | `string`  | `comark-content`      | Module specifier the `declare module` augmentation targets.                       |
| `--paths` / `--no-paths`        | `boolean` | `true`                | Emit the per-document path map. Pass `--no-paths` to skip it on very large sites. |

**Errors:**

- No `content.{ts,mts,js,mjs}` found in any searched directory: exits `1` and prints the directories searched. Pass `--config` to point at it explicitly.
- The resolved config path doesn't exist, or doesn't export a Content instance: exits `1` with a message telling you to `export const content = comarkContent({ ... })`.

**Usage:**

```bash
# Default: search for content.ts, write ./comark-content.d.ts
comark-content prepare

# Explicit config and output path
comark-content prepare --config server/content.ts --output types/comark-content.d.ts

# Skip the path map on a large site
comark-content prepare --no-paths
```

When writing to the default `comark-content.d.ts`, the command prints a reminder to add it to `.gitignore` and to your `tsconfig.json` `include` — see [Add TypeScript types](https://content.comark.dev/guide/typescript).

## `comark-content snapshot`

Writes each instance's [snapshot](https://content.comark.dev/reference/content/snapshot) — every parsed file, bodies included — and its [manifest](https://content.comark.dev/reference/content/manifest) to disk. Run it in a build step, then read the output back with the [`snapshot()` source](https://content.comark.dev/sources/snapshot) so production serves parsed content with no source to walk.

```bash [Terminal]
npx comark-content snapshot
```

Config discovery is identical to [`prepare`](#comark-content-prepare), and every instance found is written to its own directory:

```
.content/
  default/
    snapshot.json
    manifest.json
```

Instances are rebuilt from their source of truth before writing (`init({ partial: false, ignoreCache: true })`), so the output never depends on a warm cache.

**Flags:**

| Flag                            | Type      | Default         | Description                                                            |
| ------------------------------- | --------- | --------------- | ---------------------------------------------------------------------- |
| `--cwd <dir>`                   | `string`  | `process.cwd()` | Project directory to search and resolve paths from.                    |
| `--config <path/to/content.ts>` | `string`  | —               | Path to the Content instance file, bypassing directory search.         |
| `--output <dir>`, `-o`          | `string`  | `.content`      | Directory the per-instance folders are written into.                   |
| `--manifest` / `--no-manifest`  | `boolean` | `true`          | Also write `manifest.json`. Pass `--no-manifest` to write bodies only. |

**Errors:**

- Config discovery failures behave exactly as they do for [`prepare`](#comark-content-prepare).
- An instance whose only provider is a stored snapshot exits `1`. There is nothing to snapshot *from*, and writing one would freeze stale data forever. Wrap the origin with [`withSnapshot(fs('./content'), …)`](https://content.comark.dev/sources/snapshot#withsnapshot) so the command can rebuild it.

### In a build script

Add it before the build that consumes the output:

```json [package.json]
{
  "scripts": {
    "build": "comark-content snapshot && vite build"
  }
}
```

Commit nothing: the snapshot is derived, so `.content/` belongs in `.gitignore`.

To write the same files from your own script, call `writeSnapshots()` from `comark-content/build`. To store them somewhere that is not the filesystem, read [`content.snapshot()`](https://content.comark.dev/reference/content/snapshot) and [`content.manifest()`](https://content.comark.dev/reference/content/manifest) directly.


## Sitemap

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