Environment Variables and Modes in Vite
Learn how Vite loads, exposes, types, and uses environment variables and modes in browser, SSR, and configuration code.
Vite environment variables let a project receive values from the shell or dotenv files without hard-coding every deployment setting into source code. Common examples include a public API base URL, a feature flag, or an application title.
This topic assumes basic JavaScript modules, command-line usage, development versus production builds, and the security difference between browser code and trusted server-side code.
Build-time values versus runtime configuration
Vite normally handles environment variables at build time. During development, Vite reads the environment and replaces references such as import.meta.env.VITE_API_BASE_URL. During a production build, it statically replaces those references in the generated client assets.
This differs from runtime configuration. A runtime value is read after the application has been built, such as from a server-rendered response, an API endpoint, or a configuration file served by a backend. A static Vite client bundle cannot safely receive a new private value merely because the hosting server has a different process environment; the value must be supplied during the build unless the application implements a separate runtime configuration mechanism.
Because public values are substituted into client code, users can inspect them in browser developer tools or downloaded assets. A prefix prevents accidental exposure, but a prefix is not encryption or access control.
The import.meta.env object
import.meta.env is a Vite-provided object containing built-in metadata and approved client environment variables. Use it instead of process.env in browser application code.
| Property | Value type | Meaning | Typical use |
|---|---|---|---|
MODE | string | The active Vite mode, such as development, production, or a custom mode. | Select mode-specific behavior or assets. |
BASE_URL | string | The public base path configured for the application. | Build URLs that work when the app is served below a path. |
PROD | boolean | True when Vite is running a production build. | Disable diagnostics or development helpers. |
DEV | boolean | True when Vite is running in development. | Enable verbose logs or local debugging tools. |
SSR | boolean | Indicates server-side rendering execution. | Separate browser-only and server-only code paths. |
For example:
if (import.meta.env.DEV) {
console.debug('Extra diagnostics enabled');
}
if (import.meta.env.PROD) {
startProductionMonitoring();
}Vite knows the values of built-in flags during transformation. When a condition uses a statically analyzable flag, the production bundler can perform dead-code elimination: it removes branches that can never run in the resulting build.
Environment files
A dotenv file contains declarations in KEY=value form. Vite recognizes shared, local, mode-specific, and mode-specific local files:
.envapplies in every mode..env.localapplies in every mode and is intended for machine-specific values..env.[mode]applies only to the selected mode, such as.env.staging..env.[mode].localapplies only to the selected mode and is intended for local overrides.
A typical staging setup may contain:
# .env
VITE_API_BASE_URL=https://api.example.test
# .env.staging
VITE_API_BASE_URL=https://staging-api.example.test
# .env.staging.local
VITE_DEBUG_PANEL=trueWhen the same variable appears in several files, the more specific source wins. An already-existing shell or process environment variable has higher precedence than values loaded from files.
| File or source | Applies to | Typical purpose | Precedence |
|---|---|---|---|
| Existing shell or process environment | Any command receiving that variable | CI, hosting platform, or deployment-specific overrides | Highest |
.env.[mode].local | One mode on one machine | Private local override for that mode | Higher than other dotenv files |
.env.[mode] | One mode | Shared staging or testing settings | Below mode-local values |
.env.local | All modes on one machine | Developer-specific shared defaults | Below mode-specific values |
.env | All modes | Committed project defaults | Lowest dotenv precedence |
Local files commonly contain credentials, personal ports, or machine-specific settings, so add them to version control ignores. Commit a safe example file such as .env.example with placeholder values and documented variable names.
Modes
A mode is a named build or development context. The selected mode determines which mode-specific dotenv files Vite loads.
vitestarts the development server with the defaultdevelopmentmode.vite buildcreates a production-oriented build with the defaultproductionmode.vite --mode stagingstarts development using thestagingmode.vite build --mode stagingcreates a build using thestagingmode.
For example, vite build --mode staging loads shared files and the staging files, including .env.staging and .env.staging.local when present. The active name is available as import.meta.env.MODE.
Vite mode is not necessarily the same as NODE_ENV. Mode selects Vite's named environment-file context. NODE_ENV is a conventional process setting used by many tools to describe development or production behavior.
| Concept | What it controls | How it is selected | Common values |
|---|---|---|---|
| Vite mode | Vite's named context and mode-specific dotenv files | Default command behavior or --mode | development, production, staging |
NODE_ENV | A conventional environment setting consumed by Node-based tools and application logic | Shell, scripts, hosting platform, or tool configuration | development, production, test |
Which variables reach browser code?
By default, only environment variables whose names begin with VITE_ are exposed through import.meta.env. For example:
# .env
VITE_API_BASE_URL=https://api.example.test
PAYMENT_PROVIDER_SECRET=private-valueconst apiBaseUrl = import.meta.env.VITE_API_BASE_URL;VITE_API_BASE_URL is available to browser code because it uses the default VITE_ prefix. PAYMENT_PROVIDER_SECRET is not available through import.meta.env because it has no allowed public prefix.
The envPrefix configuration option can allow additional prefixes:
import { defineConfig } from 'vite';
export default defineConfig({
envPrefix: ['VITE_', 'PUBLIC_']
});After this configuration, names beginning with PUBLIC_ are also client-exposed. Never set the public prefix to an empty string: that can expose essentially all environment variables, including secrets.
| Variable example | Prefix status | Available in browser | Security guidance |
|---|---|---|---|
VITE_PUBLIC_API_URL | Allowed by default | Yes | Safe only if the URL itself is intended to be public. |
DATABASE_URL | No public prefix | No | Keep it in trusted server-side code. |
VITE_ANALYTICS_KEY | Allowed by default | Yes | Use only when the provider identifies it as a public browser key. |
PAYMENT_SECRET | No public prefix | No | Never copy it into client code or a client-exposed define value. |
Using variables in application code
Access public values with statically written property names:
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
const featureFlag = import.meta.env.VITE_FEATURE_FLAG;Values read from dotenv files are strings. Therefore, the text false is a nonempty string and is truthy in JavaScript:
const enabled = import.meta.env.VITE_FEATURE_FLAG === 'true';
const retryLimit = Number.parseInt(import.meta.env.VITE_RETRY_LIMIT ?? '3', 10);Property access should be statically analyzable. A direct expression such as import.meta.env.VITE_API_BASE_URL is reliable. Dynamically constructing a property name with bracket access, such as import.meta.env[key], may not be replaced or exposed as expected because Vite cannot determine the requested variable during transformation. If dynamic selection is needed, explicitly build a map from statically named properties.
Vite reads environment files when it starts. Restart the development server after changing a dotenv file. A browser refresh alone does not reload the changed values.
Variable expansion in dotenv files
Environment files can reference other variables. This is useful when one value is derived from another:
VITE_HOST=api.example.test
VITE_API_URL=https://${VITE_HOST}/v1Expansion depends on the dotenv expansion implementation and its compatibility rules. Define referenced variables before the variables that use them, and avoid reverse-order references, circular references, and ambiguous naming. Explicit ordering makes the files easier to understand and more portable across tools.
Type support with TypeScript
Vite supplies default TypeScript declarations for ImportMetaEnv, including built-in properties and the general shape of import.meta.env. Project-specific public variables can be added with interface augmentation.
For example, place this in a declaration file included by the project, such as a Vite client environment declaration file:
interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string;
readonly VITE_FEATURE_FLAG: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}The declaration file should not contain imports when using this ambient interface-augmentation pattern. Also verify that the file is included by the relevant TypeScript configuration.
Typing a variable as string documents its raw form; it does not parse the value or guarantee that the variable exists at runtime. For stricter projects, declare each permitted public name explicitly and use a TypeScript configuration that checks the declaration file. This provides autocomplete and catches misspelled names, while runtime validation can still be added for required values.
Environment values in HTML
Vite can replace public environment placeholders in HTML entry files. HTML uses a placeholder format rather than JavaScript's import.meta.env syntax:
<title>%VITE_APP_TITLE%</title>For a value such as VITE_APP_TITLE=Customer Portal, Vite replaces the placeholder while processing the HTML. In JavaScript, the equivalent access is import.meta.env.VITE_APP_TITLE.
If an HTML environment variable is missing, Vite replaces the placeholder with an empty string. Check required values during development or in the build pipeline if an empty result would produce an invalid page.
Loading environment values in Vite configuration
Environment files are not automatically available through import.meta.env while Vite is evaluating vite.config. Configuration code runs early because Vite needs the configuration to determine how to process the application.
Use Vite's loadEnv utility when configuration behavior depends on a mode-specific value:
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
define: {
__APP_ENV__: JSON.stringify(env.APP_ENV)
}
};
});The first argument selects the active mode, the second identifies the project root, and the third is the prefix filter. Passing '' loads all matching environment values, including values without VITE_. That is useful for configuration logic but requires care: loading a value into the configuration does not make it safe to send to the browser.
To load only public-style values, pass a prefix such as 'VITE_'. Reading process.env directly reads values already supplied by the shell or process. It does not replace explicitly loading the dotenv files needed by configuration.
Security and deployment practices
- Keep API keys, private access tokens, database credentials, payment provider secrets, and signing secrets on trusted servers.
- Use server-side code, a platform secret store, or a backend proxy for sensitive operations.
- Expose only values that a browser genuinely needs, such as a public API origin or a provider-designated browser key.
- Commit a safe example file with placeholder values, and ignore developer-specific local files.
- Review generated client assets when changing prefixes or configuration mappings.
- Provide appropriate build-time values for each deployment environment. A staging build and a production build should not accidentally share endpoints or feature settings.
Practical patterns
Public API base URL
# .env
VITE_API_BASE_URL=https://api.example.testconst response = await fetch(
`${import.meta.env.VITE_API_BASE_URL}/users`
);The URL is public because it is bundled into browser code. Authentication credentials, if required, must still be handled safely.
Development-only diagnostics
if (import.meta.env.DEV) {
enableVerboseLogging();
}The static development condition allows production builds to remove the diagnostics branch through dead-code elimination.
Staging mode
vite build --mode stagingVite uses .env, .env.staging, and available local variants. Mode-specific values override shared values, subject to process-environment precedence.
Keeping a secret private
# Used only by trusted server-side code
PAYMENT_PROVIDER_SECRET=private-valueDo not read this value from browser code. Implement the payment operation on a server or through a protected backend endpoint.
Troubleshooting
A variable is undefined in browser code
- Confirm that the name begins with an allowed prefix, normally
VITE_. - Confirm the active mode and the exact filename, such as
.env.staging. - Restart Vite after editing an environment file.
- Use
import.meta.env, notprocess.env, in browser code.
A dotenv value does not override a terminal value
The shell or process value has higher precedence. Update or unset that value, or account for it in the deployment script.
A secret appears in the client bundle
The secret may have received an allowed public prefix or been copied into a client-exposed define value. Remove the exposure, rotate the credential, and move the operation to trusted server-side infrastructure.
Configuration cannot read a dotenv value
Call loadEnv(mode, process.cwd(), prefix) inside the configuration factory. Do not expect ordinary application injection to have happened before the configuration is evaluated.
A boolean flag is true when its file says false
Dotenv values are strings. Compare explicitly with 'true' or parse the value before using it:
const isEnabled = import.meta.env.VITE_FEATURE_FLAG === 'true';TypeScript rejects a custom environment property
Add the property to an ImportMetaEnv augmentation, ensure the declaration file contains no imports for this ambient pattern, and verify that the file is included by tsconfig.
Exam-relevant summary
import.meta.envcontains Vite metadata and approved client variables.MODEselects the named Vite context; it is distinct fromNODE_ENV.- Development defaults to
developmentmode, whilevite builddefaults toproductionmode. - Only the default
VITE_prefix, or prefixes configured withenvPrefix, are exposed to browser code. - All client-exposed values are public, regardless of their name or intended use.
- Dotenv values are strings and should be parsed when used as booleans or numbers.
- Restart Vite after changing environment files.
- Use
loadEnvwhen Vite configuration needs dotenv values. - Use TypeScript
ImportMetaEnvaugmentation to describe project-specific public variables.
For related material, see Vite environment variables and modes.