Database Options

On this page

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

Overview

DatabaseBest ForDeployment
D1Cloudflare WorkersEdge, globally distributed
PostgreSQLProduction Node.jsAny platform with Postgres
libSQLRemote databasesEdge or Node.js
SQLiteNode.js, local devSingle server

Cloudflare D1

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

import { d1 } from "@emdash-cms/cloudflare";

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

Configuration

OptionTypeDefaultDescription
bindingstringD1 binding name from wrangler.jsonc
sessionstring"disabled"Read replication mode (see below)
bookmarkCookiestring"__em_d1_bookmark"Cookie name for session bookmarks

Setup

wrangler.jsonc

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "emdash-db",
      "database_id": "your-database-id"
    }
  ]
}

wrangler.toml

[[d1_databases]]
binding = "DB"
database_name = "emdash-db"
database_id = "your-database-id"

Create a D1 Database

wrangler d1 create emdash-db

Read Replicas

D1 supports 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:

import { d1 } from "@emdash-cms/cloudflare";

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

Session Modes

ModeBehavior
"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.

libSQL

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

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

OptionTypeDescription
urlstringDatabase URL (libsql://... or file:...)
authTokenstringAuth token for remote databases (optional for local)

Local Development

Use a local libSQL file during development:

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

PostgreSQL

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

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:

// 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,
});
OptionTypeDescription
connectionStringstringPostgreSQL connection URL
hoststringDatabase host
portnumberDatabase port
databasestringDatabase name
userstringDatabase user
passwordstringDatabase password
sslbooleanEnable SSL
pool.minnumberMinimum pool connections (default 0)
pool.maxnumberMaximum pool connections (default 10)

Connection Pooling

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

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

SQLite

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

import { sqlite } from "emdash/db";

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

Configuration

OptionTypeDescription
urlstringFile path with file: prefix

File Path

The url must start with file::

// 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}` });

Migrations

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

Check Migration Status

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:

# SQLite/libSQL - migrations run automatically

# D1 - run via wrangler
wrangler d1 migrations apply DB

Environment-Based Configuration

Use different databases per environment:

import { sqlite, libsql, postgres } from "emdash/db";
import { d1 } from "@emdash-cms/cloudflare";

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:

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