CommunityCodierung & Entwicklunggithub.com

neondatabase/neon-functions

>-

Was ist neon-functions?

neon-functions is a Claude Code agent skill that >-.

Funktioniert mitClaude Code~Codex CLI~Cursor
npx skills add https://github.com/neondatabase/agent-skills/tree/main/skills/neon-functions

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

FIRST: Use the parent neon skill for a Neon overview, getting started with Neon, Neon development best practices, and more.

If the neon skill is not installed, fetch it from https://neon.com/docs/ai/skills/neon/SKILL.md or install it with:

npx skills add neondatabase/agent-skills --skill neon

Neon Functions

This is a public beta feature, currently available in us-east-2 and eu-central-1.

Neon Functions are long-running Node.js HTTP handlers deployed onto a Neon branch. Each function gets a public HTTPS URL, runs in the same region as your database, and — if the branch has Postgres — gets DATABASE_URL injected automatically. You deploy and manage them through the same Neon CLI, neon.ts, and API you already use.

Use this skill to help the user define, run locally, deploy, and manage functions next to their database. Deliver a deployed function with its invocation URL, a working local neon dev loop, or a precise answer from the official Neon docs.

When to Use

Reach for Neon Functions when the workload is a request/response handler that benefits from staying alive and staying close to the data:

  • Long-running request/response flows that outlast lambda-style limits. Agents that make several LLM calls and tool invocations per request, or image/video generation, routinely blow past the ~10–60s execution caps and short streaming windows of traditional serverless functions. Neon Functions are long-running: the handler just needs to start responding within 15 minutes, and an open stream stays alive as long as bytes keep flowing. That's enough headroom for real agent workloads.
  • Stateful streaming without bolting on Redis. Because a function stays alive across a request, it can host an SSE endpoint or a WebSocket server and hold the connection open in-process — no external state store (Redis, etc.) needed just to keep a stream coherent. Module-scope state (a pg pool, an in-memory counter) persists across requests on the same isolate.
  • Compute that must sit next to Postgres. The function runs in the same region as the branch's database, so there are no cross-region round trips on every query. DATABASE_URL is injected for you.
  • A backend that branches with your data. Each branch runs its own version of the function at its own URL, against its own isolated database (and storage, and gateway) state. Preview deployments, CI, and dev environments each get a self-contained backend — deploying to a child never affects the parent.
  • Webhooks, bots, and post-response work. Webhook handlers that fan out into multiple DB writes, Discord/WebSocket bots, and fire-and-forget follow-ups via waitUntil (analytics, audit logs) all fit.
  • Recurring HTTP work. A Function Trigger POSTs to the function on a cron (type: "schedule"). Same fetch handler, same 15-minute time-to-first-byte limit. See Function Triggers.

If the workload is a pure static site, or something that must run outside the supported regions (us-east-2, eu-central-1) today, this isn't the right tool yet (see Timeouts and Runtime Limits and Availability).

What It Does

  • Long-running & serverless — Built for WebSocket servers (see WebSocket Servers), SSE endpoints (see Server-Sent Events (SSE)), long agent HTTP streams, and APIs. Still scales to zero when idle.
  • Web-standard handler — A function is any default export with a fetch(request) method returning a Response (Workers/WinterTC-compatible). A Hono app exports exactly that shape, so export default app just works. Runs on Node.js 24, so all Node APIs are available.
  • Close to your database — Runs in the branch's region; DATABASE_URL injected automatically when the branch has Postgres.
  • Branchable — Each branch runs its own function version at its own URL against its own isolated state.
  • Same CLI/API — Deploy and manage via neon, neon.ts, or the Neon API.
  • Function Triggers — Neon POSTs to the function on a cron. See Function Triggers.

Availability

Check this precondition before setting anything up: Neon Functions is a public beta feature currently available in us-east-2 and eu-central-1. Confirm the user's Neon project is in one of these regions. Functions usage isn't billed during the public beta.

Architecture: Where Functions Fit

Neon (Functions included) is backend primitives, not full-stack app hosting. Host your app on Vercel (or Netlify, or another frontend/app host); Functions are the long-running, stateful slice of your backend that lives next to your data. They compose with that platform in two ways:

  • Add a Function to a full-stack app. Your Next.js / TanStack Start app on Vercel (or Netlify) owns UI, auth (e.g. Neon Auth), and talks directly to Lakebase Postgres and Object Storage. When one workload outgrows the host's short serverless limits — a WebSocket or SSE server, or a long-running agent that would time out — move just that piece onto a Neon Function. (See Functions as an Agent Backend for the client-direct pattern.)
  • Run the whole backend control plane on Functions. Especially when the frontend is client-only — TanStack Router, React Router in client mode, and similar SPAs hosted on Vercel or Netlify — the client calls Functions directly. Build REST APIs and request/response agents, host MCP servers, and run anything stateful or that belongs close to Postgres and Object Storage.

Either way, secure a Function like any standalone REST API: verify a JWT or API key at the top of the handler (see the WARNING under Functions as an Agent Backend). Because a Function is just your backend, you can move pieces between your host and Neon — relocate an agent or a stateful WebSocket server onto a Function when it needs more runtime, and back if needed.

Setup

Functions are declared in neon.ts (see the neon skill for the branch-first workflow and neon.ts basics). Add @neon/config and declare functions under preview.functions, keyed by slug:

// neon.ts
import { defineConfig } from "@neon/config/v1";

export default defineConfig({
  preview: {
    functions: {
      todos: {
        // slug: ^[a-z0-9]{1,20}$ — lowercase letters/digits, no hyphens
        name: "todo api", // display label only
        source: "src/index.ts", // entry file, relative to neon.ts
      },
    },
  },
});

The slug is the function's permanent identity (it appears in the invocation URL and CLI commands) and can't be changed after the first deploy. Use name for a human-readable label.

A minimal function — a Hono app that queries the branch's Postgres via the injected DATABASE_URL:

// src/index.ts
import { Hono } from "hono";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { parseEnv } from "@neon/env";
import { attachDatabasePool } from "@neon/functions";
import config from "../neon";
import { todos } from "./db/schema";

const env = parseEnv(config);
const pool = new Pool({ connectionString: env.postgres.databaseUrl, max: 5 });
attachDatabasePool(pool);
const db = drizzle(pool);

const app = new Hono();
app.get("/", (c) => c.text("Neon + Hono + Drizzle"));
app.post("/todos", async (c) => {
  const { text } = await c.req.json<{ text: string }>();
  const [row] = await db.insert(todos).values({ text }).returning();
  return c.json(row, 201);
});
app.get("/todos", async (c) => c.json(await db.select().from(todos)));

export default app;

Create the pg pool at module scope (reused across requests on the same isolate) and keep max small (e.g. 5), since each isolate keeps its own pool. Call attachDatabasePool(pool) so an idle disconnect is not an uncaughtException — see Connecting to Postgres.

parseEnv(config) requires every variable the config implies. A function that only talks to Postgres over the pooled URL can scope it to just that key — parseEnv then validates and returns only what you asked for (the keys autocomplete from your neon.ts):

const { postgres } = parseEnv(config, ["DATABASE_URL"]); // not the unpooled URL, auth, etc.
const pool = new Pool({ connectionString: postgres.databaseUrl, max: 5 });
attachDatabasePool(pool);

Develop Locally and Deploy

neon dev      # serves every function in neon.ts with hot reload; injects DATABASE_URL & friends
neon deploy --env <file>   # preferred full deploy from neon.ts; --env is the file Function env is read from

Keep .env or .env.local up to date with every key under preview.functions.*.env. neon env pull writes Neon-managed vars only; add Function secrets to that file, then pass it as --env. neon deploy --env <file> loads that file into process.env each time, then uploads those values. A missing value is undefined and defineConfig throws. Omit the key from neon.ts if you do not want to write it. Never coerce a missing process.env value to an empty string (that uploads "" and deletes the live key). An empty assignment (KEY=) is also "". Use process.env.X! when TypeScript needs an assertion.

To deploy a single function without applying neon.ts: neon functions deploy <slug> --src src/index.ts (--src takes either the entry file or a directory containing index.ts, index.mjs, or index.js). That command's --env is KEY=VALUE (repeatable), not a file path. Use it for a targeted env update. Retrieve the public URL with neon functions get <slug> (the invocation_url field, of the form https://<branch_id>-<slug>.compute.<cell>.us-east-2.aws.neon.tech). Manage with neon functions list|get|delete.

When neon checkout creates a new branch and a neon.ts is present, it applies the policy automatically. That create-apply does not load --env. If Function env reads process.env, run neon deploy --env <file> after checkout (add --update-existing if checkout already created the branch). Checking out an existing branch does not re-deploy; run neon deploy --env <file> explicitly.

Neon Infrastructure as Code (neon.ts)

The preview.functions block from Setup is part of neon.ts, Neon's infrastructure-as-code file — one TypeScript file declares every function (its source, display name, and env) alongside any other branch services, in version control (see the neon skill for the full reference). Treat it like Terraform for your branch:

neon config status   # print the branch's live config (deployed functions)
neon config plan     # dry-run diff of what apply would change
neon config apply --env <file>  # bundle + deploy the declared functions  (neon deploy is an alias; pass --env when Function env reads process.env)

Functions are branch-scoped: each branch runs its own deployment at its own URL. When a neon.ts is present, neon checkout applies the policy as it creates a branch. That create-apply does not load --env. If Function env reads process.env, run neon deploy --env <file> after checkout. Checking out an existing branch doesn't redeploy — run neon deploy --env <file> to apply changes.

Per-branch deploy tuning (e.g. runtime) lives in the branch closure, keyed by slug, so it can vary by branch without changing which functions exist:

export default defineConfig({
  preview: {
    functions: { todos: { name: "todo api", source: "src/index.ts" } },
  },
  branch: (branch) => ({
    preview: { functions: { todos: { runtime: "nodejs24" } } },
  }),
});

Environment Variables

Neon injects branch-scoped connection strings and service URLs at runtime — you don't declare these or pass them at deploy time:

VariableNotes
NEON_BRANCHThe branch name (e.g. main, preview/foo). Injected on every branch, including the default.
DATABASE_URLPooled connection string. Use for most queries. Present only if the branch has Postgres.
DATABASE_URL_UNPOOLEDDirect connection. Use for migrations, LISTEN/NOTIFY, multi-round-trip transactions.
NEON_AUTH_BASE_URLPresent when Neon Auth is enabled on the branch.
NEON_DATA_API_URLPresent when the Data API is enabled on the branch.

Object storage (AWS_*) and AI Gateway (NEON_AI_GATEWAY_*) vars are also injected when those services are declared — see the neon-object-storage and neon-ai-gateway skills.

neon env pull / neon-env run / neon dev emit NEON_BRANCH (and the connection strings) into your local dev environment too, so local runs mirror the deployed runtime.

Your own secrets are per-deployment. Preferred path: declare them in neon.ts and run neon deploy --env <file>. <file> is the gitignored file env pull already writes (.env if that file exists, otherwise .env.local). Env pull writes Neon-managed vars only; add Function secrets to that file. All declared Function env keys must be present. Omit a key from neon.ts if you do not want to write it. undefined means you asked to write the key and the value is missing (defineConfig throws). Never coerce a missing process.env value to an empty string: that uploads "" and deletes the live key. An empty assignment in the file (KEY=) is also "". If TypeScript needs an assertion, use process.env.X! and make sure the file has the value:

functions: {
  todos: {
    name: "todo api",
    source: "src/index.ts",
    env: { RESEND_API_KEY: process.env.RESEND_API_KEY! },
  },
}

neon functions deploy --env KEY=VALUE is the manual path (repeatable; --env KEY= deletes a key; unmentioned keys carry over). Use it for a targeted env update, not a full neon.ts apply.

Load Function secrets into the same file env pull wrote, then neon deploy --env <file>. Pull the branch's Neon-managed vars onto disk for local dev with neon env pull (link/checkout do this automatically; pass --no-env-pull to skip and use neon-env run -- <cmd> for runtime injection). Limits: ≤1,000 vars, ≤64 KiB total, and the NEON_ prefix is reserved.

Connecting to Postgres

When the branch has Postgres, Neon injects the connection strings at runtime — you don't declare them, pass them at deploy time, or hardcode anything. The two you'll use:

  • DATABASE_URLpooled connection string (routed through Neon's connection pooler). Use it for normal request/response query traffic. Kept un-prefixed because every Postgres ORM (Drizzle, Prisma, Knex, …) reads DATABASE_URL by default.
  • DATABASE_URL_UNPOOLEDdirect connection string to the same database. Use it for migrations, LISTEN/NOTIFY, and long multi-statement transactions.

Use Drizzle (or another ORM) on top of node-postgres (pg) for queries and schema management — not Neon's serverless driver. Functions are long-running and reuse an isolate across many requests, so a persistent pg pool is the right fit; the serverless driver's HTTP transport is meant for fully isolated, lambda-style runtimes.

Create the connection pool once at module scope and reuse it across requests — don't open a connection per request:

import { attachDatabasePool } from "@neon/functions";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 5 });
attachDatabasePool(pool);
const db = drizzle(pool);

node-postgres emits idle-client failures as error on the pool. With no listener that is an uncaughtException and Node exits the isolate. Call attachDatabasePool(pool) once after new Pool. Requires @neon/functions ≥ 0.8.0. Expected idle disconnects (ECONNRESET, EPIPE, ETIMEDOUT, Postgres 57P01, node-postgres's Connection terminated unexpectedly) are silent. Anything else is console.error, or onUnexpectedError if you pass it on the first call. The first call wins; a later call that passes onUnexpectedError is ignored and warns. This does not close the pool.

Pooling is recommended because an isolate is reused across many requests (and several requests can be in flight on the same isolate at once — see Timeouts and Runtime Limits). A module-scope pool is opened once on cold start and then shared by every subsequent request that isolate serves, so you amortize connection setup instead of paying it on every request and you avoid exhausting Postgres connections under load.

Keep max small (e.g. 5): each isolate keeps its own pool, so total connections to Postgres scale with the number of live isolates. You don't need to close the pool on shutdown — when the runtime evicts an isolate it sends SIGINT/SIGTERM, and Neon's pooler reclaims those connections for you, so an explicit drain handler is redundant.

Reading process.env.DATABASE_URL directly works everywhere. The function in Setup instead uses @neon/env's parseEnv(config) to read the same value in a typed, validated way — either is fine.

Timeouts and Runtime Limits

Functions are long-running but still serverless — they are a request/response runtime, not a background job runner. The hard limits:

  • Time to first byte: 15 minutes. Your handler must begin returning a response within 15 minutes of receiving a request. Most handlers finish in seconds; the 15-minute ceiling exists so agent workloads like image/video generation have room.
  • Heartbeat: 15 minutes. Open WebSocket/SSE connections stay alive as long as data flows. The timeout only fires when a connection goes silent — send at least one byte every 15 minutes to keep a quiet stream alive.
  • waitUntil: 15 minutes. Work registered with waitUntil (from @neon/functions) keeps the invocation alive after the response is sent, up to 15 minutes — for cleanup like analytics writes and audit logs, not a background job runner. Off the Neon runtime (local neon dev, tests) it's a no-op: the promise still runs but isn't tracked.
  • Idle eviction. With no active connections Neon shuts the function down; it may also evict/restart for operational reasons — e.g. maintenance, or moving the function to a different compute node (active functions can run for hours

Individual skills in this repo

This repo contains 7 individual skills — each has its own dedicated page.

neondatabase/claimable-postgres

Provision instant temporary Postgres databases via Claimable Postgres by Neon (neon.new) with no login, signup, or credit card. Supports REST API, CLI, and SDK. Use when users ask for a quick Postgres environment, a throwaway DATABASE_URL for prototyping/tests, or "just give me a DB now". Triggers include: "quick postgres", "temporary postgres", "no signup database", "no credit card database", "instant DATABASE_URL", "npx neon-new", "neon.new", "neon.new API", "claimable postgres API".

neondatabase/neon

>-

neondatabase/neon-ai-gateway

>-

neondatabase/neon-object-storage

>-

neondatabase/neon-postgres

Guides and best practices for working with Neon Serverless Postgres. Covers setup, connection methods, branching, autoscaling, scale-to-zero, read replicas, connection pooling, Neon Auth, and the Neon CLI, MCP server, REST API, TypeScript SDK, and Python SDK. Use when users ask about "Neon setup", "connect to Neon", "Neon project", "DATABASE_URL", "serverless Postgres", "Neon CLI", "neonctl", "Neon MCP", "Neon Auth", "@neondatabase/serverless", "@neondatabase/neon-js", "scale to zero", "Neon autoscaling", "Neon read replica", or "Neon connection pooling".

neondatabase/neon-postgres-branches

>-

neondatabase/neon-postgres-egress-optimizer

Diagnose and fix excessive Postgres egress (network data transfer) in a codebase. Use when a user mentions high database bills, unexpected data transfer costs, network transfer charges, egress spikes, "why is my Neon bill so high", "database costs jumped", SELECT * optimization, query overfetching, reduce Neon costs, optimize database usage, or wants to reduce data sent from their database to their application. Also use when reviewing query patterns for cost efficiency, even if the user doesn't explicitly mention egress or data transfer.

Verwandte Skills