Functions, bindings, and operations

Bind resources and debug production

Inject D1, KV, R2, services, variables, and secrets through environment bindings, then verify production without exposing credentials.

Pages Functions reach configured resources through context.env. A KV namespace, a D1 database, an R2 bucket, a service binding, plain variables, and secrets all arrive as properties on that object:

export async function onRequestGet(context) {
  const settings = await context.env.CONFIG.get('settings', 'json')
  return Response.json({ theme: settings?.theme ?? 'default' })
}

The binding name (CONFIG here) is configuration, defined per project and per environment. Preview and production can point the same name at different namespaces. That’s exactly what you want, and exactly why you must test both targets separately.

If you use TypeScript, run npx wrangler types whenever bindings change, so context.env stays typed.

The missing-binding failure

This is the error you will meet first:

TypeError: Cannot read properties of undefined (reading 'get')

context.env.CONFIG was undefined because the deployed environment has no binding with that name. Locally everything worked, because your local configuration had it.

A successful local run does not prove the production binding exists or points at the intended resource. Check the project’s bindings for that specific environment before reading any stack trace further.

Secrets are not vars

Plain-text vars are for public values, like a Turnstile site key. Anything private goes into encrypted secrets, set per environment:

npx wrangler pages secret put RESEND_API_KEY --project-name my-site
# ✨ Success! Uploaded secret RESEND_API_KEY

For local development, put secrets in a gitignored .dev.vars file. Both kinds appear the same way in code, as context.env.RESEND_API_KEY. Only their storage differs. Never put a secret in vars or in source.

Debug production without leaking it

Stream live Function logs from a deployment:

npx wrangler pages deployment tail --project-name my-site

Every request and every console.log appears as it happens.

Make those logs useful and safe. Attach a request ID so you can follow one request. Log the resource name you resolved, not its contents. Return safe errors to clients: a generic 500 message outside, the detail in the log. And never log the secret values themselves.

Try this: bind a practice KV namespace to a Pages project, read one non-secret value from a Function, deploy, and check the logs to confirm production resolved the resource name and environment you intended.

Lesson completed