Config
@beignet/core/config validates deployment configuration and gives the app a
typed env object. Beignet apps should define server-only and client-safe
variables explicitly so secrets cannot be read from client code by accident.
bun add @beignet/core
App env
Use createEnv(...) from lib/env.ts:
import { createEnv } from "@beignet/core/config";
import { z } from "zod";
export const env = createEnv({
server: {
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
DATABASE_URL: z.string().url(),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
},
clientPrefix: "NEXT_PUBLIC_",
client: {
NEXT_PUBLIC_APP_URL: z.string().url(),
},
runtimeEnv: process.env,
});
Server variables are available on the server. Client variables must start with
clientPrefix. If a server-only key is read through the returned env object in
a client runtime, Beignet throws a descriptive error.
Server runtimes validate both server and client variables at startup. Client
runtimes validate only client variables, so a public bundle does not need server
secrets just to import the shared env object.
Strict runtime env
Some frameworks only bundle environment variables that are explicitly accessed.
Use runtimeEnvStrict to make those accesses visible:
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
},
clientPrefix: "NEXT_PUBLIC_",
client: {
NEXT_PUBLIC_APP_URL: z.string().url(),
},
runtimeEnvStrict: {
DATABASE_URL: process.env.DATABASE_URL,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
},
});
Every key validated in the current runtime must exist on runtimeEnvStrict,
even if its value is undefined. Server runtimes require all declared keys;
client runtimes require only client keys. That catches missed destructures
during build without forcing server secrets into client bundles.
Empty strings
createEnv(...) treats empty strings as undefined by default. This keeps
schema defaults working when .env contains values like:
LOG_LEVEL=
Set emptyStringAsUndefined: false when an empty string should be validated as
an actual value.
Prefix stripping
Use defineEnv(...) when you already have a whole-object schema or need prefix
stripping:
import { defineEnv } from "@beignet/core/config";
import { z } from "zod";
const appEnv = defineEnv({
prefix: "APP_",
schema: z.object({
DATABASE_URL: z.string().url(),
SECRET_KEY: z.string().min(1),
}),
});
export const config = appEnv.load();
This reads APP_DATABASE_URL and APP_SECRET_KEY, strips APP_, validates the
resulting object, and returns { DATABASE_URL, SECRET_KEY }.
Testing
Pass a custom env object instead of reading from process.env:
const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
},
runtimeEnv: {
DATABASE_URL: "postgres://localhost/test",
},
});
Provider config
Provider configuration uses the same Standard Schema helpers internally. A
provider can declare an envPrefix, and the server strips the prefix before
validating provider config:
import { createProvider } from "@beignet/core/providers";
import { z } from "zod";
createProvider({
name: "mail",
config: {
envPrefix: "MAIL_",
schema: z.object({
HOST: z.string(),
PORT: z.coerce.number().int(),
}),
},
async setup({ config }) {
// config.HOST and config.PORT are validated
},
});