netlify-database
Zero-config Postgres for Netlify apps via @netlify/database — querying data from Functions/Edge Functions, writing schema migrations, setting up Drizzle ORM, local dev with netlify dev, database branches for deploy previews, and migrating an existing Postgres project onto Netlify. Use when adding a
By netlify · 2,107 installs
npx skills add netlify/context-and-tools --skill netlify-database
Source repository · Upstream listing
Netlify Database
Zero config managed Postgres. Install @netlify/database , write migrations under netlify/database/migrations/ , deploy — Netlify provisions the DB and applies migrations automatically. Queryable from Functions, Edge Functions, Builds, and Agent Runners.
Modern client (reach for this)
Own driver / ORM instead:
Legacy — do NOT use for new code: import { neon } from "@netlify/neon" . Superseded by @netlify/database . Replace neon() calls with the Drizzle netlify db adapter or a Postgres driver via getConnectionString() . The legacy env var NETLIFY DATABASE URL is replaced by NETLIFY DB URL .
Where things go
What Location
Migrations netlify/database/migrations/ (SQL files or subdirs with migration.sql )
Query code Functions ( netlify/functions/ ), Edge Functions
Drizzle schema db/schema.ts (convention)
Drizzle client db/index.ts (convention)
Connection string NETLIFY DB URL env var, or getConnectionString()
Querying
getDatabase(options?) returns a client with sql and pool . options.connectionString overrides the auto provisioned one; options.debug enables logging.
SQLTemplate methods: execute() → Promise<T[] , stream() → AsyncGenerator<T , chunked(n) → AsyncGenerator<T[] , toSQL() → raw SQL + params without executing.
sql helpers:
sql.identifier(value) — safe table/column name. String, string[], or { schema, table, column, as } .
sql.values(rows) — bulk insert values list from a 2D array.
sql.default — the SQL DEFAULT keyword.
sql.raw(value) — injects unparameterized SQL; bypasses injection protection. Only for trusted constants (e.g. "DESC" ), never user input.
sql.unsafe(query, params?, { rowMode }) — raw query string with $1 params; rowMode is "array" or "object" .
Transactions — use pool
db.pool is a [ pg.Pool ](https://node postgres.com/apis/pool). BEGIN /queries/ COMMIT must run on the same connection:
Own drivers:
Drizzle ORM
Install both packages from @beta — required. latest lacks the drizzle orm/netlify db adapter and will fail.
drizzle.config.ts — you MUST set out to the Netlify migrations directory or Netlify won't apply generated migrations:
Generate migrations after editing the schema: npx drizzle kit generate .
Never run drizzle kit push against a Netlify hosted database, and never run drizzle kit migrate against NETLIFY DB URL . Schema reaches hosted DBs only as committed migration files applied by the deploy. generate writes files; the deploy applies them.
Migrations
Files live in netlify/database/migrations/ . Two formats:
Naming: <number <slug — number is digits (timestamp or 0001 …) defining order; slug is lowercase letters/numbers/hyphens/underscores. Sorted lexicographically , applied in order. Use timestamp prefixes ( netlify database migrations new handles this) to avoid out of order rejection.
When applied:
Production deploy: applied immediately before publish; a failure blocks publish. With auto publish off, Netlify waits for manual publish before applying.
Deploy preview: applied on every deploy before it goes live; a failure fails the deploy.
Local: not automatic — run netlify database migrations apply yourself.
Migration footguns (all detected as drift / rejected):
Never edit an applied migration — checksum drift: migration "<name " has been modified after being applied . Write a new corrective migration.
Never remove an applied migration — ... has been removed after being applied . Restore it.
Out of order: a prefix ≤ the highest applied version is rejected. Timestamps avoid this.
Prefer backwards compatible migrations. Breaking changes (rename/drop column) → expand and contract across multiple deploys. New table / nullable column → single migration is fine.
Bring your own migration system: pick a directory other than netlify/database/migrations to avoid automatic detection, and you own applying to preview branches and production.
See references/migrations.md .
Local development
Local is one database that all code targets — branches are a deploy time concept and don't exist locally. It's a real Postgres compatible engine mirroring production, but single process (not for load testing); auto scale/sleep settings don't apply.
Start it — either path, state is interchangeable:
Or the Vite plugin:
Common commands (while local DB is running):
External tools (works while netlify dev runs):
See references/local dev.md .
Setup
New project: describe your app to Agent Runners at https://app.netlify.com/start, or netlify create "<description " locally.
Existing project:
Manual: npm install @netlify/database , write a migration under netlify/database/migrations/ , write a function, netlify dev , deploy.
If @netlify/database is NOT installed, Netlify will NOT auto provision a database — you'd have to create one manually from the UI Database menu. Install the package.
CLI reference ( netlify database )
Prereqs: Node ≥ 20.12.2, Netlify CLI ≥ 26.0.0 ( npm install g netlify cli ). All commands support json .
Command Purpose Key flags
init Set up DB in project y, yes
status State: enabled, installed, connection string, applied/pending migrations b, branch , show credentials
connect SQL REPL, or query one shot q, query , json
migrations apply Apply pending to local DB to <name
migrations new Scaffold a migration d, description , s, scheme sequential\ timestamp
migrations pull Overwrite local files from a branch b, branch , force
migrations reset Delete unapplied local migration files b, branch
reset Drop all data/tables — local only —
See references/cli commands.md .
REST API
Scoped to a site, rooted at https://api.netlify.com/api/v1 , OAuth 2. Full reference: https://open api.netlify.com.
Method + path Purpose
POST /sites/{site id}/database Create DB (returns existing conn string if present); region optional
GET /sites/{site id}/database Get connection string
POST /sites/{site id}/database/branch Create branch; body deploy id (req), parent branch id (opt, defaults to production)
GET /sites/{site id}/database/branch/{deploy id} Get branch conn string (404 if none)
DELETE /sites/{site id}/database/branch/{deploy id} Delete a deploy's branch
POST /sites/{site id}/database/snapshot Snapshot a branch (defaults production)
GET /sites/{site id}/database/snapshots List snapshots
DELETE /sites/{site id}/database/snapshot/{snapshot id} Delete a snapshot
POST /sites/{site id}/database/snapshot/{snapshot id}/restore Restore snapshot to a branch (defaults production)
Branch delete and snapshot restore are destructive and require explicit user confirmation first. Snapshot restore is not a routine production rollback lever.
Testing
Bare Postgres for unit/integration tests (no functions):
NetlifyDB(options?) : directory (persist to disk; omit = in memory), port (default random), logger .
Full Netlify environment (functions/edge functions read NETLIFY DB URL as in production):
Database branches (deploy time)
Production deploys are the only deploys that touch the production database. Each deploy preview gets its own branch, seeded with a copy of production data at preview creation time; schema/data changes there never affect production. Wired up automatically, no code changes.
Preview branches can contain production data, including PII — and preview deploy links are public. Warn the user before sharing a preview link.
Runtime gotchas
Environment not configured ( getDatabase() can't resolve a connection string): running outside Netlify, on Functions in Lambda compatibility mode , or an outdated CLI. Fix: pass connectionString explicitly.
Lambda compatibility mode is the one primitive where you must pass connectionString yourself.
database feature not available for this account — requires a Credit based plan.
compute customization requires a Pro or higher plan — auto scale / sleep settings need Pro+; Free/Personal use defaults.
branch limit reached: maximum <N branches... — each active deploy preview consumes a branch; delete unneeded branches or upgrade.
database not found — no DB provisioned; run netlify database init .
cannot reset the production branch — reset is non production only.
Constraints
Plan: Netlify Database is available on Credit based plans only; active DBs consume credits for compute and bandwidth. Storage is free until July 1, 2026.
Permissions: only a Team Owner can delete a database; only Team Owners and Developers can view connection strings ( Access Denied = insufficient role).
Secrets: connection strings contain username + password. Never commit them; store in a secret manager / env var provider.
Switch an existing Postgres project to Netlify Database
Three phases: provision (baseline schema on a branch), rehearse (swap code, copy data into a preview branch, validate), cut over (import data into production, merge). Works from any Postgres source (Neon, Supabase, RDS, self managed, legacy @netlify/neon ). Uses pg dump / pg restore (versions matching the source). There is a brief data loss window — writes to the source between final export and production deploy don't cross over.
Phase 2/3 code swap (Drizzle):
Full step by step (dump flags, rollback, cleanup): references/migration from extension.md and references/legacy extension.md .
<! Gaps: plan tier naming (Credit based vs Free/Personal/Pro) not reconciled in source; exact plan limits, permission tables, and snapshot UI flows live on pages outside this grouping.
<! system: agent context/database/system.md — human owned, merged by ctx gen; edit system.md, not this section
Netlify house rules (database)
These are org conventions, not docs facts — merged into the rendered skill by
ctx gen and never generated. Owned by the skills maintainer.
1. Production data changes are expressed as DML migrations — agents never
edit rows directly (UI row editing exists for humans; it is not an agent
surface).
2. Preview branches can contain production data, including PII — and preview
deploy links are public. Warn before sharing.
3. Use only documented surfaces: no raw psql against internal endpoints, no
netlify api scraping, no reading tokens from local CLI config files.
4. Deep guides live in this skill: references/operational footguns.md ,
references/migrations.md , references/local dev.md ,
references/cli commands.md , references/migration from extension.md ,
references/legacy extension.md .
5. Schema changes reach hosted databases only as committed migration files
applied by the deploy. Never run drizzle kit push in any form against a
Netlify hosted database, never run drizzle kit migrate against
NETLIFY DB URL , and never apply DDL via netlify database connect or
any direct connection.
6. When a netlify command or a deploy fails, surface the exact error, the
deploy log URL, and the affected site/branch to the user and stop — do
not invent recovery commands or escalate to lower level tools.
7. First deploy 401 Access Denied on createSiteDatabase : if it happened
on a prod first deploy, retry preview first ( netlify deploy , no
prod ); if a preview also fails, report and stop. Never curl
api.netlify.com , run netlify api createSiteDatabase , or pull tokens
from local CLI config to work around it.
8. A request to change existing data is ambiguous between production and the
preview branch — if the prompt didn't say, ask. When acting on someone's
behalf, default to not touching production.
9. Destructive