How to Access and Use Vite Env Variables in vite.config.js for Multi-Environment Builds

You can access Vite env variables inside vite.config.js by importing the loadEnv function from the vite package, which synchronously parses .env* files based on the current mode and returns an object containing environment variables that you can use to conditionally configure plugins, server settings, and build options.

Managing different build configurations requires a flexible way to inject environment-specific values into your build pipeline. The vitejs/vite repository provides a built-in environment system that allows you to read variables from .env files directly within your configuration file using Node.js APIs. By leveraging loadEnv alongside import.meta.env, you can create distinct behaviors for development and production while keeping sensitive data out of client bundles.

Understanding Vite's Two-Stage Environment System

Vite processes environment variables in two distinct stages, each serving a different purpose in the build lifecycle.

Server-side (Config Loading): During config evaluation, Vite exposes loadEnv, implemented in [packages/vite/src/node/env.ts](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/env.ts). This function reads .env, .env.[mode], and .env.[mode].local files, expands variable references, and returns a Record<string, string> object.

Client-side (Runtime): Variables prefixed with VITE_ are statically injected into import.meta.env at build time. This object also includes three boolean flags: import.meta.env.DEV, import.meta.env.PROD, and the string import.meta.env.MODE.

Because vite.config.js executes in Node.js, you can access both process.env (raw Node environment) and the values returned by loadEnv. The latter is preferred when you need values from .env files to influence configuration logic.

Loading Variables with loadEnv

The loadEnv function is the primary API for accessing Vite env variables inside your configuration file. According to the JavaScript API documentation, it accepts three arguments:

loadEnv(mode: string, cwd: string, prefix = 'VITE_')
  • mode: The current Vite mode (development, production, or custom values passed via --mode)
  • cwd: The working directory, typically process.cwd()
  • prefix: A filter string; only keys starting with this prefix are returned. Defaults to 'VITE_'. Pass an empty string to retrieve all variables.

Vite automatically loads .env files in the following order, as documented in [docs/guide/env-and-mode.md](https://github.com/vitejs/vite/blob/main/docs/guide/env-and-mode.md):

  1. .env – loaded in all cases
  2. .env.local – loaded in all cases, ignored by version control
  3. .env.[mode] – loaded only for the specific mode
  4. .env.[mode].local – loaded last, overriding previous values

Accessing Vite Env Variables in vite.config.js

To use environment variables for build configuration, destructure mode from the config function parameter and pass it to loadEnv:

// vite.config.js
import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ command, mode }) => {
  // Load only VITE_* variables by default
  const env = loadEnv(mode, process.cwd())
  
  // Access variables to configure the dev server
  const port = Number(env.VITE_DEV_PORT) || 3000
  
  return {
    server: {
      port: port,
    },
    define: {
      // Inject build-time constants
      __APP_VERSION__: JSON.stringify(env.VITE_APP_VERSION),
    },
  }
})

For variables that should never reach the client (such as private API keys used only during the build process), you can still use process.env directly. However, loadEnv is required to access values defined in .env files.

Managing Development vs Production Builds

The mode parameter allows you to create divergent configurations for different environments without maintaining separate config files.

// vite.config.js
import { defineConfig, loadEnv } from 'vite'
import compress from 'vite-plugin-compress'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())
  const isProd = mode === 'production'

  return {
    plugins: [
      // Only enable compression plugin in production
      isProd && compress(),
    ].filter(Boolean),
    
    build: {
      // Adjust output directory based on env variable
      outDir: env.VITE_BUILD_DIR || 'dist',
    },
  }
})

When running vite (dev server), mode defaults to development. When running vite build, it defaults to production. You can override this with the --mode flag:

vite build --mode staging

This command loads .env.staging and sets mode to "staging", allowing you to test staging-specific variables locally.

Exposing Variables to Client-Side Code

Only environment variables prefixed with VITE_ are exposed to your application code via import.meta.env. This security measure prevents accidental leakage of sensitive credentials.

// src/main.ts
if (import.meta.env.DEV) {
  console.log('Development mode active')
}

// Access the variable defined in .env as VITE_API_URL
fetch(`${import.meta.env.VITE_API_URL}/users`)

Attempting to access non-prefixed variables (e.g., import.meta.env.SECRET_KEY) returns undefined in the browser, even if they exist in your .env file.

Accessing Non-Prefixed Variables for Build Logic

Sometimes you need to read variables that do not start with VITE_ to control build behavior, but you do not want to expose them to the client. Pass an empty string as the third argument to loadEnv to retrieve all variables:

// vite.config.js
import { defineConfig, loadEnv } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig(({ mode }) => {
  // Load ALL env variables, not just VITE_*
  const raw = loadEnv(mode, process.cwd(), '')
  
  const shouldAnalyze = raw.ANALYZE === 'true'
  const baseUrl = raw.BASE_URL ?? '/'

  return {
    base: baseUrl,
    plugins: [
      // Conditional plugin based on non-VITE variable
      shouldAnalyze && visualizer(),
    ].filter(Boolean),
  }
})

Be cautious when using this approach. Variables loaded without the VITE_ prefix are intended for Node.js consumption only and should not be passed to define or other options that inject values into the client bundle.

Summary

  • Use loadEnv(mode, process.cwd()) in vite.config.js to synchronously read .env* files based on the current mode, as implemented in packages/vite/src/node/env.ts.
  • Reference env.VITE_* to access variables that will also be available in your client code via import.meta.env.
  • Filter by mode (development, production, or custom) to conditionally enable plugins, adjust server settings, or modify build outputs.
  • Use an empty prefix (loadEnv(mode, process.cwd(), '')) to access non-prefixed variables for Node-only build logic, keeping secrets out of the browser bundle.
  • Prefer process.env only for CI/CD secrets that exist solely in the Node environment and are not defined in .env files.

Frequently Asked Questions

Why are my environment variables undefined in import.meta.env?

Vite only exposes environment variables prefixed with VITE_ to client-side code to prevent accidental leakage of secrets. Ensure your variable names start with VITE_ (e.g., VITE_API_URL). If you need to access non-prefixed variables, use loadEnv with an empty string prefix inside vite.config.js, but do not inject these into the client bundle.

Can I use process.env instead of loadEnv in my config file?

Yes, process.env is available in vite.config.js and contains the raw Node.js environment. However, process.env does not automatically load values from .env files. Use loadEnv when you need variables defined in .env, .env.development, or .env.production to affect your build configuration.

How do I load all environment variables without the VITE_ prefix?

Pass an empty string as the third argument to loadEnv: loadEnv(mode, process.cwd(), ''). This returns all variables regardless of prefix. Use this for build-time logic (like enabling analysis plugins), but never expose these variables to the client via the define option, as they may contain sensitive information.

What is the difference between mode and NODE_ENV?

The mode parameter is Vite-specific and determines which .env files are loaded (e.g., .env.production when mode is production). While NODE_ENV is a Node.js convention, Vite uses mode to control both environment file loading and the import.meta.env.DEV/PROD flags. You can set a custom mode (like staging) using --mode staging, which is independent of NODE_ENV.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →