---
title: "Database"
description: "An optional, opt-in SQLite-backed store behind the SQL query and full-text search plugins."
canonical_url: "https://content.comark.dev/reference/database"
---
# Database

> An optional, opt-in SQLite-backed store behind the SQL query and full-text search plugins.

The [`sqlQuery`](https://content.comark.dev/plugins/built-in/sql-query) and [`sqliteFullTextSearch`](https://content.comark.dev/plugins/built-in/full-text-search) plugins need a database. It's where they build their index and the store they query against. Nothing else in Comark Content uses it: `get()`, `list()`, and `navigation()` work without one.

## SQLite for Node

Comark Content ships `sqlite()`, built on Node's [`node:sqlite`](https://nodejs.org/api/sqlite.html). Create one and pass it to the query and search plugins:

```ts [content.ts]
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'
import sqlite from 'comark-content/database/sqlite-node'
import sqlQuery from 'comark-content/plugins/sql-query'
import sqliteFullTextSearch from 'comark-content/plugins/sqlite-full-text-search'

const database = sqlite()   // one database, shared by both plugins

const content = comarkContent({
  source: fs('./content'),
  plugins: [sqlQuery({ database }), sqliteFullTextSearch({ database })],
})
```

They add `content.query()` for SQL queries over frontmatter and [`content.search()`](https://content.comark.dev/plugins/built-in/full-text-search) for BM25 full-text search.

::tip{to="https://content.comark.dev/plugins/built-in/full-text-search#shared-database"}
One database serves any number of instances, and full-text search **needs** that: ranking across
instances only works when they all write into the same index. Create the database once and pass it to
every instance you compose with [`contentHub()`](https://content.comark.dev/advanced/hub).
::

::note
`node:sqlite` ships with Node 22.5+. To run the plugins in the browser, use [`sqliteWasm()`](#sqlite-wasm) instead: same API, same plugins. Being WebAssembly, it also runs in any other host without `node:sqlite`.
::

### Options

```ts
sqlite(options?: SqliteOptions)
```

| Option     | Type     | Default      | Description                                              |
| ---------- | -------- | ------------ | -------------------------------------------------------- |
| `filename` | `string` | `':memory:'` | Path to the SQLite file. Omit for an in-memory database. |

### Persist the database

`sqlite()` is **in-memory by default**: it builds its index in RAM and drops it when the process exits, so every start rebuilds it. That's fine in development, but a serverless cold start or a fresh deploy pays the indexing cost each time.

Give it a `filename` and the database lives on disk instead, so a fresh instance can **reuse** the index rather than rebuild it:

```ts [persist.ts]
import sqlite from 'comark-content/database/sqlite-node'

// In-memory (default): the index is rebuilt on every start.
sqlite()

// On disk: written once, then restored by the next instance.
sqlite({ filename: '.comark/content.db' })
```

To also avoid re-parsing content across restarts, cache parsed bodies with a persistent [cache](https://content.comark.dev/advanced/caching) driver, or ship a static [snapshot](https://content.comark.dev/advanced/artifacts-and-hydration).

## SQLite WASM

`sqliteWasm()` is a drop-in replacement for `sqlite()` that runs in any environment supporting WebAssembly: the browser, Cloudflare Workers, Vercel Edge, or Node when you don't have `node:sqlite`. Same interface, same `filename` option, same plugins:

```ts [wasm.ts]
import sqliteWasm from 'comark-content/database/sqlite-wasm'
import sqlQuery from 'comark-content/plugins/sql-query'

const database = sqliteWasm()
```

It needs one extra dependency:

::code-group
```bash [pnpm]
pnpm add @sqlite.org/sqlite-wasm
```

```bash [npm]
npm install @sqlite.org/sqlite-wasm
```

```bash [yarn]
yarn add @sqlite.org/sqlite-wasm
```

```bash [bun]
bun add @sqlite.org/sqlite-wasm
```
::

::warning
Exclude `@sqlite.org/sqlite-wasm` from Vite's dependency optimizer: the package ships a worker plus a `.wasm` asset that the optimizer doesn't handle. The official Vite plugin (`comark-content/vite`) does this for you.
::

```ts [vite.config.ts]
export default defineConfig({
  optimizeDeps: {
    exclude: ['@sqlite.org/sqlite-wasm'],
  },
})
```

::tip{to="https://content.comark.dev/advanced/artifacts-and-hydration"}
Running the whole Content instance in the browser? Hydrate `sqliteWasm()` from server-built snapshots instead of parsing on the client. See [Artifacts and hydration](https://content.comark.dev/advanced/artifacts-and-hydration).
::

## Cache vs database

The cache and the database persist two different things. A persistent [cache](https://content.comark.dev/advanced/caching) (or shipped [artifacts](https://content.comark.dev/advanced/artifacts-and-hydration)) keeps **parsed bodies and the manifest index**, so `get()`, `list()`, and `navigation()` skip re-reading the source. A persistent database keeps the **query and search index**, so the plugins can reuse it instead of rebuilding from scratch. Persist both and a cold start does far less work.


## Sitemap

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