Environment Variables
Create T3 App uses Zodβ for validating your environment variables at runtime and buildtime by providing some additional logic in
src/env.mjs
env.mjs
TLDR; If you want to add a new environment variable, you must add it to both your .env
src/env.mjs
This file is split into two parts - the schema and object destructuring as well as the validation logic. The validation logic should not need to be touched.
const server = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
});
const client = z.object({
// NEXT_PUBLIC_CLIENTVAR: z.string(),
});
const processEnv = {
NODE_ENV: process.env.NODE_ENV,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
};
Server Schema
Define your server-side environment variables schema here.
Make sure you do not prefix keys here with
NEXT_PUBLIC
Client Schema
Define your client-side environment variables schema here.
To expose them to the client you need to prefix them with
NEXT_PUBLIC
processEnv Object
Destruct the
process.env
We need a JavaScript object that we can parse our Zod-schemas with and due to the way Next.js handles environment variables, you canβt destruct
process.env
TypeScript will help you make sure that you have destructed all the keys from both schemas.
// β This doesn't work, we need to destruct it manually
const schema = z.object({
NEXT_PUBLIC_WS_KEY: z.string(),
});
const validated = schema.parse(process.env);
Validation Logic
For the interested reader:
Advanced: Validation logic
Depending on the environment (server or client) we validate either both or just the client schema. This means that even though the server environment variables will be undefined, they wonβt trigger the validation to fail - meaning we can have a single entrypoint for our environment variables.
const isServer = typeof window === "undefined";
const merged = server.merge(client);
const parsed = isServer
? merged.safeParse(processEnv) // <-- on server, validate all
: client.safeParse(processEnv); // <-- on client, validate only client
if (parsed.success === false) {
console.error(
"β Invalid environment variables:\n",
...formatErrors(parsed.error.format()),
);
throw new Error("Invalid environment variables");
}
Then, we use a proxy object to throw errors if you try to access a server-side environment variable on the client.
// proxy allows us to remap the getters
export const env = new Proxy(parsed.data, {
get(target, prop) {
if (typeof prop !== "string") return undefined;
// on the client we only allow NEXT_PUBLIC_ variables
if (!isServer && !prop.startsWith("NEXT_PUBLIC_"))
throw new Error(
"β Attempted to access serverside environment variable on the client",
);
return target[prop]; // <-- otherwise, return the value
},
});
Using Environment Variables
When you want to use your environment variables, you can import them from
env.mjs
import { env } from "../../env.mjs";
// `env` is fully typesafe and provides autocompletion
const dbUrl = env.DATABASE_URL;
import { env } from "../env.mjs";
// β This will throw a runtime error
const dbUrl = env.DATABASE_URL;
// β
This is fine
const wsKey = env.NEXT_PUBLIC_WS_KEY;
.env.example
Since the default
.env
.env.example
.env
Some frameworks and build tools, like Next.js, suggest that you store secrets in a
.env.local
.env
.env
.env
.gitignore
.env.example
Adding Environment Variables
To ensure your build never completes without the environment variables the project needs, you will need to add new environment variables in two locations:
π
.env
.env
KEY=VALUE
π
env.mjs
KEY: z.string()
process.env
processEnv
KEY: process.env.KEY
Optionally, you can also keep
.env.example
π
.env.example
KEY=VALUE
KEY=
Example
I want to add my Twitter API Token as a server-side environment variable
- Add the environment variable to :
.env
TWITTER_API_TOKEN=1234567890
- Add the environment variable to :
env.mjs
export const server = z.object({
// ...
TWITTER_API_TOKEN: z.string(),
});
export const processEnv = {
// ...
TWITTER_API_TOKEN: process.env.TWITTER_API_TOKEN,
};
NOTE: An empty string is still a string, so z.string()
z.string().min(1)
- Optional: Add the environment variable to , but donβt include the token
.env.example
TWITTER_API_TOKEN=