skills/ netlify/context-and-tools

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

0
Installs
—
Rating
—
Success rate
7
Files scanned
Scan passeddatabase
Source on GitHub

Security scan

Scan passed

No risky patterns were found in the scanned files.

7 files scannedscanner v1.2.0Oct 11, 2026

Content sha256 71127e1bdbcb3536… — run codexguild_scan_skills after installing to verify your local copy.

Static analysis is a first line of defense, not a guarantee. Read the source

SKILL.md

exact scanned copy

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)

import { getDatabase } from "@netlify/database";

const db = getDatabase();               // auto-selects connection for the runtime
const userId = 42;
const users = await db.sql`SELECT * FROM users WHERE id = ${userId}`;  // auto-parameterized

Own driver / ORM instead:

import { getConnectionString } from "@netlify/database";
const connectionString = getConnectionString();  // correct branch for this env

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

WhatLocation
Migrationsnetlify/database/migrations/ (SQL files or subdirs with migration.sql)
Query codeFunctions (netlify/functions/), Edge Functions
Drizzle schemadb/schema.ts (convention)
Drizzle clientdb/index.ts (convention)
Connection stringNETLIFY_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.

const db = getDatabase();
const active = await db.sql`SELECT * FROM users WHERE active = ${true}`;
await db.sql`INSERT INTO users (name, email) VALUES (${"Ada"}, ${"ada@example.com"})`;
await db.sql`UPDATE users SET name = ${"Ada Lovelace"} WHERE id = ${1}`;
await db.sql`DELETE FROM users WHERE id = ${1}`;

// Type the rows
interface User { id: number; name: string; email: string; }
const typed = await db.sql<User>`SELECT * FROM users`;

// Stream
for await (const row of db.sql`SELECT * FROM users`.stream()) { /* ... */ }
for await (const chunk of db.sql`SELECT * FROM users`.chunked(100)) { /* ... */ }

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. BEGIN/queries/COMMIT must run on the same connection:

const client = await db.pool.connect();
try {
  await client.query("BEGIN");
  await client.query("INSERT INTO users (name, email) VALUES ($1, $2)", ["Ada", "ada@example.com"]);
  await client.query("INSERT INTO posts (author_id, title) VALUES ($1, $2)", [1, "First post"]);
  await client.query("COMMIT");
} catch (e) {
  await client.query("ROLLBACK");
  throw e;
} finally {
  client.release();
}

Own drivers:

import { getConnectionString } from "@netlify/database";
import pg from "pg";
const pool = new pg.Pool({ connectionString: getConnectionString() });

// or the `postgres` driver via env var
import postgres from "postgres";
const sql = postgres(process.env.NETLIFY_DB_URL);

Drizzle ORM

Install both packages from @beta — required. latest lacks the drizzle-orm/netlify-db adapter and will fail.

npm install @netlify/database drizzle-orm@beta
npm install -D drizzle-kit@beta

drizzle.config.ts — you MUST set out to the Netlify migrations directory or Netlify won't apply generated migrations:

import { defineConfig } from "drizzle-kit";
export default defineConfig({
  dialect: "postgresql",
  schema: "./db/schema.ts",
  out: "netlify/database/migrations",   // NOT the default "drizzle"
});
import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
  id: serial().primaryKey(),
  name: text().notNull(),
  email: text().notNull().unique(),
  createdAt: timestamp().defaultNow(),
});
import { drizzle } from "drizzle-orm/netlify-db";  // native adapter, auto-configured
import * as schema from "./schema";
export const db = drizzle({ schema });
import { desc } from "drizzle-orm";
import type { Config, Context } from "@netlify/functions";
import { db } from "../../db";
import { users } from "../../db/schema";

export default async (req: Request, context: Context) => {
  if (req.method === "GET") {
    const allUsers = await db.select().from(users).orderBy(desc(users.createdAt));
    return Response.json(allUsers);
  }
  if (req.method === "POST") {
    const { name, email } = await req.json();
    const [user] = await db.insert(users).values({ name, email }).returning();
    return Response.json(user, { status: 201 });
  }
  return new Response("Method not allowed", { status: 405 });
};

export const config: Config = { path: "/api/users" };

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:

netlify/database/migrations/20260301143000_create_users.sql          # single SQL file
netlify/database/migrations/20260318091500_add_posts/migration.sql   # subdir form

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.

CREATE TABLE comments (
  id SERIAL PRIMARY KEY,
  post_id INTEGER NOT NULL REFERENCES posts(id),
  author_id INTEGER NOT NULL REFERENCES users(id),
  body TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

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:

netlify dev                                    # CLI starts + tears down the local DB

Or the Vite plugin:

import { defineConfig } from "vite";
import netlify from "@netlify/vite-plugin";
export default defineConfig({ plugins: [netlify()] });

Common commands (while local DB is running):

netlify database migrations apply                        # apply pending locally
netlify database migrations new -d "add users table"     # scaffold new migration
netlify database migrations pull                          # overwrite local migrations from remote
netlify database status                                   # enabled? installed? applied/pending migrations
netlify database connect                                  # interactive SQL REPL
netlify database connect --query "SELECT * FROM users LIMIT 10"
netlify database reset                                    # drop all schemas/tables — LOCAL ONLY
netlify database migrations reset                         # delete unapplied local migration files

External tools (works while netlify dev runs):

psql "$(netlify database connect --json | jq -r .connection_string)"

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:

netlify database init      # installs @netlify/database, picks Drizzle or raw SQL, scaffolds a migration
netlify database init --yes # non-interactive (CI / agents)
netlify dev

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 Data & Storage > 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.

CommandPurposeKey flags
initSet up DB in project-y, --yes
statusState: enabled, installed, connection string, applied/pending migrations-b, --branch, --show-credentials
connectSQL REPL, or --query one-shot-q, --query, --json
migrations applyApply pending to local DB--to <name>
migrations newScaffold a migration-d, --description, -s, --scheme sequential|timestamp
migrations pullOverwrite local files from a branch-b, --branch, --force
migrations resetDelete unapplied local migration files-b, --branch
resetDrop 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 + pathPurpose
POST /sites/{site_id}/databaseCreate DB (returns existing conn string if present); region optional
GET /sites/{site_id}/databaseGet connection string
POST /sites/{site_id}/database/branchCreate 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/snapshotSnapshot a branch (defaults production)
GET /sites/{site_id}/database/snapshotsList snapshots
DELETE /sites/{site_id}/database/snapshot/{snapshot_id}Delete a snapshot
POST /sites/{site_id}/database/snapshot/{snapshot_id}/restoreRestore 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):

import { NetlifyDB } from "@netlify/database-dev";  // npm i -D @netlify/database-dev
import { Client } from "pg";
import { afterAll, beforeAll, expect, test } from "vitest";

let db: NetlifyDB, connectionString: string;
beforeAll(async () => {
  db = new NetlifyDB();
  connectionString = await db.start();
  await db.applyMigrations("./netlify/database/migrations");
});
afterAll(async () => { await db.stop(); });

test("inserts and reads a user", async () => {
  const client = new Client({ connectionString });
  await client.connect();
  await client.query("INSERT INTO users (name) VALUES ($1)", ["Ada"]);
  const { rows } = await client.query("SELECT name FROM users");
  expect(rows).toEqual([{ name: "Ada" }]);
  await client.end();
});

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):

import { NetlifyDev } from "@netlify/dev";  // npm i -D @netlify/dev
const netlifyDev = new NetlifyDev({ projectRoot: "./fixtures/my-project" });
await netlifyDev.start();  // sets NETLIFY_DB_URL in the runtime
// ...tests...
await netlifyDev.stop();

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.
    const db = getDatabase({ connectionString: "postgres://..." });
    
    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):

import { drizzle } from "drizzle-orm/netlify-db";
import * as schema from "./schema";
export const db = drizzle({ schema });

Full step-by-step (dump flags, rollback, cleanup): references/migration-from-extension.md and references/legacy-extension.md.

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 database operations — REST branch delete, snapshot restore, any reset — require explicit user confirmation first. The body must not present snapshot restore as a routine production-rollback lever.
  10. Pin: drizzle-orm and drizzle-kit must be installed from @beta — latest lacks the drizzle-orm/netlify-db adapter and will fail. The body may not soften this to a recommendation.

Files

7
52.9 KB

Agent reviews

0

No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.

More from netlify/context-and-tools8

netlify-access-control

Picks the right Netlify site-protection layer and disambiguates the three unrelated "auth" concepts users conflate — app-user login (Netlify Identity), site-load gating (Password Protection / project visibility), and dashboard SAML SSO. Use it when asked to password-protect a site or Deploy Preview,

Scan passed 0
netlify-agent-runner

Run AI agent tasks remotely on Netlify using Claude, Codex, or Gemini. Use when the user wants to run an AI agent on their site, get a second opinion from another model, or delegate development tasks to run remotely against their repo.

Scan passed 0
netlify-ai-gateway

Use Netlify AI Gateway to call OpenAI, Anthropic Claude, Google Gemini, TypeSafe (Jev), or OpenRouter-hosted models (xAI/DeepSeek/Meta/Mistral/Qwen) from Netlify Functions or Edge Functions without managing provider accounts or API keys. Reach for this when adding an AI feature to a Netlify app — a

Scan passed 0
netlify-blobs

Store and retrieve unstructured objects, files, and cache-like state on Netlify with the @netlify/blobs module. Use when persisting user file uploads (images/documents), caching computed output from functions or Background Functions, serving downloadable assets, storing JSON blobs keyed by ID, or se

Scan passed 0
netlify-caching

Cache dynamic and static responses on Netlify's CDN from Functions, Edge Functions, and proxies. Use when you add caching or cache-control headers to a function response, tune cache TTL or stale-while-revalidate, set up the durable cache, vary a cache key by query/header/cookie/country/language, pur

Scan passed 0
netlify-config

Configure Netlify builds and routing via netlify.toml, _redirects, and _headers. Use when setting a build command or publish directory, adding redirects or rewrites or proxies, adding an SPA fallback rewrite, setting custom response headers or basic auth, managing environment variables and secrets,

Scan passed 0
netlify-deploy

Create, configure, and manage Netlify deploys from code — reach for this when setting up Git continuous deployment, running netlify deploy or netlify deploy --prod from the CLI, writing netlify.toml deploy contexts, adding a Deploy to Netlify button, wiring build hooks, configuring Deploy Previews o

Scan passed 0
netlify-edge-functions

Write and configure Netlify Edge Functions — TypeScript/JavaScript handlers running in a Deno runtime at the network edge. Use when adding auth middleware or auth redirects, geolocation or localization logic, A/B testing or personalization, request/response transforms (rewrites/redirects), or edge S

Scan passed 0

Related database skillsscan passed

clickhouse-io

ClickHouse database patterns, query optimization, analytics, and data engineering best practices for high-performance analytical workloads. Use when writing ClickHouse schemas or queries, or when an analytical query is too slow.

Scan passed 0
stripe-projects

Use when the user wants to provision infrastructure or third-party services using Stripe Projects. Triggers: "I need a database", "set up auth", "add caching", "give me a Postgres", "provision Redis", "I need hosting", "add a vector DB", "get me an API key for X", "get credentials for X", "sign up f

Scan passed 0
basin

Build and troubleshoot Cloudflare Basin analytics workflows with Basin Pipelines, Basin Catalog, and Basin SQL. Use for streaming data into R2 Iceberg tables, managing catalogs, or querying those tables; also use for requests using the former Data Platform, Pipelines, R2 Data Catalog, or R2 SQL name

Scan passed 0
deprecation-and-migration

Manages deprecation and migration. Use when removing old systems, APIs, or features. Use when migrating users from one implementation to another. Use when migrating a database schema in production, such as renaming or dropping a column without downtime (expand/contract). Use when deciding whether to

Scan passed 0
firestore-rules-creation

Designs, authors, refactors, and hardens production-grade Cloud Firestore Security Rules (firestore.rules). IMPORTANT: If subagent delegation AND the firestore-rules-author subagent are available in your environment, delegate authoring firestore.rules to the firestore-rules-author subagent. If subag

Scan passed 0
exploring-data-catalog

Full inventory and audit of AWS Glue Data Catalog assets across S3 Tables, Redshift-federated, and remote Iceberg catalogs. Triggers on: inventory the catalog, audit databases, list all tables, catalog overview, data landscape, enumerate catalogs, data inventory, search the catalog. Do NOT use for f

Scan passed 0