Skip to content

extforge/env

extforge/env loads .env files at build time and inlines variables prefixed EXTFORGE_PUBLIC_ into your bundles via esbuild’s define. It mirrors the conventions Vite users expect.

Files load in this precedence (later overrides earlier):

  1. .env
  2. .env.local
  3. .env.<mode> — where <mode> is production or development
  4. .env.<mode>.local
  5. process.env — overlays last

mode defaults to production. The dev server passes mode: 'development'.

Only keys starting with EXTFORGE_PUBLIC_ are inlined. Everything else stays in the build-process environment (available to plugins and config files) but is NOT shipped to the user’s browser.

.env
EXTFORGE_PUBLIC_API_BASE=https://api.example.com # ← inlined into bundles
EXTFORGE_BACKEND_TOKEN=secret-do-not-ship # ← never inlined
// User code
const url = import.meta.env.EXTFORGE_PUBLIC_API_BASE;
// ^? "https://api.example.com" (string literal at build time)

Both import.meta.env.<KEY> and process.env.<KEY> are populated for compatibility with code that reads either form.

The import.meta.env object also carries:

KeyValue
MODE'production' or 'development'
PROD'true' in prod, 'false' in dev
DEVinverse of PROD

The builder calls loadEnv automatically. You only need this if you’re writing a plugin or custom build script.

import { loadEnv, publicEnvToDefine } from 'extforge/env';
const { raw, publicEnv, files } = loadEnv({ cwd: process.cwd(), mode: 'production' });
// `raw` = every key/value (including non-public)
// `publicEnv` = only EXTFORGE_PUBLIC_*
// `files` = files actually read, in precedence order
const define = publicEnvToDefine(publicEnv, 'production');
// define is the esbuild `define` map: { 'import.meta.env': '...', 'process.env.X': '"..."' }

extforge/env doesn’t generate .d.ts files. Add your own ambient declaration:

src/env.d.ts
interface ImportMetaEnv {
readonly EXTFORGE_PUBLIC_API_BASE: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}