Netlify Integration Guide

Technology: netlify · Category: hosting · Last reviewed: 2026-09-24

Source: https://tech-stack.codeamanilabs.org/guide/netlify

Insight:

Netlify is the alternative host to Vercel — similar git-driven deploys and edge functions. Pick it when a project already lives there or needs Netlify-specific features (Forms, Identity, or Netlify Database, whose per-deploy-preview Postgres branching has no Vercel equivalent); otherwise default to Vercel for stack consistency.

███╗   ██╗███████╗████████╗██╗     ██╗███████╗██╗   ██╗
████╗  ██║██╔════╝╚══██╔══╝██║     ██║██╔════╝╚██╗ ██╔╝
██╔██╗ ██║█████╗     ██║   ██║     ██║█████╗   ╚████╔╝
██║╚██╗██║██╔══╝     ██║   ██║     ██║██╔══╝    ╚██╔╝
██║ ╚████║███████╗   ██║   ███████╗██║██║        ██║
╚═╝  ╚═══╝╚══════╝   ╚═╝   ╚══════╝╚═╝╚═╝        ╚═╝

Netlify Integration Guide

Focus: Deploying sites, managing edge functions, and automating Netlify projects from Claude Code using the official Netlify MCP server and netlify-cli.

Overview

Netlify is a platform for hosting web apps with built-in CI/CD, edge functions, forms, and identity. Its official MCP server (launched Feb 2025) lets Claude Code create projects, trigger deploys, manage environment variables, install extensions, and query build logs — all via natural language inside your coding session.

Here is the big picture of how a change reaches your users — once you see the flow, the commands below click into place:

flowchart LR
  A["git push"] --> B["Netlify CI/CD<br/>build"]
  B --> C{"PR or<br/>main?"}
  C -->|"PR"| D["Preview deploy<br/>unique URL"]
  C -->|"main"| E["Production deploy"]
  E --> F["Edge network<br/>CDN"]
  F --> G["Users"]

Platform Primitives

Netlify's capabilities are exposed as framework-agnostic platform primitives rather than framework features. Whatever you build with — Next.js, Astro, Nuxt, Remix, TanStack Start, SvelteKit — the framework's server code is compiled into Netlify Functions and Edge Functions at build time, and every primitive below becomes available without framework-specific plumbing.

That is the whole design argument: you are not waiting for your framework to add support for image optimisation or cache control, because those live a layer below it.

flowchart TD
  A["Your framework<br/>Next · Astro · Nuxt · Remix · TanStack"] --> B["Build"]
  B --> C["Functions<br/>regional Node"]
  B --> D["Edge Functions<br/>Deno at CDN"]
  C --> E[("Database<br/>Postgres")]
  C --> F[("Blobs<br/>key-value")]
  C --> G["AI Gateway"]
  D --> H["Edge Network<br/>cache + Netlify-Vary"]
  C --> H
  H --> I["Users"]
Primitive What it is Section
Functions Regional Node handlers, web-standard Request → Response, 60s sync / 15 min background Serverless Functions
Edge Functions Deno at the CDN node — auth gates, redirects, geo, A/B Edge Function Example
Netlify Database Managed Postgres with per-preview branching and automatic migrations Netlify Database
Blobs Zero-config key-value / object store, callable from Functions and the CLI —
Image CDN On-demand resize + format negotiation via /.netlify/images —
Caching Netlify-CDN-Cache-Control, cache tags, SWR, durable cache Edge Network
AI Gateway Keyless access to OpenAI / Anthropic / Gemini / OpenRouter models AI Gateway
Agent Runners Coding agents run on Netlify, one DB branch + preview per run Agent Runners
Forms HTML form capture with no backend code —
Identity Built-in auth (GoTrue) — codeAmani uses Clerk instead Security

Everything above runs locally. netlify dev emulates the full set — Functions, Edge Functions, Blobs, the database, AI Gateway, redirects, and the Image CDN — and Vite projects get the same through @netlify/vite-plugin without invoking the CLI. For tests, @netlify/dev exposes that emulator as a library. This is the practical reason the local/production gap on Netlify is small: it is the same engine, not a mock.


Official Documentation

Resource URL
Netlify Docs https://docs.netlify.com
Netlify MCP Server https://docs.netlify.com/build/build-with-ai/netlify-mcp-server/
Netlify CLI Reference https://docs.netlify.com/cli/get-started/
Edge Functions https://docs.netlify.com/edge-functions/overview/
Netlify Functions https://docs.netlify.com/functions/overview/
Netlify Database https://docs.netlify.com/build/data-and-storage/netlify-database/
Database: getting started https://docs.netlify.com/build/data-and-storage/netlify-database/getting-started/
Database: API reference https://docs.netlify.com/build/data-and-storage/netlify-database/api/
Database: migrations https://docs.netlify.com/build/data-and-storage/netlify-database/migrations/
Database: CLI reference https://docs.netlify.com/build/data-and-storage/netlify-database/cli/
Database: billing & limits https://docs.netlify.com/build/data-and-storage/netlify-database/billing-and-usage/
AI Gateway https://docs.netlify.com/build/ai-gateway/overview/
AI Gateway: quickstart https://docs.netlify.com/build/ai-gateway/quickstart-for-ai-gateway/
Agent Runners https://docs.netlify.com/build/build-with-ai/agent-runners/overview/
Observability https://docs.netlify.com/manage/monitoring/observability/overview/
Log drains https://docs.netlify.com/manage/monitoring/log-drains/
Security overview https://docs.netlify.com/manage/security/overview/
Secrets Controller https://docs.netlify.com/build/environment-variables/secrets-controller/
Caching (Edge Network) https://docs.netlify.com/build/caching/caching-overview/

MCP Server Setup

Official Netlify MCP Server

The official Netlify MCP server ships as the @netlify/mcp npm package (repo: netlify/netlify-mcp). The older netlify-mcp package name no longer exists — use @netlify/mcp or the hosted remote endpoint.

# Local (stdio) — runs the package via npx
claude mcp add netlify -- npx -y @netlify/mcp

# Remote (hosted) — Netlify's managed endpoint, handles auth via OAuth in the client
npx -y add-mcp https://netlify-mcp.netlify.app/mcp

.mcp.json Configuration

{
  "mcpServers": {
    "netlify": {
      "command": "npx",
      "args": ["-y", "@netlify/mcp"],
      "env": {
        "NETLIFY_AUTH_TOKEN": "${NETLIFY_AUTH_TOKEN}"
      }
    }
  }
}

Requirements: Node.js 22+, a Netlify account with a personal access token (NETLIFY_AUTH_TOKEN). The remote endpoint (https://netlify-mcp.netlify.app/mcp) authenticates over OAuth instead of a token and needs no local Node runtime.

Available MCP Tools

The current server is capability/service based (not one tool per action). Tools group into reader/updater services plus a coding-context helper:

Tool Description
get-netlify-coding-context Fetch Netlify's current best-practice context for a capability (serverless, edge-functions, blobs, image-cdn, forms, db) — call before writing Netlify code
netlify-project-services-reader / …-updater Read / create + configure projects (sites), env vars, and project settings
netlify-deploy-services-reader / …-updater Query deploy status + logs; trigger and manage deploys
netlify-extension-services-reader / …-updater Discover and install/configure Netlify extensions
netlify-team-services-reader Team, membership, and billing info
netlify-user-services-reader Authenticated user info

The server also exposes documentation skills for each primitive: netlify-functions, netlify-edge-functions, netlify-blobs, netlify-db, netlify-image-cdn, netlify-forms, netlify-config, netlify-cli-and-deploy, netlify-caching, and netlify-ai-gateway.


CLI Integration

Installation

npm install -g netlify-cli

Current major is netlify-cli v27 (requires Node.js 22+). Verify with netlify --version; upgrade with npm install -g netlify-cli@latest.

Authentication

# Interactive login (browser OAuth)
netlify login

# Token-based (for CI)
export NETLIFY_AUTH_TOKEN=...

Key Commands

# Initialize / link a site
netlify init
netlify link

# Deploy (draft)
netlify deploy

# Deploy to production
netlify deploy --prod

# Open site dashboard
netlify open

# Run dev server (with Functions/Edge Functions)
netlify dev

# Invoke a serverless function locally
netlify functions:invoke my-function --payload '{"key":"value"}'

# Manage environment variables
netlify env:set MY_VAR "my-value"
netlify env:list
netlify env:unset MY_VAR

# View build and deploy logs
netlify status
netlify logs:deploy

# Pull environment to local file
netlify env:import .env

Environment Variables

# Required for MCP and CLI
NETLIFY_AUTH_TOKEN=...          # From app.netlify.com/user/applications

# Optional project-specific
NETLIFY_SITE_ID=...             # From site settings or `netlify link`

# Variables set for your deployed site
DATABASE_URL=postgresql://...     # only if you bring your own database
NEXT_PUBLIC_API_URL=https://api.example.com

NETLIFY_DB_URL is injected for you. If the project uses Netlify Database, Netlify sets this connection string automatically in builds, agent runners, functions, and edge functions, resolved to the correct database branch for the current deploy. Never set it by hand, never commit it, and never pin it in a secrets vault — a hard-coded value will point at the wrong branch (or a rotated credential). Read it via Netlify.env.get("NETLIFY_DB_URL"), or better, call getConnectionString() from @netlify/database.

Inside function code, read variables via the global Netlify.env — not process.env. Netlify injects a web-standard Netlify object into both serverless and edge functions:

const key = Netlify.env.get("DARAJA_CONSUMER_SECRET"); // server-side only

Store all secrets (Stripe signing secret, Daraja consumer key/secret) as env vars — never in code or in netlify.toml, which is committed.


Automation Workflows

Claude Code Hook: Lint Before Deploy

.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -q 'netlify deploy --prod'; then npm run lint && npm run test; fi"
          }
        ]
      }
    ]
  }
}

Slash Command: Deploy to Netlify

.claude/commands/netlify-deploy.md:

Deploy the current project to Netlify production.

Run the following steps:
1. Use Bash to run `npm run build` and confirm it succeeds
2. Use Bash to run `netlify deploy --prod --dir=dist` (adjust dir as needed)
3. Report the deploy URL and any warnings
4. If deploy fails, use Bash to run `netlify logs:deploy` and report errors

Usage: /project:netlify-deploy

GitHub Actions: Preview + Production Deploy

# .github/workflows/netlify.yml
name: Netlify Deploy
on:
  pull_request:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '22' }
      - run: npm ci && npm run build

      - name: Deploy Preview
        if: github.event_name == 'pull_request'
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
        run: |
          npm install -g netlify-cli
          deploy_url=$(netlify deploy --dir=dist --json | jq -r '.deploy_url')
          echo "Preview: $deploy_url"

      - name: Deploy Production
        if: github.ref == 'refs/heads/main'
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
        run: netlify deploy --prod --dir=dist

Edge Function Example

Edge functions are great for running logic close to the user before a request reaches your site — here is how the auth example below fits into a request:

sequenceDiagram
  participant U as "User"
  participant E as "Edge Function"
  participant S as "Site"
  U->>E: "Request /api/*"
  E->>E: "Check Authorization header"
  alt "Missing or invalid token"
    E-->>U: "401 Unauthorized"
  else "Valid Bearer token"
    E->>S: "Forward via context.next"
    S-->>U: "Response"
  end

netlify/edge-functions/auth.ts:

import type { Config, Context } from "@netlify/edge-functions";

export default async function handler(req: Request, context: Context) {
  const token = req.headers.get("Authorization");
  if (!token || !token.startsWith("Bearer ")) {
    return new Response("Unauthorized", { status: 401 });
  }
  return context.next();
}

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

Serverless Functions

Edge functions run light logic at the CDN edge; serverless functions are full Node.js handlers for heavier work — talking to a database, calling the Daraja STK Push API, signing tokens, or handling M-Pesa callbacks. They live in netlify/functions/ and use the same web-standard Request → Response signature as edge functions, but run in a regional Node runtime with the full npm ecosystem available.

Function Example

netlify/functions/stk-push.mts:

import type { Config, Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  if (req.method !== "POST") {
    return new Response("Method Not Allowed", { status: 405 });
  }

  const { phone, amount } = await req.json();

  // Heavy lifting belongs here: DB writes, Daraja token + STK Push, etc.
  // const token = await getDarajaToken();
  // const result = await initiateStkPush({ phone, amount, token });

  return new Response(JSON.stringify({ ok: true, phone, amount }), {
    status: 200,
    headers: { "content-type": "application/json" },
  });
};

export const config: Config = {
  path: "/api/stk-push",
};

Install the types once: npm install @netlify/functions. The .mts extension opts into ES modules; netlify/functions/stk-push.mts, netlify/functions/stk-push/stk-push.mts, and netlify/functions/stk-push/index.mts all define a function named stk-push.

How to Invoke

[functions] Config

The default directory is netlify/functions. esbuild is now the default bundler, so this block is optional — set it only to change the directory or opt out:

[functions]
  directory = "netlify/functions"
  node_bundler = "esbuild"

Timeouts & long-running work

Function limits are fixed and uniform across all plans (they are not configurable — the old 10s-free / 26s-Pro tiering is gone):

Function type Execution limit How to opt in
Synchronous (default) 60 seconds default
Background 15 minutes -background suffix on the file/dir name; returns 202 immediately, response body ignored
Scheduled (cron) 30 seconds export const config = { schedule: "@hourly" } — runs on published deploys only, UTC cron

Background and scheduled functions are the escape hatch for work that exceeds 60s (e.g. Daraja STK Push polling, batch AI inference): persist the result to Netlify Blobs or Postgres and read it back from a synchronous function.

Gotcha: Edge vs Serverless

Reach for the right runtime — they are not interchangeable:

flowchart TD
  A["Incoming request"] --> B{"Needs Node APIs<br/>or npm packages?"}
  B -->|"Yes"| C["Serverless function<br/>netlify/functions"]
  B -->|"No · just rewrite<br/>headers or geo"| D{"Must run<br/>at the edge?"}
  D -->|"Yes · low latency"| E["Edge function<br/>netlify/edge-functions"]
  D -->|"No"| C
  C --> F["Regional Node runtime"]
  E --> G["Deno runtime<br/>at CDN edge"]

Edge functions run on Deno at the CDN edge (no full Node API, no node_modules bundling), so M-Pesa/Daraja calls, raw Postgres drivers like pg, and Node-only SDKs belong in serverless functions. Use edge functions only for fast request manipulation (auth gates, redirects, geo, A/B) close to the user.

Exception — @netlify/database. getDatabase() picks a connector suited to its runtime, so it does work in edge functions where a raw pg client would not. A lightweight lookup at the edge (feature flag, tenant routing, session check) is therefore fair game; keep heavier transactional work, which needs db.pool, in serverless functions. See Netlify Database below.


Netlify Database

Netlify Database is a fully managed Postgres database built into the platform. Netlify provisions it, applies migrations, and branches it for you. It is built on Neon (serverless Postgres) under the hood, but the setup and management surface is entirely Netlify's — you never interact with Neon directly, and there is no separate Neon account to link.

The headline feature is database branching. Production deploys are the only deploys allowed to touch the production database; every deploy preview gets its own isolated branch, seeded with a copy of production data taken when the preview is first created. Schema changes and data mutations made in a preview never reach production, and a bad branch can simply be reset.

flowchart TD
  A["git push"] --> B{"Production<br/>or PR?"}
  B -->|"main branch"| C["Production deploy"]
  B -->|"pull request"| D["Deploy preview"]
  C --> E["Migrations applied<br/>just before publish"]
  E --> F[("Production<br/>database")]
  D --> G["New DB branch<br/>copy of prod data"]
  G --> H["Migrations applied<br/>before preview is live"]
  H --> I[("Preview branch<br/>isolated")]
  I -.->|"reset or discard<br/>no prod impact"| G

This is the same safety model Deploy Previews gave your code, extended to your data — which is also why each Agent Runner run gets its own branch: an agent can rewrite the schema and delete rows without any risk to the live site.

When to reach for it

Situation Pick
App already hosted on Netlify, wants Postgres with zero setup Netlify Database
Need per-preview isolated data for PRs or agent runs Netlify Database (branching is the differentiator)
Need row-level security tied to an auth provider, storage, realtime Supabase (see supabase/)
Need Postgres decoupled from the host, or used from Vercel Neon directly (see neon/)
Key/value or unstructured blobs, not relational data Netlify Blobs

Prerequisites

Setup

The fastest path on an existing project is the interactive initialiser:

netlify database init          # interactive
netlify database init --yes    # non-interactive (CI / AI agents)

It installs @netlify/database, lets you pick a query style (raw SQL or Drizzle ORM), scaffolds a starter migration, optionally seeds sample data, and verifies the database is reachable.

To wire it up manually instead:

npm install @netlify/database

Then write your first migration under netlify/database/migrations/, add a function that queries it, and deploy — Netlify provisions the database and applies the migration as part of the deploy lifecycle.

Gotcha — provisioning is triggered by the package. If @netlify/database is not installed in the project, Netlify will not auto-provision a database. Either install it, or create the database by hand in the UI under Data & Storage → Database.

Querying with @netlify/database

getDatabase() returns a client already configured for wherever the code is running — it selects a different underlying connector for builds and long-running servers than it does for Functions and Edge Functions. That is why it works in an edge function, where a raw pg client would not.

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

const db = getDatabase();

// Tagged template — interpolated values are ALWAYS parameterized (no SQL injection)
const userId = 42;
const users = await db.sql`SELECT * FROM users WHERE id = ${userId}`;

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

// Stream large result sets instead of buffering them
for await (const chunk of db.sql`SELECT * FROM orders`.chunked(100)) {
  console.log(`Processing ${chunk.length} rows`);
}

The sql tagged template is based on Waddler. A SQLTemplate is thenable and also exposes execute(), stream(), chunked(size), and toSQL() (returns the SQL string + params without running it — handy for debugging).

Helpers on db.sql:

Helper Purpose
sql.identifier(v) Safely quote a dynamic table/column name
sql.values(rows) Build a bulk VALUES list from a 2-D array
sql.default The SQL DEFAULT keyword, for inserts
sql.raw(v) Inject an unparameterized fragment — bypasses injection protection
sql.unsafe(q, params?) Run a raw query string with positional $1 params

Security: sql.raw() is the one escape hatch that will happily interpolate attacker-controlled input into SQL. Never pass user input to it — use sql.identifier() for dynamic table/column names and ordinary ${} interpolation for values.

Transactions

db.sql does not pin a connection, so BEGIN/COMMIT must run on a single client from the pool. db.pool is a standard pg.Pool:

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

const db = getDatabase();
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();   // always release, or the pool leaks connections
}

Migrations

Migrations live in netlify/database/migrations/ — either as flat .sql files or as one subdirectory per migration containing migration.sql:

netlify/database/migrations/
├── 20260301143000_create_users.sql
├── 20260318091500_add_posts.sql
└── 20260425103000_create_comments.sql

Names must match <number>_<slug>: digits setting the order, then a slug of lowercase letters, digits, hyphens, and underscores. Migrations are sorted lexicographically, which is why timestamp prefixes are safer than hand-numbered ones once more than one person (or agent) is adding them.

Netlify applies them automatically:

Because they run immediately before the new code goes live, the window where old code meets a new schema is small — but it is not zero. Write backwards-compatible migrations. For breaking changes use the expand-and-contract pattern: add the new column alongside the old and write to both, backfill, then drop the old one in a later migration.

Gotcha — the directory is magic. Anything in netlify/database/migrations/ is auto-applied. If you bring your own migration tool (Prisma Migrate, Atlas, raw scripts), point it at a different directory or Netlify will run those files too.

CLI reference

Every command takes --json for structured output, which is what makes this surface agent-friendly:

netlify database status                    # enabled? package installed? applied + pending migrations
netlify database status --branch my-feat   # target a remote branch instead of local
netlify database status --show-credentials # include the full connection string

netlify database connect                             # interactive SQL REPL
netlify database connect --query "SELECT * FROM users"
netlify database connect --json                      # print connection details as JSON

netlify database migrations new --description "add users table" --scheme timestamp
netlify database migrations apply            # apply pending migrations locally
netlify database migrations apply --to 0003  # apply up to a specific migration
netlify database migrations pull             # overwrite local files from a remote branch
netlify database migrations reset            # delete local, unapplied migration files

netlify database reset                       # wipe the LOCAL dev database only

netlify database reset and netlify database migrations reset only ever touch the local development database — they cannot damage production or a preview branch.

Local development

netlify dev starts a real Postgres-compatible database on your machine and shuts it down with the dev server — there is no Docker container or local Postgres install to manage:

netlify dev
netlify database migrations apply     # the local DB does NOT auto-apply migrations

Vite projects can get the same emulated environment without netlify dev by adding @netlify/vite-plugin. Both use the same engine, so data and migrations are interchangeable between them.

Connect any Postgres tool (psql, TablePlus, DataGrip) while it runs:

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

For integration tests, @netlify/database-dev exposes the emulator as a library (new NetlifyDB() → start() / applyMigrations(dir) / stop()), defaulting to in-memory on a random port. Use @netlify/dev when a test needs the whole Netlify runtime, not just the database.

Differences from production worth knowing: it is a single local process (not a load-testing target), branching is a deploy-time concept so locally there is exactly one database, and auto-scale/sleep settings do not apply.

Bringing your own driver or ORM

The connection string is available two ways — getConnectionString() from @netlify/database, or the NETLIFY_DB_URL environment variable, which is injected into builds, agent runners, functions, and edge functions.

import { getConnectionString } from "@netlify/database";
import pg from "pg";

const pool = new pg.Pool({ connectionString: getConnectionString() });
const { rows } = await pool.query("SELECT * FROM users");

Drizzle ORM has a native adapter. Install from the beta tag (these become 1.0 shortly and carry a better migration format), and point Drizzle Kit's output at Netlify's migrations directory or the automatic runner will never see them:

npm install @netlify/database drizzle-orm@beta
npm install -D drizzle-kit@beta
// drizzle.config.ts
export default defineConfig({
  dialect: "postgresql",
  schema: "./db/schema.ts",
  out: "netlify/database/migrations",   // ← not the default "drizzle"
});
// db/index.ts — the connection is configured automatically
import { drizzle } from "drizzle-orm/netlify-db";
import * as schema from "./schema";

export const db = drizzle({ schema });

Scaling, sleep, and cost

Compute is metered in database compute units — one unit = 25% of a vCPU + 1 GB RAM. Auto-scale sets a min/max the database moves between; sleep on inactivity (default: after 5 minutes idle) pauses it so an idle database stops consuming credits.

Meter Cost
Database compute 10 credits per compute unit
Database bandwidth (data out) 20 credits per GB
Storage Free until 1 July 2026, then billed at rates announced in advance

Selected plan limits (the full table is in the billing docs):

Limit Free Personal Pro Enterprise
Databases per account 3 5 50 500
Branches per database 20 100 300 450
Max compute units 1 4 16 32
Max sleep-on-inactivity 5 min 5 min Always on Always on
Storage per database 5 GB 100 GB 100 GB No limit
Bandwidth per billing period 5 GB 100 GB 100 GB No limit

REST API

All endpoints are site-scoped, rooted at https://api.netlify.com/api/v1, and authenticate via OAuth 2:

Endpoint Purpose
POST / GET /sites/{site_id}/database Create or read the database (returns connection_string)
POST /sites/{site_id}/database/branch Create a branch for a deploy_id
GET / DELETE /sites/{site_id}/database/branch/{deploy_id} Read or delete a deploy's branch
POST /sites/{site_id}/database/snapshot Point-in-time snapshot (defaults to production)
GET /sites/{site_id}/database/snapshots List snapshots
POST /sites/{site_id}/database/snapshot/{id}/restore Restore a snapshot to a branch

codeAmani notes


Edge Network

Every deploy is published to Netlify's global edge network — a CDN with atomic deploys: a deploy either goes live completely or not at all, and publishing automatically invalidates the cache for changed content. There is no manual purge step in the normal workflow.

Cache-control headers

Netlify honours three cache-control fields, most specific wins:

Header Applies to Precedence
Netlify-CDN-Cache-Control Netlify's CDN only Highest
CDN-Cache-Control Any CDN that supports it Middle
Cache-Control Browsers and CDNs Lowest (fallback)

Use Netlify-CDN-Cache-Control to cache aggressively at the edge while keeping browsers on a short leash:

return new Response(body, {
  headers: {
    "Netlify-CDN-Cache-Control": "public, durable, s-maxage=86400, stale-while-revalidate=604800",
    "Cache-Control": "public, max-age=0, must-revalidate",   // browser always revalidates
  },
});

Two directives are worth knowing:

Cache key variation with Netlify-Vary

Netlify-Vary controls what makes a request a different cache entry — finer-grained than the standard Vary header:

Netlify-Vary: query=page|per_page, country=ke|us, language=en|sw, cookie=is_logged_in
Instruction Varies on
query / query=a|b All query params, or only the listed ones
header=X|Y Named request headers
language=en|sw Accept-Language, honouring quality values
country=ke|us Geo-IP country
cookie=key Named cookies

Rules that bite:

High-Performance Edge (Enterprise)

An Enterprise-only upgrade to the standard network: 70+ global points of presence with dynamic PoP adjustment, a dedicated Site Reliability Engineer, 24×7×365 incident response, and proactive DDoS protection. Netlify quotes up to 50% faster than their standard network (and up to 300% faster than a traditional monolith). Relevant only if a client is on Enterprise — the standard edge is what codeAmani projects actually run on.

codeAmani notes


AI Gateway

AI Gateway lets project code call OpenAI, Anthropic, Google Gemini, OpenRouter, and TypeSafe AI models with no API keys of your own. Netlify injects both the API key and a provider-specific base URL into every compute context, proxies the call, and bills the token usage to your Netlify credits.

How it works

When a Function or Edge Function initialises, Netlify sets these — but never overrides a value you have already set at project or team level:

Provider Injected variables
OpenAI OPENAI_API_KEY, OPENAI_BASE_URL
Anthropic ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL
Google Gemini GEMINI_API_KEY, GOOGLE_GEMINI_BASE_URL
OpenRouter OPENROUTER_API_KEY, OPENROUTER_BASE_URL
TypeSafe AI TYPESAFE_API_KEY, TYPESAFE_BASE_URL

NETLIFY_AI_GATEWAY_KEY and NETLIFY_AI_GATEWAY_URL are always injected and never collide with the above — use them when you deliberately mix your own keys with Netlify's, or want to be explicit about which path a call takes.

Because the official SDKs read these variables by default, the code is just the SDK with no configuration:

// netlify/functions/summarise.ts
import Anthropic from "@anthropic-ai/sdk";
import type { Config, Context } from "@netlify/functions";

// No apiKey argument — ANTHROPIC_API_KEY and ANTHROPIC_BASE_URL are injected.
const anthropic = new Anthropic();

export default async (req: Request, context: Context) => {
  const { text } = await req.json();

  const message = await anthropic.messages.create({
    model: "claude-sonnet-4-5-20250929",
    max_tokens: 1024,
    messages: [{ role: "user", content: `Summarise in two sentences:\n\n${text}` }],
  });

  return Response.json({ summary: message.content });
};

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

Framework server code (Next.js, Astro, Nuxt, TanStack Start, …) is packaged into Functions at build time, so the same variables are available there with no extra setup. The OpenRouter SDK needs v1.2.43 or later.

Requirements and gotchas

Cost and rate limits

Token usage is converted to USD at published provider rates, then to credits: $1 USD of model usage = 180 credits. Rate limits are per minute, per team, across all projects:

Plan Credits / minute
Free 90
Personal 450
Pro 1,800
Enterprise 9,000

Current limitations: context window capped at 200k tokens; Anthropic prompt caching is limited to the default 5-minute ephemeral cache; Gemini explicit context caching is unsupported; request headers are not passed through (so header-gated experimental features are unavailable); no batch inference; no OpenAI priority processing.

codeAmani notes


Agent Runners

Agent Runners run a coding agent inside Netlify's infrastructure, prompted from the dashboard (or a phone) rather than a local terminal. The agent gets the project's repo, environment variables, build settings, and deploy pipeline, and ships its work to a deploy preview for review.

Supported agents: Claude Code, OpenAI Codex, Google Gemini, and OpenCode. Each run's model and reasoning effort are configurable per agent, and those settings are a personal preference — they do not apply team-wide. OpenCode is served via OpenRouter and Netlify routes only to providers with a Zero Data Retention policy; the other three run on their vendors' own models.

Available on credit-based plans (Free, Personal, Pro); Enterprise teams go through their account manager.

flowchart LR
  A["Prompt from<br/>dashboard or phone"] --> B["Agent run<br/>Claude Code / Codex / Gemini / OpenCode"]
  B --> C["Own database branch<br/>+ deploy preview"]
  C --> D{"Review"}
  D -->|"Approve"| E["Publish to production"]
  D -->|"Reject"| F["Discard — production untouched"]

The pairing with Netlify Database is the point: every run gets its own database branch, so an agent can add tables, write migrations, and mutate data with no path to production until a human publishes.

Good fits: well-defined backlog items, broken links and redirects, copy and content updates from non-engineers, landing/404/maintenance pages, scaffolding a platform primitive, and on-the-go fixes. Poor fits: anything needing deep local iteration, a debugger, or judgement about architecture.

codeAmani notes


Observability

Analytics & metrics → Observability gives near-real-time visibility into production: requests, bandwidth, runtime behaviour, Functions, and Edge Functions. It answers "what is actually happening on the site right now", and it replaces Function Metrics on credit-based plans.

Retention depends on plan:

Plan Time window
Free / Personal Past 24 hours
Pro Past 7 days
Enterprise Past 30 days

Quick actions apply pre-built filter sets across three axes:

Axis Answers
Traffic Top URLs, top 404s, top URLs with errors, client types, top AI-crawler searches, browser-only traffic
Bandwidth Bandwidth by URL, by client type, by content type
Compute Most-invoked functions, slowest URLs, which clients drive function usage

The rest of the monitoring surface sits alongside it:

Tool Use
Log drains Stream deploy/function/traffic logs to an external sink (Datadog, S3, …)
Logs Per-deploy and per-function logs in the dashboard
Real User Monitoring Field performance data from actual visitors
Lighthouse Scores generated as part of the build
Notifications Deploy/build events to Slack, email, or webhooks
Split testing Branch-based A/B at the edge

Observability does not show credit usage. Spend lives under billing — monitor usage for credit-based plans. Two different questions, two different screens.

codeAmani notes


Security

Netlify's security surface splits into three areas: access to your sites, access to Netlify itself, and platform-level protections.

Secure access to sites

Control What it does
Password protection Single shared password on a site or deploy preview
Project visibility Public / private project listings
Firewall traffic rules Allow or block by IP address or geography
Rate limiting rules Per-visitor request caps (use these in front of AI Gateway functions)
Web Application Firewall Managed rule sets against common attack traffic
Role-based access control Per-role permissions on site access
Basic auth via custom headers Credentials enforced at the edge

Secure access to Netlify

SAML SSO through an identity provider, SCIM directory sync for provisioning, enforced 2FA, role-based access control, and the Secrets Controller — an enhanced policy for the most sensitive environment variables that blocks them from being exposed in builds and adds secret scanning. Netlify also scans deploys for leaked secrets and can fail the build when it finds one.

Platform protections

Proactive DDoS monitoring with automatic detection, rate limiting, and client blocking; global load balancing; AES-256 (or stronger) encryption at rest; TLS 1.2+ in transit; Content Security Policy support; log drains; and Private Connectivity for Enterprise. Enterprise teams also get a Security Scorecard that grades the team's posture.

Compliance

SOC 2 Type 2 and ISO 27001 reports, PCI DSS, GDPR and CCPA — current details live at the Netlify trust center.

Careful — platform PCI DSS does not extend to Netlify Database. The hosting platform carries PCI DSS, but Database Services are explicitly not PCI-DSS certified and are not HIPAA-eligible by default. Hosting a payment page on Netlify is fine; writing cardholder data into Netlify Database is not. See the Netlify Database compliance notes.

codeAmani notes


Common Use Cases

Use Case Approach
Deploy on merge GitHub Actions + netlify deploy --prod
Preview URLs for PRs netlify deploy (no --prod) in PR workflow
Edge function auth netlify/edge-functions/ directory
Form submissions Netlify Forms + MCP netlify-project-services-reader
Environment management MCP netlify-project-services-updater / CLI env:set
Extension management MCP netlify-extension-services-updater
Relational data Netlify Database — netlify database init, query via @netlify/database
Safe schema changes Migrations in netlify/database/migrations/, auto-applied per deploy
Isolated data per PR Deploy previews get their own DB branch automatically — no setup
Local DB for dev/tests netlify dev (bundled Postgres) or @netlify/database-dev in tests
LLM calls without keys AI Gateway — official SDK with no apiKey, billed to Netlify credits
Agent-driven small fixes Agent Runners from the dashboard; each run gets its own DB branch + preview
Fast pages on 2G/3G Netlify-CDN-Cache-Control: durable, stale-while-revalidate=...
Per-country cached pages Netlify-Vary: country=ke|us instead of uncached personalisation
Find slow / erroring routes Observability → Quick actions → Compute or Traffic insights
Cap per-visitor AI spend Rate limiting rules on the function that calls AI Gateway

Troubleshooting

Issue Fix
netlify: command not found npm install -g netlify-cli
Not linked to a site Run netlify link in project root
Build fails in CI Check NETLIFY_AUTH_TOKEN and NETLIFY_SITE_ID secrets
Edge function not triggering Verify config.path matches the route
Function hits the 60s timeout Timeout is fixed (not configurable) — move long work to a background (-background) or scheduled function
No database provisioned on deploy @netlify/database must be installed, or create it manually under Data & Storage → Database
netlify database command not found Needs Netlify CLI ≥ 26.0.0 and Node ≥ 20.12.2 — npm install -g netlify-cli
Drizzle migrations never run Set out: "netlify/database/migrations" in drizzle.config.ts (default drizzle dir is ignored)
Local queries hit an empty schema The local DB does not auto-migrate — run netlify database migrations apply
First request after idle is slow Sleep-on-inactivity (5 min default); Pro/Enterprise can set the database always-on
AI Gateway vars missing / OPENAI_BASE_URL undefined Needs a credit-based plan and at least one production deploy
AI calls bypass the Gateway unexpectedly You set that provider's key yourself — Netlify never overrides it; unset it or use NETLIFY_AI_GATEWAY_*
AI Gateway 429s Team-wide per-minute credit limit (Free 90 / Personal 450 / Pro 1,800); add rate-limiting rules
Netlify-Vary seems ignored First response cached for that URL wins; also unsupported under basic auth and On-demand Builders
Stale content after deploy Atomic deploys auto-invalidate — check for a manual durable policy or an upstream CDN (Cloudflare)
Observability data has vanished Retention is 24h on Free/Personal, 7d Pro, 30d Enterprise — use log drains to keep history

netlify.toml example:

[build]
  command = "npm run build"
  publish = "dist"

[functions]
  node_bundler = "esbuild"

[[edge_functions]]
  path = "/api/*"
  function = "auth"

Official docs: