Fix: Invalid Environment Variables in Astro

Error message:
Environment variables validation failed.
Configuration 2025-01-25

What Causes This Error?

This error occurs when your environment variables don’t match the schema you’ve defined in src/env.d.ts or when required variables are missing. Astro’s env validation helps catch configuration issues early.

The Problem

// src/env.d.ts
interface ImportMetaEnv {
  readonly PUBLIC_API_URL: string;  // Required
  readonly API_KEY: string;         // Required
}
# .env - Missing API_KEY
PUBLIC_API_URL=https://api.example.com
# API_KEY is not set!

The Fix

Provide Required Variables

# .env
PUBLIC_API_URL=https://api.example.com
API_KEY=your-api-key-here

Common Scenarios

Define Environment Schema

// src/env.d.ts
/// <reference types="astro/client" />

interface ImportMetaEnv {
  // Public variables (exposed to client)
  readonly PUBLIC_API_URL: string;
  readonly PUBLIC_SITE_NAME: string;

  // Private variables (server-only)
  readonly DATABASE_URL: string;
  readonly API_SECRET: string;

  // Optional with defaults
  readonly PORT?: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

Using astro:env (Astro v4.10+)

// src/env.d.ts
/// <reference types="astro/client" />

declare module 'astro:env/server' {
  export const DATABASE_URL: string;
  export const API_SECRET: string;
}

declare module 'astro:env/client' {
  export const PUBLIC_API_URL: string;
}
// astro.config.mjs
import { defineConfig, envField } from 'astro/config';

export default defineConfig({
  experimental: {
    env: {
      schema: {
        DATABASE_URL: envField.string({
          context: 'server',
          access: 'secret',
        }),
        PUBLIC_API_URL: envField.string({
          context: 'client',
          access: 'public',
        }),
        PORT: envField.number({
          context: 'server',
          access: 'public',
          optional: true,
          default: 3000,
        }),
      },
    },
  },
});

Environment Files

# .env (all environments)
PUBLIC_SITE_NAME=My Site

# .env.development (dev only)
PUBLIC_API_URL=http://localhost:3001/api
DATABASE_URL=postgresql://localhost/dev

# .env.production (production only)
PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://prod-server/db

Check Required Variables

// src/utils/env.ts
export function validateEnv() {
  const required = [
    'PUBLIC_API_URL',
    'DATABASE_URL',
    'API_SECRET',
  ];

  const missing = required.filter(key => !import.meta.env[key]);

  if (missing.length > 0) {
    throw new Error(`Missing environment variables: ${missing.join(', ')}`);
  }
}

Handling Optional Variables

// astro.config.mjs with optional fields
export default defineConfig({
  experimental: {
    env: {
      schema: {
        // Required - will error if missing
        DATABASE_URL: envField.string({
          context: 'server',
          access: 'secret',
        }),

        // Optional with default
        LOG_LEVEL: envField.string({
          context: 'server',
          access: 'public',
          optional: true,
          default: 'info',
        }),

        // Optional, may be undefined
        ANALYTICS_ID: envField.string({
          context: 'client',
          access: 'public',
          optional: true,
        }),
      },
    },
  },
});

Type Validation

// astro.config.mjs
export default defineConfig({
  experimental: {
    env: {
      schema: {
        // String validation
        NODE_ENV: envField.enum({
          context: 'server',
          access: 'public',
          values: ['development', 'production', 'test'],
        }),

        // Number validation
        MAX_ITEMS: envField.number({
          context: 'server',
          access: 'public',
          min: 1,
          max: 100,
        }),

        // Boolean
        ENABLE_CACHE: envField.boolean({
          context: 'server',
          access: 'public',
          default: false,
        }),
      },
    },
  },
});

Using Environment Variables

---
// Server-side access
const dbUrl = import.meta.env.DATABASE_URL;

// With astro:env
import { DATABASE_URL } from 'astro:env/server';
import { PUBLIC_API_URL } from 'astro:env/client';
---

<!-- Client-side only PUBLIC_ variables -->
<script>
  // Only PUBLIC_ prefixed vars are available
  const apiUrl = import.meta.env.PUBLIC_API_URL;

  // ❌ This won't work (not prefixed with PUBLIC_)
  // const secret = import.meta.env.DATABASE_URL;
</script>

Debug Environment

# Check loaded variables
npx astro info

# List env files
ls -la .env*

# Verify variable is set
echo $DATABASE_URL

Production Deployment

# Vercel
vercel env add DATABASE_URL

# Netlify
netlify env:set DATABASE_URL "value"

# Most platforms: use dashboard to set env vars

Don’t Commit Secrets

# .gitignore
.env
.env.local
.env.*.local
.env.production

# Only commit example
!.env.example
# .env.example (template for required vars)
PUBLIC_API_URL=
DATABASE_URL=
API_SECRET=

Quick Checklist

  • Define env schema in src/env.d.ts or config
  • Create .env file with required variables
  • Use PUBLIC_ prefix for client-accessible vars
  • Set optional variables with defaults if needed
  • Never commit .env files with secrets
  • Configure env vars in deployment platform