create-t3-app
create-t3-app copied to clipboard
feat: extend process.env types as well from envSchemas
Is your feature request related to a problem? Please describe.
Currently in order to get autocompletion and type-safety there's the clientEnv and serverEnv which we can import to use our environment variables.
I don't dislike this approach but I believe they're a better developer experience if we allow to use process.env instead, or at least improve it through extending global types.
Describe the solution you'd like to see
We can probably extend it just like this.
// env.d.ts
import { clientSchema, serverSchema } from "./schema.mjs";
import { z } from 'zod';
declare global {
namespace NodeJS {
interface ProcessEnv extends z.infer<typeof clientSchema & typeof serverSchema> {}
}
}
Desribe alternate solutions
Due to separation of concerns in NextJS server and client variables this might not be the best approach, but rather an alternative to import env variables from files.
Not really sure how can we achieve the same with process.env but if someone have any idea that would be a plus.
Additional information
No response
I like the idea of this as I think many users are probably just ignoring the typesafe env vars and using process.env instead because that's what they're used to. This way type safety would be the default.
I tried your code snippet and didn't get autocomplete on environment variables. Do you have a working sample app where you implemented this?
I tried your code snippet and didn't get autocomplete on environment variables. Do you have a working sample app where you implemented this?
Sure, it worked just fine for me here with this repo. Just a brand new t3-app that has that file.
Then I can see the autocomplete.

Can confirm this works, not sure I was doing wrong earlier.
Looks great. Do you want to open a PR for it? I think it should be reviewed by as many people as possible because this is such a core part of the app, but if it works without caveats then I think it's a big win.
I think it should be tested a bit more.
Totally open to make a PR about it. But for example in that specific example, we can see that NEXTAUTH_URL still has the string | undefined type. Probably due to checking it with z.string().url().
I'll give it a look tomorrow a bit more and I'll open a PR or update what I find here. 😄
Another downside I can see is that you get autocomplete on all env variables everywhere, even though they don't exist everywhere (ie non-NEXT_PUBLIC in the frontend). This is a nice thing that was solved by splitting it into server and client.
But the existing schema would still remain of course.
Assuming there are no other major issues, is type safety and autocomplete on the more familiar process.env worth the confusion/overhead of having two different things? I'm leaning yes but would be interested in other opinions.
Won't work for things that are using .tranform().
With z.input<x> it could be quite nice DX but the file isn't guaranteed to have been loaded which means it's also not guaranteed to be correct
I wonder, shouldn't that be taken care by the files being imported inside next.config.js ?
Oh man, I've sure let this one slip over my todos. Any chance we can give this one another look ?
Some updates on this.
Currently there's on issue regarding new env vars introduced into our serverSchema.
If we extend our Process.env with a declaration file like this one.
// env.d.ts
import type { clientSchema, serverSchema } from "./schema.mjs";
import type { z } from "zod";
declare global {
namespace NodeJS {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ProcessEnv
extends z.infer<typeof clientSchema & typeof serverSchema> {}
}
}
There's a issue on our serverSchema variable due to apparently 'serverSchema' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
export const serverSchema = z.object({
NEXTAUTH_SECRET:
process.env.NODE_ENV === "production" // <-- THIS BREAKS
? z.string().min(1)
: z.string().min(1).optional(),
NEXTAUTH_URL: z.preprocess(
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
// Since NextAuth.js automatically uses the VERCEL_URL if present.
(str) => process.env.VERCEL_URL ?? str, // <-- THIS BREAKS.
// VERCEL_URL doesn't include `https` so it cant be validated as a URL
process.env.VERCEL ? z.string() : z.string().url() // <-- THIS BREAKS.
),
});
Weirdly enough, If I remove these lines and leave the variable without NEXTAUTH_SECRET and NEXTAUTH_URL they still appear on the process.env type signature !
If anyone can also try it out on their machine with the following steps It'd be much appreciated.
- Bootstrap a new ct3a project.
- Create a
env.d.tsfiles inside theenvfolder with the contents above. - Test the
process.envtype signature on any TS file.
What are your guy's thoughts on this implementation? https://github.com/JacobADevore/next-validenv
I'm still getting error even after adding the interface extends
src/env.ts:16:15 - error TS2411: Property 'PORT' of type 'number' is not assignable to 'string' index type 'string | undefined'.
16 interface ProcessEnv extends Env {}
~~~~~~~~~~