diff --git a/README.md b/README.md
index f50c5b95..3ca90b45 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ npx create-better-t-stack@latest
- Databases: SQLite, PostgreSQL, MySQL, MongoDB (or none)
- ORMs: Drizzle, Prisma, Mongoose (or none)
- Auth: Better-Auth (optional)
-- Addons: Turborepo, PWA, Tauri, Biome, Husky, Starlight, Fumadocs, Ruler, Ultracite, Oxlint
+- Addons: Turborepo, PWA, Tauri, Biome, Husky, Starlight, Fumadocs, Ruler, Ultracite, Oxlint, T3 Env
- Examples: Todo, AI
- DB Setup: Turso, Neon, Supabase, Prisma PostgreSQL, MongoDB Atlas, Cloudflare D1, Docker
- Web Deploy: Cloudflare Workers
diff --git a/apps/cli/README.md b/apps/cli/README.md
index 6001e742..cb94a59b 100644
--- a/apps/cli/README.md
+++ b/apps/cli/README.md
@@ -41,7 +41,7 @@ Follow the prompts to configure your project or use the `--yes` flag for default
| **Database Setup** | • Turso (SQLite)
• Cloudflare D1 (SQLite)
• Neon (PostgreSQL)
• Supabase (PostgreSQL)
• Prisma Postgres (via Prisma Accelerate)
• MongoDB Atlas
• None (manual setup) |
| **Authentication** | Better-Auth (email/password, with more options coming soon) |
| **Styling** | Tailwind CSS with shadcn/ui components |
-| **Addons** | • PWA support
• Tauri (desktop applications)
• Starlight (documentation site)
• Biome (linting and formatting)
• Husky (Git hooks)
• Turborepo (optimized builds) |
+| **Addons** | • PWA support
• Tauri (desktop applications)
• Starlight (documentation site)
• Biome (linting and formatting)
• Husky (Git hooks)
• Turborepo (optimized builds)
• T3 Env (Type-safe environment variables) |
| **Examples** | • Todo app
• AI Chat interface (using Vercel AI SDK) |
| **Developer Experience** | • Automatic Git initialization
• Package manager choice (npm, pnpm, bun)
• Automatic dependency installation |
@@ -58,7 +58,7 @@ Options:
--auth Include authentication
--no-auth Exclude authentication
--frontend Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-nativewind, native-unistyles, none)
- --addons Additional addons (pwa, tauri, starlight, biome, husky, turborepo, fumadocs, ultracite, oxlint, none)
+ --addons Additional addons (pwa, tauri, starlight, biome, husky, turborepo, fumadocs, ultracite, oxlint, t3env, none)
--examples Examples to include (todo, ai, none)
--git Initialize git repository
--no-git Skip git initialization
@@ -192,7 +192,7 @@ npx create-better-t-stack my-app --frontend none --backend hono --api trpc --dat
- **ORM 'none'**: Can be used when you want to handle database operations manually or use a different ORM.
- **Runtime 'none'**: Only available with Convex backend or when backend is 'none'.
- **Cloudflare Workers runtime**: Only compatible with Hono backend, Drizzle ORM (or no ORM), and SQLite database (with D1 setup). Not compatible with MongoDB.
-- **Addons 'none'**: Skips all addons (PWA, Tauri, Starlight, Biome, Husky, Turborepo).
+- **Addons 'none'**: Skips all addons (PWA, Tauri, Starlight, Biome, Husky, Turborepo, T3 Env).
- **Examples 'none'**: Skips all example implementations (todo, AI chat).
- **SvelteKit, Nuxt, and SolidJS** frontends are only compatible with oRPC API layer
- **PWA support** requires React with TanStack Router, React Router, or SolidJS
diff --git a/apps/cli/package.json b/apps/cli/package.json
index 9579d8a3..c9e4997f 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -36,7 +36,8 @@
"shadcn",
"pwa",
"tauri",
- "biome"
+ "biome",
+ "t3env"
],
"repository": {
"type": "git",
diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts
index e679d3e2..1505d5a4 100644
--- a/apps/cli/src/constants.ts
+++ b/apps/cli/src/constants.ts
@@ -14,7 +14,7 @@ export const DEFAULT_CONFIG_BASE = {
database: "sqlite",
orm: "drizzle",
auth: "better-auth",
- addons: ["turborepo"],
+ addons: ["turborepo", "t3env"],
examples: [],
git: true,
install: true,
@@ -156,6 +156,11 @@ export const dependencyVersionMap = {
nitropack: "^2.12.4",
dotenv: "^17.2.1",
+
+ zod: "^4.0.14",
+ "@t3-oss/env-core": "^0.13.8",
+ "@t3-oss/env-nextjs": "^0.13.8",
+ "@t3-oss/env-nuxt": "^0.13.8",
} as const;
export type AvailableDependencies = keyof typeof dependencyVersionMap;
@@ -171,5 +176,6 @@ export const ADDON_COMPATIBILITY: Record = {
ruler: [],
oxlint: [],
fumadocs: [],
+ t3env: [],
none: [],
} as const;
diff --git a/apps/cli/src/helpers/addons/addons-setup.ts b/apps/cli/src/helpers/addons/addons-setup.ts
index 819c567a..49e39bae 100644
--- a/apps/cli/src/helpers/addons/addons-setup.ts
+++ b/apps/cli/src/helpers/addons/addons-setup.ts
@@ -9,6 +9,7 @@ import { getPackageExecutionCommand } from "../../utils/package-runner";
import { setupFumadocs } from "./fumadocs-setup";
import { setupVibeRules } from "./ruler-setup";
import { setupStarlight } from "./starlight-setup";
+import { setupT3Env } from "./t3env-setup";
import { setupTauri } from "./tauri-setup";
import { setupUltracite } from "./ultracite-setup";
import { addPwaToViteConfig } from "./vite-pwa-setup";
@@ -58,6 +59,11 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
) {
await setupTauri(config);
}
+
+ if (addons.includes("t3env")) {
+ await setupT3Env(config);
+ }
+
const hasUltracite = addons.includes("ultracite");
const hasBiome = addons.includes("biome");
const hasHusky = addons.includes("husky");
diff --git a/apps/cli/src/helpers/addons/t3env-setup.ts b/apps/cli/src/helpers/addons/t3env-setup.ts
new file mode 100644
index 00000000..f83fa5c1
--- /dev/null
+++ b/apps/cli/src/helpers/addons/t3env-setup.ts
@@ -0,0 +1,64 @@
+import path from "node:path";
+import { spinner } from "@clack/prompts";
+import { consola } from "consola";
+import fs from "fs-extra";
+import pc from "picocolors";
+import type { ProjectConfig } from "../../types";
+import { addPackageDependency } from "../../utils/add-package-deps";
+
+export async function setupT3Env(config: ProjectConfig) {
+ const { frontend, backend, projectDir } = config;
+ const s = spinner();
+
+ // T3 Env requires a backend to be present
+ if (backend === "none") {
+ s.stop(pc.yellow("T3 Env requires a backend to be configured"));
+ return;
+ }
+
+ try {
+ s.start("Setting up T3 Env for type-safe environment variables...");
+
+ // Add dependencies to server
+ const serverDir = path.join(projectDir, "apps/server");
+ if (await fs.pathExists(serverDir)) {
+ await addPackageDependency({
+ dependencies: ["@t3-oss/env-core", "zod"],
+ projectDir: serverDir,
+ });
+ }
+
+ // Add framework-specific dependencies to web app
+ const webDir = path.join(projectDir, "apps/web");
+ if (await fs.pathExists(webDir)) {
+ const hasNext = frontend.includes("next");
+ const hasNuxt = frontend.includes("nuxt");
+
+ if (hasNext) {
+ await addPackageDependency({
+ dependencies: ["@t3-oss/env-nextjs"],
+ projectDir: webDir,
+ });
+ } else if (hasNuxt) {
+ // For Nuxt, we'll use the core package
+ await addPackageDependency({
+ dependencies: ["@t3-oss/env-nuxt", "zod"],
+ projectDir: webDir,
+ });
+ } else {
+ // For other frameworks, use the core package
+ await addPackageDependency({
+ dependencies: ["@t3-oss/env-core", "zod"],
+ projectDir: webDir,
+ });
+ }
+ }
+
+ s.stop("T3 Env configured successfully!");
+ } catch (error) {
+ s.stop(pc.red("Failed to set up T3 Env"));
+ if (error instanceof Error) {
+ consola.error(pc.red(error.message));
+ }
+ }
+}
diff --git a/apps/cli/src/helpers/core/create-readme.ts b/apps/cli/src/helpers/core/create-readme.ts
index 5cb8430a..0de04078 100644
--- a/apps/cli/src/helpers/core/create-readme.ts
+++ b/apps/cli/src/helpers/core/create-readme.ts
@@ -480,6 +480,8 @@ function generateFeaturesList(
addonsList.push("- **Starlight** - Documentation site with Astro");
} else if (addon === "turborepo") {
addonsList.push("- **Turborepo** - Optimized monorepo build system");
+ } else if (addon === "t3env") {
+ addonsList.push("- **T3 Env** - Type-safe environment variables");
}
}
diff --git a/apps/cli/src/prompts/addons.ts b/apps/cli/src/prompts/addons.ts
index 24b2aebf..c1548dd4 100644
--- a/apps/cli/src/prompts/addons.ts
+++ b/apps/cli/src/prompts/addons.ts
@@ -58,6 +58,10 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } {
label = "Fumadocs";
hint = "Build excellent documentation site";
break;
+ case "t3env":
+ label = "T3 Env";
+ hint = "Type-safe environment variables";
+ break;
default:
label = addon;
hint = `Add ${addon}`;
@@ -69,7 +73,7 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } {
const ADDON_GROUPS = {
Documentation: ["starlight", "fumadocs"],
Linting: ["biome", "oxlint", "ultracite"],
- Other: ["ruler", "turborepo", "pwa", "tauri", "husky"],
+ Other: ["ruler", "turborepo", "pwa", "tauri", "husky", "t3env"],
};
export async function getAddonsChoice(
diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts
index 62043688..c7e21d1a 100644
--- a/apps/cli/src/types.ts
+++ b/apps/cli/src/types.ts
@@ -48,6 +48,7 @@ export const AddonsSchema = z
"fumadocs",
"ultracite",
"oxlint",
+ "t3env",
"none",
])
.describe("Additional addons");
diff --git a/apps/cli/templates/addons/t3env/apps/server/next/next.config.ts b/apps/cli/templates/addons/t3env/apps/server/next/next.config.ts
new file mode 100644
index 00000000..077e3c62
--- /dev/null
+++ b/apps/cli/templates/addons/t3env/apps/server/next/next.config.ts
@@ -0,0 +1 @@
+import "@/env/server";
\ No newline at end of file
diff --git a/apps/cli/templates/addons/t3env/apps/server/next/src/env/server.ts.hbs b/apps/cli/templates/addons/t3env/apps/server/next/src/env/server.ts.hbs
new file mode 100644
index 00000000..664c71a7
--- /dev/null
+++ b/apps/cli/templates/addons/t3env/apps/server/next/src/env/server.ts.hbs
@@ -0,0 +1,15 @@
+import { createEnv } from "@t3-oss/env-nextjs";
+import { z } from "zod";
+
+export const env = createEnv({
+ server: {
+ DATABASE_URL: z.string().url(),
+ BETTER_AUTH_SECRET: z.string().min(1),
+ BETTER_AUTH_URL: z.string().url(),
+ CORS_ORIGIN: z.string().url(),
+ {{#if (includes examples 'ai')}}
+ GOOGLE_GENERATIVE_AI_API_KEY: z.string().min(1),
+ {{/if}}
+ },
+ experimental__runtimeEnv: process.env,
+});
\ No newline at end of file
diff --git a/apps/cli/templates/addons/t3env/apps/web/next/src/env/client.ts.hbs b/apps/cli/templates/addons/t3env/apps/web/next/src/env/client.ts.hbs
new file mode 100644
index 00000000..93523e35
--- /dev/null
+++ b/apps/cli/templates/addons/t3env/apps/web/next/src/env/client.ts.hbs
@@ -0,0 +1,17 @@
+import { createEnv } from "@t3-oss/env-nextjs";
+import { z } from "zod";
+
+export const env = createEnv({
+ client: {
+ NEXT_PUBLIC_SERVER_URL: z.string().url(),
+ {{#if (eq backend 'convex')}}
+ NEXT_PUBLIC_CONVEX_URL: z.string().url(),
+ {{/if}}
+ },
+ runtimeEnv: {
+ NEXT_PUBLIC_SERVER_URL: process.env.NEXT_PUBLIC_SERVER_URL,
+ {{#if (eq backend 'convex')}}
+ NEXT_PUBLIC_CONVEX_URL: process.env.NEXT_PUBLIC_CONVEX_URL,
+ {{/if}}
+ },
+});
\ No newline at end of file
diff --git a/apps/cli/templates/addons/t3env/apps/web/next/src/next.config.ts.hbs b/apps/cli/templates/addons/t3env/apps/web/next/src/next.config.ts.hbs
new file mode 100644
index 00000000..1a77485f
--- /dev/null
+++ b/apps/cli/templates/addons/t3env/apps/web/next/src/next.config.ts.hbs
@@ -0,0 +1 @@
+import "@/env/client";
\ No newline at end of file
diff --git a/apps/cli/templates/addons/t3env/apps/web/nuxt/src/env/client.ts.hbs b/apps/cli/templates/addons/t3env/apps/web/nuxt/src/env/client.ts.hbs
new file mode 100644
index 00000000..443028d7
--- /dev/null
+++ b/apps/cli/templates/addons/t3env/apps/web/nuxt/src/env/client.ts.hbs
@@ -0,0 +1,11 @@
+import { createEnv } from "@t3-oss/env-nuxt";
+import { z } from "zod";
+
+export const env = createEnv({
+ client: {
+ NUXT_PUBLIC_SERVER_URL: z.string().url(),
+ {{#if (eq backend 'convex')}}
+ NUXT_PUBLIC_CONVEX_URL: z.string().url(),
+ {{/if}}
+ }
+});
\ No newline at end of file
diff --git a/apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs b/apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs
index 03447fbc..a8873d3b 100644
--- a/apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs
+++ b/apps/cli/templates/frontend/nuxt/nuxt.config.ts.hbs
@@ -1,3 +1,5 @@
+import "./env";
+
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: 'latest',
diff --git a/apps/web/content/docs/cli/options.mdx b/apps/web/content/docs/cli/options.mdx
index a32c598b..eceb3a44 100644
--- a/apps/web/content/docs/cli/options.mdx
+++ b/apps/web/content/docs/cli/options.mdx
@@ -253,6 +253,7 @@ Additional features to include:
- `ultracite`: Ultracite configuration
- `oxlint`: Oxlint fast linting
- `ruler`: Centralize your AI rules with Ruler
+- `t3env`: Type-safe environment variables
```bash
create-better-t-stack --addons pwa biome husky
diff --git a/apps/web/content/docs/index.mdx b/apps/web/content/docs/index.mdx
index af4b1ca3..80e0b2f5 100644
--- a/apps/web/content/docs/index.mdx
+++ b/apps/web/content/docs/index.mdx
@@ -114,7 +114,7 @@ Skip prompts and use the default stack:
--database sqlite \
--orm drizzle \
--auth better-auth \
- --addons turborepo
+ --addons turborepo t3env
```
diff --git a/apps/web/public/icon/t3env.svg b/apps/web/public/icon/t3env.svg
new file mode 100644
index 00000000..a7f9d598
--- /dev/null
+++ b/apps/web/public/icon/t3env.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/apps/web/src/app/(home)/_components/npm-package.tsx b/apps/web/src/app/(home)/_components/npm-package.tsx
index 246361e7..36904e8e 100644
--- a/apps/web/src/app/(home)/_components/npm-package.tsx
+++ b/apps/web/src/app/(home)/_components/npm-package.tsx
@@ -31,4 +31,4 @@ const NpmPackage = () => {
);
};
-export default NpmPackage;
+export default NpmPackage;
\ No newline at end of file
diff --git a/apps/web/src/app/(home)/analytics/_components/types.ts b/apps/web/src/app/(home)/analytics/_components/types.ts
index 9428cc1b..eaa92068 100644
--- a/apps/web/src/app/(home)/analytics/_components/types.ts
+++ b/apps/web/src/app/(home)/analytics/_components/types.ts
@@ -369,13 +369,17 @@ export const addonsConfig = {
label: "Starlight",
color: "hsl(var(--chart-5))",
},
+ t3env: {
+ label: "T3 Env",
+ color: "hsl(var(--chart-6))",
+ },
turborepo: {
label: "Turborepo",
- color: "hsl(var(--chart-6))",
+ color: "hsl(var(--chart-7))",
},
none: {
label: "No Addons",
- color: "hsl(var(--chart-7))",
+ color: "hsl(var(--chart-8))",
},
} satisfies ChartConfig;
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index 925a5382..d71fd6f5 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -35,6 +35,7 @@ export const metadata: Metadata = {
"hono",
"elysia",
"turborepo",
+ "t3env",
"trpc",
"orpc",
"turso",
diff --git a/apps/web/src/lib/constant.ts b/apps/web/src/lib/constant.ts
index 5f0ec837..e1b15edf 100644
--- a/apps/web/src/lib/constant.ts
+++ b/apps/web/src/lib/constant.ts
@@ -526,6 +526,14 @@ export const TECH_OPTIONS: Record<
color: "from-violet-500 to-violet-700",
default: false,
},
+ {
+ id: "t3env",
+ name: "T3 Env",
+ description: "Type-safe environment variables",
+ icon: "/icon/t3env.svg",
+ color: "from-yellow-500 to-yellow-700",
+ default: true,
+ },
{
id: "turborepo",
name: "Turborepo",
@@ -605,7 +613,7 @@ export const PRESET_TEMPLATES = [
dbSetup: "none",
auth: "better-auth",
packageManager: "bun",
- addons: ["turborepo"],
+ addons: ["turborepo", "t3env"],
examples: [],
git: "true",
install: "true",
@@ -630,7 +638,7 @@ export const PRESET_TEMPLATES = [
dbSetup: "none",
auth: "none",
packageManager: "bun",
- addons: ["turborepo"],
+ addons: ["turborepo", "t3env"],
examples: ["todo"],
git: "true",
install: "true",
@@ -655,7 +663,7 @@ export const PRESET_TEMPLATES = [
dbSetup: "none",
auth: "better-auth",
packageManager: "bun",
- addons: ["turborepo"],
+ addons: ["turborepo", "t3env"],
examples: [],
git: "true",
install: "true",
@@ -680,7 +688,7 @@ export const PRESET_TEMPLATES = [
dbSetup: "none",
auth: "better-auth",
packageManager: "bun",
- addons: ["turborepo"],
+ addons: ["turborepo", "t3env"],
examples: [],
git: "true",
install: "true",
@@ -705,7 +713,15 @@ export const PRESET_TEMPLATES = [
dbSetup: "turso",
auth: "better-auth",
packageManager: "bun",
- addons: ["pwa", "biome", "husky", "tauri", "starlight", "turborepo"],
+ addons: [
+ "pwa",
+ "biome",
+ "husky",
+ "tauri",
+ "starlight",
+ "turborepo",
+ "t3env",
+ ],
examples: ["todo", "ai"],
git: "true",
install: "true",
@@ -749,7 +765,7 @@ export const DEFAULT_STACK: StackState = {
dbSetup: "none",
auth: "better-auth",
packageManager: "bun",
- addons: ["turborepo"],
+ addons: ["turborepo", "t3env"],
examples: [],
git: "true",
install: "true",
diff --git a/apps/web/src/lib/stack-utils.ts b/apps/web/src/lib/stack-utils.ts
index a5d5b8e9..997d4969 100644
--- a/apps/web/src/lib/stack-utils.ts
+++ b/apps/web/src/lib/stack-utils.ts
@@ -111,6 +111,7 @@ export function generateStackCommand(stack: StackState): string {
"fumadocs",
"oxlint",
"ruler",
+ "t3env",
].includes(addon),
)
.join(" ") || "none"
diff --git a/demo.gif b/demo.gif
index 65a7f9f9..618928ab 100644
Binary files a/demo.gif and b/demo.gif differ