Netlify: env vars not loading in functions [2026 fix]

Env vars fail in Netlify Functions because they're not bundled into the function package. Move vars to netlify.toml [functions] or use context.clientContext.

TL;DR

Cause: Environment variables defined in Netlify UI aren't automatically injected into serverless Functions; they're only available at build-time unless explicitly configured.

Fix: Define function-specific env vars in netlify.toml under [functions] section or access them via context.clientContext in newer runtimes.

---

Real Console Error Messages

``` 1. TypeError: Cannot read properties of undefined (reading 'DATABASE_URL') at Runtime.handler (/var/task/index.js:15:23)

2. ReferenceError: STRIPE_API_KEY is not defined at Object.<anonymous> (/var/task/functions/stripe.js:3:5)

3. Error: env var SECRET_TOKEN returned undefined in function execution at checkEnv (/var/task/middleware.js:42:10)

4. Uncaught SyntaxError: Invalid or unexpected token at process.env.API_URL Context: function logs show 'undefined' when accessing process.env

5. TypeError: Cannot set property AUTH_SECRET, which has only a getter indicates immutable env state in Netlify Functions container ```

---

The Problem: Broken vs Fixed Code

❌ BROKEN: Relying on UI-defined env vars in Functions

```javascript // netlify/functions/api.js exports.handler = async (event, context) => { const dbUrl = process.env.DATABASE_URL; // ← UNDEFINED const apiKey = process.env.STRIPE_KEY; // ← UNDEFINED const response = await fetch(dbUrl); return { statusCode: 200, body: JSON.stringify({ key: apiKey }) }; }; ```

Result at 2am: Function returns undefined for all env vars despite being set in Netlify dashboard.

---

✅ FIXED: Method 1 – netlify.toml configuration

```toml

netlify.toml

[functions] node_bundler = "esbuild" [[functions]] name = "api" external_node_modules = ["lodash"] environment = { DATABASE_URL = "postgresql://user:pass@host/db", STRIPE_KEY = "sk_live_..." } ```

```javascript // netlify/functions/api.js exports.handler = async (event, context) => { const dbUrl = process.env.DATABASE_URL; // ✅ NOW DEFINED const apiKey = process.env.STRIPE_KEY; // ✅ NOW DEFINED const response = await fetch(dbUrl); return { statusCode: 200, body: JSON.stringify({ key: apiKey }) }; }; ```

---

✅ FIXED: Method 2 – context.clientContext (Netlify Runtime v1+)

```javascript // netlify/functions/api.js exports.handler = async (event, context) => { // Access from context injected by Netlify runtime const { DATABASE_URL, STRIPE_KEY } = context.clientContext.custom || {}; if (!DATABASE_URL) { return { statusCode: 500, body: JSON.stringify({ error: "DATABASE_URL not found in context" }) }; } const response = await fetch(DATABASE_URL); return { statusCode: 200, body: JSON.stringify({ key: STRIPE_KEY }) }; }; ```

Note: context.clientContext approach requires Netlify Functions Runtime v1.0+. If uncertain about your runtime version, check your netlify.toml for aws_lambda_memory or test locally with netlify dev.

---

✅ FIXED: Method 3 – Secrets via Build Context (TypeScript example)

```typescript // netlify/functions/secure.ts import { Handler, HandlerContext } from "@netlify/functions";

const handler: Handler = async (event, context: HandlerContext) => { // For secrets, prefer netlify.toml [functions] block // Avoid logging sensitive data const hasToken = !!process.env.AUTH_SECRET; return { statusCode: 200, body: JSON.stringify({ authenticated: hasToken }) }; };

export { handler }; ```

---

Still Broken? Check These Too

1. Build cache issue – Netlify might be serving stale function bundles. Go to Deploy Settings → Trigger Deploy → "Deploy site" (not "Clear cache and deploy" first, that's often ineffective). Wait 2–3 minutes for Functions to redeploy.

2. Function bundling mismatch – If using a custom build command, ensure node_bundler = "esbuild" is set in netlify.toml. Older zisi bundler doesn't always pick up env vars correctly. See [Netlify build environment setup](/?guide=build-env) for details.

3. Scope confusion: Build vs Runtime – Env vars in Netlify UI apply at *build time* (for Next.js, Hugo, etc.). Functions need *runtime* vars. These are two separate systems. Double-check you're not confusing process.env in build scripts with process.env in function handlers. See [environment variables in Netlify functions](/?guide=netlify-function-env) for comparison.

---

Debugging Checklist

  • [ ] Restart dev server: netlify dev --clear
  • [ ] Check function logs: Netlify Dashboard → Functions → view real-time logs
  • [ ] Verify netlify.toml has correct [[functions]] block (note double brackets)
  • [ ] Confirm vars are not secrets (secrets go in UI, but Functions need them in netlify.toml or passed explicitly)
  • [ ] Test locally: netlify dev loads env from .env file in project root
  • ---

    Official Resources

  • [Netlify Functions Environment Variables](https://docs.netlify.com/functions/overview/?fn-language=js#environment-variables)
  • [netlify.toml Functions Configuration](https://docs.netlify.com/configure-builds/file-api-reference/#functions)
  • ---

    Found a different variation? Drop it in the comments – If you hit a unique error or discovered another cause (Docker layer caching, monorepo-specific issues, region-based behavior), let us know and we'll add it to this guide.

    🔥 0d
    LIVE
    ↓ PlanetScale rage spiking •⚠ Vercel pricing complaints •↑ Railway gaining fast •↑ Supabase happiness rising •↑ Resend loved by devs •↓ PlanetScale rage spiking •⚠ Vercel pricing complaints •↑ Railway gaining fast •↑ Supabase happiness rising
    DEVELOPER PAIN RADAR // Loading...

    Developers complain.
    Opportunities appear.

    We track what developers are struggling with today — and what opportunities that creates.

    —
    guides today
    —
    avg happiness
    🔥 Pain
    📖 Guides
    🔭 Explore
    👤 Mine
    🔥 Pain Radar — rage scores today
    ↗ share
    💡 Opportunity Feed — pain = market gap
    📈 Tool Momentum
    all scores →
    📖 Latest Guide
    all guides →
    📖 All Guides
    📊 Tool Scores
    + Submit
    📰 Hacker News
    ➕ Submit a Tool
    ← back