Skip to main content
Neon Postgres Docs
current

Search documentation

Type to search this documentation.

On this pageOverview

Deploy and manage Neon Functions

Deploy with neon.ts If your project has a neon.ts config, this is the recommended way to deploy. neon deploy reads the config and applies the entire branch policy in one step services, per branch tuni...

If your project has a neon.ts config, this is the recommended way to deploy. neon deploy reads the config and applies the entire branch policy in one step: services, per-branch tuning, and every function it declares:

Bash
neon deploy
Flag Default Description
--config walks up from cwd Path to the neon.ts policy
--env (none) Path to a .env file loaded before neon.ts is evaluated, so function env values resolve from it
--env-pull true Pull the branch's env vars into a local .env after a successful apply (--no-env-pull to skip)
--branch linked branch Target branch ID or name
--project-id linked project Project ID
--update-existing false Auto-confirm overriding existing remote settings on the branch
--allow-protected false Auto-confirm applying to a branch marked protected on Neon

neon deploy is an alias for neon config apply. To preview what a deploy would change without applying it, run neon config plan.

You can declare scheduled Function Triggers and custom domains in the same neon.ts config, so they deploy alongside the function.

Note that --env here takes a path to a .env file. The --env flag on neon functions deploy below takes KEY=VALUE pairs instead.

To deploy one function directly, without a neon.ts config:

Bash
neon functions deploy <slug> [--src <dir-or-entry-file>] [--env KEY=VALUE] [--wait]

By default, the CLI bundles your source with esbuild, zips the output, and uploads it. Pass --no-bundle to skip esbuild and ship a prebuilt source as-is. The first deploy creates the function; subsequent deploys update it. See the neon functions reference for the full command surface.

Flag Default Description
--src (none) Function source: a directory containing index.ts, index.js, or index.mjs (the first match is the entry), or a path to the entry file
--env KEY=VALUE (none) Set an environment variable. Repeatable. Stored with the deployment. Takes KEY=VALUE pairs, not a .env file path like neon deploy --env
--runtime nodejs24 Function runtime. nodejs24 is the only valid value
--branch linked branch Target branch. Defaults to the branch in .neon
--wait true Poll until completed or failed, up to 10 minutes
--no-bundle off (bundles) Skip esbuild and zip a prebuilt source as-is. The directory root, or the file you point at, must be index.mjs or index.js (TypeScript can't ship unbundled). Use it when you run your own build step

Examples:

Bash
neon functions deploy hello --src functions/hello.ts
Bash
neon functions deploy hello --src . --env RESEND_API_KEY=re_...
Bash
neon functions deploy hello --src functions/hello.ts --branch feat/my-feature

The CLI doesn't support a config-only deploy. Every neon functions deploy call bundles and uploads source, whether you pass --src or let it default to the current directory, so there's no way to change just an environment variable without also pointing at valid source, as in the example above.

For a deploy that skips bundling and updates only the environment or runtime, use the API, which accepts config-only updates.

Bundle with esbuild, zip the output, then POST to the deploy endpoint.

1. Bundle:

Bash
esbuild functions/hello.ts --bundle --platform=node --target=node24 --format=esm \
  --banner:js="import{createRequire as ___cr}from'module';import{fileURLToPath as ___f}from'url';import{dirname as ___d}from'path';const require=___cr(import.meta.url);const __filename=___f(import.meta.url);const __dirname=___d(__filename);" \
  --outfile=dist/index.mjs

2. Zip:

Bash
zip -j function.zip dist/index.mjs

The archive's entry file must be named index.mjs or index.js; the runtime looks for those names.

From Node.js, buildFunctionBundle from @neon/config-runtime does both steps in one call and produces exactly the archive the deploy endpoint expects. See the @neon/config-runtime reference for the rest of that package's programmatic API (inspect, plan, apply), useful for calling a deploy from a custom CI step instead of the CLI:

TypeScript
import { buildFunctionBundle } from "@neon/config-runtime/v1";

const zip = await buildFunctionBundle({
  slug: "hello",
  name: "My first function",
  source: "./functions/hello.ts",
  env: {},
  runtime: "nodejs24",
});

3. Deploy:

Bash
curl -X POST \
  "https://console.neon.tech/api/v2/projects/{project_id}/branches/{branch_id}/functions/{slug}/deployments" \
  -H "Authorization: Bearer $NEON_API_KEY" \
  -F "zip=@function.zip" \
  -F 'environment={"MY_SECRET":"value"}'
Field Type Required Description
zip binary When code changes ZIP of the bundled function. Omit only to update environment or runtime on an existing deployment
runtime string No nodejs24 is the only valid value
environment string No JSON-encoded string-to-string map

The deploy endpoint accepts multipart/form-data. Use a zip part for code deploys and a single environment part containing the JSON-encoded map; don't send bracketed fields such as environment[KEY]=value. The first deployment for a function must include zip; later deployments can omit it for config-only changes.

The API returns immediately for code deploys. Poll the get endpoint (see Check status) until the deployment completes. Config-only deployments can complete synchronously because they reuse the latest bundle. Builds have an absolute 2-minute budget from the time the deploy is accepted; if the build can't complete within that window, the deployment fails.

The beta @neon/sdk client includes a neon.functions namespace for branch-scoped function management:

TypeScript
import { createNeonClient } from '@neon/sdk';
import { readFile } from 'node:fs/promises';

const neon = createNeonClient({ apiKey: process.env.NEON_API_KEY! });
const projectId = process.env.NEON_PROJECT_ID!;
const branchId = process.env.NEON_BRANCH_ID!;
const zipBytes = await readFile('function.zip');

const { data: deployment } = await neon.functions.deploy({
  projectId,
  branchId,
  slug: 'hello',
  zip: new File([zipBytes], 'function.zip', { type: 'application/zip' }),
  runtime: 'nodejs24',
  environment: JSON.stringify({ MY_SECRET: 'value' }),
});

neon.functions.deploy uses the same multipart API fields as the raw endpoint. list, get, update, and delete are also available under neon.functions.

The slug is assigned at first deploy: either the key in neon.ts or the positional argument to neon functions deploy. It becomes part of the invocation URL and can't be changed afterward. Slugs must match ^[a-z0-9]{1,20}$: lowercase letters and digits only, 1 to 20 characters, no hyphens.

State Meaning
pending Queued, not yet building
building Source is being compiled and bundled
completed Function is live and accepting requests
failed Build or deployment error
CLI
neon functions get hello
API
GET /projects/{project_id}/branches/{branch_id}/functions/{slug}

The response includes invocation_url, the public URL for your function:

https://<branch_id>-<slug>.compute.<cell>.us-east-2.aws.neon.tech/
CLI
neon functions list
API
GET /projects/{project_id}/branches/{branch_id}/functions
CLI
neon functions delete hello
API
DELETE /projects/{project_id}/branches/{branch_id}/functions/{slug}

Join our Discord Server to ask questions or see what others are doing with Neon. For paid plan support options, see Support.

Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu