├── .config ├── .oxfmtrc.json ├── turbo.jsonc └── .oxlintrc.json ├── examples ├── nextjs │ ├── .env │ ├── app │ │ ├── page.tsx │ │ ├── api │ │ │ ├── node │ │ │ │ └── route.ts │ │ │ └── edge │ │ │ │ └── route.ts │ │ ├── node │ │ │ └── page.tsx │ │ ├── edge │ │ │ └── page.tsx │ │ ├── env.ts │ │ ├── layout.tsx │ │ └── globals.css │ ├── next.config.ts │ ├── tsconfig.json │ └── package.json ├── astro-zod │ ├── .env │ ├── astro.config.mjs │ ├── src │ │ ├── counter.tsx │ │ ├── pages │ │ │ └── index.astro │ │ └── t3-env.ts │ ├── tsconfig.json │ └── package.json ├── astro-arktype │ ├── .env │ ├── astro.config.mjs │ ├── src │ │ ├── counter.tsx │ │ ├── pages │ │ │ └── index.astro │ │ └── t3-env.ts │ ├── tsconfig.json │ └── package.json └── astro-valibot │ ├── .env │ ├── astro.config.mjs │ ├── src │ ├── counter.tsx │ ├── pages │ │ └── index.astro │ └── t3-env.ts │ ├── tsconfig.json │ └── package.json ├── docs ├── public │ ├── favicon.ico │ └── opengraph-image.png ├── src │ ├── styles │ │ ├── calsans.ttf │ │ └── globals.css │ ├── lib │ │ └── cn.ts │ ├── components │ │ ├── layout.tsx │ │ ├── tailwind-indicator.tsx │ │ ├── theme-toggle.tsx │ │ ├── ui │ │ │ ├── popover.tsx │ │ │ ├── scroll-area.tsx │ │ │ └── button.tsx │ │ ├── main-nav.tsx │ │ ├── mdx │ │ │ ├── code-block.tsx │ │ │ └── callout.tsx │ │ ├── site-header.tsx │ │ ├── sidebar.tsx │ │ ├── mobile-nav.tsx │ │ └── icons.tsx │ └── app │ │ ├── docs │ │ ├── layout.tsx │ │ ├── recipes │ │ │ └── page.mdx │ │ ├── nuxt │ │ │ └── page.mdx │ │ ├── standard-schema │ │ │ └── page.mdx │ │ ├── introduction │ │ │ └── page.mdx │ │ ├── nextjs │ │ │ └── page.mdx │ │ ├── core │ │ │ └── page.mdx │ │ └── customization │ │ │ └── page.mdx │ │ ├── site-config.ts │ │ ├── page.tsx │ │ └── layout.tsx ├── postcss.config.js ├── tsconfig.json ├── package.json ├── next.config.ts └── mdx-components.tsx ├── .github ├── funding.yml ├── setup │ └── action.yml └── workflows │ ├── ci.yml │ ├── release.yml │ └── release-canary.yaml ├── packages ├── core │ ├── tsconfig.json │ ├── jsr.json │ ├── README.md │ ├── package.json │ └── src │ │ ├── standard.ts │ │ ├── presets.ts │ │ ├── presets-valibot.ts │ │ ├── presets-zod.ts │ │ └── presets-arktype.ts ├── nextjs │ ├── tsconfig.json │ ├── src │ │ ├── presets-zod.ts │ │ ├── presets-arktype.ts │ │ ├── presets-valibot.ts │ │ └── index.ts │ ├── jsr.json │ ├── package.json │ ├── README.md │ └── test │ │ └── smoke.test.ts └── nuxt │ ├── tsconfig.json │ ├── src │ ├── presets-zod.ts │ ├── presets-arktype.ts │ ├── presets-valibot.ts │ └── index.ts │ ├── jsr.json │ ├── README.md │ └── package.json ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── scripts ├── replace-workspace-protocol.ts ├── version.ts ├── publish.ts └── canary.ts ├── .changeset ├── README.md └── config.json ├── tsconfig.json ├── LICENSE ├── package.json ├── .cursor └── rules │ └── general.mdc └── README.md /.config/.oxfmtrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [] 3 | } 4 | -------------------------------------------------------------------------------- /examples/nextjs/.env: -------------------------------------------------------------------------------- 1 | SECRET=sause 2 | NEXT_PUBLIC_GREETING=hello -------------------------------------------------------------------------------- /examples/astro-zod/.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | PUBLIC_API_URL=http://localhost:3000 3 | -------------------------------------------------------------------------------- /examples/astro-arktype/.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | PUBLIC_API_URL=http://localhost:3000 3 | -------------------------------------------------------------------------------- /examples/astro-valibot/.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | PUBLIC_API_URL=http://localhost:3000 3 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3-oss/t3-env/HEAD/docs/public/favicon.ico -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: juliusmarminge 4 | -------------------------------------------------------------------------------- /docs/src/styles/calsans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3-oss/t3-env/HEAD/docs/src/styles/calsans.ttf -------------------------------------------------------------------------------- /docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /docs/public/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3-oss/t3-env/HEAD/docs/public/opengraph-image.png -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"], 4 | "exclude": ["dist"], 5 | } 6 | -------------------------------------------------------------------------------- /packages/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"], 4 | "exclude": ["dist"], 5 | } 6 | -------------------------------------------------------------------------------- /packages/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"], 4 | "exclude": ["dist"], 5 | } 6 | -------------------------------------------------------------------------------- /examples/nextjs/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { env } from "~/env"; 3 | 4 | export default function HomePage() { 5 | return
Client says {env.NEXT_PUBLIC_GREETING}!
; 6 | } 7 | -------------------------------------------------------------------------------- /packages/nuxt/src/presets-zod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Presets for Zod 3 | * @see https://env.t3.gg/docs/customization#extending-presets 4 | * @module 5 | */ 6 | export * from "@t3-oss/env-core/presets-zod"; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .cache 4 | 5 | .astro 6 | .next 7 | .nuxt 8 | .output 9 | .turbo 10 | .vercel 11 | .DS_Store 12 | 13 | env.d.ts 14 | next-env.d.ts 15 | **/.vscode 16 | **/.zed -------------------------------------------------------------------------------- /packages/nextjs/src/presets-zod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Presets for Zod 3 | * @see https://env.t3.gg/docs/customization#extending-presets 4 | * @module 5 | */ 6 | export * from "@t3-oss/env-core/presets-zod"; 7 | -------------------------------------------------------------------------------- /packages/nextjs/src/presets-arktype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Presets for Arktype 3 | * @see https://env.t3.gg/docs/customization#extending-presets 4 | * @module 5 | */ 6 | export * from "@t3-oss/env-core/presets-arktype"; 7 | -------------------------------------------------------------------------------- /packages/nextjs/src/presets-valibot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Presets for Valibot 3 | * @see https://env.t3.gg/docs/customization#extending-presets 4 | * @module 5 | */ 6 | export * from "@t3-oss/env-core/presets-valibot"; 7 | -------------------------------------------------------------------------------- /packages/nuxt/src/presets-arktype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Presets for Arktype 3 | * @see https://env.t3.gg/docs/customization#extending-presets 4 | * @module 5 | */ 6 | export * from "@t3-oss/env-core/presets-arktype"; 7 | -------------------------------------------------------------------------------- /packages/nuxt/src/presets-valibot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Presets for Valibot 3 | * @see https://env.t3.gg/docs/customization#extending-presets 4 | * @module 5 | */ 6 | export * from "@t3-oss/env-core/presets-valibot"; 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arktypeio.arkdark", 4 | "oxc.oxc-vscode", 5 | "TypeScriptTeam.native-preview", 6 | "unifiedjs.vscode-mdx", 7 | "vitest.explorer" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /examples/astro-zod/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import solidJs from "@astrojs/solid-js"; 2 | import { defineConfig } from "astro/config"; 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | integrations: [solidJs()], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/nextjs/app/api/node/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { env } from "~/env"; 3 | 4 | export function GET(_request: Request) { 5 | return NextResponse.json({ env, processEnv: process.env }); 6 | } 7 | -------------------------------------------------------------------------------- /examples/nextjs/next.config.ts: -------------------------------------------------------------------------------- 1 | // Import env here to validate during build 2 | import { env as _env } from "./app/env"; 3 | 4 | import type { NextConfig } from "next"; 5 | 6 | export default { 7 | // ... 8 | } satisfies NextConfig; 9 | -------------------------------------------------------------------------------- /examples/astro-arktype/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import solidJs from "@astrojs/solid-js"; 2 | import { defineConfig } from "astro/config"; 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | integrations: [solidJs()], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/astro-valibot/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import solidJs from "@astrojs/solid-js"; 2 | import { defineConfig } from "astro/config"; 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | integrations: [solidJs()], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/astro-arktype/src/counter.tsx: -------------------------------------------------------------------------------- 1 | import { env } from "./t3-env"; 2 | 3 | export function ClientComponent() { 4 | // Try changing PUBLIC_API_URL to PORT - the component will throw 5 | return
Client API Url:{env.PUBLIC_API_URL}
; 6 | } 7 | -------------------------------------------------------------------------------- /examples/astro-valibot/src/counter.tsx: -------------------------------------------------------------------------------- 1 | import { env } from "./t3-env"; 2 | 3 | export function ClientComponent() { 4 | // Try changing PUBLIC_API_URL to PORT - the component will throw 5 | return
Client API Url:{env.PUBLIC_API_URL}
; 6 | } 7 | -------------------------------------------------------------------------------- /examples/astro-zod/src/counter.tsx: -------------------------------------------------------------------------------- 1 | import { env } from "./t3-env"; 2 | 3 | export function ClientComponent() { 4 | // Try changing PUBLIC_API_URL to PORT - the component will throw 5 | return
Client API Url:{env.PUBLIC_API_URL}
; 6 | } 7 | -------------------------------------------------------------------------------- /examples/nextjs/app/api/edge/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { env } from "~/env"; 3 | 4 | export const runtime = "edge"; 5 | 6 | export function GET() { 7 | return NextResponse.json({ env, processEnv: process.env }); 8 | } 9 | -------------------------------------------------------------------------------- /docs/src/lib/cn.ts: -------------------------------------------------------------------------------- 1 | import { cx } from "class-variance-authority"; 2 | import type { CxOptions } from "class-variance-authority"; 3 | import { twMerge } from "tailwind-merge"; 4 | 5 | export function cn(...inputs: CxOptions) { 6 | return twMerge(cx(...inputs)); 7 | } 8 | -------------------------------------------------------------------------------- /docs/src/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import { SiteHeader } from "@/components/site-header"; 2 | 3 | interface LayoutProps { 4 | children: React.ReactNode; 5 | } 6 | 7 | export function Layout({ children }: LayoutProps) { 8 | return ( 9 | <> 10 | 11 |
{children}
12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /examples/nextjs/app/node/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { env } from "~/env"; 4 | 5 | export default function Page() { 6 | return ( 7 | <> 8 |

Node.js Runtime

9 |

t3-env

10 |
{JSON.stringify(env, null, 4)}
11 | 12 |

process.env

13 |
{JSON.stringify(process.env, null, 4)}
14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /.github/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Workflow 2 | description: Composite action that sets up bun and installs dependencies 3 | runs: 4 | using: "composite" 5 | steps: 6 | - uses: actions/setup-node@v6 7 | with: 8 | node-version-file: package.json 9 | 10 | - uses: oven-sh/setup-bun@v2 11 | with: 12 | bun-version-file: package.json 13 | 14 | - run: bun install 15 | shell: bash 16 | -------------------------------------------------------------------------------- /scripts/replace-workspace-protocol.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Hack to replace the workspace protocol with the actual version 3 | */ 4 | const corePkg = await Bun.file("../core/package.json").json(); 5 | const version = corePkg.version; 6 | 7 | const workspacePkg = await Bun.file("package.json").json(); 8 | workspacePkg.dependencies["@t3-oss/env-core"] = version; 9 | await Bun.write("package.json", JSON.stringify(workspacePkg, null, 2)); 10 | -------------------------------------------------------------------------------- /examples/nextjs/app/edge/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { env } from "~/env"; 4 | 5 | export const runtime = "edge"; 6 | 7 | export default function Page() { 8 | return ( 9 | <> 10 |

Edge Runtime

11 |

t3-env

12 |
{JSON.stringify(env, null, 4)}
13 | 14 |

process.env

15 |
{JSON.stringify(process.env, null, 4)}
16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@t3-oss/env-core", 3 | "version": "0.13.10", 4 | "exports": { 5 | ".": "./src/index.ts", 6 | "./presets-arktype": "./src/presets-arktype.ts", 7 | "./presets-zod": "./src/presets-zod.ts", 8 | "./presets-valibot": "./src/presets-valibot.ts" 9 | }, 10 | "include": ["src/**/*.ts", "README.md", "LICENSE", "package.json"], 11 | "exclude": ["node_modules", "dist", "test"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/nextjs/jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@t3-oss/env-nextjs", 3 | "version": "0.13.10", 4 | "exports": { 5 | ".": "./src/index.ts", 6 | "./presets-arktype": "./src/presets-arktype.ts", 7 | "./presets-zod": "./src/presets-zod.ts", 8 | "./presets-valibot": "./src/presets-valibot.ts" 9 | }, 10 | "include": ["src/**/*.ts", "README.md", "LICENSE", "package.json"], 11 | "exclude": ["node_modules", "dist", "test"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/nuxt/jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@t3-oss/env-nuxt", 3 | "version": "0.13.10", 4 | "exports": { 5 | ".": "./src/index.ts", 6 | "./presets-arktype": "./src/presets-arktype.ts", 7 | "./presets-zod": "./src/presets-zod.ts", 8 | "./presets-valibot": "./src/presets-valibot.ts" 9 | }, 10 | "include": ["src/**/*.ts", "README.md", "LICENSE", "package.json"], 11 | "exclude": ["node_modules", "dist", "test"] 12 | } 13 | -------------------------------------------------------------------------------- /examples/astro-zod/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "Preserve", 5 | "jsx": "preserve", 6 | "jsxImportSource": "solid-js", 7 | "isolatedDeclarations": false, 8 | "composite": false, 9 | "declaration": false, 10 | "declarationMap": false, 11 | "noEmit": true, 12 | "types": ["astro/client"], 13 | }, 14 | "include": ["src", "astro.config.mjs"], 15 | } 16 | -------------------------------------------------------------------------------- /examples/astro-arktype/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "Preserve", 5 | "jsx": "preserve", 6 | "jsxImportSource": "solid-js", 7 | "isolatedDeclarations": false, 8 | "composite": false, 9 | "declaration": false, 10 | "declarationMap": false, 11 | "noEmit": true, 12 | "types": ["astro/client"], 13 | }, 14 | "include": ["src", "astro.config.mjs"], 15 | } 16 | -------------------------------------------------------------------------------- /examples/astro-valibot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "Preserve", 5 | "jsx": "preserve", 6 | "jsxImportSource": "solid-js", 7 | "isolatedDeclarations": false, 8 | "composite": false, 9 | "declaration": false, 10 | "declarationMap": false, 11 | "noEmit": true, 12 | "types": ["astro/client"], 13 | }, 14 | "include": ["src", "astro.config.mjs"], 15 | } 16 | -------------------------------------------------------------------------------- /examples/astro-zod/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { ClientComponent } from "../counter"; 3 | import { env } from "../t3-env"; 4 | --- 5 | 6 | 7 | 8 | 9 | 10 | 11 | Astro 12 | 13 | 14 |

Astro

15 |

PORT: {env.PORT}

16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/astro-arktype/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { ClientComponent } from "../counter"; 3 | import { env } from "../t3-env"; 4 | --- 5 | 6 | 7 | 8 | 9 | 10 | 11 | Astro 12 | 13 | 14 |

Astro

15 |

PORT: {env.PORT}

16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/astro-valibot/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { ClientComponent } from "../counter"; 3 | import { env } from "../t3-env"; 4 | --- 5 | 6 | 7 | 8 | 9 | 10 | 11 | Astro 12 | 13 | 14 |

Astro

15 |

PORT: {env.PORT}

16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/astro-zod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/astro-zod", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "build": "astro build", 7 | "clean": "rm -rf node_modules", 8 | "dev": "astro dev", 9 | "preview": "astro preview", 10 | "start": "astro dev" 11 | }, 12 | "dependencies": { 13 | "@astrojs/solid-js": "^5.1.0", 14 | "@t3-oss/env-core": "workspace:*", 15 | "astro": "^5.13.5", 16 | "solid-js": "^1.9.9", 17 | "zod": "4.1.5" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/astro-arktype/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/astro-arktype", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "build": "astro build", 7 | "clean": "rm -rf node_modules", 8 | "dev": "astro dev", 9 | "preview": "astro preview", 10 | "start": "astro dev" 11 | }, 12 | "dependencies": { 13 | "@astrojs/solid-js": "^5.1.0", 14 | "@t3-oss/env-core": "workspace:*", 15 | "arktype": "2.1.22", 16 | "astro": "^5.13.5", 17 | "solid-js": "^1.9.9" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/astro-valibot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/astro-valibot", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "build": "astro build", 7 | "clean": "rm -rf node_modules", 8 | "dev": "astro dev", 9 | "preview": "astro preview", 10 | "start": "astro dev" 11 | }, 12 | "dependencies": { 13 | "@astrojs/solid-js": "^5.1.0", 14 | "@t3-oss/env-core": "workspace:*", 15 | "astro": "^5.13.5", 16 | "solid-js": "^1.9.9", 17 | "valibot": "1.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "Preserve", 5 | "jsx": "react-jsx", 6 | "isolatedDeclarations": false, 7 | "composite": false, 8 | "declaration": false, 9 | "declarationMap": false, 10 | "noEmit": true, 11 | "plugins": [{ "name": "next" }], 12 | "paths": { 13 | "~/*": ["./app/*"], 14 | }, 15 | }, 16 | "include": ["next-env.d.ts", "app", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", "*.js"], 17 | } 18 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "t3-oss/t3-env" }], 4 | "commit": false, 5 | "access": "public", 6 | "baseBranch": "main", 7 | "updateInternalDependencies": "patch", 8 | "fixed": [["@t3-oss/env-core", "@t3-oss/env-nextjs", "@t3-oss/env-nuxt"]], 9 | "ignore": [ 10 | "@examples/astro-zod", 11 | "@examples/astro-valibot", 12 | "@examples/astro-arktype", 13 | "@examples/nextjs", 14 | "@t3-env/docs" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/astro-zod/src/t3-env.ts: -------------------------------------------------------------------------------- 1 | import { createEnv } from "@t3-oss/env-core"; 2 | import { z } from "zod"; 3 | 4 | export const env = createEnv({ 5 | server: { 6 | PORT: z.string(), 7 | }, 8 | client: { 9 | PUBLIC_API_URL: z.string(), 10 | }, 11 | // Astro bundles all environment variables so 12 | // we don't need to manually destructure them 13 | runtimeEnv: import.meta.env, 14 | // process is not available in Astro, so we must set this explicitly 15 | skipValidation: import.meta.env.SKIP_ENV_VALIDATION === "development", 16 | clientPrefix: "PUBLIC_", 17 | }); 18 | -------------------------------------------------------------------------------- /examples/nextjs/app/env.ts: -------------------------------------------------------------------------------- 1 | import { createEnv } from "@t3-oss/env-nextjs"; 2 | import { vercel } from "@t3-oss/env-nextjs/presets-zod"; 3 | import { z } from "zod"; 4 | 5 | export const env = createEnv({ 6 | client: { 7 | NEXT_PUBLIC_GREETING: z.string(), 8 | }, 9 | server: { 10 | SECRET: z.string(), 11 | }, 12 | shared: { 13 | NODE_ENV: z.enum(["development", "production"]), 14 | }, 15 | experimental__runtimeEnv: { 16 | NODE_ENV: process.env.NODE_ENV, 17 | NEXT_PUBLIC_GREETING: process.env.NEXT_PUBLIC_GREETING, 18 | }, 19 | extends: [vercel()], 20 | }); 21 | -------------------------------------------------------------------------------- /examples/astro-valibot/src/t3-env.ts: -------------------------------------------------------------------------------- 1 | import { createEnv } from "@t3-oss/env-core"; 2 | import { string } from "valibot"; 3 | 4 | export const env = createEnv({ 5 | server: { 6 | PORT: string(), 7 | }, 8 | client: { 9 | PUBLIC_API_URL: string(), 10 | }, 11 | // Astro bundles all environment variables so 12 | // we don't need to manually destructure them 13 | runtimeEnv: import.meta.env, 14 | // process is not available in Astro, so we must set this explicitly 15 | skipValidation: import.meta.env.SKIP_ENV_VALIDATION === "development", 16 | clientPrefix: "PUBLIC_", 17 | }); 18 | -------------------------------------------------------------------------------- /examples/astro-arktype/src/t3-env.ts: -------------------------------------------------------------------------------- 1 | import { createEnv } from "@t3-oss/env-core"; 2 | import { type } from "arktype"; 3 | 4 | export const env = createEnv({ 5 | server: { 6 | PORT: type("string"), 7 | }, 8 | client: { 9 | PUBLIC_API_URL: type("string.url"), 10 | }, 11 | // Astro bundles all environment variables so 12 | // we don't need to manually destructure them 13 | runtimeEnv: import.meta.env, 14 | // process is not available in Astro, so we must set this explicitly 15 | skipValidation: import.meta.env.SKIP_ENV_VALIDATION === "development", 16 | clientPrefix: "PUBLIC_", 17 | }); 18 | -------------------------------------------------------------------------------- /scripts/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Script for publishing the packages to JSR after an NPM release 3 | */ 4 | 5 | const packages = ["core", "nextjs", "nuxt"]; 6 | 7 | /** 8 | * 1. Bump versions 9 | */ 10 | await Bun.$`bunx changeset version`; 11 | 12 | for (const pkg of packages) { 13 | /** 14 | * 2. Sync versions 15 | */ 16 | const pkgJson = await Bun.file(`packages/${pkg}/package.json`).json(); 17 | const jsrJson = await Bun.file(`packages/${pkg}/jsr.json`).json(); 18 | 19 | jsrJson.version = pkgJson.version; 20 | await Bun.write(`packages/${pkg}/jsr.json`, JSON.stringify(jsrJson, null, 2)); 21 | } 22 | -------------------------------------------------------------------------------- /examples/nextjs/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Inter } from "next/font/google"; 2 | 3 | import { env } from "~/env"; 4 | import "./globals.css"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export const metadata = { 9 | title: "Create Next App", 10 | description: "Generated by create next app", 11 | }; 12 | 13 | export default function RootLayout(props: { children: React.ReactNode }) { 14 | return ( 15 | 16 | 17 |
Server says {env.SECRET}!
18 |
{props.children}
19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /scripts/publish.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Script for publishing the packages to JSR after an NPM release 3 | */ 4 | 5 | const packages = ["core", "nextjs", "nuxt"]; 6 | 7 | /** 8 | * 1. Publish to NPM 9 | */ 10 | await Bun.$`bunx changeset publish`; 11 | 12 | for (const pkg of packages) { 13 | const pkgJson = await Bun.file(`packages/${pkg}/package.json`).json(); 14 | 15 | /** 16 | * 2. Run prepack (if exists) 17 | */ 18 | if (pkgJson.scripts?.prepack) { 19 | await Bun.$`bun run prepack`.cwd(`packages/${pkg}`); 20 | } 21 | 22 | /** 23 | * 3. Publish to JSR 24 | */ 25 | await Bun.$`bunx jsr publish --allow-dirty`.cwd(`packages/${pkg}`); 26 | } 27 | -------------------------------------------------------------------------------- /examples/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/nextjs", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "clean": "rm -rf node_modules", 8 | "dev": "next dev", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@t3-oss/env-nextjs": "workspace:*", 13 | "next": "16.0.10", 14 | "react": "19.2.3", 15 | "react-dom": "19.2.3", 16 | "zod": "4.1.5" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.14.1", 20 | "@types/react": "19.2.7", 21 | "@types/react-dom": "19.2.3", 22 | "@typescript/native-preview": "7.0.0-dev.20251125.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/src/components/tailwind-indicator.tsx: -------------------------------------------------------------------------------- 1 | export function TailwindIndicator() { 2 | if (process.env.NODE_ENV === "production") return null; 3 | 4 | return ( 5 |
6 |
xs
7 |
sm
8 |
md
9 |
lg
10 |
xl
11 |
2xl
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /docs/src/components/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | 5 | import { Icons } from "@/components/icons"; 6 | import { Button } from "@/components/ui/button"; 7 | 8 | export function ThemeToggle() { 9 | const { setTheme, theme } = useTheme(); 10 | 11 | return ( 12 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.oxc": "explicit" 4 | }, 5 | "editor.formatOnSave": true, 6 | "editor.defaultFormatter": "oxc.oxc-vscode", 7 | "[tailwindcss]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode" 9 | }, 10 | "files.readonlyInclude": { 11 | "**/routeTree.gen.ts": true 12 | }, 13 | "[json]": { 14 | "editor.defaultFormatter": "esbenp.prettier-vscode" 15 | }, 16 | "[jsonc]": { 17 | "editor.defaultFormatter": "esbenp.prettier-vscode" 18 | }, 19 | "files.associations": { 20 | "*.css": "tailwindcss" 21 | }, 22 | "oxc.fmt.experimental": true, 23 | "oxc.typeAware": true, 24 | "oxc.configPath": ".config/.oxlintrc.json", 25 | "oxc.fmt.configPath": ".config/.oxfmtrc.json", 26 | "typescript.tsdk": "node_modules/typescript/lib", 27 | "typescript.experimental.useTsgo": true, 28 | "typescript.enablePromptUseWorkspaceTsdk": true, 29 | "oxc.enable": true 30 | } 31 | -------------------------------------------------------------------------------- /.config/turbo.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "ui": "tui", 4 | "tasks": { 5 | "dev": { 6 | "dependsOn": ["^dev"], 7 | "cache": false, 8 | }, 9 | "clean": { 10 | "cache": false, 11 | }, 12 | "build": { 13 | "dependsOn": ["^build"], 14 | "outputs": ["dist/**", ".next/**", ".nuxt"], 15 | }, 16 | "typecheck": { 17 | "dependsOn": ["transit", "^build"], 18 | "outputs": [".cache/tsbuildinfo.json"], 19 | }, 20 | "//#test": { 21 | "persistent": true, 22 | "outputs": [], 23 | }, 24 | "//#test:run": { 25 | "outputs": [], 26 | }, 27 | // Transit node in the turbo graph 28 | // no package.json scrips actually implement transit but it creates a dependency link for other tasks: 29 | // https://turbo.build/docs/crafting-your-repository/configuring-tasks#dependent-tasks-that-can-be-run-in-parallel 30 | "transit": { 31 | "dependsOn": ["^transit"], 32 | }, 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Base Options: */ 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "target": "es2022", 7 | "allowJs": true, 8 | "resolveJsonModule": true, 9 | "moduleDetection": "force", 10 | "isolatedModules": true, 11 | 12 | /* Strictness */ 13 | "strict": true, 14 | // "noUncheckedIndexedAccess": true, 15 | "checkJs": true, 16 | 17 | /* Bundled projects */ 18 | "lib": ["dom", "dom.iterable", "ES2022"], 19 | "noEmit": true, 20 | "module": "ESNext", 21 | "moduleResolution": "Bundler", 22 | "jsx": "react-jsx", 23 | "plugins": [{ "name": "next" }], 24 | "incremental": true, 25 | 26 | /* Path Aliases */ 27 | "paths": { 28 | "@/*": ["./src/*"], 29 | }, 30 | }, 31 | "include": [ 32 | "next-env.d.ts", 33 | "**/*.ts", 34 | "**/*.tsx", 35 | "*.js", 36 | ".next/types/**/*.ts", 37 | ".next/dev/types/**/*.ts", 38 | ], 39 | "exclude": ["node_modules"], 40 | } 41 | -------------------------------------------------------------------------------- /docs/src/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import type { ReactNode } from "react"; 3 | import { siteConfig } from "@/app/site-config"; 4 | import { DocsSidebarNav } from "@/components/sidebar"; 5 | import { ScrollArea } from "@/components/ui/scroll-area"; 6 | 7 | export const metadata: Metadata = { 8 | title: { 9 | default: "Docs ⋅ T3 Env", 10 | template: "%s ⋅ T3 Env", 11 | }, 12 | }; 13 | 14 | export default function DocsLayout(props: { children: ReactNode }) { 15 | return ( 16 |
17 | 22 |
{props.children}
23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Build Performance 4 | "incremental": true, 5 | "composite": true, 6 | "libReplacement": false, 7 | "tsBuildInfoFile": "${configDir}/.cache/tsbuildinfo.json", 8 | 9 | // Module System 10 | "target": "ES2022", 11 | "lib": ["dom", "dom.iterable", "ES2022"], 12 | "module": "NodeNext", 13 | "moduleDetection": "force", 14 | "isolatedModules": true, 15 | "isolatedDeclarations": true, 16 | "erasableSyntaxOnly": true, 17 | "noEmit": true, 18 | 19 | // Import Resolution 20 | "verbatimModuleSyntax": true, 21 | "rewriteRelativeImportExtensions": true, 22 | "allowImportingTsExtensions": true, 23 | 24 | // Type Safety 25 | "strict": true, 26 | "noImplicitOverride": true, 27 | // "noUncheckedIndexedAccess": true, 28 | 29 | // Development 30 | "declaration": true, 31 | "declarationMap": true, 32 | "sourceMap": true, 33 | "skipLibCheck": true, 34 | }, 35 | "include": ["./**/*.ts"], 36 | "exclude": ["./node_modules", "./packages"], 37 | } 38 | -------------------------------------------------------------------------------- /.config/.oxlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../node_modules/oxlint/configuration_schema.json", 3 | "plugins": ["typescript", "import"], 4 | "categories": { 5 | "correctness": "warn", 6 | "suspicious": "warn" 7 | }, 8 | "env": { 9 | "builtin": true 10 | }, 11 | "rules": { 12 | "eslint/prefer-rest-params": "warn", 13 | "eslint/no-case-declarations": "warn", 14 | "eslint/no-var": "warn", 15 | "import/consistent-type-specifier-style": ["warn", "prefer-top-level"], 16 | "import/no-unassigned-import": [ 17 | "warn", 18 | { 19 | "allow": ["**/*.css"] 20 | } 21 | ], 22 | "typescript/consistent-type-imports": [ 23 | "warn", 24 | { 25 | "prefer": "type-imports", 26 | "fixStyle": "separate-type-imports" 27 | } 28 | ], 29 | "typescript/ban-ts-comment": "warn", 30 | "typescript/no-explicit-any": "off", 31 | "typescript/no-require-imports": "warn", 32 | "typescript/no-unnecessary-type-constraint": "warn", 33 | "typescript/no-unsafe-type-assertion": "off", 34 | "typescript/require-await": "warn" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Julius Marminge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: ["*"] 6 | push: 7 | branches: ["main"] 8 | merge_group: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} 13 | 14 | env: 15 | FORCE_COLOR: 3 16 | TURBO_TEAM: ${{ secrets.TURBO_TEAM }} 17 | TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} 18 | 19 | jobs: 20 | check: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout repo 24 | uses: actions/checkout@v4 25 | - uses: ./.github/setup 26 | - run: bun turbo build --filter=@t3-oss/env* 27 | - run: bun lint 28 | - run: bun fmt --check 29 | 30 | sherif: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout repo 34 | uses: actions/checkout@v6 35 | - uses: oven-sh/setup-bun@v2 36 | with: 37 | bun-version-file: package.json 38 | - run: bunx sherif@1.9.0 39 | 40 | test: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout repo 44 | uses: actions/checkout@v6 45 | - uses: ./.github/setup 46 | - run: bun run build --filter=@t3-oss/env* 47 | - run: bun run test 48 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@t3-env/docs", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --webpack", 7 | "clean": "rm -rf .next node_modules .turbo .cache", 8 | "build": "next build --webpack", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@mdx-js/loader": "^3.1.0", 13 | "@next/mdx": "16.0.10", 14 | "@vercel/analytics": "^1.5.0", 15 | "class-variance-authority": "^0.7.0", 16 | "lucide-react": "0.368.0", 17 | "next": "16.0.10", 18 | "next-themes": "^0.4.6", 19 | "next-view-transitions": "^0.3.4", 20 | "radix-ui": "^1.4.3", 21 | "react": "19.2.3", 22 | "react-dom": "19.2.3", 23 | "react-wrap-balancer": "^1.1.1", 24 | "rehype-pretty-code": "0.9.5", 25 | "sharp": "^0.33.3", 26 | "shiki": "0.14.1", 27 | "tailwind-merge": "^3.3.1" 28 | }, 29 | "devDependencies": { 30 | "@tailwindcss/postcss": "^4.1.13", 31 | "@types/mdx": "^2.0.13", 32 | "@types/node": "^22.14.1", 33 | "@types/react": "19.2.7", 34 | "@types/react-dom": "19.2.3", 35 | "@typescript/native-preview": "7.0.0-dev.20251125.1", 36 | "postcss": "^8.4.38", 37 | "tailwindcss": "^4.1.13", 38 | "tw-animate-css": "1.3.8" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Popover as PopoverPrimitive } from "radix-ui"; 4 | import * as React from "react"; 5 | import { cn } from "@/lib/cn"; 6 | 7 | const Popover = PopoverPrimitive.Root; 8 | 9 | const PopoverTrigger = PopoverPrimitive.Trigger; 10 | const PopoverClose = PopoverPrimitive.Close; 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )); 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName; 30 | 31 | export { Popover, PopoverTrigger, PopoverClose, PopoverContent }; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@t3-oss/env-root", 3 | "packageManager": "bun@1.3.1", 4 | "engines": { 5 | "node": "^24.0.0", 6 | "bun": "^1.3.0" 7 | }, 8 | "private": true, 9 | "type": "module", 10 | "workspaces": [ 11 | "docs", 12 | "examples/*", 13 | "packages/*" 14 | ], 15 | "scripts": { 16 | "turbo": "turbo --root-turbo-json .config/turbo.jsonc", 17 | "build": "bun run turbo run build --filter @t3-oss/env*", 18 | "clean": "bun run turbo run clean && rm -rf node_modules", 19 | "dev": "bun run turbo watch build test --filter @t3-oss/env* --filter //", 20 | "fmt": "oxfmt -c .config/.oxfmtrc.json", 21 | "lint": "oxlint -c .config/.oxlintrc.json --type-aware --type-check --deny-warnings --report-unused-disable-directives", 22 | "lint:fix": "oxlint -c .config/.oxlintrc.json --fix && oxfmt -c .config/.oxfmtrc.json", 23 | "test": "vitest --silent=passed-only", 24 | "test:run": "vitest run --silent=passed-only" 25 | }, 26 | "devDependencies": { 27 | "@changesets/changelog-github": "^0.5.1", 28 | "@changesets/cli": "^2.29.7", 29 | "@types/bun": "^1.3.2", 30 | "@typescript/native-preview": "7.0.0-dev.20251125.1", 31 | "oxfmt": "0.17.0", 32 | "oxlint": "1.32.0", 33 | "oxlint-tsgolint": "0.9.0", 34 | "turbo": "2.6.1", 35 | "vitest": "^4.0.15" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/next.config.ts: -------------------------------------------------------------------------------- 1 | // oxlint-disable-next-line ban-ts-comment 2 | // @ts-nocheck - whatever 3 | import withMdx from "@next/mdx"; 4 | import type { NextConfig } from "next"; 5 | import rehypePrettyCode from "rehype-pretty-code"; 6 | import type { Options } from "rehype-pretty-code"; 7 | import { getHighlighter } from "shiki"; 8 | 9 | const nextConfig: NextConfig = { 10 | pageExtensions: ["ts", "tsx", "mdx"], 11 | // oxlint-disable-next-line require-await 12 | redirects: async () => [{ source: "/docs", destination: "/docs/introduction", permanent: true }], 13 | }; 14 | 15 | export default withMdx({ 16 | options: { 17 | rehypePlugins: [ 18 | [ 19 | rehypePrettyCode, 20 | { 21 | theme: { dark: "one-dark-pro", light: "min-light" }, 22 | getHighlighter, 23 | onVisitLine(node) { 24 | // Prevent lines from collapsing in `display: grid` mode, and allow empty 25 | // lines to be copy/pasted 26 | if (node.children.length === 0) { 27 | node.children = [{ type: "text", value: " " }]; 28 | } 29 | }, 30 | onVisitHighlightedLine(node) { 31 | node.properties.className.push("line--highlighted"); 32 | }, 33 | onVisitHighlightedWord(node, id) { 34 | node.properties.className = ["word"]; 35 | node.properties["data-word-id"] = id; 36 | }, 37 | } satisfies Options, 38 | ], 39 | ], 40 | }, 41 | })(nextConfig); 42 | -------------------------------------------------------------------------------- /scripts/canary.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Script for bumping the version of the packages to a canary version 3 | * and then publishing them to NPM and JSR 4 | */ 5 | 6 | const packages = ["core", "nextjs", "nuxt"]; 7 | 8 | const commitHash = (await Bun.$`git rev-parse --short HEAD`.text()).trim(); 9 | 10 | for (const pkg of packages) { 11 | const pkgJson = await Bun.file(`packages/${pkg}/package.json`).json(); 12 | const jsrJson = await Bun.file(`packages/${pkg}/jsr.json`).json(); 13 | 14 | const oldVersion = pkgJson.version; 15 | const [major, minor, patch] = oldVersion.split(".").map(Number); 16 | const newVersion = `${major}.${minor}.${patch + 1}-canary.${commitHash}`; 17 | 18 | pkgJson.version = newVersion; 19 | jsrJson.version = newVersion; 20 | const content = `${JSON.stringify(pkgJson, null, "\t")}\n`; 21 | const newContent = content.replace( 22 | new RegExp(`"@t3-oss/\\*": "${oldVersion}"`, "g"), 23 | `"@t3-oss/*": "${newVersion}"`, 24 | ); 25 | 26 | await Bun.write(`packages/${pkg}/package.json`, newContent); 27 | await Bun.write(`packages/${pkg}/jsr.json`, JSON.stringify(jsrJson, null, 2)); 28 | 29 | /** 30 | * 2. Run prepack (if exists) 31 | */ 32 | if (pkgJson.scripts?.prepack) { 33 | await Bun.$`bun run prepack`.cwd(`packages/${pkg}`); 34 | } 35 | 36 | /** 37 | * 3. Publish to NPM 38 | */ 39 | await Bun.$`npm publish --access public --tag canary`.cwd(`packages/${pkg}`); 40 | 41 | /** 42 | * 4. Publish to JSR 43 | */ 44 | await Bun.$`bunx jsr publish --allow-dirty`.cwd(`packages/${pkg}`); 45 | } 46 | -------------------------------------------------------------------------------- /docs/src/components/main-nav.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useSelectedLayoutSegment } from "next/navigation"; 4 | import { Link } from "next-view-transitions"; 5 | import type { Icons } from "@/components/icons"; 6 | import { cn } from "@/lib/cn"; 7 | 8 | export interface NavItem { 9 | title: string; 10 | href?: string; 11 | disabled?: boolean; 12 | external?: boolean; 13 | icon?: keyof typeof Icons; 14 | label?: string; 15 | } 16 | 17 | export function MainNav(props: { items: NavItem[] }) { 18 | const segment = useSelectedLayoutSegment(); 19 | 20 | const isActive = (href: string) => { 21 | if (!segment) return false; 22 | return href.startsWith(`/${segment}`); 23 | }; 24 | 25 | return ( 26 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /packages/nuxt/README.md: -------------------------------------------------------------------------------- 1 | # Typesafe Envs made Simple 2 | 3 | ![NPM Version](https://img.shields.io/npm/v/%40t3-oss%2Fenv-core) 4 | [![JSR](https://jsr.io/badges/@t3-oss/env-core)](https://jsr.io/@t3-oss/env-core) 5 | [![JSR Score](https://jsr.io/badges/@t3-oss/env-core/score)](https://jsr.io/@t3-oss/env-core) 6 | [![Socket Badge](https://socket.dev/api/badge/npm/package/@t3-oss/env-core)](https://socket.dev/npm/package/@t3-oss/env-core/overview) 7 | 8 | The Nuxt package comes preconfigured for Nuxt, and fills the runtimeEnv option automatically. All you need to do is define your schemas! 9 | 10 | > For full documentation, see https://env.t3.gg 11 | 12 | ## Installation 13 | 14 | ```bash 15 | # npm 16 | npm i @t3-oss/env-nuxt 17 | 18 | # pnpm 19 | pnpm add @t3-oss/env-nuxt 20 | 21 | # bun 22 | bun add @t3-oss/env-nuxt 23 | 24 | # deno 25 | deno add jsr:@t3-oss/env-nuxt 26 | ``` 27 | 28 | ## Usage 29 | 30 | > [!NOTE] 31 | > 32 | > You may use any [Standard Schema](https://standardschema.dev) compliant validator of your choice. This example uses Zod 33 | 34 | ```ts 35 | // src/env.ts 36 | import { createEnv } from "@t3-oss/env-nuxt"; 37 | import { z } from "zod"; 38 | 39 | export const env = createEnv({ 40 | /* 41 | * Serverside Environment variables, not available on the client. 42 | * Will throw if you access these variables on the client. 43 | */ 44 | server: { 45 | DATABASE_URL: z.url(), 46 | OPEN_AI_API_KEY: z.string().min(1), 47 | }, 48 | /* 49 | * Environment variables available on the client (and server). 50 | * 51 | * 💡 You'll get type errors if these are not prefixed with NUXT_PUBLIC_. 52 | */ 53 | client: { 54 | NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), 55 | }, 56 | }); 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/src/app/site-config.ts: -------------------------------------------------------------------------------- 1 | export type SiteConfig = typeof siteConfig; 2 | 3 | export const siteConfig = { 4 | name: "Env", 5 | description: 6 | "Never build your apps with invalid environment variables again. Validate and transform your environment with ease.", 7 | mainNav: [ 8 | { 9 | title: "Documentation", 10 | href: "/docs/introduction", 11 | }, 12 | ], 13 | sidebarNav: [ 14 | { 15 | title: "Getting Started", 16 | items: [ 17 | { 18 | title: "Introduction", 19 | href: "/docs/introduction", 20 | items: [], 21 | }, 22 | ], 23 | }, 24 | { 25 | title: "Framework Guides", 26 | items: [ 27 | { 28 | title: "Agnostic Core", 29 | href: "/docs/core", 30 | items: [], 31 | }, 32 | { 33 | title: "Next.js", 34 | href: "/docs/nextjs", 35 | items: [], 36 | }, 37 | { 38 | title: "Nuxt", 39 | href: "/docs/nuxt", 40 | items: [], 41 | }, 42 | ], 43 | }, 44 | { 45 | title: "Further Reading", 46 | items: [ 47 | { 48 | title: "Recipes", 49 | href: "/docs/recipes", 50 | items: [], 51 | }, 52 | { 53 | title: "Standard Schema", 54 | href: "/docs/standard-schema", 55 | items: [], 56 | }, 57 | { 58 | title: "Customization", 59 | href: "/docs/customization", 60 | label: "New", 61 | items: [], 62 | }, 63 | ], 64 | }, 65 | ], 66 | links: { 67 | // twitter: "https://twitter.com/", 68 | github: "https://github.com/t3-oss/t3-env", 69 | docs: "/docs", 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /docs/src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/cn"; 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )); 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 41 | 42 | 43 | )); 44 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; 45 | 46 | export { ScrollArea, ScrollBar }; 47 | -------------------------------------------------------------------------------- /docs/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import type { VariantProps } from "class-variance-authority"; 2 | import { cva } from "class-variance-authority"; 3 | import * as React from "react"; 4 | 5 | import { cn } from "@/lib/cn"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", 14 | outline: "border border-input hover:bg-accent hover:text-accent-foreground", 15 | secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", 16 | ghost: "hover:bg-accent hover:text-accent-foreground", 17 | link: "underline-offset-4 hover:underline text-primary", 18 | }, 19 | size: { 20 | lg: "h-11 px-8 rounded-md", 21 | default: "h-10 py-2 px-4", 22 | sm: "h-9 px-3 rounded-md", 23 | icon: "h-9 w-9", 24 | }, 25 | }, 26 | defaultVariants: { 27 | variant: "default", 28 | size: "default", 29 | }, 30 | }, 31 | ); 32 | 33 | export interface ButtonProps 34 | extends React.ButtonHTMLAttributes, VariantProps {} 35 | 36 | const Button = React.forwardRef( 37 | ({ className, variant, size, ...props }, ref) => { 38 | return ( 39 | 59 |
64 |         {children}
65 |       
66 | 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /packages/nextjs/README.md: -------------------------------------------------------------------------------- 1 | # Typesafe Envs made Simple 2 | 3 | ![NPM Version](https://img.shields.io/npm/v/%40t3-oss%2Fenv-core) 4 | [![JSR](https://jsr.io/badges/@t3-oss/env-core)](https://jsr.io/@t3-oss/env-core) 5 | [![JSR Score](https://jsr.io/badges/@t3-oss/env-core/score)](https://jsr.io/@t3-oss/env-core) 6 | [![Socket Badge](https://socket.dev/api/badge/npm/package/@t3-oss/env-core)](https://socket.dev/npm/package/@t3-oss/env-core/overview) 7 | 8 | The Next.js package comes preconfigured for Next.js and also enforces some extra rules by default to make sure you have out-of-the-box compatibility in all different Next.js runtimes. 9 | 10 | > For full documentation, see https://env.t3.gg 11 | 12 | ## Installation 13 | 14 | ```bash 15 | # npm 16 | npm i @t3-oss/env-nextjs 17 | 18 | # pnpm 19 | pnpm add @t3-oss/env-nextjs 20 | 21 | # bun 22 | bun add @t3-oss/env-nextjs 23 | 24 | # deno 25 | deno add jsr:@t3-oss/env-nextjs 26 | ``` 27 | 28 | ## Usage 29 | 30 | > [!NOTE] 31 | > 32 | > You may use any [Standard Schema](https://standardschema.dev) compliant validator of your choice. This example uses Zod 33 | 34 | ```ts 35 | // src/env.ts 36 | import { createEnv } from "@t3-oss/env-nextjs"; 37 | import { z } from "zod"; 38 | 39 | export const env = createEnv({ 40 | /* 41 | * Serverside Environment variables, not available on the client. 42 | * Will throw if you access these variables on the client. 43 | */ 44 | server: { 45 | DATABASE_URL: z.url(), 46 | OPEN_AI_API_KEY: z.string().min(1), 47 | }, 48 | /* 49 | * Environment variables available on the client (and server). 50 | * 51 | * 💡 You'll get type errors if these are not prefixed with NEXT_PUBLIC_. 52 | */ 53 | client: { 54 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), 55 | }, 56 | /* 57 | * Specify what values should be validated by your schemas above. 58 | * 59 | * If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually 60 | * For Next.js >= 13.4.4, you can use the experimental__runtimeEnv option and 61 | * only specify client-side variables. 62 | */ 63 | runtimeEnv: { 64 | DATABASE_URL: process.env.DATABASE_URL, 65 | OPEN_AI_API_KEY: process.env.OPEN_AI_API_KEY, 66 | NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, 67 | }, 68 | // experimental__runtimeEnv: { 69 | // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, 70 | // } 71 | }); 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "next-view-transitions"; 2 | import { Balancer } from "react-wrap-balancer"; 3 | 4 | import { siteConfig } from "@/app/site-config"; 5 | import { Icons } from "@/components/icons"; 6 | import { buttonVariants } from "@/components/ui/button"; 7 | 8 | export default function IndexPage() { 9 | return ( 10 |
11 |
12 |

19 | 20 | Framework agnostic validation for{" "} 21 | 22 | type-safe 23 | {" "} 24 | environment variables. 25 | 26 |

27 |

34 | {siteConfig.description} 35 |

36 |
43 | 44 | Documentation 45 | 46 | 55 | 56 | GitHub 57 | 58 |
59 |
60 |
61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /docs/src/components/site-header.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "next-view-transitions"; 2 | 3 | import { siteConfig } from "@/app/site-config"; 4 | import { Icons } from "@/components/icons"; 5 | import { MainNav } from "@/components/main-nav"; 6 | import { MobileDropdown } from "@/components/mobile-nav"; 7 | import { ThemeToggle } from "@/components/theme-toggle"; 8 | import { buttonVariants } from "@/components/ui/button"; 9 | 10 | export function SiteHeader() { 11 | return ( 12 |
13 |
14 |
15 | 19 | 20 | {siteConfig.name} 21 | 22 | 23 |
24 |
25 | 34 | 35 | GitHub 36 | 37 | {/* 42 |
48 | 49 | Twitter 50 |
51 | */} 52 | 53 | 59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /docs/src/components/sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { usePathname } from "next/navigation"; 4 | import { Link } from "next-view-transitions"; 5 | 6 | import { cn } from "@/lib/cn"; 7 | import type { NavItem } from "./main-nav"; 8 | 9 | export interface NestedNavItem extends NavItem { 10 | items: NestedNavItem[]; 11 | } 12 | 13 | export function DocsSidebarNav(props: { items: NestedNavItem[] }) { 14 | const pathname = usePathname(); 15 | 16 | return props.items.length ? ( 17 |
18 | {props.items.map((item) => ( 19 |
20 |

{item.title}

21 | {item?.items?.length && } 22 |
23 | ))} 24 |
25 | ) : null; 26 | } 27 | 28 | export function DocsSidebarNavItems(props: { items: NestedNavItem[]; pathname: string | null }) { 29 | return props.items?.length ? ( 30 |
31 | {props.items.map((item) => 32 | item.href ? ( 33 | 47 | {item.title} 48 | {item.label && ( 49 | 50 | {item.label} 51 | 52 | )} 53 | 54 | ) : ( 55 | 59 | {item.title} 60 | 61 | ), 62 | )} 63 |
64 | ) : null; 65 | } 66 | -------------------------------------------------------------------------------- /docs/src/components/mdx/callout.tsx: -------------------------------------------------------------------------------- 1 | /** Originally from `nextra-theme-docs` 2 | * @link https://github.com/shuding/nextra/blob/main/packages/nextra-theme-docs/src/components/callout.tsx 3 | */ 4 | 5 | import { cx } from "class-variance-authority"; 6 | import type { ComponentProps, ReactElement, ReactNode } from "react"; 7 | 8 | export function InformationCircleIcon(props: ComponentProps<"svg">): ReactElement { 9 | return ( 10 | 18 | 23 | 24 | ); 25 | } 26 | 27 | const TypeToEmoji = { 28 | default: "💡", 29 | error: "🚫", 30 | info: , 31 | warning: "⚠️", 32 | }; 33 | 34 | type CalloutType = keyof typeof TypeToEmoji; 35 | 36 | const classes: Record = { 37 | default: cx( 38 | "border-orange-200 bg-orange-100 text-orange-800 dark:border-orange-400/30 dark:bg-orange-400/20 dark:text-orange-300", 39 | ), 40 | error: cx( 41 | "border-red-200 bg-red-100 text-red-900 dark:border-red-200/30 dark:bg-red-900/30 dark:text-red-200", 42 | ), 43 | info: cx( 44 | "border-blue-200 bg-blue-100 text-blue-900 dark:border-blue-200/30 dark:bg-blue-900/30 dark:text-blue-200", 45 | ), 46 | warning: cx( 47 | "border-yellow-200 bg-yellow-100 text-yellow-900 dark:border-yellow-200/30 dark:bg-yellow-700/30 dark:text-yellow-200", 48 | ), 49 | }; 50 | 51 | interface CalloutProps { 52 | type?: CalloutType; 53 | emoji?: string | ReactElement; 54 | children: ReactNode; 55 | } 56 | 57 | export function Callout(props: CalloutProps): ReactElement { 58 | const { children, type = "default", emoji = TypeToEmoji[type] } = props; 59 | 60 | return ( 61 |
68 |
74 | {emoji} 75 |
76 |
{children}
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /docs/src/app/docs/recipes/page.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Recipes", 3 | description: "Recipes for common use cases", 4 | }; 5 | 6 | # Recipes 7 | 8 | `t3-env` supports [Standard Schema](/docs/standard-schema) compliant validators, meaning you can use transforms, default values, and all the powerful features your chosen validator provides. Below we'll look at a few example recipes for common use cases. 9 | 10 | 11 | All environment variables are strings, so your schema should start with a string validator before applying transforms or coercions. 12 | 13 | 14 | ## Booleans 15 | 16 | Coercing booleans from strings is a common use case. Below are examples of how to do this with different validators. 17 | 18 | 19 | 20 | Zod's default primitives coercion should not be used for booleans, since every string gets coerced to true. 21 | 22 | 23 | 24 | ```ts 25 | export const env = createEnv({ 26 | server: { 27 | COERCED_BOOLEAN: z 28 | .string() 29 | // transform to boolean using preferred coercion logic 30 | .transform((s) => s !== "false" && s !== "0"), 31 | 32 | ONLY_BOOLEAN: z 33 | .string() 34 | // only allow "true" or "false" 35 | .refine((s) => s === "true" || s === "false") 36 | // transform to boolean 37 | .transform((s) => s === "true"), 38 | }, 39 | // ... 40 | }); 41 | ``` 42 | 43 | ## Numbers 44 | 45 | Coercing numbers from strings is another common use case. 46 | 47 | ```ts 48 | export const env = createEnv({ 49 | server: { 50 | SOME_NUMBER: z 51 | .string() 52 | // transform to number 53 | .transform((s) => parseInt(s, 10)) 54 | // make sure transform worked 55 | .pipe(z.number()), 56 | 57 | // Alternatively, use Zod's default primitives coercion 58 | // https://zod.dev/?id=coercion-for-primitives 59 | ZOD_NUMBER_COERCION: z.coerce.number(), 60 | }, 61 | // ... 62 | }); 63 | ``` 64 | 65 | ## Storybook 66 | 67 | [Storybook](https://storybook.js.org/) uses its own bundler, which is otherwise 68 | unaware of `t3-env` and won't call into `runtimeEnv` to ensure that the environment 69 | variables are present. You can use Storybook's support for defining environment 70 | variables separately to ensure that all environment variables are actually 71 | available for Storybook: 72 | 73 | ```ts 74 | // .storybook/main.ts 75 | 76 | import { env as t3Env } from "~/env/client.mjs"; 77 | 78 | const config: StorybookConfig = { 79 | // other Storybook config... 80 | env: (config1) => ({ 81 | ...config1, 82 | ...t3Env, 83 | }) 84 | }; 85 | 86 | export default config; 87 | ``` 88 | -------------------------------------------------------------------------------- /.cursor/rules/general.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | 7 | # Overview 8 | 9 | You are an expert in TypeScript library development with excellent taste in API design. 10 | 11 | - Follow the user's requirements carefully & to the letter. 12 | - First think step-by-step - describe your plan for what to build in pseudocode, written out in great detail. 13 | 14 | ## Tech Stack 15 | 16 | You're working in a monorepo using: 17 | 18 | - TypeScript 19 | - Bun 20 | - Vitest 21 | 22 | ## Core Principles 23 | 24 | - Write straightforward, readable, and maintainable code. Use explicit types on exported functions. 25 | - Use strong typing, avoid 'any'. 26 | - Restate what the objective is of what you are being asked to change clearly in a short summary. 27 | - Do not use any third party library. The library must confirm to [Standard Schema](mdc:https:/standardschema.dev) and not be tied to any one schema validation library. 28 | - Reason about the API design before implementing changes. Is it possible to achieve strong and generic typing for your chosen API? If not, reconsider. 29 | - Create clearly documented changesets for your introduced changes using `bun changeset`. 30 | 31 | ## Coding Standards 32 | 33 | ### Naming Conventions 34 | 35 | - Variables, functions, methods: camelCase 36 | - Files, directories: kebab-case 37 | - Constants, env variables: UPPERCASE 38 | 39 | ### Functions 40 | 41 | - Use descriptive names: verbs & nouns (e.g., getUserData) 42 | - Prefer the `function` keyword over arrow functions where their differences doesn't matter. 43 | - Document with JSDoc annotations 44 | 45 | ### Types and Interfaces 46 | 47 | - Prefer custom interfaces over inline types 48 | - Use 'readonly' for immutable properties 49 | - If an import is only used as a type in the file, use 'import type' instead of 'import' 50 | 51 | ### Validating changes 52 | 53 | - Always validate your changes confirm to the project lint configuration by running `bun run lint`. 54 | - Write tests for changes and validate they pass using `bun run test`. 55 | 56 | ## Code Review Checklist 57 | 58 | - Ensure proper typing 59 | - Check for code duplication 60 | - Verify error handling 61 | - Confirm test coverage 62 | - Review naming conventions 63 | - Assess overall code structure and readability 64 | 65 | ## Documentation 66 | 67 | - Use the active voice 68 | - Use the present tense 69 | - Write in a clear and concise manner 70 | - Present information in a logical order 71 | - Use lists and tables when appropriate 72 | - When writing JSDocs, only use TypeDoc compatible tags. 73 | - Always write JSDocs for all code: classes, functions, methods, fields, types, interfaces. 74 | -------------------------------------------------------------------------------- /.github/workflows/release-canary.yaml: -------------------------------------------------------------------------------- 1 | name: Release - Canary 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | branches: 7 | - main 8 | 9 | env: 10 | TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} 11 | TURBO_TEAM: ${{ secrets.TURBO_TEAM }} 12 | 13 | jobs: 14 | release: 15 | if: contains(github.event.pull_request.labels.*.name, 'release canary') 16 | name: Build & Publish a canary release 17 | runs-on: ubuntu-latest # blacksmith-4vcpu-ubuntu-2404 18 | permissions: 19 | contents: write 20 | id-token: write 21 | issues: write 22 | pull-requests: write 23 | 24 | steps: 25 | - name: Checkout repo 26 | uses: actions/checkout@v6 27 | - uses: ./.github/setup 28 | - run: bun lint 29 | - run: bun fmt --check 30 | 31 | - name: Build 32 | run: bun run build 33 | 34 | - name: Authenticate to npm 35 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc 36 | 37 | - name: Publish 38 | run: bun run scripts/canary.ts 39 | 40 | - name: Create a new comment notifying of the new canary version 41 | uses: actions/github-script@v8 42 | with: 43 | github-token: ${{ secrets.GITHUB_TOKEN }} 44 | script: | 45 | // Get package version 46 | const fs = require("fs"); 47 | const packageJson = JSON.parse(fs.readFileSync("./packages/core/package.json")); 48 | const version = packageJson.version; 49 | 50 | // Create a comment on the PR with the new canary version 51 | github.rest.issues.createComment({ 52 | owner: context.repo.owner, 53 | repo: context.repo.repo, 54 | issue_number: context.payload.pull_request.number, 55 | body: `A new canary is available for testing. You can install this latest build in your project with: 56 | 57 | \`\`\`sh 58 | pnpm add @t3-oss/env-core@${version} 59 | \`\`\` 60 | 61 | or, if you're using Next.js: 62 | 63 | \`\`\`sh 64 | pnpm add @t3-oss/env-nextjs@${version} 65 | \`\`\` 66 | 67 | or, if you're using Nuxt: 68 | 69 | \`\`\`sh 70 | pnpm add @t3-oss/env-nuxt@${version} 71 | \`\`\` 72 | `, 73 | }) 74 | 75 | // Remove the label 76 | github.rest.issues.removeLabel({ 77 | owner: context.repo.owner, 78 | repo: context.repo.repo, 79 | issue_number: context.payload.pull_request.number, 80 | name: 'release canary', 81 | }); 82 | -------------------------------------------------------------------------------- /examples/nextjs/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --max-width: 1100px; 3 | --border-radius: 12px; 4 | --font-mono: 5 | ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", 6 | "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace; 7 | 8 | --foreground-rgb: 0, 0, 0; 9 | --background-start-rgb: 214, 219, 220; 10 | --background-end-rgb: 255, 255, 255; 11 | 12 | --primary-glow: conic-gradient( 13 | from 180deg at 50% 50%, 14 | #16abff33 0deg, 15 | #0885ff33 55deg, 16 | #54d6ff33 120deg, 17 | #0071ff33 160deg, 18 | transparent 360deg 19 | ); 20 | --secondary-glow: radial-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); 21 | 22 | --tile-start-rgb: 239, 245, 249; 23 | --tile-end-rgb: 228, 232, 233; 24 | --tile-border: conic-gradient( 25 | #00000080, 26 | #00000040, 27 | #00000030, 28 | #00000020, 29 | #00000010, 30 | #00000010, 31 | #00000080 32 | ); 33 | 34 | --callout-rgb: 238, 240, 241; 35 | --callout-border-rgb: 172, 175, 176; 36 | --card-rgb: 180, 185, 188; 37 | --card-border-rgb: 131, 134, 135; 38 | } 39 | 40 | @media (prefers-color-scheme: dark) { 41 | :root { 42 | --foreground-rgb: 255, 255, 255; 43 | --background-start-rgb: 0, 0, 0; 44 | --background-end-rgb: 0, 0, 0; 45 | 46 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); 47 | --secondary-glow: linear-gradient( 48 | to bottom right, 49 | rgba(1, 65, 255, 0), 50 | rgba(1, 65, 255, 0), 51 | rgba(1, 65, 255, 0.3) 52 | ); 53 | 54 | --tile-start-rgb: 2, 13, 46; 55 | --tile-end-rgb: 2, 5, 19; 56 | --tile-border: conic-gradient( 57 | #ffffff80, 58 | #ffffff40, 59 | #ffffff30, 60 | #ffffff20, 61 | #ffffff10, 62 | #ffffff10, 63 | #ffffff80 64 | ); 65 | 66 | --callout-rgb: 20, 20, 20; 67 | --callout-border-rgb: 108, 108, 108; 68 | --card-rgb: 100, 100, 100; 69 | --card-border-rgb: 200, 200, 200; 70 | } 71 | } 72 | 73 | * { 74 | box-sizing: border-box; 75 | padding: 0; 76 | margin: 0; 77 | } 78 | 79 | html, 80 | body { 81 | max-width: 100vw; 82 | min-height: 100vh; 83 | overflow-x: hidden; 84 | display: flex; 85 | flex-direction: column; 86 | align-items: center; 87 | justify-content: center; 88 | gap: 8; 89 | } 90 | 91 | body { 92 | color: rgb(var(--foreground-rgb)); 93 | background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) 94 | rgb(var(--background-start-rgb)); 95 | } 96 | 97 | a { 98 | color: inherit; 99 | text-decoration: none; 100 | } 101 | 102 | @media (prefers-color-scheme: dark) { 103 | html { 104 | color-scheme: dark; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /docs/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "@/app/site-config"; 2 | import { SiteHeader } from "@/components/site-header"; 3 | import { TailwindIndicator } from "@/components/tailwind-indicator"; 4 | import { cn } from "@/lib/cn"; 5 | import "@/styles/globals.css"; 6 | import { Analytics } from "@vercel/analytics/react"; 7 | import type { Metadata, Viewport } from "next"; 8 | import { Inter, JetBrains_Mono } from "next/font/google"; 9 | import localFont from "next/font/local"; 10 | import { ThemeProvider } from "next-themes"; 11 | import { ViewTransitions } from "next-view-transitions"; 12 | 13 | const fontSans = Inter({ 14 | subsets: ["latin"], 15 | variable: "--font-sans", 16 | }); 17 | 18 | const fontCal = localFont({ 19 | src: "../styles/calsans.ttf", 20 | variable: "--font-cal", 21 | display: "swap", 22 | }); 23 | 24 | const fontMono = JetBrains_Mono({ 25 | subsets: ["latin"], 26 | variable: "--font-mono", 27 | }); 28 | 29 | export const metadata: Metadata = { 30 | metadataBase: new URL("https://env.t3.gg"), 31 | title: { 32 | default: siteConfig.name, 33 | template: `%s - ${siteConfig.name}`, 34 | }, 35 | description: siteConfig.description, 36 | icons: { 37 | icon: "/favicon.ico", 38 | shortcut: "/favicon-16x16.png", 39 | apple: "/apple-touch-icon.png", 40 | }, 41 | openGraph: { 42 | type: "website", 43 | locale: "en_US", 44 | title: siteConfig.name, 45 | description: siteConfig.description, 46 | images: [ 47 | { 48 | url: "/opengraph-image.png", 49 | width: 910, 50 | height: 455, 51 | alt: siteConfig.name, 52 | }, 53 | ], 54 | }, 55 | twitter: { 56 | card: "summary_large_image", 57 | title: siteConfig.name, 58 | description: siteConfig.description, 59 | images: [{ url: "/opengraph-image.png" }], 60 | }, 61 | }; 62 | 63 | export const viewport: Viewport = { 64 | themeColor: [ 65 | { media: "(prefers-color-scheme: light)", color: "white" }, 66 | { media: "(prefers-color-scheme: dark)", color: "black" }, 67 | ], 68 | }; 69 | 70 | interface RootLayoutProps { 71 | children: React.ReactNode; 72 | } 73 | 74 | export default function RootLayout({ children }: RootLayoutProps) { 75 | return ( 76 | 77 | 78 | 79 | 87 | 88 |
89 | 90 |
{children}
91 |
92 | 93 |
94 | 95 | 96 | 97 |
98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /docs/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import type { MDXComponents } from "mdx/types"; 2 | import { Link } from "next-view-transitions"; 3 | 4 | import { Icons } from "@/components/icons"; 5 | import { Callout } from "@/components/mdx/callout"; 6 | import { Codeblock } from "@/components/mdx/code-block"; 7 | 8 | // This file is required to use MDX in `app` directory. 9 | export function useMDXComponents(components: MDXComponents): MDXComponents { 10 | return { 11 | // Allows customizing built-in components, e.g. to add styling. 12 | h1: ({ children, ...props }) => ( 13 |

14 | {children} 15 |

16 | ), 17 | h2: ({ children, ...props }) => ( 18 |

23 | 24 | {children} 25 | 26 | 27 |

28 | ), 29 | h3: ({ children, ...props }) => ( 30 |

31 | 32 | {children} 33 | 34 | 35 |

36 | ), 37 | h4: ({ children, ...props }) => ( 38 |

39 | 40 | {children} 41 | 42 | 43 |

44 | ), 45 | p: (props) =>

, 46 | a: ({ children, href }) => { 47 | const isExternal = href?.startsWith("http"); 48 | const Component = isExternal ? "a" : Link; 49 | return ( 50 | 54 | {children} 55 | 56 | ); 57 | }, 58 | ul: (props) =>

    , 59 | code: (props) => ( 60 | 64 | ), 65 | pre: Codeblock, 66 | 67 | img: (props) => , 68 | 69 | // Add custom components. 70 | Callout, 71 | Steps: ({ ...props }) => ( 72 |
    73 | ), 74 | 75 | // Pass through all other components. 76 | ...components, 77 | }; 78 | } 79 | 80 | function slugify(input: unknown) { 81 | if (typeof input !== "string") { 82 | return ""; 83 | } 84 | return input.replaceAll(" ", "-").toLowerCase().trim(); 85 | } 86 | -------------------------------------------------------------------------------- /docs/src/components/mobile-nav.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { usePathname } from "next/navigation"; 4 | import { Link } from "next-view-transitions"; 5 | import * as React from "react"; 6 | import { cn } from "@/lib/cn"; 7 | import { Icons } from "./icons"; 8 | import type { NavItem } from "./main-nav"; 9 | import type { NestedNavItem } from "./sidebar"; 10 | import { ThemeToggle } from "./theme-toggle"; 11 | import { Button } from "./ui/button"; 12 | import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from "./ui/popover"; 13 | import { ScrollArea } from "./ui/scroll-area"; 14 | 15 | export function MobileDropdown(props: { items: { main: NavItem[]; docs: NestedNavItem[] } }) { 16 | const [isOpen, setIsOpen] = React.useState(false); 17 | const pathname = usePathname(); 18 | 19 | React.useEffect(() => { 20 | if (isOpen) { 21 | document.body.classList.add("overflow-hidden"); 22 | } else { 23 | document.body.classList.remove("overflow-hidden"); 24 | } 25 | }, [isOpen]); 26 | 27 | return ( 28 | 29 | 30 | 38 | 39 | 40 | 41 | {props.items.docs.map((item) => ( 42 |
    43 |

    {item.title}

    44 | {item?.items?.length && 45 | item.items.map((item) => ( 46 | 47 | {item.href ? ( 48 | 57 | {item.title} 58 | {item.label && ( 59 | 60 | {item.label} 61 | 62 | )} 63 | 64 | ) : ( 65 | item.title 66 | )} 67 | 68 | ))} 69 |
    70 | ))} 71 |
    72 |
    73 | 74 |
    75 |
    76 |
    77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /packages/nextjs/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the nextjs package of t3-env. 3 | * It contains the `createEnv` function that you can use to create your schema. 4 | * @module 5 | */ 6 | import type { 7 | CreateEnv, 8 | CreateSchemaOptions, 9 | DefaultCombinedSchema, 10 | ServerClientOptions, 11 | StandardSchemaDictionary, 12 | StandardSchemaV1, 13 | StrictOptions, 14 | } from "@t3-oss/env-core"; 15 | import { createEnv as createEnvCore } from "@t3-oss/env-core"; 16 | 17 | const CLIENT_PREFIX = "NEXT_PUBLIC_"; 18 | type ClientPrefix = typeof CLIENT_PREFIX; 19 | 20 | type Options< 21 | TServer extends StandardSchemaDictionary, 22 | TClient extends Record<`${ClientPrefix}${string}`, StandardSchemaV1>, 23 | TShared extends StandardSchemaDictionary, 24 | TExtends extends Array>, 25 | TFinalSchema extends StandardSchemaV1<{}, {}>, 26 | > = Omit< 27 | StrictOptions & 28 | ServerClientOptions & 29 | CreateSchemaOptions, 30 | "runtimeEnvStrict" | "runtimeEnv" | "clientPrefix" 31 | > & 32 | ( 33 | | { 34 | /** 35 | * Manual destruction of `process.env`. Required for Next.js < 13.4.4. 36 | */ 37 | runtimeEnv: StrictOptions< 38 | ClientPrefix, 39 | TServer, 40 | TClient, 41 | TShared, 42 | TExtends 43 | >["runtimeEnvStrict"]; 44 | experimental__runtimeEnv?: never; 45 | } 46 | | { 47 | runtimeEnv?: never; 48 | /** 49 | * Can be used for Next.js ^13.4.4 since they stopped static analysis of server side `process.env`. 50 | * Only client side `process.env` is statically analyzed and needs to be manually destructured. 51 | */ 52 | experimental__runtimeEnv: Record< 53 | | { 54 | [TKey in keyof TClient]: TKey extends `${ClientPrefix}${string}` ? TKey : never; 55 | }[keyof TClient] 56 | | { 57 | [TKey in keyof TShared]: TKey extends string ? TKey : never; 58 | }[keyof TShared], 59 | string | boolean | number | undefined 60 | >; 61 | } 62 | ); 63 | 64 | /** 65 | * Create a new environment variable schema. 66 | */ 67 | export function createEnv< 68 | TServer extends StandardSchemaDictionary = NonNullable, 69 | TClient extends Record<`${ClientPrefix}${string}`, StandardSchemaV1> = NonNullable, 70 | TShared extends StandardSchemaDictionary = NonNullable, 71 | const TExtends extends Array> = [], 72 | TFinalSchema extends StandardSchemaV1<{}, {}> = DefaultCombinedSchema, 73 | >( 74 | opts: Options, 75 | ): CreateEnv { 76 | const client = typeof opts.client === "object" ? opts.client : {}; 77 | const server = typeof opts.server === "object" ? opts.server : {}; 78 | const shared = opts.shared; 79 | 80 | const runtimeEnv = opts.runtimeEnv 81 | ? opts.runtimeEnv 82 | : { 83 | ...process.env, 84 | ...opts.experimental__runtimeEnv, 85 | }; 86 | 87 | return createEnvCore({ 88 | ...opts, 89 | shared, 90 | client, 91 | server, 92 | clientPrefix: CLIENT_PREFIX, 93 | runtimeEnv, 94 | }); 95 | } 96 | -------------------------------------------------------------------------------- /docs/src/app/docs/nuxt/page.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Nuxt", 3 | description: "Nuxt integration for T3 Env", 4 | foo: 5445, 5 | }; 6 | 7 | # Nuxt 8 | 9 | The Nuxt package comes preconfigured for Nuxt, and fills the `runtimeEnv` option automatically. All you need to do is define your schemas! 10 | 11 | 12 | 13 | ### Install dependencies 14 | 15 | Install the required dependencies: 16 | 17 | ```bash 18 | pnpm add @t3-oss/env-nuxt zod 19 | 20 | # or using JSR 21 | deno add jsr:@t3-oss/env-nuxt 22 | ``` 23 | 24 | 25 | 26 | Although we'll use Zod as examples throughout these docs, you can use any validator that supports [Standard Schema](/docs/standard-schema). 27 | 28 | 29 | 30 | 31 | 32 | `@t3-oss/env-core` requires a minimum of `typescript@5`. 33 | 34 | 35 | 36 | 37 | 38 | `@t3-oss/env-core` is an ESM only package. Make sure that your tsconfig uses a module resolution that can read `package.json#exports` (`Bundler` is recommended). 39 | 40 | 41 | 42 | ### Create your schema 43 | 44 | ```ts title="env.ts" 45 | import { createEnv } from "@t3-oss/env-nuxt"; 46 | import { z } from "zod"; 47 | 48 | export const env = createEnv({ 49 | server: { 50 | DATABASE_URL: z.url(), 51 | OPEN_AI_API_KEY: z.string().min(1), 52 | }, 53 | client: { 54 | NUXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1), 55 | }, 56 | }); 57 | ``` 58 | 59 | 60 | 61 | While defining both the client and server schemas in a single file provides the best developer experience, 62 | it also means that your validation schemas for the server variables will be shipped to the client. 63 | If you consider the **names** of your variables sensitive, you should split your schemas into two files. 64 | 65 | ```ts title="src/env/server.ts" 66 | import { createEnv } from "@t3-oss/env-nuxt"; 67 | import { z } from "zod"; 68 | 69 | export const env = createEnv({ 70 | server: { 71 | DATABASE_URL: z.url(), 72 | OPEN_AI_API_KEY: z.string().min(1), 73 | }, 74 | }); 75 | ``` 76 | 77 | ```ts title="src/env/client.ts" 78 | import { createEnv } from "@t3-oss/env-nuxt"; 79 | import { z } from "zod"; 80 | 81 | export const env = createEnv({ 82 | client: { 83 | PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), 84 | }, 85 | }); 86 | ``` 87 | 88 | 89 | 90 | ### Validate schema on build (recommended) 91 | 92 | We recommend you importing your newly created file in your `nuxt.config.ts`. This will make sure your environment variables are validated at build time which will save you a lot of time and headaches down the road. 93 | 94 | ```ts title="nuxt.config.ts" {1} 95 | import "./env"; 96 | 97 | export default defineNuxtConfig({ 98 | // ... 99 | }); 100 | ``` 101 | 102 | ### Use your schema 103 | 104 | Then, import the `env` object in your application and use it, taking advantage of type-safety and auto-completion: 105 | 106 | ```ts title="server/api/some-endpoint.ts" /env.OPEN_AI_API_KEY/ 107 | import { env } from "~~/env"; // On server 108 | 109 | export default defineEventHandler(() => { 110 | // do fancy ai stuff 111 | const magic = await fetch("...", { 112 | headers: { Authorization: env.OPEN_AI_API_KEY }, 113 | }); 114 | // ... 115 | }); 116 | ``` 117 | 118 | ```vue title="app.vue" /env.NUXT_PUBLIC_GREETING/ 119 | 122 | 126 | ``` 127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/src/app/docs/standard-schema/page.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Standard Schema", 3 | description: 4 | "Learn about Standard Schema support in T3 Env and how to use any compliant validator of your choice.", 5 | }; 6 | 7 | # Standard Schema support 8 | 9 | T3 Env is built on the [Standard Schema](https://standardschema.dev) specification, which means you can use **any** validator library that implements this standard. This gives you the flexibility to choose the validation library that best fits your project's needs, or the one that you're already using. 10 | 11 | ## What is Standard Schema? 12 | 13 | Standard Schema is a unified specification for TypeScript schema validation libraries. It provides a common interface that allows different validation libraries to be interoperable. This means that libraries like T3 Env can work with any validator that implements the Standard Schema interface. 14 | 15 | The specification defines: 16 | 17 | - A standardized way to validate input data 18 | - Type inference for input and output types 19 | - A consistent error format 20 | 21 | For more details, visit the [Standard Schema specification](https://standardschema.dev). 22 | 23 | ## Supported validators 24 | 25 | T3 Env supports any validator that implements the Standard Schema v1 interface. Here are some popular options: 26 | 27 | ### Zod 28 | 29 | [Zod](https://zod.dev) is a TypeScript-first schema validation library with a simple, composable API. 30 | 31 | ```bash 32 | npm install zod 33 | ``` 34 | 35 | ```ts title="src/env.ts" 36 | import { createEnv } from "@t3-oss/env-core"; 37 | import { z } from "zod"; 38 | 39 | export const env = createEnv({ 40 | server: { 41 | DATABASE_URL: z.string().url(), 42 | PORT: z.coerce.number().default(3000), 43 | }, 44 | clientPrefix: "PUBLIC_", 45 | client: { 46 | PUBLIC_API_URL: z.string().url(), 47 | }, 48 | runtimeEnv: process.env, 49 | }); 50 | ``` 51 | 52 | ### Valibot 53 | 54 | [Valibot](https://valibot.dev) is a modular, tree-shakeable validation library focused on bundle size and performance. 55 | 56 | ```bash 57 | npm install valibot 58 | ``` 59 | 60 | ```ts title="src/env.ts" 61 | import { createEnv } from "@t3-oss/env-core"; 62 | import * as v from "valibot"; 63 | 64 | export const env = createEnv({ 65 | server: { 66 | DATABASE_URL: v.pipe(v.string(), v.url()), 67 | PORT: v.optional(v.pipe(v.string(), v.transform(Number)), "3000"), 68 | }, 69 | clientPrefix: "PUBLIC_", 70 | client: { 71 | PUBLIC_API_URL: v.pipe(v.string(), v.url()), 72 | }, 73 | runtimeEnv: process.env, 74 | }); 75 | ``` 76 | 77 | ### ArkType 78 | 79 | [ArkType](https://arktype.io) is a runtime validation library with TypeScript-like syntax and exceptional performance. 80 | 81 | ```bash 82 | npm install arktype 83 | ``` 84 | 85 | ```ts title="src/env.ts" 86 | import { createEnv } from "@t3-oss/env-core"; 87 | import { type } from "arktype"; 88 | 89 | export const env = createEnv({ 90 | server: { 91 | DATABASE_URL: type("string.url"), 92 | PORT: type("string.numeric.parse"), 93 | }, 94 | clientPrefix: "PUBLIC_", 95 | client: { 96 | PUBLIC_API_URL: type("string.url"), 97 | }, 98 | runtimeEnv: process.env, 99 | }); 100 | ``` 101 | 102 | ### Other validators 103 | 104 | Any validator that implements the Standard Schema v1 interface will work with T3 Env. Check the [Standard Schema ecosystem](https://standardschema.dev) for more options. 105 | 106 | ## Presets 107 | 108 | T3 Env ships with [presets](/docs/customization#extending-presets) for common deployment platforms (Vercel, Netlify, Railway, etc.). These presets are available for multiple validators: 109 | 110 | - `@t3-oss/env-core/presets-zod` - Presets using Zod 111 | - `@t3-oss/env-core/presets-valibot` - Presets using Valibot 112 | - `@t3-oss/env-core/presets-arktype` - Presets using ArkType 113 | 114 | ```ts title="src/env.ts" 115 | import { createEnv } from "@t3-oss/env-core"; 116 | import { vercel } from "@t3-oss/env-core/presets-valibot"; 117 | import * as v from "valibot"; 118 | 119 | export const env = createEnv({ 120 | server: { 121 | DATABASE_URL: v.pipe(v.string(), v.url()), 122 | }, 123 | extends: [vercel()], 124 | runtimeEnv: process.env, 125 | }); 126 | ``` 127 | 128 | Choose the preset import that matches your validator to keep your bundle size optimized. 129 | -------------------------------------------------------------------------------- /packages/core/src/standard.ts: -------------------------------------------------------------------------------- 1 | /** The Standard Schema interface. */ 2 | export interface StandardSchemaV1 { 3 | /** The Standard Schema properties. */ 4 | readonly "~standard": StandardSchemaV1.Props; 5 | } 6 | 7 | export declare namespace StandardSchemaV1 { 8 | /** The Standard Schema properties interface. */ 9 | export interface Props { 10 | /** The version number of the standard. */ 11 | readonly version: 1; 12 | /** The vendor name of the schema library. */ 13 | readonly vendor: string; 14 | /** Validates unknown input values. */ 15 | readonly validate: (value: unknown) => Result | Promise>; 16 | /** Inferred types associated with the schema. */ 17 | readonly types?: Types | undefined; 18 | } 19 | 20 | /** The result interface of the validate function. */ 21 | export type Result = SuccessResult | FailureResult; 22 | 23 | /** The result interface if validation succeeds. */ 24 | export interface SuccessResult { 25 | /** The typed output value. */ 26 | readonly value: Output; 27 | /** The non-existent issues. */ 28 | readonly issues?: undefined; 29 | } 30 | 31 | /** The result interface if validation fails. */ 32 | export interface FailureResult { 33 | /** The issues of failed validation. */ 34 | readonly issues: ReadonlyArray; 35 | } 36 | 37 | /** The issue interface of the failure output. */ 38 | export interface Issue { 39 | /** The error message of the issue. */ 40 | readonly message: string; 41 | /** The path of the issue, if any. */ 42 | readonly path?: ReadonlyArray | undefined; 43 | } 44 | 45 | /** The path segment interface of the issue. */ 46 | export interface PathSegment { 47 | /** The key representing a path segment. */ 48 | readonly key: PropertyKey; 49 | } 50 | 51 | /** The Standard Schema types interface. */ 52 | export interface Types { 53 | /** The input type of the schema. */ 54 | readonly input: Input; 55 | /** The output type of the schema. */ 56 | readonly output: Output; 57 | } 58 | 59 | /** Infers the input type of a Standard Schema. */ 60 | export type InferInput = NonNullable< 61 | Schema["~standard"]["types"] 62 | >["input"]; 63 | 64 | /** Infers the output type of a Standard Schema. */ 65 | export type InferOutput = NonNullable< 66 | Schema["~standard"]["types"] 67 | >["output"]; 68 | } 69 | 70 | export type StandardSchemaDictionary< 71 | Input = Record, 72 | Output extends Record = Input, 73 | > = { 74 | [K in keyof Input]-?: StandardSchemaV1; 75 | }; 76 | 77 | export namespace StandardSchemaDictionary { 78 | export type InferInput = { 79 | [K in keyof T]: StandardSchemaV1.InferInput; 80 | }; 81 | export type InferOutput = { 82 | [K in keyof T]: StandardSchemaV1.InferOutput; 83 | }; 84 | } 85 | 86 | export function ensureSynchronous(value: T | Promise, message: string): asserts value is T { 87 | if (value instanceof Promise) { 88 | throw new Error(message); 89 | } 90 | } 91 | 92 | export function parseWithDictionary( 93 | dictionary: TDict, 94 | value: Record, 95 | ): StandardSchemaV1.Result> { 96 | const result: Record = {}; 97 | const issues: StandardSchemaV1.Issue[] = []; 98 | for (const key in dictionary) { 99 | const propResult = dictionary[key]["~standard"].validate(value[key]); 100 | 101 | ensureSynchronous(propResult, `Validation must be synchronous, but ${key} returned a Promise.`); 102 | 103 | if (propResult.issues) { 104 | issues.push( 105 | ...propResult.issues.map((issue) => ({ 106 | ...issue, 107 | message: issue.message, // https://github.com/t3-oss/t3-env/pull/346 108 | path: [key, ...(issue.path ?? [])], 109 | })), 110 | ); 111 | continue; 112 | } 113 | result[key] = propResult.value; 114 | } 115 | if (issues.length) { 116 | return { issues }; 117 | } 118 | return { value: result as never }; 119 | } 120 | -------------------------------------------------------------------------------- /packages/core/src/presets.ts: -------------------------------------------------------------------------------- 1 | export interface VercelEnv { 2 | VERCEL?: string; 3 | CI?: string; 4 | VERCEL_ENV?: "development" | "preview" | "production"; 5 | VERCEL_TARGET_ENV?: "development" | "preview" | "production" | (string & {}); 6 | VERCEL_URL?: string; 7 | VERCEL_PROJECT_PRODUCTION_URL?: string; 8 | VERCEL_BRANCH_URL?: string; 9 | VERCEL_REGION?: string; 10 | VERCEL_DEPLOYMENT_ID?: string; 11 | VERCEL_SKEW_PROTECTION_ENABLED?: string; 12 | VERCEL_AUTOMATION_BYPASS_SECRET?: string; 13 | VERCEL_GIT_PROVIDER?: string; 14 | VERCEL_GIT_REPO_SLUG?: string; 15 | VERCEL_GIT_REPO_OWNER?: string; 16 | VERCEL_GIT_REPO_ID?: string; 17 | VERCEL_GIT_COMMIT_REF?: string; 18 | VERCEL_GIT_COMMIT_SHA?: string; 19 | VERCEL_GIT_COMMIT_MESSAGE?: string; 20 | VERCEL_GIT_COMMIT_AUTHOR_LOGIN?: string; 21 | VERCEL_GIT_COMMIT_AUTHOR_NAME?: string; 22 | VERCEL_GIT_PREVIOUS_SHA?: string; 23 | VERCEL_GIT_PULL_REQUEST_ID?: string; 24 | } 25 | 26 | export interface NeonVercelEnv { 27 | DATABASE_URL: string; 28 | DATABASE_URL_UNPOOLED?: string; 29 | PGHOST?: string; 30 | PGHOST_UNPOOLED?: string; 31 | PGUSER?: string; 32 | PGDATABASE?: string; 33 | PGPASSWORD?: string; 34 | POSTGRES_URL?: string; 35 | POSTGRES_URL_NON_POOLING?: string; 36 | POSTGRES_USER?: string; 37 | POSTGRES_HOST?: string; 38 | POSTGRES_PASSWORD?: string; 39 | POSTGRES_DATABASE?: string; 40 | POSTGRES_URL_NO_SSL?: string; 41 | POSTGRES_PRISMA_URL?: string; 42 | } 43 | 44 | export interface SupabaseVercelEnv { 45 | POSTGRES_URL: string; 46 | POSTGRES_PRISMA_URL?: string; 47 | POSTGRES_URL_NON_POOLING?: string; 48 | POSTGRES_USER?: string; 49 | POSTGRES_HOST?: string; 50 | POSTGRES_PASSWORD?: string; 51 | POSTGRES_DATABASE?: string; 52 | SUPABASE_SERVICE_ROLE_KEY?: string; 53 | SUPABASE_ANON_KEY?: string; 54 | SUPABASE_URL?: string; 55 | SUPABASE_JWT_SECRET?: string; 56 | NEXT_PUBLIC_SUPABASE_ANON_KEY?: string; 57 | NEXT_PUBLIC_SUPABASE_URL?: string; 58 | } 59 | 60 | export interface UploadThingV6Env { 61 | UPLOADTHING_TOKEN: string; 62 | } 63 | 64 | export interface UploadThingEnv { 65 | UPLOADTHING_TOKEN: string; 66 | } 67 | 68 | export interface RenderEnv { 69 | IS_PULL_REQUEST?: string; 70 | RENDER_DISCOVERY_SERVICE?: string; 71 | RENDER_EXTERNAL_HOSTNAME?: string; 72 | RENDER_EXTERNAL_URL?: string; 73 | RENDER_GIT_BRANCH?: string; 74 | RENDER_GIT_COMMIT?: string; 75 | RENDER_GIT_REPO_SLUG?: string; 76 | RENDER_INSTANCE_ID?: string; 77 | RENDER_SERVICE_ID?: string; 78 | RENDER_SERVICE_NAME?: string; 79 | RENDER_SERVICE_TYPE?: "web" | "pserv" | "cron" | "worker" | "static"; 80 | RENDER?: string; 81 | } 82 | 83 | export interface RailwayEnv { 84 | RAILWAY_PUBLIC_DOMAIN?: string; 85 | RAILWAY_PRIVATE_DOMAIN?: string; 86 | RAILWAY_TCP_PROXY_DOMAIN?: string; 87 | RAILWAY_TCP_PROXY_PORT?: string; 88 | RAILWAY_TCP_APPLICATION_PORT?: string; 89 | RAILWAY_PROJECT_NAME?: string; 90 | RAILWAY_PROJECT_ID?: string; 91 | RAILWAY_ENVIRONMENT_NAME?: string; 92 | RAILWAY_ENVIRONMENT_ID?: string; 93 | RAILWAY_SERVICE_NAME?: string; 94 | RAILWAY_SERVICE_ID?: string; 95 | RAILWAY_REPLICA_ID?: string; 96 | RAILWAY_DEPLOYMENT_ID?: string; 97 | RAILWAY_SNAPSHOT_ID?: string; 98 | RAILWAY_VOLUME_NAME?: string; 99 | RAILWAY_VOLUME_MOUNT_PATH?: string; 100 | RAILWAY_RUN_UID?: string; 101 | RAILWAY_GIT_COMMIT_SHA?: string; 102 | RAILWAY_GIT_AUTHOR_EMAIL?: string; 103 | RAILWAY_GIT_BRANCH?: string; 104 | RAILWAY_GIT_REPO_NAME?: string; 105 | RAILWAY_GIT_REPO_OWNER?: string; 106 | RAILWAY_GIT_COMMIT_MESSAGE?: string; 107 | } 108 | 109 | export interface FlyEnv { 110 | FLY_APP_NAME?: string; 111 | FLY_MACHINE_ID?: string; 112 | FLY_ALLOC_ID?: string; 113 | FLY_REGION?: string; 114 | FLY_PUBLIC_IP?: string; 115 | FLY_IMAGE_REF?: string; 116 | FLY_MACHINE_VERSION?: string; 117 | FLY_PRIVATE_IP?: string; 118 | FLY_PROCESS_GROUP?: string; 119 | FLY_VM_MEMORY_MB?: string; 120 | PRIMARY_REGION?: string; 121 | } 122 | 123 | export interface NetlifyEnv { 124 | NETLIFY?: string; 125 | BUILD_ID?: string; 126 | CONTEXT?: "production" | "deploy-preview" | "branch-deploy" | "dev"; 127 | REPOSITORY_URL?: string; 128 | BRANCH?: string; 129 | URL?: string; 130 | DEPLOY_URL?: string; 131 | DEPLOY_PRIME_URL?: string; 132 | DEPLOY_ID?: string; 133 | SITE_NAME?: string; 134 | SITE_ID?: string; 135 | } 136 | 137 | export interface UpstashRedisEnv { 138 | UPSTASH_REDIS_REST_URL: string; 139 | UPSTASH_REDIS_REST_TOKEN: string; 140 | } 141 | 142 | export interface CoolifyEnv { 143 | COOLIFY_FQDN?: string; 144 | COOLIFY_URL?: string; 145 | COOLIFY_BRANCH?: string; 146 | COOLIFY_RESOURCE_UUID?: string; 147 | COOLIFY_CONTAINER_NAME?: string; 148 | SOURCE_COMMIT?: string; 149 | PORT?: string; 150 | HOST?: string; 151 | } 152 | 153 | export interface ViteEnv { 154 | BASE_URL: string; 155 | MODE: string; 156 | DEV: boolean; 157 | PROD: boolean; 158 | SSR: boolean; 159 | } 160 | 161 | export interface WxtEnv { 162 | MANIFEST_VERSION?: 2 | 3; 163 | BROWSER?: "chrome" | "firefox" | "safari" | "edge" | "opera"; 164 | CHROME?: boolean; 165 | FIREFOX?: boolean; 166 | SAFARI?: boolean; 167 | EDGE?: boolean; 168 | OPERA?: boolean; 169 | } 170 | -------------------------------------------------------------------------------- /docs/src/app/docs/introduction/page.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Introduction", 3 | description: 4 | "Forgetting environment variables during build can be a hassle and difficult to debug if a bug is caused by a missing environment variable. This package provides a simple way to define environment variables validation for your app.", 5 | }; 6 | 7 | # T3 Env 8 | 9 | Forgetting environment variables during build can be a hassle and difficult to debug if a bug is caused by a missing environment variable. This package provides a simple way to define environment variables validation for your app. 10 | 11 | This library does all the grunt work for you, simply define your schema and use your environment variables safely. 12 | 13 | ## Features 14 | 15 | - 🔒 **Type-safe environment variables** - Get autocomplete and type checking for your environment variables 16 | - 🔄 **Standard Schema support** - Use any [Standard Schema](https://standardschema.dev) compliant validator (Zod, Valibot, ArkType, Typia, etc.) 17 | - 🎯 **Server/Client separation** - Prevent accidental exposure of server variables to the client 18 | - 🚀 **Framework agnostic** - Works with Next.js, Nuxt, Vite, and any other framework 19 | - 📦 **Presets included** - Ready-to-use configurations for Vercel, Netlify, Railway, and more 20 | 21 | import { buttonVariants } from "@/components/ui/button"; 22 | import { cn } from "@/lib/cn"; 23 | import { Link } from "next-view-transitions"; 24 | 25 | 26 | Take me to the installation! 27 | 28 | 29 | ## Rationale 30 | 31 | For a while, we've had validated environment variables in [create-t3-app](https://create.t3.gg) which has been super appreciated by the community. However, the code was quite scary and lived in user land which caused some confusion for new users. This library aims to move that complexity into a library that abstracts the implementation details and lets the user focus on just the necessary parts. It also allows other framework and stacks to benefit from the same validation strategy - which we've polished over a number of iterations up until now. 32 | 33 | ## Advantages over simpler solutions 34 | 35 | Validating envs are quite easy and can be done in a few lines of code. You can also infer the result from the validation onto your `process.env` object to benefit from autocompletion throughout your application. [Matt Pocock](https://www.youtube.com/watch?v=q1im-hMlKhM) did a video explaining how you can implement this approach: 36 | 37 | ```ts 38 | import { z } from "zod"; 39 | 40 | const envVariables = z.object({ 41 | DATABASE_URL: z.string(), 42 | CUSTOM_STUFF: z.string(), 43 | }); 44 | 45 | envVariables.parse(process.env); 46 | 47 | declare global { 48 | namespace NodeJS { 49 | interface ProcessEnv extends z.infer {} 50 | } 51 | } 52 | ``` 53 | 54 | However, it has a few drawbacks that this library solves: 55 | 56 | ### Transforms and Default values 57 | 58 | Since the above implementation doesn't mutate the `process.env` object, any transforms applied will make your types lie to you, as the type will be of the transformed type, but the value on `process.env` will be the original string. You also cannot apply default values to your environment variables which can be useful in some cases. 59 | 60 | By having an object you import and use throughout the application, you can use both of the above which unlocks some quite powerful features. 61 | 62 | ### Support for multiple environments 63 | 64 | By default, some frameworks (e.g. Next.js) treeshake away unused environment variables unless you explicitly access them on the `process.env` object. This means that the above implementation would fail even if you export and use the `envVariables` object in your application, as no environment will be included in the bundle for some environments / runtimes. 65 | 66 | Another pitfall is client side validation. Importing `envVariables` on the client will throw an error as the server side environment variables `DATABASE_URL` & `CUSTOM_STUFF` is not defined on the client. This library solves this issue by using a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) based implementation combined with your validator's safe parsing method. 67 | 68 | ### Standard Schema support 69 | 70 | T3 Env is built on the [Standard Schema](https://standardschema.dev) specification, which means you can use **any** validator library that implements this standard. Choose from popular options like: 71 | 72 | - [Zod](https://zod.dev) - TypeScript-first schema validation 73 | - [Valibot](https://valibot.dev) - Modular and tree-shakeable 74 | - [ArkType](https://arktype.io) - TypeScript-like syntax with exceptional performance 75 | - [Typia](https://typia.io) - Transformer-based validation 76 | - [And more...](https://github.com/standard-schema/standard-schema?tab=readme-ov-file#what-schema-libraries-implement-the-spec) 77 | 78 | This gives you the flexibility to pick the validator that best fits your project's needs, or the one that you're already using. 79 | 80 | Learn more in the [Standard Schema documentation](/docs/standard-schema). 81 | 82 | 83 | 84 | We're not leaking your server variables onto the client. Your server variables will be undefined on the client, and attempting to access one will throw a descriptive error message to ease debugging: 85 | 86 | ![invalid access](https://user-images.githubusercontent.com/51714798/234414211-33d9a91d-4bd7-42ff-a4af-1e101ee8cd93.png) 87 | 88 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typesafe Envs made Simple 2 | 3 | ![NPM Version](https://img.shields.io/npm/v/%40t3-oss%2Fenv-core) 4 | [![JSR](https://jsr.io/badges/@t3-oss/env-core)](https://jsr.io/@t3-oss/env-core) 5 | [![JSR Score](https://jsr.io/badges/@t3-oss/env-core/score)](https://jsr.io/@t3-oss/env-core) 6 | [![Socket Badge](https://socket.dev/api/badge/npm/package/@t3-oss/env-core)](https://socket.dev/npm/package/@t3-oss/env-core/overview) 7 | 8 | Deploying your app with invalid environment variables is a hassle. This package helps you to avoid that. 9 | 10 | > For full documentation, see https://env.t3.gg 11 | 12 | ## Installation 13 | 14 | > [!NOTE] 15 | > 16 | > This is an ESM only package that requires a tsconfig with a module resolution that can read package.json#exports (`NodeNext` if transpiling with `tsc`, `Bundler` if using a bundler). 17 | 18 | ```bash 19 | # npm 20 | npm i @t3-oss/env-nuxt 21 | 22 | # pnpm 23 | pnpm add @t3-oss/env-nuxt 24 | 25 | # bun 26 | bun add @t3-oss/env-nuxt 27 | 28 | # deno 29 | deno add jsr:@t3-oss/env-nuxt 30 | ``` 31 | 32 | ## Usage 33 | 34 | > [!NOTE] 35 | > 36 | > You may use any [Standard Schema](https://standardschema.dev) compliant validator of your choice. This example uses Zod. 37 | > See the [documentation](https://env.t3.gg/docs/standard-schema) for more details. 38 | 39 | This package supports the full power of Zod/Valibot etc, meaning you can use `transforms` and `default` values. 40 | 41 | ### Define your schema 42 | 43 | ```ts 44 | // src/env.mjs 45 | import { createEnv } from "@t3-oss/env-nextjs"; // or core package 46 | import { z } from "zod"; 47 | 48 | export const env = createEnv({ 49 | /* 50 | * Serverside Environment variables, not available on the client. 51 | * Will throw if you access these variables on the client. 52 | */ 53 | server: { 54 | DATABASE_URL: z.url(), 55 | OPEN_AI_API_KEY: z.string().min(1), 56 | }, 57 | /* 58 | * Environment variables available on the client (and server). 59 | * 60 | * 💡 You'll get type errors if these are not prefixed with NEXT_PUBLIC_. 61 | */ 62 | client: { 63 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), 64 | }, 65 | /* 66 | * Due to how Next.js bundles environment variables on Edge and Client, 67 | * we need to manually destructure them to make sure all are included in bundle. 68 | * 69 | * 💡 You'll get type errors if not all variables from `server` & `client` are included here. 70 | */ 71 | runtimeEnv: { 72 | DATABASE_URL: process.env.DATABASE_URL, 73 | OPEN_AI_API_KEY: process.env.OPEN_AI_API_KEY, 74 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: 75 | process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, 76 | }, 77 | }); 78 | ``` 79 | 80 | ### Use the schema in your app with autocompletion and type inference 81 | 82 | ```ts 83 | // src/app/hello/route.ts 84 | import { env } from "../env.mjs"; 85 | 86 | export const GET = (req: Request) => { 87 | const DATABASE_URL = env.DATABASE_URL; 88 | // use it... 89 | }; 90 | ``` 91 | 92 | ## Contributing 93 | 94 | This project uses [Bun](https://bun.sh) as its package manager. To install: 95 | 96 | ```sh 97 | # Linux & macOS 98 | curl -fsSL https://bun.sh/install | bash 99 | 100 | # Windows 101 | powershell -c "irm bun.sh/install.ps1 | iex" 102 | ``` 103 | 104 | To get started, clone the repo: 105 | 106 | ```sh 107 | git clone https://github.com/t3-oss/t3-env.git 108 | ``` 109 | 110 | It is recommended to use VSCode (or a fork like [Cursor](https://www.cursor.com) or [Windsurf](https://windsurf.com)) with the [recommended extensions](./.vscode/extensions.json) installed. 111 | 112 | Then, install the project dependencies: 113 | 114 | ```sh 115 | cd t3-env 116 | bun i 117 | ``` 118 | 119 | Checkout a feature branch for the change you're about to make: 120 | 121 | ```sh 122 | git checkout 123 | ``` 124 | 125 | Implement your changes, then submit a [Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) describing your changes. 126 | 127 | ### General Guidelines 128 | 129 | - When developing changes, checkout a feature branch. **DO NOT** create pull requests from your main branch as these are sometimes protected from maintainer edits. 130 | - Start the build watchers using `bun run dev`. This will recompile the necessary packages whenever you make changes. 131 | - Make as many changes as possible in the [core](./packages/core) package, re-export framework specific code from the respective package. 132 | - Add tests (both runtime and types) to verify your changes. 133 | - Add a changeset using `bun changeset` to ensure your changes trigger a release with appropriate changelog. 134 | 135 | ### When it's time to commit 136 | 137 | - Run the linters using `bun lint` (`bun lint:fix` to fix auto-fixable issues) 138 | - Run the tests using `bun run test`. Alternatively, you can use the test explorer in VSCode to target individual tests. 139 | - Commit with whatever commit message you want. We squash PRs so the individual commit messages does not matter. 140 | - For bug fixes, it's a good practise to first add a failing regression test in one commit, and then implement the fix in another. This helps the review process as it's easy to see what was failing before, and how it succeeded after your change. 141 | - Add a pull request description explaining your changes. Link any relevant issues (e.g. by including `Closes #123` in the description). 142 | - If some change might require extra consideration, a quick self-review can help maintainers more easily review your code and get it merged faster. 143 | 144 | Please note that this repo is maintained by individuals with full-time jobs as their primary source of income. Please allow for appropriate time to review as this is done on their spare time. If you have not received a review in a week, you may tag `@juliusmarminge` to bump the issue. 145 | 146 | ## License 147 | 148 | [MIT](./LICENSE.md) License © 2025 [Julius Marminge](https://github.com/juliusmarminge) 149 | -------------------------------------------------------------------------------- /docs/src/app/docs/nextjs/page.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Next.js", 3 | description: "Next.js integration for T3 Env", 4 | }; 5 | 6 | # Next.js 7 | 8 | The Next.js package comes preconfigured for Next.js and also enforces some extra rules by default to make sure you have out-of-the-box compatibility in all different Next.js runtimes. 9 | 10 | 11 | 12 | ### Install dependencies 13 | 14 | Install the required dependencies: 15 | 16 | ```bash 17 | pnpm add @t3-oss/env-nextjs zod 18 | 19 | # or using JSR 20 | deno add jsr:@t3-oss/env-nextjs 21 | ``` 22 | 23 | 24 | 25 | Although we'll use Zod as examples throughout these docs, you can use any validator that supports [Standard Schema](/docs/standard-schema). 26 | 27 | 28 | 29 | 30 | 31 | `@t3-oss/env-nextjs` requires a minimum of `typescript@5.0.0`. 32 | 33 | 34 | 35 | 36 | 37 | `@t3-oss/env-nextjs` is an ESM only package. Make sure that your tsconfig uses a module resolution that can read `package.json#exports` (`Bundler` is recommended). 38 | 39 | 40 | 41 | ### Create your schema 42 | 43 | ```ts title="src/env.ts" 44 | import { createEnv } from "@t3-oss/env-nextjs"; 45 | import { z } from "zod"; 46 | 47 | export const env = createEnv({ 48 | server: { 49 | DATABASE_URL: z.url(), 50 | OPEN_AI_API_KEY: z.string().min(1), 51 | }, 52 | client: { 53 | NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1), 54 | }, 55 | // If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually 56 | runtimeEnv: { 57 | DATABASE_URL: process.env.DATABASE_URL, 58 | OPEN_AI_API_KEY: process.env.OPEN_AI_API_KEY, 59 | NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, 60 | }, 61 | // For Next.js >= 13.4.4, you only need to destructure client variables: 62 | // experimental__runtimeEnv: { 63 | // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, 64 | // } 65 | }); 66 | ``` 67 | 68 | 69 | 70 | Unlike in the core package, `runtimeEnv` is strict by default, meaning you'll have to destructure all the keys manually. This is due to how Next.js bundles environment variables and only explicitly accessed variables are included in the bundle. Missing keys will result in a type-error: 71 | 72 | ![missing runtimeEnv](https://user-images.githubusercontent.com/51714798/234409775-fee3edbd-a73b-415a-829f-28f6f6092707.png) 73 | 74 | 75 | 76 | 77 | 78 | While defining both the client and server schemas in a single file provides the best developer experience, 79 | it also means that your validation schemas for the server variables will be shipped to the client. 80 | If you consider the **names** of your variables sensitive, you should split your schemas into two files. 81 | 82 | ```ts title="src/env/server.ts" 83 | import { createEnv } from "@t3-oss/env-nextjs"; 84 | import { z } from "zod"; 85 | 86 | export const env = createEnv({ 87 | server: { 88 | DATABASE_URL: z.url(), 89 | OPEN_AI_API_KEY: z.string().min(1), 90 | }, 91 | // If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually 92 | // runtimeEnv: { 93 | // DATABASE_URL: process.env.DATABASE_URL, 94 | // OPEN_AI_API_KEY: process.env.OPEN_AI_API_KEY, 95 | // }, 96 | // For Next.js >= 13.4.4, you can just reference process.env: 97 | experimental__runtimeEnv: process.env 98 | }); 99 | ``` 100 | 101 | ```ts title="src/env/client.ts" 102 | import { createEnv } from "@t3-oss/env-nextjs"; 103 | import { z } from "zod"; 104 | 105 | export const env = createEnv({ 106 | client: { 107 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), 108 | }, 109 | runtimeEnv: { 110 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, 111 | }, 112 | }); 113 | ``` 114 | 115 | 116 | 117 | 118 | 119 | If you're using the `standalone` output in your `next.config.ts`, make sure to include the following: 120 | 121 | ```ts title="next.config.ts" 122 | import type { NextConfig } from "next" 123 | 124 | const nextConfig: NextConfig = { 125 | output: "standalone", 126 | // Add the packages in transpilePackages 127 | transpilePackages: ["@t3-oss/env-nextjs", "@t3-oss/env-core"], 128 | } 129 | 130 | export default nextConfig 131 | ``` 132 | 133 | 134 | 135 | ### Validate schema on build (recommended) 136 | 137 | We recommend you importing your newly created file in your `next.config.js`. This will make sure your environment variables are validated at build time which will save you a lot of time and headaches down the road. You can use [unjs/jiti](https://github.com/unjs/jiti) to import TypeScript files in your `next.config.js`: 138 | 139 | #### Version 16+ 140 | 141 | ```ts title="next.config.ts" 142 | import "./app/env"; 143 | 144 | /** @type {import('next').NextConfig} */ 145 | const nextConfig: NextConfig = { 146 | /** ... */ 147 | }; 148 | 149 | export default nextConfig; 150 | ``` 151 | 152 | #### Version < NextJS 16 153 | 154 | ```js title="next.config.js" {6} 155 | import { fileURLToPath } from "node:url"; 156 | import createJiti from "jiti"; 157 | const jiti = createJiti(fileURLToPath(import.meta.url)); 158 | 159 | // Import env here to validate during build. Using jiti@^1 we can import .ts files :) 160 | jiti("./app/env"); 161 | 162 | /** @type {import('next').NextConfig} */ 163 | export default { 164 | /** ... */ 165 | }; 166 | ``` 167 | 168 | ### Use your schema 169 | 170 | Then, import the `env` object in your application and use it, taking advantage of type-safety and auto-completion: 171 | 172 | ```ts title="some-api-endpoint.ts" 173 | import { env } from "~/env"; // On server 174 | 175 | export const GET = async () => { 176 | // do fancy ai stuff 177 | const magic = await fetch("...", { 178 | headers: { Authorization: env.OPEN_AI_API_KEY }, 179 | }); 180 | // ... 181 | }; 182 | ``` 183 | 184 | ```ts title="some-component.tsx" 185 | import { env } from "~/env"; // On client - same import! 186 | 187 | export const SomeComponent = () => { 188 | return ( 189 | 190 | {/* ... */} 191 | 192 | ); 193 | }; 194 | ``` 195 | 196 | 197 | -------------------------------------------------------------------------------- /docs/src/components/icons.tsx: -------------------------------------------------------------------------------- 1 | import type { LucideIcon, LucideProps } from "lucide-react"; 2 | import { Link, Moon, SunMedium, TerminalSquare, Twitter } from "lucide-react"; 3 | 4 | export type { LucideIcon as Icon }; 5 | 6 | export const Icons = { 7 | sun: SunMedium, 8 | moon: Moon, 9 | twitter: Twitter, 10 | logo: (props: LucideProps) => ( 11 | 19 | 20 | 24 | 28 | 29 | 30 | ), 31 | gitHub: (props: LucideProps) => ( 32 | 33 | 37 | 38 | ), 39 | javascript: (props: LucideProps) => ( 40 | 41 | 45 | 46 | ), 47 | typescript: (props: LucideProps) => ( 48 | 49 | 53 | 54 | ), 55 | vue: (props: LucideProps) => ( 56 | 57 | 61 | 62 | ), 63 | bash: TerminalSquare, 64 | menu: (props: LucideProps) => ( 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ), 75 | link: Link, 76 | }; 77 | -------------------------------------------------------------------------------- /docs/src/app/docs/core/page.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Core", 3 | description: 4 | "Get started with the framework agnostic core package of T3 Env.", 5 | }; 6 | 7 | # Core 8 | 9 | The core package can be used in any framework of your choice. To use it, figure out what prefix your framework uses for exposing environment variables to the client. For example, Astro uses `PUBLIC_`, while Vite uses `VITE_`. You should be able to find this in the frameworks documentation. 10 | 11 | 12 | 13 | Using Next.js or Nuxt? We have dedicated packages with preconfigured settings for these frameworks: 14 | 15 | import { buttonVariants } from "@/components/ui/button"; 16 | import { cn } from "@/lib/cn"; 17 | import { Link } from "next-view-transitions"; 18 | 19 |
    20 | 21 | Next.js → 22 | 23 | 24 | Nuxt → 25 | 26 |
    27 | 28 |
    29 | 30 | 31 | 32 | ### Install dependencies 33 | 34 | First, install the core package: 35 | 36 | ```bash 37 | npm install @t3-oss/env-core zod 38 | 39 | # or using JSR 40 | deno add jsr:@t3-oss/env-core 41 | ``` 42 | 43 | 44 | 45 | Although we'll use Zod as examples throughout these docs, you can use any validator that supports [Standard Schema](/docs/core/standard-schema). T3 Env works with [Zod](https://zod.dev), [Valibot](https://valibot.dev), [ArkType](https://arktype.dev), [Typia](https://typia.io), and other compliant validators. 46 | 47 | 48 | 49 | 50 | 51 | `@t3-oss/env-core` requires a minimum of `typescript@5.0.0`. 52 | 53 | 54 | 55 | 56 | 57 | `@t3-oss/env-core` is an ESM only package. Make sure that your tsconfig uses a module resolution that can read `package.json#exports` (`Bundler` is recommended). 58 | 59 | 60 | 61 | ### Create your schema 62 | 63 | Then, you can create your schema like so: 64 | 65 | 66 | 67 | The file below is named `env.ts`, but you can name it whatever you want. Some frameworks even generate a `env.d.ts` file that will collide with `env.ts`, which means you will have to name it something else. 68 | 69 | 70 | 71 | ```ts title="src/env.ts" 72 | import { createEnv } from "@t3-oss/env-core"; 73 | import { z } from "zod"; 74 | 75 | export const env = createEnv({ 76 | server: { 77 | DATABASE_URL: z.url(), 78 | OPEN_AI_API_KEY: z.string().min(1), 79 | }, 80 | 81 | /** 82 | * The prefix that client-side variables must have. This is enforced both at 83 | * a type-level and at runtime. 84 | */ 85 | clientPrefix: "PUBLIC_", 86 | 87 | client: { 88 | PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), 89 | }, 90 | 91 | /** 92 | * What object holds the environment variables at runtime. This is usually 93 | * `process.env` or `import.meta.env`. 94 | */ 95 | runtimeEnv: process.env, 96 | 97 | /** 98 | * By default, this library will feed the environment variables directly to 99 | * the Zod validator. 100 | * 101 | * This means that if you have an empty string for a value that is supposed 102 | * to be a number (e.g. `PORT=` in a ".env" file), Zod will incorrectly flag 103 | * it as a type mismatch violation. Additionally, if you have an empty string 104 | * for a value that is supposed to be a string with a default value (e.g. 105 | * `DOMAIN=` in an ".env" file), the default value will never be applied. 106 | * 107 | * In order to solve these issues, we recommend that all new projects 108 | * explicitly specify this option as true. 109 | */ 110 | emptyStringAsUndefined: true, 111 | }); 112 | ``` 113 | 114 | Remove the `clientPrefix` and `client` properties if you only want the environment variables to exist on the server. 115 | 116 | 117 | 118 | While defining both the client and server schemas in a single file provides the best developer experience, 119 | it also means that your validation schemas for the server variables will be shipped to the client. 120 | If you consider the **names** of your variables sensitive, you should split your schemas into two files. 121 | 122 | ```ts title="src/env/server.ts" 123 | import { createEnv } from "@t3-oss/env-core"; 124 | import { z } from "zod"; 125 | 126 | export const env = createEnv({ 127 | server: { 128 | DATABASE_URL: z.url(), 129 | OPEN_AI_API_KEY: z.string().min(1), 130 | }, 131 | runtimeEnv: process.env, 132 | }); 133 | ``` 134 | 135 | ```ts title="src/env/client.ts" 136 | import { createEnv } from "@t3-oss/env-core"; 137 | import { z } from "zod"; 138 | 139 | export const env = createEnv({ 140 | clientPrefix: "PUBLIC_", 141 | client: { 142 | PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), 143 | }, 144 | runtimeEnv: process.env, 145 | }); 146 | ``` 147 | 148 | 149 | 150 | For all available options, see [Customization](/docs/customization). 151 | 152 | You'll notice that if your `clientPrefix` is `PUBLIC_`, you won't be allowed to enter any other keys in the `client` object without getting type-errors. Below you can see we get a descriptive error when we set `VITE_PUBLIC_API_URL` instead of `PUBLIC_API_URL`: 153 | 154 | ![invalid client prefix](https://user-images.githubusercontent.com/51714798/234410449-271d7afe-b541-45bf-acaa-134cbec4c51a.png) 155 | 156 | This client prefix is also enforced at runtime to make sure validation works on both the server and client. 157 | 158 | ### Validate schema on build (recommended) 159 | 160 | The steps required to validate your schema on build will vary from framework to framework, but you'll usually be able to import the env file in your configuration file, or in any file that's pulled in the beginning of the build process. 161 | 162 | Note that some frameworks don't import their environment variables in their configuration file. 163 | 164 | ### Use your schema 165 | 166 | Then, import the `env` object in your application and use it, taking advantage of type-safety and auto-completion: 167 | 168 | ```ts title="some-api-endpoint.ts" 169 | import { env } from "~/env"; // On server 170 | 171 | export const GET = async () => { 172 | // do fancy ai stuff 173 | const magic = await fetch("...", { 174 | headers: { Authorization: env.OPEN_AI_API_KEY }, 175 | }); 176 | // ... 177 | }; 178 | ``` 179 | 180 | 181 | 182 | ## Additional strictness for `runtimeEnv` 183 | 184 | 185 | 186 | Exactly one of `runtimeEnv` and `runtimeEnvStrict` should be specified. 187 | 188 | 189 | 190 | If your framework doesn't bundle all environment variables by default, but instead only bundles the ones you use, you can use the `runtimeEnvStrict` option to make sure you don't forget to add any variables to your runtime. 191 | 192 | ```ts title="src/env.ts" 193 | import { createEnv } from "@t3-oss/env-core"; 194 | 195 | export const env = createEnv({ 196 | clientPrefix: "PUBLIC_", 197 | server: { 198 | DATABASE_URL: z.url(), 199 | OPEN_AI_API_KEY: z.string().min(1), 200 | }, 201 | client: { 202 | PUBLIC_PUBLISHABLE_KEY: z.string().min(1), 203 | }, 204 | /** 205 | * Makes sure you explicitly access **all** environment variables 206 | * from `server` and `client` in your `runtimeEnv`. 207 | */ 208 | runtimeEnvStrict: { 209 | DATABASE_URL: process.env.DATABASE_URL, 210 | OPEN_AI_API_KEY: process.env.OPEN_AI_API_KEY, 211 | PUBLIC_PUBLISHABLE_KEY: process.env.PUBLIC_PUBLISHABLE_KEY, 212 | }, 213 | }); 214 | ``` 215 | 216 | When using the strict option, missing any of the variables in `runtimeEnvStrict` will result in a type error: 217 | 218 | ![missing runtimeEnv](https://user-images.githubusercontent.com/51714798/234409775-fee3edbd-a73b-415a-829f-28f6f6092707.png) 219 | -------------------------------------------------------------------------------- /docs/src/app/docs/customization/page.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Customization", 3 | description: 4 | "Learn how to customize the library to suit your needs by overriding the default behaviors.", 5 | }; 6 | 7 | # Customization 8 | 9 | Below are some examples of how you can customize the behavior of the library. The default values are shown in the snippets below. 10 | 11 | ## Skipping validation 12 | 13 | 14 | 15 | Skipping validation is not encouraged and will lead to your types and runtime values being out of sync. It is available to let you opt out of validation during linting (or similar), or if you're building with Docker and not all environment variables are present when building the image. 16 | 17 | 18 | 19 | When enabled, validation will be skipped for both your environment variables and any [extended presets](#extending-presets). 20 | 21 | ```ts title="src/env.ts" 22 | import { createEnv } from "@t3-oss/env-core"; 23 | 24 | export const env = createEnv({ 25 | // ... 26 | // Tell the library to skip validation if condition is true. 27 | skipValidation: false, 28 | }); 29 | ``` 30 | 31 | ## Overriding the default error handler 32 | 33 | ```ts title="src/env.ts" 34 | import { createEnv } from "@t3-oss/env-core"; 35 | 36 | export const env = createEnv({ 37 | // ... 38 | // Called when the schema validation fails. 39 | onValidationError: (issues: StandardSchemaV1.Issue[]) => { 40 | console.error( 41 | "❌ Invalid environment variables:", 42 | issues 43 | ); 44 | throw new Error("Invalid environment variables"); 45 | }, 46 | // Called when server variables are accessed on the client. 47 | onInvalidAccess: (variable: string) => { 48 | throw new Error( 49 | "❌ Attempted to access a server-side environment variable on the client" 50 | ); 51 | }, 52 | }); 53 | ``` 54 | 55 | ## Tell when we're in a server context 56 | 57 | ```ts title="src/env.ts" 58 | import { createEnv } from "@t3-oss/env-core"; 59 | 60 | export const env = createEnv({ 61 | // ... 62 | // Tell the library when we're in a server context. 63 | isServer: typeof window === "undefined", 64 | }); 65 | ``` 66 | 67 | ## Treat empty strings as undefined 68 | 69 | By default, T3 Env will feed the environment variables directly to the Zod validator. This means that if you have an empty string for a value that is supposed to be a number (e.g. `PORT=` in a ".env" file), Zod will flag 70 | it as a type mismatch violation. Additionally, if you have an empty string for a value that is supposed to be a string with a default value (e.g. `DOMAIN=`in an ".env" file), the default value will never be applied. In order to solve these issues, you can set the`emptyStringAsUndefined`option to`true`. 71 | 72 | ```ts title="src/env.ts" 73 | import { createEnv } from "@t3-oss/env-core"; 74 | 75 | export const env = createEnv({ 76 | // ... 77 | // Treat empty strings as undefined. 78 | emptyStringAsUndefined: false, 79 | }); 80 | ``` 81 | 82 | ## Extending presets 83 | 84 | Your env object may extend other presets by using the `extends` property. This can be used to include system environment variables for your deployment provider, or if you have a monorepo with multiple packages that share some environment variables. 85 | 86 | ```ts title="src/env.ts" 87 | import { createEnv } from "@t3-oss/env-core"; 88 | import { vercel } from "@t3-oss/env-core/presets-zod"; 89 | 90 | export const env = createEnv({ 91 | // ... 92 | // Extend the Vercel preset. 93 | extends: [vercel()], 94 | }); 95 | 96 | env.VERCEL_URL; // string 97 | ``` 98 | 99 | ### Available validators 100 | 101 | Presets are available for multiple [Standard Schema](/docs/standard-schema) compliant validators. Choose the import that matches your validator: 102 | 103 | - `@t3-oss/env-core/presets-zod` - Presets using [Zod](https://zod.dev) 104 | - `@t3-oss/env-core/presets-valibot` - Presets using [Valibot](https://valibot.dev) 105 | - `@t3-oss/env-core/presets-arktype` - Presets using [ArkType](https://arktype.io) 106 | 107 | ```ts title="src/env.ts (using Valibot)" 108 | import { createEnv } from "@t3-oss/env-core"; 109 | import { vercel } from "@t3-oss/env-core/presets-valibot"; 110 | import * as v from "valibot"; 111 | 112 | export const env = createEnv({ 113 | server: { 114 | DATABASE_URL: v.pipe(v.string(), v.url()), 115 | }, 116 | extends: [vercel()], 117 | runtimeEnv: process.env, 118 | }); 119 | ``` 120 | 121 | 122 | 123 | Using the preset import that matches your validator ensures you only bundle the validator you're actually using, optimizing your bundle size. 124 | 125 | 126 | 127 | ### Available presets 128 | 129 | T3 Env ships the following presets out of the box, all importable from the `/presets` entrypoint. 130 | 131 | - `vercel` - Vercel environment variables. See full list [here](https://vercel.com/docs/projects/environment-variables/system-environment-variables#system-environment-variables). 132 | - `neonVercel` - Neon provided system environment variables when using the Vercel integration. See full list [here](https://neon.tech/docs/guides/vercel-native-integration#environment-variables-set-by-the-integration). 133 | - `supabaseVercel` - Supabase provided system environment variables when using the Vercel integration. See full list [here](https://vercel.com/marketplace/supabase). 134 | - `uploadthing` - All environment variables required to use [UploadThing](https://uploadthing.com/). More info [here](https://docs.uploadthing.com/getting-started/appdir#add-env-variables). 135 | - `render` - Render environment variables. See full list [here](https://docs.render.com/environment-variables#all-runtimes). 136 | - `railway` - Railway provided system environment variables. See full list [here](https://docs.railway.app/reference/variables#railway-provided-variables). 137 | - `fly.io` - Fly.io provided machine runtime environment variables. See full list [here](https://fly.io/docs/machines/runtime-environment/#environment-variables). 138 | - `netlify` - Netlify provided system environment variables. See full list [here](https://docs.netlify.com/configure-builds/environment-variables). 139 | - `upstashRedis` - Upstash Redis environment variables. More info [here](https://upstash.com/docs/redis/howto/connectwithupstashredis). 140 | - `coolify` - Coolify environment variables. More info [here](https://coolify.io/docs/knowledge-base/environment-variables#predefined-variables). 141 | - `vite` - Vite environment variables. More info [here](https://vite.dev/guide/env-and-mode). 142 | - `wxt` - WXT environment variables. More info [here](https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables). 143 | 144 | 145 | Feel free to open a PR with more presets! 146 | 147 | 148 | A preset is just like any other env object, so you can easily create your own: 149 | 150 | ```ts 151 | // packages/auth/env.ts 152 | import { createEnv } from "@t3-oss/env-core"; 153 | export const env = createEnv({ 154 | // ... 155 | }); 156 | 157 | // apps/web/env.ts 158 | import { createEnv } from "@t3-oss/env-nextjs"; 159 | import { env as authEnv } from "@repo/auth/env"; 160 | 161 | export const env = createEnv({ 162 | // ... 163 | extends: [authEnv], 164 | }); 165 | ``` 166 | 167 | ## Further refinement or transformation 168 | 169 | You can use the `createFinalSchema` option to further refine or transform the environment variables. 170 | 171 | ```ts title="src/env.ts" 172 | import { createEnv } from "@t3-oss/env-core"; 173 | import { z } from "zod"; 174 | 175 | export const env = createEnv({ 176 | server: { 177 | SKIP_AUTH: z.boolean().optional(), 178 | EMAIL: z.string().email().optional(), 179 | PASSWORD: z.string().min(1).optional(), 180 | }, 181 | // ... 182 | createFinalSchema: (shape, isServer) => 183 | z.object(shape).transform((env, ctx) => { 184 | if (env.SKIP_AUTH || !isServer) return { SKIP_AUTH: true } as const; 185 | if (!env.EMAIL || !env.PASSWORD) { 186 | ctx.addIssue({ 187 | code: z.ZodIssueCode.custom, 188 | message: "EMAIL and PASSWORD are required if SKIP_AUTH is false", 189 | }); 190 | return z.NEVER; 191 | } 192 | return { 193 | EMAIL: env.EMAIL, 194 | PASSWORD: env.PASSWORD, 195 | }; 196 | }), 197 | }); 198 | ``` 199 | -------------------------------------------------------------------------------- /packages/nextjs/test/smoke.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, expectTypeOf, test, vi } from "vitest"; 2 | import { z } from "zod"; 3 | import { createEnv } from "../src/index.ts"; 4 | 5 | function ignoreErrors(cb: () => void) { 6 | try { 7 | cb(); 8 | } catch { 9 | // ignore 10 | } 11 | } 12 | 13 | test("server vars should not be prefixed", () => { 14 | ignoreErrors(() => { 15 | createEnv({ 16 | server: { 17 | // @ts-expect-error - server should not have NEXT_PUBLIC_ prefix 18 | NEXT_PUBLIC_BAR: z.string(), 19 | BAR: z.string(), 20 | }, 21 | client: {}, 22 | runtimeEnv: { 23 | BAR: "foo", 24 | }, 25 | }); 26 | }); 27 | }); 28 | 29 | test("client vars should be correctly prefixed", () => { 30 | ignoreErrors(() => { 31 | createEnv({ 32 | server: {}, 33 | client: { 34 | NEXT_PUBLIC_BAR: z.string(), 35 | // @ts-expect-error - no NEXT_PUBLIC_ prefix 36 | BAR: z.string(), 37 | }, 38 | runtimeEnv: { 39 | NEXT_PUBLIC_BAR: "foo", 40 | }, 41 | }); 42 | }); 43 | }); 44 | 45 | test("runtimeEnv enforces all keys", () => { 46 | createEnv({ 47 | server: {}, 48 | client: { NEXT_PUBLIC_BAR: z.string() }, 49 | runtimeEnv: { NEXT_PUBLIC_BAR: "foo" }, 50 | }); 51 | 52 | createEnv({ 53 | server: { BAR: z.string() }, 54 | client: { NEXT_PUBLIC_BAR: z.string() }, 55 | runtimeEnv: { BAR: "foo", NEXT_PUBLIC_BAR: "foo" }, 56 | }); 57 | 58 | createEnv({ 59 | server: {}, 60 | client: { NEXT_PUBLIC_BAR: z.string() }, 61 | runtimeEnv: { 62 | NEXT_PUBLIC_BAR: "foo", 63 | // @ts-expect-error - FOO_BAZ is extraneous 64 | FOO_BAZ: "baz", 65 | }, 66 | }); 67 | 68 | ignoreErrors(() => { 69 | createEnv({ 70 | server: { BAR: z.string() }, 71 | client: { NEXT_PUBLIC_BAR: z.string() }, 72 | // @ts-expect-error - BAR is missing 73 | runtimeEnvStrict: { 74 | NEXT_PUBLIC_BAR: "foo", 75 | }, 76 | }); 77 | }); 78 | }); 79 | 80 | test("new experimental runtime option only requires client vars", () => { 81 | ignoreErrors(() => { 82 | createEnv({ 83 | server: { BAR: z.string() }, 84 | client: { NEXT_PUBLIC_BAR: z.string() }, 85 | // @ts-expect-error - NEXT_PUBLIC_BAR is missing 86 | experimental__runtimeEnv: {}, 87 | }); 88 | createEnv({ 89 | server: { BAR: z.string() }, 90 | client: { NEXT_PUBLIC_BAR: z.string() }, 91 | experimental__runtimeEnv: { 92 | // @ts-expect-error - BAR should not be specified 93 | BAR: "bar", 94 | }, 95 | }); 96 | }); 97 | 98 | process.env = { 99 | BAR: "bar", 100 | NEXT_PUBLIC_BAR: "foo", 101 | NODE_ENV: "development", 102 | }; 103 | 104 | const env = createEnv({ 105 | shared: { 106 | NODE_ENV: z.enum(["development", "production"]), 107 | }, 108 | server: { BAR: z.string() }, 109 | client: { NEXT_PUBLIC_BAR: z.string() }, 110 | experimental__runtimeEnv: { 111 | NODE_ENV: process.env.NODE_ENV, 112 | NEXT_PUBLIC_BAR: process.env.NEXT_PUBLIC_BAR, 113 | }, 114 | }); 115 | 116 | expectTypeOf(env).toEqualTypeOf< 117 | Readonly<{ 118 | BAR: string; 119 | NEXT_PUBLIC_BAR: string; 120 | NODE_ENV: "development" | "production"; 121 | }> 122 | >(); 123 | 124 | expect(env).toMatchObject({ 125 | BAR: "bar", 126 | NEXT_PUBLIC_BAR: "foo", 127 | NODE_ENV: "development", 128 | }); 129 | }); 130 | 131 | describe("return type is correctly inferred", () => { 132 | test("simple", () => { 133 | const env = createEnv({ 134 | server: { BAR: z.string() }, 135 | client: { NEXT_PUBLIC_BAR: z.string() }, 136 | runtimeEnv: { 137 | BAR: "bar", 138 | NEXT_PUBLIC_BAR: "foo", 139 | }, 140 | }); 141 | 142 | expectTypeOf(env).toEqualTypeOf< 143 | Readonly<{ 144 | BAR: string; 145 | NEXT_PUBLIC_BAR: string; 146 | }> 147 | >(); 148 | 149 | expect(env).toMatchObject({ 150 | BAR: "bar", 151 | NEXT_PUBLIC_BAR: "foo", 152 | }); 153 | }); 154 | 155 | test("with transforms", () => { 156 | const env = createEnv({ 157 | server: { BAR: z.string().transform(Number) }, 158 | client: { NEXT_PUBLIC_BAR: z.string() }, 159 | runtimeEnv: { 160 | BAR: "123", 161 | NEXT_PUBLIC_BAR: "foo", 162 | }, 163 | }); 164 | 165 | expectTypeOf(env).toEqualTypeOf< 166 | Readonly<{ 167 | BAR: number; 168 | NEXT_PUBLIC_BAR: string; 169 | }> 170 | >(); 171 | 172 | expect(env).toMatchObject({ 173 | BAR: 123, 174 | NEXT_PUBLIC_BAR: "foo", 175 | }); 176 | }); 177 | }); 178 | 179 | test("can specify only server", () => { 180 | const onlyServer = createEnv({ 181 | server: { BAR: z.string() }, 182 | runtimeEnv: { BAR: "FOO" }, 183 | }); 184 | 185 | expectTypeOf(onlyServer).toMatchTypeOf<{ 186 | BAR: string; 187 | }>(); 188 | 189 | expect(onlyServer).toMatchObject({ 190 | BAR: "FOO", 191 | }); 192 | }); 193 | 194 | test("can specify only client", () => { 195 | const onlyClient = createEnv({ 196 | client: { NEXT_PUBLIC_BAR: z.string() }, 197 | runtimeEnv: { NEXT_PUBLIC_BAR: "FOO" }, 198 | }); 199 | 200 | expectTypeOf(onlyClient).toMatchTypeOf<{ 201 | NEXT_PUBLIC_BAR: string; 202 | }>(); 203 | 204 | expect(onlyClient).toMatchObject({ 205 | NEXT_PUBLIC_BAR: "FOO", 206 | }); 207 | }); 208 | 209 | describe("extending presets", () => { 210 | test("with invalid runtime envs", () => { 211 | const processEnv = { 212 | SERVER_ENV: "server", 213 | NEXT_PUBLIC_ENV: "client", 214 | }; 215 | 216 | function lazyCreateEnv() { 217 | const preset = createEnv({ 218 | server: { 219 | PRESET_ENV: z.string(), 220 | }, 221 | experimental__runtimeEnv: processEnv, 222 | }); 223 | 224 | return createEnv({ 225 | server: { 226 | SERVER_ENV: z.string(), 227 | }, 228 | client: { 229 | NEXT_PUBLIC_ENV: z.string(), 230 | }, 231 | extends: [preset], 232 | runtimeEnv: processEnv, 233 | }); 234 | } 235 | 236 | expectTypeOf(lazyCreateEnv).returns.toEqualTypeOf< 237 | Readonly<{ 238 | SERVER_ENV: string; 239 | NEXT_PUBLIC_ENV: string; 240 | PRESET_ENV: string; 241 | }> 242 | >(); 243 | 244 | const consoleError = vi.spyOn(console, "error"); 245 | expect(() => lazyCreateEnv()).toThrow("Invalid environment variables"); 246 | expect(consoleError.mock.calls[0]).toEqual([ 247 | "❌ Invalid environment variables:", 248 | [ 249 | expect.objectContaining({ 250 | message: expect.any(String), 251 | path: ["PRESET_ENV"], 252 | }), 253 | ], 254 | ]); 255 | }); 256 | describe("single preset", () => { 257 | const processEnv = { 258 | PRESET_ENV: "preset", 259 | SHARED_ENV: "shared", 260 | SERVER_ENV: "server", 261 | NEXT_PUBLIC_ENV: "client", 262 | }; 263 | 264 | function lazyCreateEnv() { 265 | const preset = createEnv({ 266 | server: { 267 | PRESET_ENV: z.enum(["preset"]), 268 | }, 269 | runtimeEnv: processEnv, 270 | }); 271 | 272 | return createEnv({ 273 | server: { 274 | SERVER_ENV: z.string(), 275 | }, 276 | shared: { 277 | SHARED_ENV: z.string(), 278 | }, 279 | client: { 280 | NEXT_PUBLIC_ENV: z.string(), 281 | }, 282 | extends: [preset], 283 | runtimeEnv: processEnv, 284 | }); 285 | } 286 | 287 | expectTypeOf(lazyCreateEnv).returns.toEqualTypeOf< 288 | Readonly<{ 289 | SERVER_ENV: string; 290 | SHARED_ENV: string; 291 | NEXT_PUBLIC_ENV: string; 292 | PRESET_ENV: "preset"; 293 | }> 294 | >(); 295 | 296 | test("server", () => { 297 | const { window } = globalThis; 298 | globalThis.window = undefined as any; 299 | 300 | const env = lazyCreateEnv(); 301 | 302 | expect(env).toMatchObject({ 303 | SERVER_ENV: "server", 304 | SHARED_ENV: "shared", 305 | NEXT_PUBLIC_ENV: "client", 306 | PRESET_ENV: "preset", 307 | }); 308 | 309 | globalThis.window = window; 310 | }); 311 | 312 | test("client", () => { 313 | const { window } = globalThis; 314 | globalThis.window = {} as any; 315 | 316 | const env = lazyCreateEnv(); 317 | 318 | expect(() => env.SERVER_ENV).toThrow( 319 | "❌ Attempted to access a server-side environment variable on the client", 320 | ); 321 | expect(() => env.PRESET_ENV).toThrow( 322 | "❌ Attempted to access a server-side environment variable on the client", 323 | ); 324 | expect(env.SHARED_ENV).toBe("shared"); 325 | expect(env.NEXT_PUBLIC_ENV).toBe("client"); 326 | 327 | globalThis.window = window; 328 | }); 329 | }); 330 | }); 331 | -------------------------------------------------------------------------------- /docs/src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @import "tw-animate-css"; 3 | 4 | @custom-variant dark (&:is(.dark *)); 5 | 6 | @custom-variant fixed (&:is(.layout-fixed *)); 7 | 8 | :root { 9 | --background: oklch(1 0 0); 10 | --foreground: oklch(0.145 0 0); 11 | --card: oklch(1 0 0); 12 | --card-foreground: oklch(0.145 0 0); 13 | --popover: oklch(1 0 0); 14 | --popover-foreground: oklch(0.145 0 0); 15 | --primary: oklch(0.205 0 0); 16 | --primary-foreground: oklch(0.985 0 0); 17 | --secondary: oklch(0.97 0 0); 18 | --secondary-foreground: oklch(0.205 0 0); 19 | --muted: oklch(0.97 0 0); 20 | --muted-foreground: oklch(0.556 0 0); 21 | --accent: oklch(0.97 0 0); 22 | --accent-foreground: oklch(0.205 0 0); 23 | --destructive: oklch(0.577 0.245 27.325); 24 | --destructive-foreground: oklch(0.577 0.245 27.325); 25 | --border: oklch(0.922 0 0); 26 | --input: oklch(0.922 0 0); 27 | --ring: oklch(0.708 0 0); 28 | --chart-1: oklch(0.646 0.222 41.116); 29 | --chart-2: oklch(0.6 0.118 184.704); 30 | --chart-3: oklch(0.398 0.07 227.392); 31 | --chart-4: oklch(0.828 0.189 84.429); 32 | --chart-5: oklch(0.769 0.188 70.08); 33 | --radius: 0.625rem; 34 | --sidebar: oklch(0.985 0 0); 35 | --sidebar-foreground: oklch(0.145 0 0); 36 | --sidebar-primary: oklch(0.205 0 0); 37 | --sidebar-primary-foreground: oklch(0.985 0 0); 38 | --sidebar-accent: oklch(0.97 0 0); 39 | --sidebar-accent-foreground: oklch(0.205 0 0); 40 | --sidebar-border: oklch(0.922 0 0); 41 | --sidebar-ring: oklch(0.708 0 0); 42 | } 43 | 44 | .dark { 45 | --background: oklch(0.145 0 0); 46 | --foreground: oklch(0.985 0 0); 47 | --card: oklch(0.145 0 0); 48 | --card-foreground: oklch(0.985 0 0); 49 | --popover: oklch(0.145 0 0); 50 | --popover-foreground: oklch(0.985 0 0); 51 | --primary: oklch(0.985 0 0); 52 | --primary-foreground: oklch(0.205 0 0); 53 | --secondary: oklch(0.269 0 0); 54 | --secondary-foreground: oklch(0.985 0 0); 55 | --muted: oklch(0.269 0 0); 56 | --muted-foreground: oklch(0.708 0 0); 57 | --accent: oklch(0.269 0 0); 58 | --accent-foreground: oklch(0.985 0 0); 59 | --destructive: oklch(0.396 0.141 25.723); 60 | --destructive-foreground: oklch(0.637 0.237 25.331); 61 | --border: oklch(0.269 0 0); 62 | --input: oklch(0.269 0 0); 63 | --ring: oklch(0.556 0 0); 64 | --chart-1: oklch(0.488 0.243 264.376); 65 | --chart-2: oklch(0.696 0.17 162.48); 66 | --chart-3: oklch(0.769 0.188 70.08); 67 | --chart-4: oklch(0.627 0.265 303.9); 68 | --chart-5: oklch(0.645 0.246 16.439); 69 | --sidebar: oklch(0.205 0 0); 70 | --sidebar-foreground: oklch(0.985 0 0); 71 | --sidebar-primary: oklch(0.488 0.243 264.376); 72 | --sidebar-primary-foreground: oklch(0.985 0 0); 73 | --sidebar-accent: oklch(0.269 0 0); 74 | --sidebar-accent-foreground: oklch(0.985 0 0); 75 | --sidebar-border: oklch(0.269 0 0); 76 | --sidebar-ring: oklch(0.439 0 0); 77 | } 78 | 79 | @theme inline { 80 | --animate-fade-up: fade-up 0.5s; 81 | --animate-fade-down: fade-down 0.5s; 82 | --animate-accordion-down: accordion-down 0.2s ease-out; 83 | --animate-accordion-up: accordion-up 0.2s ease-out; 84 | --breakpoint-3xl: 1600px; 85 | --breakpoint-4xl: 2000px; 86 | --color-background: var(--background); 87 | --color-foreground: var(--foreground); 88 | --color-card: var(--card); 89 | --color-card-foreground: var(--card-foreground); 90 | --color-popover: var(--popover); 91 | --color-popover-foreground: var(--popover-foreground); 92 | --color-primary: var(--primary); 93 | --color-primary-foreground: var(--primary-foreground); 94 | --color-secondary: var(--secondary); 95 | --color-secondary-foreground: var(--secondary-foreground); 96 | --color-muted: var(--muted); 97 | --color-muted-foreground: var(--muted-foreground); 98 | --color-accent: var(--accent); 99 | --color-accent-foreground: var(--accent-foreground); 100 | --color-destructive: var(--destructive); 101 | --color-destructive-foreground: var(--destructive-foreground); 102 | --color-border: var(--border); 103 | --color-input: var(--input); 104 | --color-ring: var(--ring); 105 | --color-chart-1: var(--chart-1); 106 | --color-chart-2: var(--chart-2); 107 | --color-chart-3: var(--chart-3); 108 | --color-chart-4: var(--chart-4); 109 | --color-chart-5: var(--chart-5); 110 | --radius-sm: calc(var(--radius) - 4px); 111 | --radius-md: calc(var(--radius) - 2px); 112 | --radius-lg: var(--radius); 113 | --radius-xl: calc(var(--radius) + 4px); 114 | --color-sidebar: var(--sidebar); 115 | --color-sidebar-foreground: var(--sidebar-foreground); 116 | --color-sidebar-primary: var(--sidebar-primary); 117 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 118 | --color-sidebar-accent: var(--sidebar-accent); 119 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 120 | --color-sidebar-border: var(--sidebar-border); 121 | --color-sidebar-ring: var(--sidebar-ring); 122 | --font-sans: var(--font-sans); 123 | --font-mono: var(--font-mono); 124 | --font-cal: var(--font-cal); 125 | } 126 | 127 | @layer base { 128 | * { 129 | @apply border-border outline-ring/50; 130 | } 131 | body { 132 | @apply bg-background text-foreground; 133 | } 134 | } 135 | 136 | @keyframes fade-up { 137 | 0% { 138 | opacity: 0; 139 | transform: translateY(10px); 140 | } 141 | 80% { 142 | opacity: 0.6; 143 | } 144 | 100% { 145 | opacity: 1; 146 | transform: translateY(0px); 147 | } 148 | } 149 | 150 | @keyframes fade-down { 151 | 0% { 152 | opacity: 0; 153 | transform: translateY(-10px); 154 | } 155 | 80% { 156 | opacity: 0.6; 157 | } 158 | 100% { 159 | opacity: 1; 160 | transform: translateY(0px); 161 | } 162 | } 163 | 164 | @keyframes accordion-down { 165 | from { 166 | height: 0; 167 | } 168 | to { 169 | height: var(--radix-accordion-content-height); 170 | } 171 | } 172 | 173 | @keyframes accordion-up { 174 | from { 175 | height: var(--radix-accordion-content-height); 176 | } 177 | to { 178 | height: 0; 179 | } 180 | } 181 | 182 | @utility container-wrapper { 183 | @apply 3xl:fixed:max-w-[calc(var(--breakpoint-2xl)+2rem)] mx-auto w-full px-2; 184 | } 185 | 186 | @utility container { 187 | @apply 3xl:max-w-screen-2xl mx-auto max-w-[1400px] px-4 lg:px-8; 188 | } 189 | 190 | @utility step { 191 | counter-increment: step; 192 | 193 | &:before { 194 | @apply absolute w-8 h-8 bg-accent font-cal rounded-full font-medium text-center text-base inline-flex items-center justify-center -indent-px; 195 | @apply ml-[-41px]; 196 | content: counter(step); 197 | } 198 | } 199 | 200 | @utility hamburger { 201 | /* Hamburger menu icon animation */ 202 | 203 | & svg g { 204 | @apply origin-center; 205 | transition: transform 0.1s ease-in-out; 206 | } 207 | & svg path { 208 | @apply opacity-100; 209 | transition: 210 | transform 0.1s ease-in-out 0.1s, 211 | opacity 0.1s ease-in-out; 212 | } 213 | & svg.open path { 214 | transition: 215 | transform 0.1s ease-in-out, 216 | opacity 0s ease-in-out; 217 | } 218 | & svg.open g { 219 | transition: transform 0.1s ease-in-out 0.1s; 220 | } 221 | & svg.open > path { 222 | @apply opacity-0; 223 | } 224 | & svg.open > g:nth-of-type(1) { 225 | @apply rotate-45; 226 | } 227 | & svg.open > g:nth-of-type(1) path { 228 | transform: translate3d(0, 6px, 0); 229 | } 230 | & svg.open > g:nth-of-type(2) { 231 | @apply -rotate-45; 232 | } 233 | & svg.open > g:nth-of-type(2) path { 234 | transform: translate3d(0, -6px, 0); 235 | } 236 | } 237 | 238 | @utility open { 239 | .hamburger &svg path { 240 | transition: 241 | transform 0.1s ease-in-out, 242 | opacity 0s ease-in-out; 243 | } 244 | .hamburger &svg g { 245 | transition: transform 0.1s ease-in-out 0.1s; 246 | } 247 | .hamburger &svg > path { 248 | @apply opacity-0; 249 | } 250 | .hamburger &svg > g:nth-of-type(1) { 251 | @apply rotate-45; 252 | } 253 | .hamburger &svg > g:nth-of-type(1) path { 254 | transform: translate3d(0, 6px, 0); 255 | } 256 | .hamburger &svg > g:nth-of-type(2) { 257 | @apply -rotate-45; 258 | } 259 | .hamburger &svg > g:nth-of-type(2) path { 260 | transform: translate3d(0, -6px, 0); 261 | } 262 | } 263 | 264 | @utility line { 265 | pre > code > &::before { 266 | counter-increment: line; 267 | content: counter(line); 268 | 269 | /* Other styling */ 270 | display: inline-block; 271 | width: 1rem; 272 | margin-right: 1rem; 273 | text-align: right; 274 | color: var(--muted-foreground); 275 | } 276 | } 277 | 278 | @layer components { 279 | pre > code { 280 | counter-reset: line; 281 | padding: unset !important; 282 | } 283 | 284 | [data-rehype-pretty-code-fragment] { 285 | @apply relative; 286 | } 287 | 288 | [data-language-icon] { 289 | @apply hidden; 290 | } 291 | 292 | [data-rehype-pretty-code-title] { 293 | padding: 0.75rem 0 0.75rem 2.5rem; 294 | @apply mt-4 rounded-t-lg border border-b-0 bg-accent font-cal text-foreground; 295 | } 296 | 297 | [data-rehype-pretty-code-title]:has(+ [data-language-icon]) { 298 | @apply pl-12; 299 | } 300 | 301 | [data-rehype-pretty-code-title] ~ pre { 302 | @apply mt-0 rounded-t-none; 303 | } 304 | 305 | [data-rehype-pretty-code-title] ~ [data-language-icon][data-theme="light"] { 306 | @apply block dark:hidden; 307 | } 308 | 309 | [data-rehype-pretty-code-title] ~ [data-language-icon][data-theme="dark"] { 310 | @apply hidden dark:block; 311 | } 312 | 313 | [data-theme="light"]:not([data-language-icon]) { 314 | @apply block dark:hidden; 315 | } 316 | 317 | [data-theme="dark"]:not([data-language-icon]) { 318 | @apply hidden dark:block; 319 | } 320 | } 321 | 322 | [data-rehype-pretty-code-fragment] .line--highlighted { 323 | @apply rounded p-1 bg-muted-foreground/40; 324 | } 325 | [data-rehype-pretty-code-fragment] .word { 326 | @apply rounded p-1 bg-muted-foreground/40; 327 | } 328 | -------------------------------------------------------------------------------- /packages/core/src/presets-valibot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Presets for Valibot 3 | * @see https://env.t3.gg/docs/customization#extending-presets 4 | * @module 5 | */ 6 | import { boolean, optional, picklist, pipe, string, url } from "valibot"; 7 | import { createEnv } from "./index.ts"; 8 | import type { 9 | CoolifyEnv, 10 | FlyEnv, 11 | NeonVercelEnv, 12 | NetlifyEnv, 13 | RailwayEnv, 14 | RenderEnv, 15 | SupabaseVercelEnv, 16 | UploadThingEnv, 17 | UploadThingV6Env, 18 | UpstashRedisEnv, 19 | VercelEnv, 20 | ViteEnv, 21 | WxtEnv, 22 | } from "./presets.ts"; 23 | 24 | /** 25 | * Vercel System Environment Variables 26 | * @see https://vercel.com/docs/projects/environment-variables/system-environment-variables#system-environment-variables 27 | */ 28 | export const vercel = (): Readonly => 29 | createEnv({ 30 | server: { 31 | VERCEL: optional(string()), 32 | CI: optional(string()), 33 | VERCEL_ENV: optional(picklist(["development", "preview", "production"])), 34 | VERCEL_TARGET_ENV: optional(string()), 35 | VERCEL_URL: optional(string()), 36 | VERCEL_PROJECT_PRODUCTION_URL: optional(string()), 37 | VERCEL_BRANCH_URL: optional(string()), 38 | VERCEL_REGION: optional(string()), 39 | VERCEL_DEPLOYMENT_ID: optional(string()), 40 | VERCEL_SKEW_PROTECTION_ENABLED: optional(string()), 41 | VERCEL_AUTOMATION_BYPASS_SECRET: optional(string()), 42 | VERCEL_GIT_PROVIDER: optional(string()), 43 | VERCEL_GIT_REPO_SLUG: optional(string()), 44 | VERCEL_GIT_REPO_OWNER: optional(string()), 45 | VERCEL_GIT_REPO_ID: optional(string()), 46 | VERCEL_GIT_COMMIT_REF: optional(string()), 47 | VERCEL_GIT_COMMIT_SHA: optional(string()), 48 | VERCEL_GIT_COMMIT_MESSAGE: optional(string()), 49 | VERCEL_GIT_COMMIT_AUTHOR_LOGIN: optional(string()), 50 | VERCEL_GIT_COMMIT_AUTHOR_NAME: optional(string()), 51 | VERCEL_GIT_PREVIOUS_SHA: optional(string()), 52 | VERCEL_GIT_PULL_REQUEST_ID: optional(string()), 53 | }, 54 | runtimeEnv: process.env, 55 | }); 56 | 57 | /** 58 | * Neon for Vercel Environment Variables 59 | * @see https://neon.tech/docs/guides/vercel-native-integration#environment-variables-set-by-the-integration 60 | */ 61 | export const neonVercel = (): Readonly => 62 | createEnv({ 63 | server: { 64 | DATABASE_URL: string(), 65 | DATABASE_URL_UNPOOLED: optional(string()), 66 | PGHOST: optional(string()), 67 | PGHOST_UNPOOLED: optional(string()), 68 | PGUSER: optional(string()), 69 | PGDATABASE: optional(string()), 70 | PGPASSWORD: optional(string()), 71 | POSTGRES_URL: optional(pipe(string(), url())), 72 | POSTGRES_URL_NON_POOLING: optional(pipe(string(), url())), 73 | POSTGRES_USER: optional(string()), 74 | POSTGRES_HOST: optional(string()), 75 | POSTGRES_PASSWORD: optional(string()), 76 | POSTGRES_DATABASE: optional(string()), 77 | POSTGRES_URL_NO_SSL: optional(pipe(string(), url())), 78 | POSTGRES_PRISMA_URL: optional(pipe(string(), url())), 79 | }, 80 | runtimeEnv: process.env, 81 | }); 82 | 83 | /** 84 | * Supabase for Vercel Environment Variables 85 | * @see https://vercel.com/marketplace/supabase 86 | */ 87 | export const supabaseVercel = (): Readonly => 88 | createEnv({ 89 | server: { 90 | POSTGRES_URL: pipe(string(), url()), 91 | POSTGRES_PRISMA_URL: optional(pipe(string(), url())), 92 | POSTGRES_URL_NON_POOLING: optional(pipe(string(), url())), 93 | POSTGRES_USER: optional(string()), 94 | POSTGRES_HOST: optional(string()), 95 | POSTGRES_PASSWORD: optional(string()), 96 | POSTGRES_DATABASE: optional(string()), 97 | SUPABASE_SERVICE_ROLE_KEY: optional(string()), 98 | SUPABASE_ANON_KEY: optional(string()), 99 | SUPABASE_URL: optional(pipe(string(), url())), 100 | SUPABASE_JWT_SECRET: optional(string()), 101 | NEXT_PUBLIC_SUPABASE_ANON_KEY: optional(string()), 102 | NEXT_PUBLIC_SUPABASE_URL: optional(pipe(string(), url())), 103 | }, 104 | runtimeEnv: process.env, 105 | }); 106 | 107 | /** 108 | * @see https://v6.docs.uploadthing.com/getting-started/nuxt#add-env-variables 109 | */ 110 | export const uploadthingV6 = (): Readonly => 111 | createEnv({ 112 | server: { 113 | UPLOADTHING_TOKEN: string(), 114 | }, 115 | runtimeEnv: process.env, 116 | }); 117 | 118 | /** 119 | * @see https://docs.uploadthing.com/getting-started/appdir#add-env-variables 120 | */ 121 | export const uploadthing = (): Readonly => 122 | createEnv({ 123 | server: { 124 | UPLOADTHING_TOKEN: string(), 125 | }, 126 | runtimeEnv: process.env, 127 | }); 128 | 129 | /** 130 | * Render System Environment Variables 131 | * @see https://docs.render.com/environment-variables#all-runtimes 132 | */ 133 | export const render = (): Readonly => 134 | createEnv({ 135 | server: { 136 | IS_PULL_REQUEST: optional(string()), 137 | RENDER_DISCOVERY_SERVICE: optional(string()), 138 | RENDER_EXTERNAL_HOSTNAME: optional(string()), 139 | RENDER_EXTERNAL_URL: optional(pipe(string(), url())), 140 | RENDER_GIT_BRANCH: optional(string()), 141 | RENDER_GIT_COMMIT: optional(string()), 142 | RENDER_GIT_REPO_SLUG: optional(string()), 143 | RENDER_INSTANCE_ID: optional(string()), 144 | RENDER_SERVICE_ID: optional(string()), 145 | RENDER_SERVICE_NAME: optional(string()), 146 | RENDER_SERVICE_TYPE: optional(picklist(["web", "pserv", "cron", "worker", "static"])), 147 | RENDER: optional(string()), 148 | }, 149 | runtimeEnv: process.env, 150 | }); 151 | 152 | /** 153 | * Railway Environment Variables 154 | * @see https://docs.railway.app/reference/variables#railway-provided-variables 155 | */ 156 | export const railway = (): Readonly => 157 | createEnv({ 158 | server: { 159 | RAILWAY_PUBLIC_DOMAIN: optional(string()), 160 | RAILWAY_PRIVATE_DOMAIN: optional(string()), 161 | RAILWAY_TCP_PROXY_DOMAIN: optional(string()), 162 | RAILWAY_TCP_PROXY_PORT: optional(string()), 163 | RAILWAY_TCP_APPLICATION_PORT: optional(string()), 164 | RAILWAY_PROJECT_NAME: optional(string()), 165 | RAILWAY_PROJECT_ID: optional(string()), 166 | RAILWAY_ENVIRONMENT_NAME: optional(string()), 167 | RAILWAY_ENVIRONMENT_ID: optional(string()), 168 | RAILWAY_SERVICE_NAME: optional(string()), 169 | RAILWAY_SERVICE_ID: optional(string()), 170 | RAILWAY_REPLICA_ID: optional(string()), 171 | RAILWAY_DEPLOYMENT_ID: optional(string()), 172 | RAILWAY_SNAPSHOT_ID: optional(string()), 173 | RAILWAY_VOLUME_NAME: optional(string()), 174 | RAILWAY_VOLUME_MOUNT_PATH: optional(string()), 175 | RAILWAY_RUN_UID: optional(string()), 176 | RAILWAY_GIT_COMMIT_SHA: optional(string()), 177 | RAILWAY_GIT_AUTHOR_EMAIL: optional(string()), 178 | RAILWAY_GIT_BRANCH: optional(string()), 179 | RAILWAY_GIT_REPO_NAME: optional(string()), 180 | RAILWAY_GIT_REPO_OWNER: optional(string()), 181 | RAILWAY_GIT_COMMIT_MESSAGE: optional(string()), 182 | }, 183 | runtimeEnv: process.env, 184 | }); 185 | 186 | /** 187 | * Fly.io Environment Variables 188 | * @see https://fly.io/docs/machines/runtime-environment/#environment-variables 189 | */ 190 | export const fly = (): Readonly => 191 | createEnv({ 192 | server: { 193 | FLY_APP_NAME: optional(string()), 194 | FLY_MACHINE_ID: optional(string()), 195 | FLY_ALLOC_ID: optional(string()), 196 | FLY_REGION: optional(string()), 197 | FLY_PUBLIC_IP: optional(string()), 198 | FLY_IMAGE_REF: optional(string()), 199 | FLY_MACHINE_VERSION: optional(string()), 200 | FLY_PRIVATE_IP: optional(string()), 201 | FLY_PROCESS_GROUP: optional(string()), 202 | FLY_VM_MEMORY_MB: optional(string()), 203 | PRIMARY_REGION: optional(string()), 204 | }, 205 | runtimeEnv: process.env, 206 | }); 207 | 208 | /** 209 | * Netlify Environment Variables 210 | * @see https://docs.netlify.com/configure-builds/environment-variables 211 | */ 212 | export const netlify = (): Readonly => 213 | createEnv({ 214 | server: { 215 | NETLIFY: optional(string()), 216 | BUILD_ID: optional(string()), 217 | CONTEXT: optional(picklist(["production", "deploy-preview", "branch-deploy", "dev"])), 218 | REPOSITORY_URL: optional(string()), 219 | BRANCH: optional(string()), 220 | URL: optional(string()), 221 | DEPLOY_URL: optional(string()), 222 | DEPLOY_PRIME_URL: optional(string()), 223 | DEPLOY_ID: optional(string()), 224 | SITE_NAME: optional(string()), 225 | SITE_ID: optional(string()), 226 | }, 227 | runtimeEnv: process.env, 228 | }); 229 | 230 | /** 231 | * Upstash redis Environment Variables 232 | * @see https://upstash.com/docs/redis/howto/connectwithupstashredis 233 | */ 234 | export const upstashRedis = (): Readonly => 235 | createEnv({ 236 | server: { 237 | UPSTASH_REDIS_REST_URL: pipe(string(), url()), 238 | UPSTASH_REDIS_REST_TOKEN: string(), 239 | }, 240 | runtimeEnv: process.env, 241 | }); 242 | 243 | /** 244 | * Coolify Environment Variables 245 | * @see https://coolify.io/docs/knowledge-base/environment-variables#predefined-variables 246 | */ 247 | export const coolify = (): Readonly => 248 | createEnv({ 249 | server: { 250 | COOLIFY_FQDN: optional(string()), 251 | COOLIFY_URL: optional(string()), 252 | COOLIFY_BRANCH: optional(string()), 253 | COOLIFY_RESOURCE_UUID: optional(string()), 254 | COOLIFY_CONTAINER_NAME: optional(string()), 255 | SOURCE_COMMIT: optional(string()), 256 | PORT: optional(string()), 257 | HOST: optional(string()), 258 | }, 259 | runtimeEnv: process.env, 260 | }); 261 | 262 | /** 263 | * Vite Environment Variables 264 | * @see https://vite.dev/guide/env-and-mode 265 | */ 266 | export const vite = (): Readonly => 267 | createEnv({ 268 | server: { 269 | BASE_URL: string(), 270 | MODE: string(), 271 | DEV: boolean(), 272 | PROD: boolean(), 273 | SSR: boolean(), 274 | }, 275 | runtimeEnv: import.meta.env, 276 | }); 277 | 278 | /** 279 | * WXT Environment Variables 280 | * @see https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables 281 | */ 282 | export const wxt = (): Readonly => 283 | createEnv({ 284 | server: { 285 | MANIFEST_VERSION: optional(picklist([2, 3])), 286 | BROWSER: optional(picklist(["chrome", "firefox", "safari", "edge", "opera"])), 287 | CHROME: optional(boolean()), 288 | FIREFOX: optional(boolean()), 289 | SAFARI: optional(boolean()), 290 | EDGE: optional(boolean()), 291 | OPERA: optional(boolean()), 292 | }, 293 | runtimeEnv: import.meta.env, 294 | }); 295 | -------------------------------------------------------------------------------- /packages/core/src/presets-zod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Presets for Zod 3 | * @see https://env.t3.gg/docs/customization#extending-presets 4 | * @module 5 | */ 6 | import { z } from "zod"; 7 | import { createEnv } from "./index.ts"; 8 | import type { 9 | CoolifyEnv, 10 | FlyEnv, 11 | NeonVercelEnv, 12 | NetlifyEnv, 13 | RailwayEnv, 14 | RenderEnv, 15 | SupabaseVercelEnv, 16 | UploadThingEnv, 17 | UploadThingV6Env, 18 | UpstashRedisEnv, 19 | VercelEnv, 20 | ViteEnv, 21 | WxtEnv, 22 | } from "./presets.ts"; 23 | 24 | /** 25 | * Vercel System Environment Variables 26 | * @see https://vercel.com/docs/projects/environment-variables/system-environment-variables#system-environment-variables 27 | */ 28 | export const vercel = (): Readonly => 29 | createEnv({ 30 | server: { 31 | VERCEL: z.string().optional(), 32 | CI: z.string().optional(), 33 | VERCEL_ENV: z.enum(["development", "preview", "production"]).optional(), 34 | VERCEL_TARGET_ENV: z.string().optional(), 35 | VERCEL_URL: z.string().optional(), 36 | VERCEL_PROJECT_PRODUCTION_URL: z.string().optional(), 37 | VERCEL_BRANCH_URL: z.string().optional(), 38 | VERCEL_REGION: z.string().optional(), 39 | VERCEL_DEPLOYMENT_ID: z.string().optional(), 40 | VERCEL_SKEW_PROTECTION_ENABLED: z.string().optional(), 41 | VERCEL_AUTOMATION_BYPASS_SECRET: z.string().optional(), 42 | VERCEL_GIT_PROVIDER: z.string().optional(), 43 | VERCEL_GIT_REPO_SLUG: z.string().optional(), 44 | VERCEL_GIT_REPO_OWNER: z.string().optional(), 45 | VERCEL_GIT_REPO_ID: z.string().optional(), 46 | VERCEL_GIT_COMMIT_REF: z.string().optional(), 47 | VERCEL_GIT_COMMIT_SHA: z.string().optional(), 48 | VERCEL_GIT_COMMIT_MESSAGE: z.string().optional(), 49 | VERCEL_GIT_COMMIT_AUTHOR_LOGIN: z.string().optional(), 50 | VERCEL_GIT_COMMIT_AUTHOR_NAME: z.string().optional(), 51 | VERCEL_GIT_PREVIOUS_SHA: z.string().optional(), 52 | VERCEL_GIT_PULL_REQUEST_ID: z.string().optional(), 53 | }, 54 | runtimeEnv: process.env, 55 | }); 56 | 57 | /** 58 | * Neon for Vercel Environment Variables 59 | * @see https://neon.tech/docs/guides/vercel-native-integration#environment-variables-set-by-the-integration 60 | */ 61 | export const neonVercel = (): Readonly => 62 | createEnv({ 63 | server: { 64 | DATABASE_URL: z.string(), 65 | DATABASE_URL_UNPOOLED: z.string().optional(), 66 | PGHOST: z.string().optional(), 67 | PGHOST_UNPOOLED: z.string().optional(), 68 | PGUSER: z.string().optional(), 69 | PGDATABASE: z.string().optional(), 70 | PGPASSWORD: z.string().optional(), 71 | POSTGRES_URL: z.string().url().optional(), 72 | POSTGRES_URL_NON_POOLING: z.string().url().optional(), 73 | POSTGRES_USER: z.string().optional(), 74 | POSTGRES_HOST: z.string().optional(), 75 | POSTGRES_PASSWORD: z.string().optional(), 76 | POSTGRES_DATABASE: z.string().optional(), 77 | POSTGRES_URL_NO_SSL: z.string().url().optional(), 78 | POSTGRES_PRISMA_URL: z.string().url().optional(), 79 | }, 80 | runtimeEnv: process.env, 81 | }); 82 | 83 | /** 84 | * Supabase for Vercel Environment Variables 85 | * @see https://vercel.com/marketplace/supabase 86 | */ 87 | export const supabaseVercel = (): Readonly => 88 | createEnv({ 89 | server: { 90 | POSTGRES_URL: z.string().url(), 91 | POSTGRES_PRISMA_URL: z.string().url().optional(), 92 | POSTGRES_URL_NON_POOLING: z.string().url().optional(), 93 | POSTGRES_USER: z.string().optional(), 94 | POSTGRES_HOST: z.string().optional(), 95 | POSTGRES_PASSWORD: z.string().optional(), 96 | POSTGRES_DATABASE: z.string().optional(), 97 | SUPABASE_SERVICE_ROLE_KEY: z.string().optional(), 98 | SUPABASE_ANON_KEY: z.string().optional(), 99 | SUPABASE_URL: z.string().url().optional(), 100 | SUPABASE_JWT_SECRET: z.string().optional(), 101 | NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().optional(), 102 | NEXT_PUBLIC_SUPABASE_URL: z.string().url().optional(), 103 | }, 104 | runtimeEnv: process.env, 105 | }); 106 | 107 | /** 108 | * @see https://v6.docs.uploadthing.com/getting-started/nuxt#add-env-variables 109 | */ 110 | export const uploadthingV6 = (): Readonly => 111 | createEnv({ 112 | server: { 113 | UPLOADTHING_TOKEN: z.string(), 114 | }, 115 | runtimeEnv: process.env, 116 | }); 117 | 118 | /** 119 | * @see https://docs.uploadthing.com/getting-started/appdir#add-env-variables 120 | */ 121 | export const uploadthing = (): Readonly => 122 | createEnv({ 123 | server: { 124 | UPLOADTHING_TOKEN: z.string(), 125 | }, 126 | runtimeEnv: process.env, 127 | }); 128 | 129 | /** 130 | * Render System Environment Variables 131 | * @see https://docs.render.com/environment-variables#all-runtimes 132 | */ 133 | export const render = (): Readonly => 134 | createEnv({ 135 | server: { 136 | IS_PULL_REQUEST: z.string().optional(), 137 | RENDER_DISCOVERY_SERVICE: z.string().optional(), 138 | RENDER_EXTERNAL_HOSTNAME: z.string().optional(), 139 | RENDER_EXTERNAL_URL: z.string().url().optional(), 140 | RENDER_GIT_BRANCH: z.string().optional(), 141 | RENDER_GIT_COMMIT: z.string().optional(), 142 | RENDER_GIT_REPO_SLUG: z.string().optional(), 143 | RENDER_INSTANCE_ID: z.string().optional(), 144 | RENDER_SERVICE_ID: z.string().optional(), 145 | RENDER_SERVICE_NAME: z.string().optional(), 146 | RENDER_SERVICE_TYPE: z.enum(["web", "pserv", "cron", "worker", "static"]).optional(), 147 | RENDER: z.string().optional(), 148 | }, 149 | runtimeEnv: process.env, 150 | }); 151 | 152 | /** 153 | * Railway Environment Variables 154 | * @see https://docs.railway.app/reference/variables#railway-provided-variables 155 | */ 156 | export const railway = (): Readonly => 157 | createEnv({ 158 | server: { 159 | RAILWAY_PUBLIC_DOMAIN: z.string().optional(), 160 | RAILWAY_PRIVATE_DOMAIN: z.string().optional(), 161 | RAILWAY_TCP_PROXY_DOMAIN: z.string().optional(), 162 | RAILWAY_TCP_PROXY_PORT: z.string().optional(), 163 | RAILWAY_TCP_APPLICATION_PORT: z.string().optional(), 164 | RAILWAY_PROJECT_NAME: z.string().optional(), 165 | RAILWAY_PROJECT_ID: z.string().optional(), 166 | RAILWAY_ENVIRONMENT_NAME: z.string().optional(), 167 | RAILWAY_ENVIRONMENT_ID: z.string().optional(), 168 | RAILWAY_SERVICE_NAME: z.string().optional(), 169 | RAILWAY_SERVICE_ID: z.string().optional(), 170 | RAILWAY_REPLICA_ID: z.string().optional(), 171 | RAILWAY_DEPLOYMENT_ID: z.string().optional(), 172 | RAILWAY_SNAPSHOT_ID: z.string().optional(), 173 | RAILWAY_VOLUME_NAME: z.string().optional(), 174 | RAILWAY_VOLUME_MOUNT_PATH: z.string().optional(), 175 | RAILWAY_RUN_UID: z.string().optional(), 176 | RAILWAY_GIT_COMMIT_SHA: z.string().optional(), 177 | RAILWAY_GIT_AUTHOR_EMAIL: z.string().optional(), 178 | RAILWAY_GIT_BRANCH: z.string().optional(), 179 | RAILWAY_GIT_REPO_NAME: z.string().optional(), 180 | RAILWAY_GIT_REPO_OWNER: z.string().optional(), 181 | RAILWAY_GIT_COMMIT_MESSAGE: z.string().optional(), 182 | }, 183 | runtimeEnv: process.env, 184 | }); 185 | 186 | /** 187 | * Fly.io Environment Variables 188 | * @see https://fly.io/docs/machines/runtime-environment/#environment-variables 189 | */ 190 | export const fly = (): Readonly => 191 | createEnv({ 192 | server: { 193 | FLY_APP_NAME: z.string().optional(), 194 | FLY_MACHINE_ID: z.string().optional(), 195 | FLY_ALLOC_ID: z.string().optional(), 196 | FLY_REGION: z.string().optional(), 197 | FLY_PUBLIC_IP: z.string().optional(), 198 | FLY_IMAGE_REF: z.string().optional(), 199 | FLY_MACHINE_VERSION: z.string().optional(), 200 | FLY_PRIVATE_IP: z.string().optional(), 201 | FLY_PROCESS_GROUP: z.string().optional(), 202 | FLY_VM_MEMORY_MB: z.string().optional(), 203 | PRIMARY_REGION: z.string().optional(), 204 | }, 205 | runtimeEnv: process.env, 206 | }); 207 | 208 | /** 209 | * Netlify Environment Variables 210 | * @see https://docs.netlify.com/configure-builds/environment-variables 211 | */ 212 | export const netlify = (): Readonly => 213 | createEnv({ 214 | server: { 215 | NETLIFY: z.string().optional(), 216 | BUILD_ID: z.string().optional(), 217 | CONTEXT: z.enum(["production", "deploy-preview", "branch-deploy", "dev"]).optional(), 218 | REPOSITORY_URL: z.string().optional(), 219 | BRANCH: z.string().optional(), 220 | URL: z.string().optional(), 221 | DEPLOY_URL: z.string().optional(), 222 | DEPLOY_PRIME_URL: z.string().optional(), 223 | DEPLOY_ID: z.string().optional(), 224 | SITE_NAME: z.string().optional(), 225 | SITE_ID: z.string().optional(), 226 | }, 227 | runtimeEnv: process.env, 228 | }); 229 | 230 | /** 231 | * Upstash redis Environment Variables 232 | * @see https://upstash.com/docs/redis/howto/connectwithupstashredis 233 | */ 234 | export const upstashRedis = (): Readonly => 235 | createEnv({ 236 | server: { 237 | UPSTASH_REDIS_REST_URL: z.string().url(), 238 | UPSTASH_REDIS_REST_TOKEN: z.string(), 239 | }, 240 | runtimeEnv: process.env, 241 | }); 242 | 243 | /** 244 | * Coolify Environment Variables 245 | * @see https://coolify.io/docs/knowledge-base/environment-variables#predefined-variables 246 | */ 247 | export const coolify = (): Readonly => 248 | createEnv({ 249 | server: { 250 | COOLIFY_FQDN: z.string().optional(), 251 | COOLIFY_URL: z.string().optional(), 252 | COOLIFY_BRANCH: z.string().optional(), 253 | COOLIFY_RESOURCE_UUID: z.string().optional(), 254 | COOLIFY_CONTAINER_NAME: z.string().optional(), 255 | SOURCE_COMMIT: z.string().optional(), 256 | PORT: z.string().optional(), 257 | HOST: z.string().optional(), 258 | }, 259 | runtimeEnv: process.env, 260 | }); 261 | 262 | /** 263 | * Vite Environment Variables 264 | * @see https://vite.dev/guide/env-and-mode 265 | */ 266 | export const vite = (): Readonly => 267 | createEnv({ 268 | server: { 269 | BASE_URL: z.string(), 270 | MODE: z.string(), 271 | DEV: z.boolean(), 272 | PROD: z.boolean(), 273 | SSR: z.boolean(), 274 | }, 275 | runtimeEnv: import.meta.env, 276 | }); 277 | 278 | /** 279 | * WXT Environment Variables 280 | * @see https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables 281 | */ 282 | export const wxt = (): Readonly => 283 | createEnv({ 284 | server: { 285 | MANIFEST_VERSION: z.union([z.literal(2), z.literal(3)]).optional(), 286 | BROWSER: z.enum(["chrome", "firefox", "safari", "edge", "opera"]).optional(), 287 | CHROME: z.boolean().optional(), 288 | FIREFOX: z.boolean().optional(), 289 | SAFARI: z.boolean().optional(), 290 | EDGE: z.boolean().optional(), 291 | OPERA: z.boolean().optional(), 292 | }, 293 | runtimeEnv: import.meta.env, 294 | }); 295 | -------------------------------------------------------------------------------- /packages/core/src/presets-arktype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Presets for Arktype 3 | * @see https://env.t3.gg/docs/customization#extending-presets 4 | * @module 5 | */ 6 | import { type } from "arktype"; 7 | import { createEnv } from "./index.ts"; 8 | import type { 9 | CoolifyEnv, 10 | FlyEnv, 11 | NeonVercelEnv, 12 | NetlifyEnv, 13 | RailwayEnv, 14 | RenderEnv, 15 | SupabaseVercelEnv, 16 | UploadThingEnv, 17 | UploadThingV6Env, 18 | UpstashRedisEnv, 19 | VercelEnv, 20 | ViteEnv, 21 | WxtEnv, 22 | } from "./presets.ts"; 23 | 24 | /** 25 | * Vercel System Environment Variables 26 | * @see https://vercel.com/docs/projects/environment-variables/system-environment-variables#system-environment-variables 27 | */ 28 | export const vercel = (): Readonly => 29 | createEnv({ 30 | server: { 31 | VERCEL: type("string | undefined"), 32 | CI: type("string | undefined"), 33 | VERCEL_ENV: type("'development' | 'preview' | 'production' | undefined"), 34 | VERCEL_TARGET_ENV: type("string | undefined"), 35 | VERCEL_URL: type("string | undefined"), 36 | VERCEL_PROJECT_PRODUCTION_URL: type("string | undefined"), 37 | VERCEL_BRANCH_URL: type("string | undefined"), 38 | VERCEL_REGION: type("string | undefined"), 39 | VERCEL_DEPLOYMENT_ID: type("string | undefined"), 40 | VERCEL_SKEW_PROTECTION_ENABLED: type("string | undefined"), 41 | VERCEL_AUTOMATION_BYPASS_SECRET: type("string | undefined"), 42 | VERCEL_GIT_PROVIDER: type("string | undefined"), 43 | VERCEL_GIT_REPO_SLUG: type("string | undefined"), 44 | VERCEL_GIT_REPO_OWNER: type("string | undefined"), 45 | VERCEL_GIT_REPO_ID: type("string | undefined"), 46 | VERCEL_GIT_COMMIT_REF: type("string | undefined"), 47 | VERCEL_GIT_COMMIT_SHA: type("string | undefined"), 48 | VERCEL_GIT_COMMIT_MESSAGE: type("string | undefined"), 49 | VERCEL_GIT_COMMIT_AUTHOR_LOGIN: type("string | undefined"), 50 | VERCEL_GIT_COMMIT_AUTHOR_NAME: type("string | undefined"), 51 | VERCEL_GIT_PREVIOUS_SHA: type("string | undefined"), 52 | VERCEL_GIT_PULL_REQUEST_ID: type("string | undefined"), 53 | }, 54 | runtimeEnv: process.env, 55 | }); 56 | 57 | /** 58 | * Neon for Vercel Environment Variables 59 | * @see https://neon.tech/docs/guides/vercel-native-integration#environment-variables-set-by-the-integration 60 | */ 61 | export const neonVercel = (): Readonly => 62 | createEnv({ 63 | server: { 64 | DATABASE_URL: type("string"), 65 | DATABASE_URL_UNPOOLED: type("string | undefined"), 66 | PGHOST: type("string | undefined"), 67 | PGHOST_UNPOOLED: type("string | undefined"), 68 | PGUSER: type("string | undefined"), 69 | PGDATABASE: type("string | undefined"), 70 | PGPASSWORD: type("string | undefined"), 71 | POSTGRES_URL: type("string | undefined"), 72 | POSTGRES_URL_NON_POOLING: type("string.url | undefined"), 73 | POSTGRES_USER: type("string | undefined"), 74 | POSTGRES_HOST: type("string | undefined"), 75 | POSTGRES_PASSWORD: type("string | undefined"), 76 | POSTGRES_DATABASE: type("string.url | undefined"), 77 | POSTGRES_URL_NO_SSL: type("string.url | undefined"), 78 | POSTGRES_PRISMA_URL: type("string.url | undefined"), 79 | }, 80 | runtimeEnv: process.env, 81 | }); 82 | 83 | /** 84 | * Supabase for Vercel Environment Variables 85 | * @see https://vercel.com/marketplace/supabase 86 | */ 87 | export const supabaseVercel = (): Readonly => 88 | createEnv({ 89 | server: { 90 | POSTGRES_URL: type("string.url"), 91 | POSTGRES_PRISMA_URL: type("string.url | undefined"), 92 | POSTGRES_URL_NON_POOLING: type("string.url | undefined"), 93 | POSTGRES_USER: type("string | undefined"), 94 | POSTGRES_HOST: type("string | undefined"), 95 | POSTGRES_PASSWORD: type("string | undefined"), 96 | POSTGRES_DATABASE: type("string | undefined"), 97 | SUPABASE_SERVICE_ROLE_KEY: type("string | undefined"), 98 | SUPABASE_ANON_KEY: type("string | undefined"), 99 | SUPABASE_URL: type("string.url | undefined"), 100 | SUPABASE_JWT_SECRET: type("string | undefined"), 101 | NEXT_PUBLIC_SUPABASE_ANON_KEY: type("string | undefined"), 102 | NEXT_PUBLIC_SUPABASE_URL: type("string.url | undefined"), 103 | }, 104 | runtimeEnv: process.env, 105 | }); 106 | 107 | /** 108 | * @see https://v6.docs.uploadthing.com/getting-started/nuxt#add-env-variables 109 | */ 110 | export const uploadthingV6 = (): Readonly => 111 | createEnv({ 112 | server: { 113 | UPLOADTHING_TOKEN: type("string"), 114 | }, 115 | runtimeEnv: process.env, 116 | }); 117 | 118 | /** 119 | * @see https://docs.uploadthing.com/getting-started/appdir#add-env-variables 120 | */ 121 | export const uploadthing = (): Readonly => 122 | createEnv({ 123 | server: { 124 | UPLOADTHING_TOKEN: type("string"), 125 | }, 126 | runtimeEnv: process.env, 127 | }); 128 | 129 | /** 130 | * Render System Environment Variables 131 | * @see https://docs.render.com/environment-variables#all-runtimes 132 | */ 133 | export const render = (): Readonly => 134 | createEnv({ 135 | server: { 136 | IS_PULL_REQUEST: type("string | undefined"), 137 | RENDER_DISCOVERY_SERVICE: type("string | undefined"), 138 | RENDER_EXTERNAL_HOSTNAME: type("string | undefined"), 139 | RENDER_EXTERNAL_URL: type("string.url | undefined"), 140 | RENDER_GIT_BRANCH: type("string | undefined"), 141 | RENDER_GIT_COMMIT: type("string | undefined"), 142 | RENDER_GIT_REPO_SLUG: type("string | undefined"), 143 | RENDER_INSTANCE_ID: type("string | undefined"), 144 | RENDER_SERVICE_ID: type("string | undefined"), 145 | RENDER_SERVICE_NAME: type("string | undefined"), 146 | RENDER_SERVICE_TYPE: type("'web' | 'pserv' | 'cron' | 'worker' | 'static' | undefined"), 147 | RENDER: type("string | undefined"), 148 | }, 149 | runtimeEnv: process.env, 150 | }); 151 | 152 | /** 153 | * Railway Environment Variables 154 | * @see https://docs.railway.app/reference/variables#railway-provided-variables 155 | */ 156 | export const railway = (): Readonly => 157 | createEnv({ 158 | server: { 159 | RAILWAY_PUBLIC_DOMAIN: type("string | undefined"), 160 | RAILWAY_PRIVATE_DOMAIN: type("string | undefined"), 161 | RAILWAY_TCP_PROXY_DOMAIN: type("string | undefined"), 162 | RAILWAY_TCP_PROXY_PORT: type("string | undefined"), 163 | RAILWAY_TCP_APPLICATION_PORT: type("string | undefined"), 164 | RAILWAY_PROJECT_NAME: type("string | undefined"), 165 | RAILWAY_PROJECT_ID: type("string | undefined"), 166 | RAILWAY_ENVIRONMENT_NAME: type("string | undefined"), 167 | RAILWAY_ENVIRONMENT_ID: type("string | undefined"), 168 | RAILWAY_SERVICE_NAME: type("string | undefined"), 169 | RAILWAY_SERVICE_ID: type("string | undefined"), 170 | RAILWAY_REPLICA_ID: type("string | undefined"), 171 | RAILWAY_DEPLOYMENT_ID: type("string | undefined"), 172 | RAILWAY_SNAPSHOT_ID: type("string | undefined"), 173 | RAILWAY_VOLUME_NAME: type("string | undefined"), 174 | RAILWAY_VOLUME_MOUNT_PATH: type("string | undefined"), 175 | RAILWAY_RUN_UID: type("string | undefined"), 176 | RAILWAY_GIT_COMMIT_SHA: type("string | undefined"), 177 | RAILWAY_GIT_AUTHOR_EMAIL: type("string | undefined"), 178 | RAILWAY_GIT_BRANCH: type("string | undefined"), 179 | RAILWAY_GIT_REPO_NAME: type("string | undefined"), 180 | RAILWAY_GIT_REPO_OWNER: type("string | undefined"), 181 | RAILWAY_GIT_COMMIT_MESSAGE: type("string | undefined"), 182 | }, 183 | runtimeEnv: process.env, 184 | }); 185 | 186 | /** 187 | * Fly.io Environment Variables 188 | * @see https://fly.io/docs/machines/runtime-environment/#environment-variables 189 | */ 190 | export const fly = (): Readonly => 191 | createEnv({ 192 | server: { 193 | FLY_APP_NAME: type("string | undefined"), 194 | FLY_MACHINE_ID: type("string | undefined"), 195 | FLY_ALLOC_ID: type("string | undefined"), 196 | FLY_REGION: type("string | undefined"), 197 | FLY_PUBLIC_IP: type("string | undefined"), 198 | FLY_IMAGE_REF: type("string | undefined"), 199 | FLY_MACHINE_VERSION: type("string | undefined"), 200 | FLY_PRIVATE_IP: type("string | undefined"), 201 | FLY_PROCESS_GROUP: type("string | undefined"), 202 | FLY_VM_MEMORY_MB: type("string | undefined"), 203 | PRIMARY_REGION: type("string | undefined"), 204 | }, 205 | runtimeEnv: process.env, 206 | }); 207 | 208 | /** 209 | * Netlify Environment Variables 210 | * @see https://docs.netlify.com/configure-builds/environment-variables 211 | */ 212 | export const netlify = (): Readonly => 213 | createEnv({ 214 | server: { 215 | NETLIFY: type("string | undefined"), 216 | BUILD_ID: type("string | undefined"), 217 | CONTEXT: type("'production' | 'deploy-preview' | 'branch-deploy' | 'dev' | undefined"), 218 | REPOSITORY_URL: type("string | undefined"), 219 | BRANCH: type("string | undefined"), 220 | URL: type("string | undefined"), 221 | DEPLOY_URL: type("string | undefined"), 222 | DEPLOY_PRIME_URL: type("string | undefined"), 223 | DEPLOY_ID: type("string | undefined"), 224 | SITE_NAME: type("string | undefined"), 225 | SITE_ID: type("string | undefined"), 226 | }, 227 | runtimeEnv: process.env, 228 | }); 229 | 230 | /** 231 | * Upstash redis Environment Variables 232 | * @see https://upstash.com/docs/redis/howto/connectwithupstashredis 233 | */ 234 | export const upstashRedis = (): Readonly => 235 | createEnv({ 236 | server: { 237 | UPSTASH_REDIS_REST_URL: type("string.url"), 238 | UPSTASH_REDIS_REST_TOKEN: type("string"), 239 | }, 240 | runtimeEnv: process.env, 241 | }); 242 | 243 | /** 244 | * Coolify Environment Variables 245 | * @see https://coolify.io/docs/knowledge-base/environment-variables#predefined-variables 246 | */ 247 | export const coolify = (): Readonly => 248 | createEnv({ 249 | server: { 250 | COOLIFY_FQDN: type("string | undefined"), 251 | COOLIFY_URL: type("string | undefined"), 252 | COOLIFY_BRANCH: type("string | undefined"), 253 | COOLIFY_RESOURCE_UUID: type("string | undefined"), 254 | COOLIFY_CONTAINER_NAME: type("string | undefined"), 255 | SOURCE_COMMIT: type("string | undefined"), 256 | PORT: type("string | undefined"), 257 | HOST: type("string | undefined"), 258 | }, 259 | runtimeEnv: process.env, 260 | }); 261 | 262 | /** 263 | * Vite Environment Variables 264 | * @see https://vite.dev/guide/env-and-mode 265 | */ 266 | export const vite = (): Readonly => 267 | createEnv({ 268 | server: { 269 | BASE_URL: type("string"), 270 | MODE: type("string"), 271 | DEV: type("boolean"), 272 | PROD: type("boolean"), 273 | SSR: type("boolean"), 274 | }, 275 | runtimeEnv: import.meta.env, 276 | }); 277 | 278 | /** 279 | * WXT Environment Variables 280 | * @see https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables 281 | */ 282 | export const wxt = (): Readonly => 283 | createEnv({ 284 | server: { 285 | MANIFEST_VERSION: type("2 | 3 | undefined"), 286 | BROWSER: type("'chrome' | 'firefox' | 'safari' | 'edge' | 'opera' | undefined"), 287 | CHROME: type("boolean | undefined"), 288 | FIREFOX: type("boolean | undefined"), 289 | SAFARI: type("boolean | undefined"), 290 | EDGE: type("boolean | undefined"), 291 | OPERA: type("boolean | undefined"), 292 | }, 293 | runtimeEnv: import.meta.env, 294 | }); 295 | --------------------------------------------------------------------------------