ProvenPin Install

Zod 4 + dotenv typed environment config: working versions (tested 2026-10-15)

Load .env files with dotenv and validate process.env with Zod 4 at startup: coercion, defaults, enums, URLs and readable errors.

passed3 passed · 0 failed2026-10-15 13:34 UTC
Status
passed
Last passed
2026-10-15 13:34 UTC
Tests
3 / 3
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
zod@4.6.7dotenv@18.0.8tsx@4.23.15@types/node@24.19.2
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38742087302

The code

Exactly the files that passed. Your agent can pull them with get_recipe("zod-env-dotenv-config") (install), or see them on GitHub.

package.json
{
  "name": "zod-env-dotenv-config",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "zod": "4.6.7",
    "dotenv": "18.0.8"
  },
  "devDependencies": {
    "tsx": "4.23.15",
    "@types/node": "24.19.2"
  }
}
src/env.ts
import { config } from "dotenv";
import { z } from "zod";

const EnvSchema = z.object({
  NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
  PORT: z.coerce.number().int().min(1).max(65535).default(3000),
  DATABASE_URL: z.url(),
  FEATURE_SIGNUPS: z.stringbool().default(false),
  API_KEY: z.string().min(16),
});
export type Env = z.infer<typeof EnvSchema>;

export function loadEnv(opts: { path?: string; source?: NodeJS.ProcessEnv } = {}): Env {
  const source = { ...(opts.source ?? process.env) };
  if (opts.path) config({ path: opts.path, processEnv: source as Record<string, string>, quiet: true });
  const parsed = EnvSchema.safeParse(source);
  if (!parsed.success) throw new Error("Invalid environment:\n" + z.prettifyError(parsed.error));
  return parsed.data;
}
test/env.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { loadEnv } from "../src/env.ts";

const base = { DATABASE_URL: "postgres://u:p@localhost:5432/db", API_KEY: "0123456789abcdef" };

test("coerces and defaults", () => {
  const env = loadEnv({ source: { ...base, PORT: "8080", FEATURE_SIGNUPS: "true" } });
  assert.equal(env.PORT, 8080);
  assert.equal(env.FEATURE_SIGNUPS, true);
  assert.equal(env.NODE_ENV, "development");
});

test("reads a .env file without overriding real env", () => {
  const dir = mkdtempSync(join(tmpdir(), "env-"));
  writeFileSync(join(dir, ".env"), `DATABASE_URL=${base.DATABASE_URL}\nAPI_KEY=fromfile-1234567890\nPORT=9000\n`);
  const env = loadEnv({ path: join(dir, ".env"), source: { PORT: "7000" } });
  assert.equal(env.API_KEY, "fromfile-1234567890");
  assert.equal(env.PORT, 7000);
});

test("readable error listing every bad key", () => {
  assert.throws(() => loadEnv({ source: { DATABASE_URL: "nope", API_KEY: "short", PORT: "0" } }), (e: Error) =>
    /DATABASE_URL/.test(e.message) && /API_KEY/.test(e.message) && /PORT/.test(e.message));
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.