Vite provides built-in support for environment variables, making it easy to configure your extension for different environments (development, staging, production).
Basic Usage
Create a .env file in your project root:
VITE_API_URL=https://api.example.com
VITE_API_KEY=your-api-key
VITE_FEATURE_FLAG=true
Access them in your code:
const apiUrl = import.meta.env.VITE_API_URL
const apiKey = import.meta.env.VITE_API_KEY
const featureEnabled = import.meta.env.VITE_FEATURE_FLAG === 'true'
console.log('API URL:', apiUrl)
Only variables prefixed with VITE_ are exposed to your client-side code.
Environment Files
Vite loads environment variables from multiple files:
.env # Loaded in all cases
.env.local # Loaded in all cases, ignored by git
.env.[mode] # Only loaded in specified mode
.env.[mode].local # Only loaded in specified mode, ignored by git
Example Setup
# Defaults for all environments
VITE_APP_NAME=My Extension
# Development environment
VITE_API_URL=http://localhost:3000
VITE_DEBUG=true
# Production environment
VITE_API_URL=https://api.production.com
VITE_DEBUG=false
# Local overrides (not committed to git)
VITE_API_KEY=your-secret-key
Add to .gitignore
# Local env files
.env.local
.env.*.local
Built-in Variables
Vite provides several built-in environment variables:
import.meta.env.MODE // 'development' or 'production'
import.meta.env.DEV // true in dev, false in production
import.meta.env.PROD // false in dev, true in production
import.meta.env.BASE_URL // The base URL your app is served from
import.meta.env.SSR // true if running in server-side
Use them to conditionally enable features:
if (import.meta.env.DEV) {
console.log('Running in development mode')
// Enable debug logging
}
if (import.meta.env.PROD) {
// Enable production optimizations
}
TypeScript Support
Create type definitions for your environment variables:
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_API_KEY: string
readonly VITE_DEBUG: string
readonly VITE_FEATURE_FLAG: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
Now TypeScript will provide autocomplete and type checking:
// TypeScript knows this is a string
const apiUrl: string = import.meta.env.VITE_API_URL
// TypeScript error: Property doesn't exist
const invalid = import.meta.env.VITE_NONEXISTENT
Using in Manifest
Environment variables work in your manifest config:
import { defineManifest } from '@crxjs/vite-plugin'
import pkg from './package.json'
const isDev = process.env.NODE_ENV === 'development'
export default defineManifest({
manifest_version: 3,
name: isDev ? `[DEV] ${pkg.name}` : pkg.name,
version: pkg.version,
action: {
default_popup: 'src/popup/index.html',
},
permissions: [
'storage',
...(isDev ? ['tabs'] : []),
],
host_permissions: [
import.meta.env.VITE_API_URL || 'https://*/*',
],
})
Use process.env in manifest config files, and import.meta.env in your application code.
Environment Modes
Run Vite in different modes:
{
"scripts": {
"dev": "vite",
"dev:staging": "vite --mode staging",
"build": "vite build",
"build:staging": "vite build --mode staging",
"build:production": "vite build --mode production"
}
}
Create corresponding environment files:
.env.staging
.env.production
Configuration Example
Create a centralized config:
export const config = {
api: {
url: import.meta.env.VITE_API_URL,
key: import.meta.env.VITE_API_KEY,
timeout: 5000,
},
features: {
analytics: import.meta.env.PROD,
debug: import.meta.env.VITE_DEBUG === 'true',
},
app: {
name: import.meta.env.VITE_APP_NAME,
version: import.meta.env.VITE_APP_VERSION,
},
} as const
Use throughout your app:
import { config } from '@/config'
const response = await fetch(`${config.api.url}/users`, {
headers: {
'Authorization': `Bearer ${config.api.key}`,
},
})
if (config.features.debug) {
console.log('Response:', response)
}
Runtime Environment Variables
For values that change at runtime, use Chrome storage:
// Set at install or update
chrome.runtime.onInstalled.addListener(async () => {
await chrome.storage.local.set({
apiUrl: import.meta.env.VITE_API_URL,
installTime: Date.now(),
})
})
// Retrieve in popup or content script
const { apiUrl } = await chrome.storage.local.get('apiUrl')
console.log('Using API:', apiUrl)
Security Best Practices
Don’t Commit Secrets
Never commit sensitive data:
# NEVER commit this file
VITE_API_KEY=super-secret-key
VITE_STRIPE_KEY=sk_live_xxxxx
Client-Side Exposure
Remember: All VITE_* variables are embedded in your client code:
// This is visible to anyone who inspects your extension!
const apiKey = import.meta.env.VITE_API_KEY
Never put sensitive credentials in VITE_* variables. They’re visible in the built extension.
Use a Backend
For sensitive operations, use a backend server:
// Bad: Exposes API key
const response = await fetch('https://api.example.com', {
headers: {
'Authorization': `Bearer ${import.meta.env.VITE_API_KEY}`,
},
})
// Good: Key stays on your server
const response = await fetch('https://your-backend.com/api/proxy', {
method: 'POST',
body: JSON.stringify({ action: 'getData' }),
})
Conditional Features
Enable features based on environment:
const features = {
analytics: import.meta.env.PROD,
devTools: import.meta.env.DEV,
betaFeatures: import.meta.env.VITE_BETA === 'true',
}
if (features.analytics) {
// Initialize analytics
analytics.init()
}
if (features.devTools) {
// Expose debug helpers
window.__DEBUG__ = {
clearStorage: () => chrome.storage.local.clear(),
getStorage: () => chrome.storage.local.get(),
}
}
Loading States
Handle missing environment variables gracefully:
function getEnvVar(key: string, defaultValue?: string): string {
const value = import.meta.env[key]
if (!value && !defaultValue) {
throw new Error(`Missing required environment variable: ${key}`)
}
return value || defaultValue!
}
const apiUrl = getEnvVar('VITE_API_URL', 'https://api.example.com')
const apiKey = getEnvVar('VITE_API_KEY') // Throws if missing
Common Patterns
API Configuration
VITE_API_URL=https://api.example.com
VITE_API_VERSION=v1
VITE_API_TIMEOUT=5000
const api = {
baseUrl: import.meta.env.VITE_API_URL,
version: import.meta.env.VITE_API_VERSION,
timeout: Number(import.meta.env.VITE_API_TIMEOUT),
}
const endpoint = `${api.baseUrl}/${api.version}/users`
Feature Flags
VITE_FEATURE_NEW_UI=true
VITE_FEATURE_ANALYTICS=false
VITE_FEATURE_BETA_TOOLS=true
const features = {
newUI: import.meta.env.VITE_FEATURE_NEW_UI === 'true',
analytics: import.meta.env.VITE_FEATURE_ANALYTICS === 'true',
betaTools: import.meta.env.VITE_FEATURE_BETA_TOOLS === 'true',
}
Debug Configuration
VITE_DEBUG=true
VITE_LOG_LEVEL=verbose
VITE_SOURCE_MAPS=true
if (import.meta.env.VITE_DEBUG === 'true') {
console.log('Debug mode enabled')
// Enable verbose logging
}
Next Steps