Prefixes & Client Exposure
Learn how environment variable prefixes work and which variables are exposed to the client.
What are Prefixes?
Environment variable prefixes determine which variables are exposed to the browser and which are kept server-side. The prefix tells Vite and Bini.js how to handle each variable.
BINI_ and VITE_ prefixes are exposed to the browser by default. Variables without a prefix are never exposed to the client.BINI_ Prefix
BINI_ is the default prefix for client-side environment variables in Bini.js. These variables are exposed to the browser via import.meta.env.
BINI_* variables are bundled into your client-side JavaScript. Never put secrets in BINI_* variables.VITE_ Prefix
VITE_ is Vite's standard prefix for client-side environment variables. Any variable starting with VITE_ is exposed to the browser.
VITE_* and BINI_* work exactly the same way. Both are exposed to the browser. Choose whichever you prefer.No Prefix (Secrets)
Variables without a prefix are never exposed to the browser. They are only accessible server-side via getEnv(ctx, key) in API routes.
BINI_* or VITE_* for sensitive data.Custom Prefixes
You can add custom prefixes to expose additional variables to the client:
| Prefix | Exposed to browser |
|---|---|
| BINI_ | Yes (default) |
| VITE_ | Yes (default) |
| PUBLIC_ | Yes (custom) |
| MY_APP_ | Yes (custom) |
| No prefix | No |
Client-Side Access
Client-side variables are accessed via import.meta.env in any component:
import.meta.env is available in all client-side code including pages, components, and MDX files.Server-Side Access
Server-side variables are accessed via getEnv(ctx, key) and requireEnv(ctx, key) in API routes:
| Access Method | Where | Variables |
|---|---|---|
| import.meta.env | Client components | BINI_, VITE_, custom prefixes |
| getEnv(ctx, key) | API routes | All variables (including no prefix) |
| requireEnv(ctx, key) | API routes | All variables (throws if missing) |
getEnv vs requireEnv
Both getEnv and requireEnv read environment variables from the Hono request context, but they behave differently:
| Feature | getEnv(ctx, key) | requireEnv(ctx, key) |
|---|---|---|
| Returns | string | undefined | string |
| On missing | Returns undefined | Throws error immediately |
| Use case | Optional configuration with defaults | Required configuration |
| Default pattern | getEnv(ctx, 'KEY') ?? 'default' | requireEnv(ctx, 'KEY') |
| Error handling | Manual check for undefined | Try/catch or let it bubble |
| When to use | Feature flags, optional settings | Database URLs, API keys, credentials |
requireEnv for critical configuration that your app cannot function without. Use getEnv with ?? defaults for optional configuration.On terminal failure: requireEnv logs a descriptive error to the terminal:
Security Best Practices
- •Never prefix secrets — Use no prefix for database URLs, API keys, and tokens.
- •Use BINI_ or VITE_ for public config — Use these for non-sensitive configuration like API URLs.
- •Use requireEnv for critical values — Fail fast when required configuration is missing.
- •Use getEnv with defaults for optional values — Keep your app flexible with sensible defaults.
- •Never expose secrets in responses — Don't return secret values from API routes.
- •Use .env.example — Document required variables without committing actual values.
- •Keep .env in .gitignore — Never commit environment files with secrets.