├── .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 | 
4 | [](https://jsr.io/@t3-oss/env-core)
5 | [](https://jsr.io/@t3-oss/env-core)
6 | [](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 |
40 | );
41 | },
42 | );
43 | Button.displayName = "Button";
44 |
45 | export { Button, buttonVariants };
46 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # Typesafe Envs made Simple
2 |
3 | 
4 | [](https://jsr.io/@t3-oss/env-core)
5 | [](https://jsr.io/@t3-oss/env-core)
6 | [](https://socket.dev/npm/package/@t3-oss/env-core/overview)
7 |
8 | This is the framework agnostic core package of t3-env.
9 |
10 | > For full documentation, see https://env.t3.gg
11 |
12 | ## Installation
13 |
14 | ```bash
15 | # npm
16 | npm i @t3-oss/env-core
17 |
18 | # pnpm
19 | pnpm add @t3-oss/env-core
20 |
21 | # bun
22 | bun add @t3-oss/env-core
23 |
24 | # deno
25 | deno add jsr:@t3-oss/env-core
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 | > See the [documentation](https://env.t3.gg/docs/standard-schema) for more details.
34 |
35 | ```ts
36 | // src/env.ts
37 | import { createEnv } from "@t3-oss/env-core";
38 | import { z } from "zod";
39 |
40 | export const env = createEnv({
41 | /*
42 | * Serverside Environment variables, not available on the client.
43 | * Will throw if you access these variables on the client.
44 | */
45 | server: {
46 | DATABASE_URL: z.url(),
47 | OPEN_AI_API_KEY: z.string().min(1),
48 | },
49 | /*
50 | * Environment variables available on the client (and server).
51 | *
52 | * 💡 You'll get type errors if these are not prefixed with PUBLIC_.
53 | */
54 | clientPrefix: 'PUBLIC_',
55 | client: {
56 | PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
57 | },
58 | /*
59 | * Specify what values should be validated by your schemas above.
60 | */
61 | runtimeEnv: process.env,
62 | });
63 | ```
64 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | env:
8 | TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
9 | TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
10 |
11 | jobs:
12 | release:
13 | name: Release
14 | runs-on: ubuntu-latest # blacksmith-4vcpu-ubuntu-2404
15 | permissions:
16 | contents: write # For fixing formatting
17 | pull-requests: write # For creating release PRs
18 | id-token: write # For OIDC publishing
19 |
20 | steps:
21 | - name: Checkout repo
22 | uses: actions/checkout@v6
23 | - uses: ./.github/setup
24 |
25 | - name: Build
26 | run: bun run build
27 |
28 | - run: bun add -g npm@latest
29 |
30 | # Using custom token `MY_GITHUB_TOKEN` with more access to avoid rate limiting
31 | - name: Create Release
32 | id: changeset
33 | uses: changesets/action@v1
34 | with:
35 | commit: "chore(release): 📦 version packages"
36 | title: "chore(release): 📦 version packages"
37 | publish: bun run scripts/publish.ts
38 | version: bun run scripts/version.ts
39 | env:
40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 | NPM_CONFIG_PROVENANCE: true
42 |
43 | - name: Fix formatting
44 | if: steps.changeset.outputs.hasChangesets == 'true'
45 | run: |
46 | git config user.name "${{ github.actor }}"
47 | git config user.email "${{ github.actor }}@users.noreply.github.com"
48 | git checkout changeset-release/main
49 | bun fmt
50 | git add .
51 | # Check if there are staged changes before committing and pushing
52 | if ! git diff --staged --quiet; then
53 | git commit -m "chore(release): 📦 fix formatting"
54 | git push origin changeset-release/main
55 | else
56 | echo "No formatting changes to commit."
57 | fi
58 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@t3-oss/env-core",
3 | "version": "0.13.10",
4 | "type": "module",
5 | "keywords": [
6 | "create-t3-app",
7 | "environment variables",
8 | "zod"
9 | ],
10 | "author": "Julius Marminge",
11 | "license": "MIT",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/t3-oss/t3-env",
15 | "directory": "packages/core"
16 | },
17 | "exports": {
18 | "./package.json": "./package.json",
19 | ".": {
20 | "types": "./dist/index.d.ts",
21 | "default": "./dist/index.js"
22 | },
23 | "./presets-arktype": {
24 | "types": "./dist/presets-arktype.d.ts",
25 | "default": "./dist/presets-arktype.js"
26 | },
27 | "./presets-zod": {
28 | "types": "./dist/presets-zod.d.ts",
29 | "default": "./dist/presets-zod.js"
30 | },
31 | "./presets-valibot": {
32 | "types": "./dist/presets-valibot.d.ts",
33 | "default": "./dist/presets-valibot.js"
34 | }
35 | },
36 | "files": [
37 | "dist",
38 | "package.json",
39 | "LICENSE",
40 | "README.md"
41 | ],
42 | "scripts": {
43 | "build": "tsdown src/index.ts src/presets-arktype.ts src/presets-zod.ts src/presets-valibot.ts --dts --platform neutral --unbundle",
44 | "clean": "rm -rf dist node_modules .turbo .cache",
45 | "typecheck": "tsgo --noEmit"
46 | },
47 | "peerDependencies": {
48 | "arktype": "^2.1.0",
49 | "typescript": ">=5.0.0",
50 | "valibot": "^1.0.0-beta.7 || ^1.0.0",
51 | "zod": "^3.24.0 || ^4.0.0"
52 | },
53 | "peerDependenciesMeta": {
54 | "typescript": {
55 | "optional": true
56 | },
57 | "arktype": {
58 | "optional": true
59 | },
60 | "zod": {
61 | "optional": true
62 | },
63 | "valibot": {
64 | "optional": true
65 | }
66 | },
67 | "devDependencies": {
68 | "@typescript/native-preview": "7.0.0-dev.20251125.1",
69 | "arktype": "2.1.22",
70 | "tsdown": "0.16.7",
71 | "valibot": "1.2.0",
72 | "zod": "4.1.5"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/packages/nuxt/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the Nuxt 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 = "NUXT_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 | * Create a new environment variable schema.
35 | */
36 | export function createEnv<
37 | TServer extends StandardSchemaDictionary = NonNullable,
38 | TClient extends Record<`${ClientPrefix}${string}`, StandardSchemaV1> = NonNullable,
39 | TShared extends StandardSchemaDictionary = NonNullable,
40 | const TExtends extends Array> = [],
41 | TFinalSchema extends StandardSchemaV1<{}, {}> = DefaultCombinedSchema,
42 | >(
43 | opts: Options,
44 | ): CreateEnv {
45 | const client = typeof opts.client === "object" ? opts.client : {};
46 | const server = typeof opts.server === "object" ? opts.server : {};
47 | const shared = opts.shared;
48 |
49 | return createEnvCore({
50 | ...opts,
51 | shared,
52 | client,
53 | server,
54 | clientPrefix: CLIENT_PREFIX,
55 | runtimeEnv: process.env,
56 | });
57 | }
58 |
--------------------------------------------------------------------------------
/packages/nextjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@t3-oss/env-nextjs",
3 | "version": "0.13.10",
4 | "type": "module",
5 | "keywords": [
6 | "create-t3-app",
7 | "environment variables",
8 | "zod",
9 | "nextjs"
10 | ],
11 | "author": "Julius Marminge",
12 | "license": "MIT",
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/t3-oss/t3-env",
16 | "directory": "packages/nextjs"
17 | },
18 | "exports": {
19 | "./package.json": "./package.json",
20 | ".": {
21 | "types": "./dist/index.d.ts",
22 | "default": "./dist/index.js"
23 | },
24 | "./presets-arktype": {
25 | "types": "./dist/presets-arktype.d.ts",
26 | "default": "./dist/presets-arktype.js"
27 | },
28 | "./presets-zod": {
29 | "types": "./dist/presets-zod.d.ts",
30 | "default": "./dist/presets-zod.js"
31 | },
32 | "./presets-valibot": {
33 | "types": "./dist/presets-valibot.d.ts",
34 | "default": "./dist/presets-valibot.js"
35 | }
36 | },
37 | "files": [
38 | "dist",
39 | "package.json",
40 | "LICENSE",
41 | "README.md"
42 | ],
43 | "scripts": {
44 | "build": "tsdown src/index.ts src/presets-arktype.ts src/presets-zod.ts src/presets-valibot.ts --dts --platform neutral --unbundle",
45 | "clean": "rm -rf dist node_modules .turbo .cache",
46 | "typecheck": "tsgo --noEmit",
47 | "prepack": "bun ../../scripts/replace-workspace-protocol.ts"
48 | },
49 | "dependencies": {
50 | "@t3-oss/env-core": "workspace:*"
51 | },
52 | "peerDependencies": {
53 | "arktype": "^2.1.0",
54 | "typescript": ">=5.0.0",
55 | "valibot": "^1.0.0-beta.7 || ^1.0.0",
56 | "zod": "^3.24.0 || ^4.0.0"
57 | },
58 | "peerDependenciesMeta": {
59 | "typescript": {
60 | "optional": true
61 | },
62 | "arktype": {
63 | "optional": true
64 | },
65 | "zod": {
66 | "optional": true
67 | },
68 | "valibot": {
69 | "optional": true
70 | }
71 | },
72 | "devDependencies": {
73 | "@typescript/native-preview": "7.0.0-dev.20251125.1",
74 | "arktype": "2.1.22",
75 | "tsdown": "0.16.7",
76 | "valibot": "1.2.0",
77 | "zod": "4.1.5"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/packages/nuxt/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@t3-oss/env-nuxt",
3 | "version": "0.13.10",
4 | "type": "module",
5 | "keywords": [
6 | "create-t3-app",
7 | "environment variables",
8 | "zod",
9 | "nuxt",
10 | "vue"
11 | ],
12 | "author": "Julius Marminge",
13 | "license": "MIT",
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/t3-oss/t3-env",
17 | "directory": "packages/nuxt"
18 | },
19 | "exports": {
20 | "./package.json": "./package.json",
21 | ".": {
22 | "types": "./dist/index.d.ts",
23 | "default": "./dist/index.js"
24 | },
25 | "./presets-arktype": {
26 | "types": "./dist/presets-arktype.d.ts",
27 | "default": "./dist/presets-arktype.js"
28 | },
29 | "./presets-zod": {
30 | "types": "./dist/presets-zod.d.ts",
31 | "default": "./dist/presets-zod.js"
32 | },
33 | "./presets-valibot": {
34 | "types": "./dist/presets-valibot.d.ts",
35 | "default": "./dist/presets-valibot.js"
36 | }
37 | },
38 | "files": [
39 | "dist",
40 | "package.json",
41 | "LICENSE",
42 | "README.md"
43 | ],
44 | "scripts": {
45 | "build": "tsdown src/index.ts src/presets-arktype.ts src/presets-zod.ts src/presets-valibot.ts --dts --platform neutral --unbundle",
46 | "clean": "rm -rf dist node_modules .turbo .cache",
47 | "typecheck": "tsgo --noEmit",
48 | "prepack": "bun ../../scripts/replace-workspace-protocol.ts"
49 | },
50 | "dependencies": {
51 | "@t3-oss/env-core": "workspace:*"
52 | },
53 | "peerDependencies": {
54 | "arktype": "^2.1.0",
55 | "typescript": ">=5.0.0",
56 | "valibot": "^1.0.0-beta.7 || ^1.0.0",
57 | "zod": "^3.24.0 || ^4.0.0"
58 | },
59 | "peerDependenciesMeta": {
60 | "typescript": {
61 | "optional": true
62 | },
63 | "arktype": {
64 | "optional": true
65 | },
66 | "zod": {
67 | "optional": true
68 | },
69 | "valibot": {
70 | "optional": true
71 | }
72 | },
73 | "devDependencies": {
74 | "@typescript/native-preview": "7.0.0-dev.20251125.1",
75 | "arktype": "2.1.22",
76 | "tsdown": "0.16.7",
77 | "valibot": "1.2.0",
78 | "zod": "4.1.5"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/docs/src/components/mdx/code-block.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CheckCheck, Copy } from "lucide-react";
4 | import { useRef, useState } from "react";
5 | import { cn } from "@/lib/cn";
6 | import { Icons } from "../icons";
7 |
8 | export type CodeblockProps = React.DetailedHTMLProps<
9 | React.HTMLAttributes,
10 | HTMLPreElement
11 | > & {
12 | /** set by `rehype-pretty-code` */
13 | "data-language"?: string;
14 | /** set by `rehype-pretty-code` */
15 | "data-theme"?: string;
16 | };
17 |
18 | export function Codeblock(props: CodeblockProps) {
19 | const { children, ...rest } = props;
20 | const language = props["data-language"] as string;
21 | const theme = props["data-theme"] as string;
22 | const Icon = {
23 | js: Icons.javascript,
24 | ts: Icons.typescript,
25 | vue: Icons.vue,
26 | bash: Icons.bash,
27 | }[language];
28 |
29 | const ref = useRef(null);
30 | const [copied, setCopied] = useState(false);
31 |
32 | return (
33 | <>
34 | {Icon && (
35 |
40 | )}
41 |
59 |
64 | {children}
65 |
66 | >
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/packages/nextjs/README.md:
--------------------------------------------------------------------------------
1 | # Typesafe Envs made Simple
2 |
3 | 
4 | [](https://jsr.io/@t3-oss/env-core)
5 | [](https://jsr.io/@t3-oss/env-core)
6 | [](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 |
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 |
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 |
28 | ),
29 | h3: ({ children, ...props }) => (
30 |
36 | ),
37 | h4: ({ children, ...props }) => (
38 |
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 |
123 | Client says {{ env.NUXT_PUBLIC_GREETING }}!
124 | See what the server has to say!
125 |
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