Configuration

Gateway Environment Variables

Applies to
Gatewayruntime for API, AI, and MCP gateways
Hosting
Managed EdgeManaged DedicatedDiffersSelf-HostedDiffers
2 differences by hosting option
Managed Dedicated

ZUPLO_ENVIRONMENT_TYPE values depend on the hosting environment, so check for the values your deployment reports instead of assuming edge.

Self-Hosted

ZUPLO_ENVIRONMENT_TYPE values depend on the hosting environment, so check for the values your deployment reports instead of assuming edge.

Your API gateway can read every environment variable set for its environment, including secrets. This page covers how to read variables in code, configuration files, and URL handlers, and which variables Zuplo sets for you.

Configuration Guide

To set and manage environment variables, and to learn how gateway variables differ from Developer Portal variables, see Configuring Environment Variables.

Where to Use Variables

Each location in your project has its own syntax:

LocationSyntaxResolved
Custom code (handlers, policies, hooks)environment.VAR_NAMERuntime
Configuration files (policies.json, OpenAPI routes)$env(VAR_NAME)Build time
URL Rewrite, URL Forward, and WebSocket handlers${env.VAR_NAME}Runtime (per request)

In Code

The environment object from @zuplo/runtime returns a record of environment variable names to their values.

Interface

TypeScriptCode
const environment: Record<string, string | undefined>;

Usage

Access environment variables using the environment object:

TypeScriptCode
import { environment, ZuploContext, ZuploRequest } from "@zuplo/runtime"; export default async function handler( request: ZuploRequest, context: ZuploContext, ) { // Access environment variables const apiKey = environment.API_KEY; const apiUrl = environment.API_BASE_URL; if (!apiKey) { throw new Error("API_KEY environment variable isn't set"); } const response = await fetch(`${apiUrl}/data`, { headers: { Authorization: `Bearer ${apiKey}`, }, }); return response; }

Type Safety

Since environment variables might not be defined, they return string | undefined:

TypeScriptCode
import { environment } from "@zuplo/runtime"; // TypeScript knows this could be undefined const value = environment.MY_VAR; // string | undefined // Always check before using if (value) { // value is string here console.log(value.toUpperCase()); } // Or provide a default const port = environment.PORT || "3000";

In Configuration Files

Inside policy options, route handler options, and CORS policy options, environment variables can be referenced with the $env(VAR_NAME) pattern. Substitutions happen at build time — the build replaces each $env() expression with a reference to the runtime environment object before the project is deployed.

Where $env() is allowed

  • config/policies.json — any property under policies[].handler.options, including nested objects and array elements
  • config/policies.json — any direct property of a corsPolicies[] entry, such as allowedOrigins, allowedHeaders, allowedMethods, exposeHeaders, and maxAge
  • config/routes.oas.json (and other OpenAPI route files) — any property under a route's x-zuplo-route.handler.options, including nested objects and array elements

$env() is not allowed in other locations such as policy name, policyType, module, route paths, or top-level OpenAPI fields. Using it elsewhere produces a build error:

Code
An $env() statement is not expected at this location.

Standalone substitution

When the value is only an $env() expression, the variable's value is inserted directly. The runtime type matches the type of the environment variable (always a string when set, undefined when not).

JSONCode
{ "name": "my-custom-code-inbound-policy", "policyType": "custom-code-inbound", "handler": { "export": "default", "module": "$import(./modules/YOUR_MODULE)", "options": { "apiKey": "$env(BACKEND_API_KEY)", "config2": true } } }

String interpolation

You can mix $env() with literal text to compose a value. The build wraps the result in a JavaScript template literal that's evaluated at runtime, so each request gets the current value of the variable.

JSONCode
{ "options": { "endpoint": "https://$env(API_HOST)/v1", "userAgent": "MyGateway/$env(APP_VERSION) ($env(ENVIRONMENT_NAME))" } }

If the variable isn't set, the interpolated portion resolves to an empty string. For example, with API_HOST unset, "https://$env(API_HOST)/v1" becomes "https:///v1". Use a standalone $env() (no surrounding text) if you need to detect an unset variable in your handler or policy code.

In arrays

$env() works inside string array elements, including mixed arrays where some elements are static and others are interpolated:

JSONCode
{ "options": { "allowedKeys": ["$env(PRIMARY_KEY)", "$env(SECONDARY_KEY)"], "tags": ["public", "$env(REGION)", "tier-$env(SERVICE_TIER)"] } }

In nested objects

$env() works at any depth within a handler or policy options object:

JSONCode
{ "options": { "database": { "host": "$env(DB_HOST)", "credentials": { "username": "$env(DB_USER)", "password": "$env(DB_PASSWORD)" } } } }

Variable name rules

The name inside $env(...) is matched literally up to the first closing parenthesis. Use only letters, digits, and underscores in the name — matching what the Zuplo Portal accepts when you create the variable.

Rewrite, Forwarding, and WebSocket Handlers

The URL Rewrite handler's rewritePattern, the URL Forward handler's baseUrl, and the WebSocket handler's rewritePattern use a different syntax. These options are evaluated as JavaScript template literals at request time, so you reference variables with ${env.VAR_NAME}:

JSONCode
{ "handler": { "export": "urlForwardHandler", "module": "$import(@zuplo/runtime)", "options": { "baseUrl": "https://${env.BACKEND_HOST}" } } }

Because rewritePattern and baseUrl run as code, they also have access to the request, URL parts, route params, and query string. See the URL Rewrite Handler docs for the full list of available variables.

Using $env(VAR_NAME) inside rewritePattern or baseUrl is the most common mistake. The build will warn you when it detects this, but the value will be passed through to the handler as a literal string and the rewrite will fail at runtime. Always use ${env.VAR_NAME} in these two options.

System Environment Variables

Zuplo sets the following variables for your gateway. You can't create variables with these names.

VariableValue
ZUPLO_ENVIRONMENT_STAGEThe environment stage: production, preview, working-copy, or local. Use this variable to check which environment the gateway runs in.
ZUPLO_ENVIRONMENT_TYPEThe kind of host that runs the gateway, such as edge or local.
ZUPLO_ENVIRONMENT_NAMEThe globally unique name of the environment, as used in its default URL. For example, my-project-main-1235 in my-project-main-1235.zuplo.app. Custom domains don't change this value.
ZUPLO_ACCOUNT_NAMEThe name of the Zuplo account that owns the environment.
ZUPLO_PROJECT_NAMEThe name of the project that owns the environment.
ZUPLO_BUILD_IDA unique ID (UUID) for the build of the environment.
ZUPLO_COMPATIBILITY_DATEThe compatibility date of the runtime environment.

Your Developer Portal gets a different set of system variables, and its ZUPLO_ENVIRONMENT_TYPE has different values. See Dev Portal environment variables.

Environment Detection

Zuplo doesn't set NODE_ENV, because the gateway runs on a V8 runtime, not Node.js. Use ZUPLO_ENVIRONMENT_STAGE instead:

TypeScriptCode
import { environment } from "@zuplo/runtime"; const isProduction = environment.ZUPLO_ENVIRONMENT_STAGE === "production";

Common Patterns

Required Environment Variables

Create a helper to validate required variables:

TypeScriptCode
import { environment, ConfigurationError } from "@zuplo/runtime"; function getRequiredEnv(name: string): string { const value = environment[name]; if (!value) { throw new ConfigurationError( `Required environment variable '${name}' isn't set`, ); } return value; } // Usage export default async function handler( request: ZuploRequest, context: ZuploContext, ) { const apiKey = getRequiredEnv("API_KEY"); const apiUrl = getRequiredEnv("API_BASE_URL"); // Both are guaranteed to be strings return fetch(`${apiUrl}/endpoint`, { headers: { "X-API-Key": apiKey }, }); }

Configuration Object

Create a configuration object from environment variables:

TypeScriptCode
import { environment, ConfigurationError } from "@zuplo/runtime"; interface Config { apiKey: string; apiUrl: string; maxRetries: number; timeout: number; debug: boolean; } function loadConfig(): Config { const apiKey = environment.API_KEY; const apiUrl = environment.API_URL; if (!apiKey || !apiUrl) { throw new ConfigurationError("Missing required environment variables"); } return { apiKey, apiUrl, maxRetries: parseInt(environment.MAX_RETRIES || "3"), timeout: parseInt(environment.TIMEOUT_MS || "5000"), debug: environment.DEBUG === "true", }; } // Load once and reuse const config = loadConfig(); export default async function handler( request: ZuploRequest, context: ZuploContext, ) { if (config.debug) { context.log.debug("Request received", { url: request.url }); } // Use config values return fetchWithRetry(config.apiUrl, { headers: { Authorization: `Bearer ${config.apiKey}` }, maxRetries: config.maxRetries, timeout: config.timeout, }); }

Environment-Specific Configuration

Use ZUPLO_ENVIRONMENT_STAGE to change behavior between production, preview, and development environments:

TypeScriptCode
import { environment, ZuploContext, ZuploRequest } from "@zuplo/runtime"; const isProduction = environment.ZUPLO_ENVIRONMENT_STAGE === "production"; export default async function handler( request: ZuploRequest, context: ZuploContext, ) { // Enable debug logging outside production if (!isProduction) { context.log.debug("Incoming request", { headers: Object.fromEntries(request.headers), url: request.url, }); } return new Response("OK"); }

To use different values per environment, such as a backend URL, set the same variable with a different value for each environment instead of branching in code.

Secret Management

TypeScriptCode
import { environment } from "@zuplo/runtime"; // Group related secrets const secrets = { database: { host: environment.DB_HOST || "localhost", port: environment.DB_PORT || "5432", user: environment.DB_USER, password: environment.DB_PASSWORD, name: environment.DB_NAME || "myapp", }, redis: { url: environment.REDIS_URL || "redis://localhost:6379", }, external: { stripeKey: environment.STRIPE_SECRET_KEY, sendgridKey: environment.SENDGRID_API_KEY, }, }; // Validate all required secrets on startup function validateSecrets() { const missing: string[] = []; if (!secrets.database.user) missing.push("DB_USER"); if (!secrets.database.password) missing.push("DB_PASSWORD"); if (!secrets.external.stripeKey) missing.push("STRIPE_SECRET_KEY"); if (missing.length > 0) { throw new ConfigurationError( `Missing required secrets: ${missing.join(", ")}`, ); } } // Run validation validateSecrets();

Best Practices

1. Use Descriptive Names

TypeScriptCode
// Good environment.STRIPE_WEBHOOK_SECRET; environment.DATABASE_CONNECTION_STRING; environment.SLACK_WEBHOOK_URL; // Less clear environment.KEY; environment.URL; environment.SECRET;

2. Provide Defaults Where Appropriate

TypeScriptCode
const config = { port: environment.PORT || "3000", logLevel: environment.LOG_LEVEL || "info", maxConnections: parseInt(environment.MAX_CONNECTIONS || "100"), enableCache: environment.ENABLE_CACHE !== "false", // Default true };

3. Validate Early

TypeScriptCode
// Validate at module load time, not request time const apiKey = environment.API_KEY; if (!apiKey) { throw new ConfigurationError("API_KEY must be set"); } export default async function handler( request: ZuploRequest, context: ZuploContext, ) { // apiKey is guaranteed to exist here return fetch("https://api.example.com", { headers: { "X-API-Key": apiKey }, }); }

4. Don't Log Secrets

TypeScriptCode
// DON'T do this context.log.info("Config loaded", { apiKey: environment.API_KEY, }); // DO this instead context.log.info("Config loaded", { apiKeyPresent: !!environment.API_KEY, apiKeyLength: environment.API_KEY?.length, });

Setting Environment Variables

Set environment variables in the Zuplo Portal under Settings > Environment Variables. For local development, use a .env file. See Configuring Environment Variables Locally.

See Also

Last modified on