Neon Functions
A Neon Function runs
JavaScript or TypeScript on Node.js 24 and gets its branch's DATABASE_URL
automatically. Use seekrit for credentials Neon does not supply, such as a
third-party API key. The function holds one SEEKRIT_TOKEN; @seekrit/sdk
fetches ciphertext and decrypts inside the function when a request needs a
secret. You do not need to copy every provider key into Neon's function settings.
This guide makes a small function that reads OPENAI_API_KEY from seekrit and
calls the OpenAI Models
API.
It also checks a separate caller token before resolving anything: Neon Function URLs are
public.
1. Prepare a seekrit environment
Start with an app and environment. Add the provider key to
that environment, then mint a service token
bound to it. For an existing api/production environment:
read -r -s OPENAI_API_KEY # paste your key, then press Enter
seekrit secrets set OPENAI_API_KEY "$OPENAI_API_KEY" --app api --env production
unset OPENAI_API_KEY
seekrit token create --name neon-function --app api --env production
# Save the skt_… value shown once.
You can also set the key and mint the token in the dashboard. A service token can decrypt the entire environment it is bound to, including composed groups. Give the function a narrow environment if it should only read a few keys.
2. Define the function
Install the Neon CLI and dependencies in your function project, then link it to a Neon project in a supported region:
npm install -g neon@latest
npm install @neon/config @seekrit/sdk
npm install --save-dev @types/node
neon link --no-env-pull
Create neon.ts. Neon's env values are captured at deploy time from the
shell running neon deploy. Keep the two token values out of this file and
out of version control:
// neon.ts
import { defineConfig } from "@neon/config/v1";
const seekritToken = process.env.SEEKRIT_TOKEN;
const functionAccessToken = process.env.FUNCTION_ACCESS_TOKEN;
if (!seekritToken || !functionAccessToken) {
throw new Error("Set SEEKRIT_TOKEN and FUNCTION_ACCESS_TOKEN before deploying");
}
export default defineConfig({
functions: {
openaimodels: {
name: "OpenAI models",
source: "./functions/models.ts",
env: {
SEEKRIT_TOKEN: seekritToken,
FUNCTION_ACCESS_TOKEN: functionAccessToken,
},
},
},
});
FUNCTION_ACCESS_TOKEN authenticates callers of your function. It is
different from SEEKRIT_TOKEN, which lets the function decrypt its seekrit
environment. Never send SEEKRIT_TOKEN to a caller. For a user-facing app,
replace this simple caller token with JWT
verification.
Create the handler:
// functions/models.ts
import { timingSafeEqual } from "node:crypto";
import { Seekrit } from "@seekrit/sdk";
const seekrit = new Seekrit(); // reads SEEKRIT_TOKEN from the function environment
const encoder = new TextEncoder();
function authorized(request: Request): boolean {
const supplied = request.headers.get("authorization")?.match(/^Bearer (.+)$/i)?.[1];
const expected = process.env.FUNCTION_ACCESS_TOKEN;
if (!supplied || !expected) return false;
const a = encoder.encode(supplied);
const b = encoder.encode(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
export default {
async fetch(request: Request): Promise<Response> {
if (request.method !== "GET") {
return new Response("Method not allowed", { status: 405 });
}
if (!authorized(request)) {
return new Response("Unauthorized", { status: 401 });
}
let apiKey: string | undefined;
try {
apiKey = await seekrit.get("OPENAI_API_KEY");
} catch {
return new Response("Secret unavailable", { status: 503 });
}
if (!apiKey) {
return new Response("OPENAI_API_KEY is missing", { status: 503 });
}
let upstream: Response;
try {
upstream = await fetch("https://api.openai.com/v1/models", {
headers: { authorization: `Bearer ${apiKey}` },
});
} catch {
return new Response("Provider unavailable", { status: 502 });
}
if (!upstream.ok) {
return new Response("Provider request failed", { status: 502 });
}
const result = (await upstream.json()) as { data: unknown[] };
return Response.json({ models: result.data.length });
},
};
The Seekrit client is reused across requests on one function instance, but
get() resolves on each call. A changed provider key is available on the next
request without redeploying the function. Resolution fails closed; the handler
returns an error rather than continuing with an empty key.
3. Run and deploy
Set the one-time service token in the shell used for development and deployment. The first command below accepts it without echoing it or putting it in shell history. Make a separate random token for callers:
read -r -s SEEKRIT_TOKEN
export SEEKRIT_TOKEN
export FUNCTION_ACCESS_TOKEN="$(openssl rand -hex 32)"
neon dev
Save FUNCTION_ACCESS_TOKEN for the caller in your usual secret store before
closing this shell. It is needed to invoke the deployed function and is not a
seekrit credential.
neon dev prints the local function URL. You can call it with the same
authorization header shown below while the dev server runs. Stop the dev
server, then run neon deploy from the shell holding both tokens. Retrieve
the function's public invocation_url:
neon deploy
neon functions get openaimodels -o yaml
# Copy the invocation_url from the output above:
FUNCTION_URL="https://..."
curl -H "Authorization: Bearer $FUNCTION_ACCESS_TOKEN" "$FUNCTION_URL"
The response is a count such as {"models":42}; neither token nor the
provider key is returned. For more about configuration and deployment, see
Neon's function setup
and environment variable docs.
Branches and rotation
Neon gives each branch its own function and database connection. A seekrit service token still points to the seekrit environment chosen when that token was minted. A Neon preview branch does not automatically switch it to a seekrit preview environment. Deploy each branch with a token bound to the matching seekrit environment, especially before exposing a preview URL.
Neon injects DATABASE_URL from the function's branch. Read that directly if
your function uses Postgres; do not copy a production database URL into this
seekrit environment for the function to use. Neon also injects credentials for
its own enabled services, such as AI Gateway. Use seekrit for credentials you
manage separately.
Changing OPENAI_API_KEY in seekrit takes effect on the next get() call. A
revoked service token fails on the next resolution. To replace it, put a newly
minted SEEKRIT_TOKEN in Neon's function environment and redeploy: Neon stores
a deployment snapshot of user-defined variables.