Astro shows “Invalid environment variables” when variables declared in the env.schema option of astro.config.mjs are missing or don’t match their declared type. The message lists each one: “The following environment variables defined in env.schema are invalid”, followed by the names and problems. Set the missing values (in .env locally, in your host’s dashboard for deploys), fix types, or give optional ones a default.
Key Takeaways
- Every non-optional variable in
env.schemamust be set where the code runs.- Locally, values come from
.envfiles; on the host, from its environment settings.- Numbers and booleans are parsed:
PORT=abcfailsenvField.number().- Mark non-essential variables
optional: trueor give them adefault.
A typical schema
// astro.config.mjs
import { defineConfig, envField } from 'astro/config';
export default defineConfig({
env: {
schema: {
PUBLIC_SITE_NAME: envField.string({ context: 'client', access: 'public', default: 'My Site' }),
NEWSLETTER_API_KEY: envField.string({ context: 'server', access: 'secret' }),
CACHE_SECONDS: envField.number({ context: 'server', access: 'public', default: 60 }),
ENABLE_SEARCH: envField.boolean({ context: 'client', access: 'public', optional: true }),
},
},
});
Fix 1: set the missing value
Locally:
# .env (never commit secrets)
NEWSLETTER_API_KEY=xxxxxxxx
On the host, add the same name in its settings: Cloudflare Pages (Settings → Variables and Secrets), Netlify (Site configuration → Environment variables), Vercel (Project → Settings → Environment Variables). Set it for each environment that builds (production, preview) or previews will fail.
Fix 2: fix the type
CACHE_SECONDS=sixty # fails envField.number()
CACHE_SECONDS=60 # passes
ENABLE_SEARCH=yes # fails envField.boolean()
ENABLE_SEARCH=true # passes
Fix 3: make it optional
If a variable isn’t needed in every environment:
ANALYTICS_ID: envField.string({ context: 'client', access: 'public', optional: true }),
Then handle undefined in code.
When the check runs
Public variables are validated at build time. Secret server variables are read at runtime on on-demand routes, so a missing secret can surface only when that route is requested. Test with npm run build && npx astro preview with production-like values before deploying.
Reading the values
import { PUBLIC_SITE_NAME } from 'astro:env/client';
import { NEWSLETTER_API_KEY } from 'astro:env/server';
Importing astro:env/server in client code fails with Module is only available server-side.
Environment variables per host are covered in the Astro deploy guide, and their use in forms in the newsletter signup tip. The environment variables guide documents every envField option.
FAQ
Why does the build pass locally but fail on the host?
The value exists in your local .env, which isn’t committed, but hasn’t been added in the host’s settings.
Do I need astro:env, or can I use import.meta.env?
import.meta.env still works. astro:env adds types, validation and a clear split between client and server values.