| name | vite-env |
| description | Use when adding env variables to a Vite project, setting up env validation, debugging env errors, or importing from virtual:env/*. Provides type-safe env with build-time leak detection. |
| license | MIT |
| metadata | {"author":"pyyupsk","version":"1.0"} |
vite-env
Type-safe environment variable management for Vite 8. Define once in env.ts, import typed values from virtual:env/client and virtual:env/server.
When to Activate
- User asks to add, modify, or remove environment variables in a Vite project
- Project has
@vite-env/core in dependencies
- User references
virtual:env/client or virtual:env/server
- User wants env validation, type safety, or leak detection
- User is migrating from raw
import.meta.env to typed env
- Build fails with env validation or leak detection errors
Setup Workflow
When setting up vite-env in a new project, follow these steps in order:
Step 1 — Install
bun add @vite-env/core
bun add -d zod
For Standard Schema (Valibot) instead of Zod:
bun add @vite-env/core valibot
Step 2 — Create env.ts at project root
import { defineEnv } from "@vite-env/core";
import { z } from "zod";
export default defineEnv({
server: {
DATABASE_URL: z.url(),
JWT_SECRET: z.string().min(32),
},
client: {
VITE_APP_URL: z.url(),
VITE_APP_NAME: z.string().min(1),
},
});
Step 3 — Register the plugin
import ViteEnv from "@vite-env/core/plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [ViteEnv()],
});
Step 4 — Start dev server to generate types
bun run dev
This generates vite-env.d.ts automatically. Commit this file.
Adding a New Environment Variable
- Decide scope — is it a server secret or client-visible?
- Add to
env.ts:
- Server vars →
server object, no VITE_ prefix
- Client vars →
client object, must have VITE_ prefix
- Add the value to
.env (or .env.local for secrets)
- Import from the correct virtual module:
- Client code →
import { env } from 'virtual:env/client'
- Server/SSR code →
import { env } from 'virtual:env/server'
- Restart dev server — types regenerate automatically
Common Workflows
Using platform presets
import { defineEnv } from "@vite-env/core";
import { vercel } from "@vite-env/core/presets";
export default defineEnv({
presets: [vercel],
server: { ... },
client: { ... },
});
Presets add platform-injected vars (e.g. VERCEL_URL, VERCEL_ENV). User definitions override preset values.
Using Standard Schema (Valibot)
import { defineStandardEnv } from "@vite-env/core";
import * as v from "valibot";
export default defineStandardEnv({
server: {
DATABASE_URL: v.pipe(v.string(), v.url()),
},
client: {
VITE_API_URL: v.pipe(v.string(), v.url()),
},
});
CLI commands (no dev server needed)
vite-env check
vite-env generate
vite-env types
Common Pitfalls
Client vars MUST have VITE_ prefix
client: {
API_URL: z.url(),
}
client: {
VITE_API_URL: z.url(),
}
Server vars MUST NOT have VITE_ prefix
server: {
VITE_DATABASE_URL: z.url(),
}
server: {
DATABASE_URL: z.url(),
}
Never import virtual:env/server in client code
import { env } from "virtual:env/server";
import { env } from "virtual:env/client";
Don't access env via import.meta.env when vite-env is set up
const url = import.meta.env.VITE_API_URL;
import { env } from "virtual:env/client";
const url = env.VITE_API_URL;
Missing .env value fails the build
If a required variable is missing, the build fails with a validation error. Either:
- Add the value to
.env
- Mark it optional in schema:
z.string().optional()
- Provide a default:
z.string().default('fallback')
Plugin Options
ViteEnv({
configFile: "./env.ts",
serverEnvironments: ["ssr"],
onClientAccessOfServerModule: "warn",
});
Environment Variable Priority
1. process.env (CI secrets always win)
2. .env.[mode].local
3. .env.[mode]
4. .env.local
5. .env
References
- API Reference — full type inference rules, plugin hooks, and module structure