---
title: Database Options
description: Configure EmDash with D1, PostgreSQL, libSQL, or SQLite.
---

import { Aside, Tabs, TabItem } from "@astrojs/starlight/components";

EmDash supports multiple database backends. Choose based on your deployment target.

## Overview

| Database       | Best For           | Deployment                 |
| -------------- | ------------------ | -------------------------- |
| **D1**         | Cloudflare Workers | Edge, globally distributed |
| **PostgreSQL** | Production Node.js | Any platform with Postgres |
| **libSQL**     | Remote databases   | Edge or Node.js            |
| **SQLite**     | Node.js, local dev | Single server              |

## Cloudflare D1

D1 is Cloudflare's serverless SQLite database. Use it when deploying to Cloudflare Workers.

```js title="astro.config.mjs"
import { d1 } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: d1({ binding: "DB" }),
		}),
	],
});
```

### Configuration

| Option           | Type     | Default              | Description                           |
| ---------------- | -------- | -------------------- | ------------------------------------- |
| `binding`        | `string` | —                    | D1 binding name from `wrangler.jsonc` |
| `session`        | `string` | `"disabled"`         | Read replication mode (see below)     |
| `bookmarkCookie` | `string` | `"__ec_d1_bookmark"` | Cookie name for session bookmarks     |

### Setup

<Tabs>
  <TabItem label="wrangler.jsonc">
    ```jsonc
    {
      "d1_databases": [
        {
          "binding": "DB",
          "database_name": "emdash-db",
          "database_id": "your-database-id"
        }
      ]
    }
    ```
  </TabItem>
  <TabItem label="wrangler.toml">
    ```toml
    [[d1_databases]]
    binding = "DB"
    database_name = "emdash-db"
    database_id = "your-database-id"
    ```
  </TabItem>
</Tabs>

<Aside type="caution">
	D1 does not allow DDL statements at runtime. Run migrations via Wrangler CLI before deployment:
	```bash wrangler d1 migrations apply emdash-db ```
</Aside>

### Create a D1 Database

```bash
wrangler d1 create emdash-db
```

### Read Replicas

D1 supports [read replication](https://developers.cloudflare.com/d1/configuration/read-replication/) to lower read latency for globally distributed sites. When enabled, read queries are routed to nearby replicas instead of always hitting the primary database.

EmDash uses the D1 Sessions API to manage this transparently. Enable it with the `session` option:

```js title="astro.config.mjs"
import { d1 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: d1({
				binding: "DB",
				session: "auto",
			}),
		}),
	],
});
```

#### Session Modes

| Mode             | Behavior                                                                                           |
| ---------------- | -------------------------------------------------------------------------------------------------- |
| `"disabled"`     | No sessions. All queries go to primary. Default.                                                   |
| `"auto"`         | Anonymous requests read from the nearest replica. Authenticated users get read-your-writes consistency via bookmark cookies. |
| `"primary-first"`| Like `"auto"`, but the first query always goes to the primary. Use for sites with very frequent writes. |

#### How It Works

- **Anonymous visitors** get `first-unconstrained` — reads go to the nearest replica for the lowest latency. Since anonymous users never write, they don't need consistency guarantees.
- **Authenticated users** (editors, authors) get bookmark-based sessions. After a write, a bookmark cookie ensures the next request sees at least that state.
- **Write requests** (`POST`, `PUT`, `DELETE`) always start at the primary database.
- **Build-time queries** (Astro content collections) bypass sessions entirely and use the primary directly.

<Aside type="tip">
	Read replication must also be enabled on the D1 database itself via the Cloudflare dashboard or
	REST API. The `session` option only controls EmDash's client-side session management.
</Aside>

<Aside>
	Anonymous visitors may see data that is a few seconds stale. For a CMS this is expected — content
	changes are not real-time, and if you have route caching enabled the response is already stale by
	definition.
</Aside>

## libSQL

libSQL is a fork of SQLite that supports remote connections. Use it when you need a remote database without Cloudflare D1.

```js title="astro.config.mjs"
import { libsql } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: libsql({
				url: process.env.LIBSQL_DATABASE_URL,
				authToken: process.env.LIBSQL_AUTH_TOKEN,
			}),
		}),
	],
});
```

### Configuration

| Option      | Type     | Description                                          |
| ----------- | -------- | ---------------------------------------------------- |
| `url`       | `string` | Database URL (`libsql://...` or `file:...`)          |
| `authToken` | `string` | Auth token for remote databases (optional for local) |

### Local Development

Use a local libSQL file during development:

```js
database: libsql({ url: "file:./data.db" });
```

## PostgreSQL

PostgreSQL is supported for Node.js deployments that need a full relational database.

```js title="astro.config.mjs"
import { postgres } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: postgres({
				connectionString: process.env.DATABASE_URL,
			}),
		}),
	],
});
```

### Configuration

You can connect with a connection string or individual parameters:

```js
// Connection string
database: postgres({
	connectionString: "postgres://user:password@localhost:5432/emdash",
});

// Individual parameters
database: postgres({
	host: "localhost",
	port: 5432,
	database: "emdash",
	user: "emdash",
	password: process.env.DB_PASSWORD,
	ssl: true,
});
```

| Option             | Type      | Description                          |
| ------------------ | --------- | ------------------------------------ |
| `connectionString` | `string`  | PostgreSQL connection URL            |
| `host`             | `string`  | Database host                        |
| `port`             | `number`  | Database port                        |
| `database`         | `string`  | Database name                        |
| `user`             | `string`  | Database user                        |
| `password`         | `string`  | Database password                    |
| `ssl`              | `boolean` | Enable SSL                           |
| `pool.min`         | `number`  | Minimum pool connections (default 0) |
| `pool.max`         | `number`  | Maximum pool connections (default 10)|

### Connection Pooling

The adapter uses `pg.Pool` under the hood. Tune pool size based on your deployment:

```js
database: postgres({
	connectionString: process.env.DATABASE_URL,
	pool: { min: 2, max: 20 },
});
```

<Aside>
	PostgreSQL requires the `pg` package. Install it as a dependency:
	```bash
	pnpm add pg
	```
</Aside>

## SQLite

SQLite with better-sqlite3 is the simplest option for Node.js deployments.

```js title="astro.config.mjs"
import { sqlite } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: sqlite({ url: "file:./data.db" }),
		}),
	],
});
```

### Configuration

| Option | Type     | Description                   |
| ------ | -------- | ----------------------------- |
| `url`  | `string` | File path with `file:` prefix |

### File Path

The `url` must start with `file:`:

```js
// Relative path
database: sqlite({ url: "file:./data/emdash.db" });

// Absolute path
database: sqlite({ url: "file:/var/data/emdash.db" });

// From environment variable
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });
```

<Aside>
	SQLite requires a persistent filesystem. It does not work on platforms with ephemeral storage
	without additional configuration.
</Aside>

## Migrations

EmDash handles migrations automatically for SQLite, libSQL, and PostgreSQL. For D1, run migrations via Wrangler.

### Check Migration Status

```bash
npx emdash init --database ./data.db
```

This command:

1. Creates the database file if needed
2. Runs any pending migrations
3. Reports the current migration status

### Migration Files

Migrations are bundled with EmDash. To run them manually:

```bash
# SQLite/libSQL - migrations run automatically

# D1 - run via wrangler
wrangler d1 migrations apply DB
```

## Environment-Based Configuration

Use different databases per environment:

```js title="astro.config.mjs"
import { d1, sqlite, libsql, postgres } from "emdash/db";

const database = import.meta.env.PROD ? d1({ binding: "DB" }) : sqlite({ url: "file:./data.db" });

export default defineConfig({
	integrations: [emdash({ database })],
});
```

Or switch based on environment variables:

```js
const database = process.env.DATABASE_URL
	? postgres({ connectionString: process.env.DATABASE_URL })
	: sqlite({ url: "file:./data.db" });
```
