Recipes
t3-env
supports the full power of Zod meaning you can use transforms, default values etc. to create a set of powerful and flexible validation schemas for your environment variables. Below we'll look at a few example recipes for
All environment variables are strings, so make sure that the first ZodType
is a z.string()
. This will be enforced on type-level in the future.
Booleans
Coercing booleans from strings is a common use case. Below are 2 examples of how to do this, but you can choose any coercian logic you want.
Zod's default primitives coercion should not be used for booleans, since every string gets coerced to true.
export const env = createEnv({
server: {
COERCED_BOOLEAN: z
.string()
// transform to boolean using preferred coercion logic
.transform((s) => s !== "false" && s !== "0"),
ONLY_BOOLEAN: z
.string()
// only allow "true" or "false"
.refine((s) => s === "true" || s === "false")
// transform to boolean
.transform((s) => s === "true"),
},
// ...
});
export const env = createEnv({
server: {
COERCED_BOOLEAN: z
.string()
// transform to boolean using preferred coercion logic
.transform((s) => s !== "false" && s !== "0"),
ONLY_BOOLEAN: z
.string()
// only allow "true" or "false"
.refine((s) => s === "true" || s === "false")
// transform to boolean
.transform((s) => s === "true"),
},
// ...
});
Numbers
Coercing numbers from strings is another common use case.
export const env = createEnv({
server: {
SOME_NUMBER: z
.string()
// transform to number
.transform((s) => parseInt(s, 10))
// make sure transform worked
.pipe(z.number()),
// Alternatively, use Zod's default primitives coercion
// https://zod.dev/?id=coercion-for-primitives
ZOD_NUMBER_COERCION: z.coerce.number(),
},
// ...
});
export const env = createEnv({
server: {
SOME_NUMBER: z
.string()
// transform to number
.transform((s) => parseInt(s, 10))
// make sure transform worked
.pipe(z.number()),
// Alternatively, use Zod's default primitives coercion
// https://zod.dev/?id=coercion-for-primitives
ZOD_NUMBER_COERCION: z.coerce.number(),
},
// ...
});
Storybook
Storybook uses its own bundler, which is otherwise
unaware of t3-env
and won't call into runtimeEnv
to ensure that the environment
variables are present. You can use Storybook's support for defining environment
variables separately to ensure that all environment variables are actually
available for Storybook:
// .storybook/main.ts
import { env as t3Env } from "~/env/client.mjs";
const config: StorybookConfig = {
// other Storybook config...
env: (config1) => ({
...config1,
...t3Env,
})
};
export default config;
// .storybook/main.ts
import { env as t3Env } from "~/env/client.mjs";
const config: StorybookConfig = {
// other Storybook config...
env: (config1) => ({
...config1,
...t3Env,
})
};
export default config;
isServer
Determining whether the code is running on the server or client is a common requirement.
You can use the shared
section to define an environment variable that's accessible in both contexts:
export const env = createEnv({
shared: {
IS_SERVER: z.boolean().default(false),
},
runtimeEnv: {
IS_SERVER: typeof window === "undefined",
}
})
export const env = createEnv({
shared: {
IS_SERVER: z.boolean().default(false),
},
runtimeEnv: {
IS_SERVER: typeof window === "undefined",
}
})
Make sure to read the customization docs to learn how to override the isServer
value used by the library itself.