bunderstackbunderstack/ docs

Environment Validation

Standard Schema validation for server and browser environment values

Bunderstack validates environment values before the application starts. Server-only and public values stay separate, while app.env and procedure contexts remain fully typed.

Configure schemas

Each entry accepts Standard Schema. This example uses Valibot:

import * as v from 'valibot'

export const env = {
  server: {
    STRIPE_API_KEY: v.string(),
    WEBHOOK_SECRET: v.string(),
  },
  client: {
    PUBLIC_APP_URL: v.pipe(v.string(), v.url()),
    PUBLIC_SENTRY_DSN: v.optional(v.string()),
  },
}

export const backend = bunderstack({ schema, database, env })
export const app = await backend.start()

Server keys must not start with PUBLIC_; browser-safe keys must. Naming violations and invalid values produce a BunderstackEnvError with all issues, not only the first one.

Describe keys for deployment

Hosting platforms read the committed blueprint to work out what an environment needs before the first deploy. meta adds value-free metadata per key:

export const env = {
  server: {
    STRIPE_API_KEY: v.string(),
    LOG_LEVEL: v.optional(v.string()),
  },
  client: {
    PUBLIC_APP_URL: v.pipe(v.string(), v.url()),
  },
  meta: {
    STRIPE_API_KEY: { description: 'Secret key from the Stripe dashboard' },
    LOG_LEVEL: { sensitive: false, description: 'debug | info | warn | error' },
  },
}

Server keys are treated as secrets by default and client keys never are — a PUBLIC_* value is compiled into the browser bundle, so declaring one sensitive is an error. Descriptions are static prose, at most 200 characters.

Values never reach the blueprint. Only the key name, whether it is required, its scope, its secrecy, and its description do.

Use validated values

app.env.STRIPE_API_KEY
app.env.PUBLIC_APP_URL

api: (o) => ({
  publicConfig: o.public.handler(({ context }) => ({
    appUrl: context.env.PUBLIC_APP_URL,
  })),
})

Browser values

createClientEnv() validates only the client section. Server keys become runtime traps if code tries to access them in a browser bundle.

import { createClientEnv } from 'bunderstack/env'
import { env } from './env-schema'

export const clientEnv = createClientEnv({
  ...env,
  runtimeEnv: import.meta.env,
})

Built-in values

Bunderstack also understands database, auth, Redis, email, and storage variables used by its own facilities. Production requires a secure AUTH_SECRET. Database URLs have adapter-specific development defaults; Redis is required when separate worker processes publish realtime changes.

import { BunderstackEnvError } from 'bunderstack'

try {
  await bunderstack({ schema, database, env }).start()
} catch (error) {
  if (error instanceof BunderstackEnvError) {
    console.error(error.issues)
  }
}

On this page