Chrome Extension Environment Variables — Developer Guide
5 min readEnvironment Variables in Chrome Extensions
Chrome extensions run in a browser context where Node.js APIs like process.env are unavailable. Unlike traditional web applications, extensions cannot read .env files at runtime. This guide covers the patterns for managing configuration and environment variables throughout the extension development lifecycle.
Table of Contents
- The Core Challenge
- Build-Time Injection
- Vite Configuration
- Webpack Configuration
- Conditional Code Paths
- Multiple Environments
- API Key Management
- Runtime Configuration
- Extension ID Differences
- CI/CD Integration
The Core Challenge
Extensions load in the browser with no access to Node.js runtime:
// This will NOT work in extension contexts
console.log(process.env.API_KEY); // undefined
You must inject environment variables at build time, or read them from storage at runtime. Build-time injection is preferred for values that are constant across all users.
Build-Time Injection
Vite: Using define
Vite provides the define option to replace strings at build time:
// vite.config.js
import { defineConfig } from 'vite';
import chromeExtension from 'vite-plugin-chrome-extension';
export default defineConfig({
plugins: [chromeExtension()],
define: {
'import.meta.env.VITE_API_URL': JSON.stringify(process.env.VITE_API_URL),
'import.meta.env.VITE_API_KEY': JSON.stringify(process.env.VITE_API_KEY),
'import.meta.env.DEV': JSON.stringify(process.env.NODE_ENV !== 'production'),
},
});
Vite: Using .env Files
Vite automatically loads variables from .env files prefixed with VITE_:
# .env.development
VITE_API_URL=http://localhost:3000
VITE_DEBUG=true
// Any extension script
if (import.meta.env.VITE_DEBUG) {
console.log('[Dev] Debug logging enabled');
}
Webpack: DefinePlugin
Webpack’s DefinePlugin replaces global constants:
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify(process.env.API_URL),
'process.env.API_KEY': JSON.stringify(process.env.API_KEY),
'process.env.DEBUG': JSON.stringify(process.env.NODE_ENV === 'development'),
}),
],
};
Rollup: @rollup/plugin-replace
// rollup.config.js
import replace from '@rollup/plugin-replace';
export default {
plugins: [
replace({
preventAssignment: true,
'process.env.API_URL': JSON.stringify(process.env.API_URL),
}),
],
};
Conditional Code Paths
Use environment flags to include or exclude code based on the build:
// Development-only logging
if (import.meta.env.DEV || process.env.NODE_ENV === 'development') {
console.log('[Extension] Initialized with config:', config);
}
// Production-only feature flags
const FEATURE_PREMIUM = import.meta.env.VITE_ENABLE_PREMIUM === 'true';
if (FEATURE_PREMIUM) {
// Load premium features only in production builds
}
Multiple Environments
Create separate .env files for each environment:
.env # Shared defaults
.env.development # Local dev (loaded by default with vite)
.env.staging # Staging builds
.env.production # Production builds
# Build for specific environment
VITE_API_URL=https://staging.api.com vite build
API Key Management
Never commit API keys to version control.
- Add
.envto.gitignore:
.env
.env.local
.env.*.local
- Create
.env.examplewith placeholder values:
# .env.example
VITE_API_KEY=your_api_key_here
VITE_API_URL=https://api.example.com
- Document required variables in your README or CONTRIBUTING guide.
Runtime Configuration
For user-specific or sensitive values, use the options page:
// options.js - save user-provided API key
document.getElementById('save-btn').addEventListener('click', () => {
const apiKey = document.getElementById('api-key').value;
chrome.storage.local.set({ userApiKey: apiKey });
});
// background.js - read at runtime
async function getApiKey() {
const { userApiKey } = await chrome.storage.local.get('userApiKey');
return userApiKey || import.meta.env.VITE_API_KEY; // Fallback to build-time key
}
Extension ID Differences
The extension ID changes between development (unpacked) and production (Chrome Web Store):
// Get current extension ID
const extensionId = chrome.runtime.id;
// Dev: "abcdefghijklmnopqrstuvwxyz123456"
// Prod: "a1b2c3d4e5f6g7h8i9j0"
Handle different IDs in your configuration:
const isDev = chrome.runtime.id.length < 32; // Dev IDs are longer
const redirectUri = isDev
? 'http://localhost:3000/callback'
: 'https://your-app.com/callback';
CI/CD Integration
Inject secrets from CI environment variables:
# GitHub Actions example
- name: Build Extension
env:
VITE_API_KEY: ${{ secrets.API_KEY }}
VITE_API_URL: ${{ secrets.API_URL }}
run: npm run build
# Netlify example
[build.environment]
VITE_API_KEY = "@my-api-key-secret"
See Also
Related Articles
Related Articles
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.