Comparison

Typespun vs t3-env, envalid and Zod

Which tool to use for typed configuration in TypeScript, and the cases where Typespun is the wrong choice.

Configuration is a well-served corner of the TypeScript ecosystem, and for many projects a simpler tool is the right answer. This page tells you which one to pick, and where Typespun is the wrong choice.

The short answer

You want…Use
A TypeScript interface as the single source of truth, a reviewable generated loader, an explicit precedence chain, or secret-aware errorsTypespun
The smallest thing that validates environment variablesZod inline, or envalid
A client/server variable split, especially in Next.js or Nuxtt3-env
Hierarchical config files with per-environment overlaysnode-config or convict
Async secret providers, custom transforms, or dynamic schemasZod, Valibot, or ArkType

Capability matrix

CapabilityTypespunZodt3-envenvalidconvictnode-config
Where Typespun is different
A TypeScript interface is the source of truth *YesNoNoNoNoNo
Generated loader you commit, with a CI drift checkYesNoNoNoNoNo
Env vars, .env files and defaults in one documented order *YesNoNoNoPartlyPartly
Secret values kept out of error output *YesNoNoNoPartlyNo
Table stakes
Validates every field at startup *YesPartlyYesYesYesNo
Reports every problem at onceYesYesYesYesYesNo
Where the others win
No build step or generated codeNoYesYesYesYesYes
Custom transforms and refinementsNoYesYesYesYesNo
Client / server variable split (Next.js, Nuxt)NoNoYesNoNoNo

Yes, partly and no, judged against each project's own documentation on 2026-09-22. A missing tick means the documentation does not describe the capability, not that it is impossible. Swipe the table sideways to see every tool.

  • * A TypeScript interface is the source of truth. The others derive the type from a schema, or leave it loose.
  • * Env vars, .env files and defaults in one documented order. convict and node-config layer files, env vars and arguments, but have no dotenv layer. envalid needs dotenv called separately; t3-env takes the env object you pass.
  • * Secret values kept out of error output. convict masks a sensitive field when you print the config, not in validation errors.
  • * Validates every field at startup. With Zod you write and call the parse yourself.

What Typespun does differently

Most configuration libraries ask you to write a schema, then derive a type from it. Typespun inverts that: the TypeScript declaration is the source of truth, and the loader is generated from it.

src/config.ts
/** @typespun */
export interface AppConfig {
  server: { host: string; port: number };
  mode: 'development' | 'production';
  /** @secret */
  apiToken: string;
}
typespun generate
src/index.ts
import { loadConfig } from './generated/typespun.js';

const config = loadConfig(); // fully typed, validated at startup

No schema to keep in sync. No type inference gymnastics. The interface is the contract, and the generated module is a reviewable artifact you commit.

Three capabilities follow from that, and each is uncommon in this category:

A documented precedence chain across five source kinds

Most environment libraries validate one source. Typespun resolves every leaf independently, highest wins:

  1. typed overrides
  2. explicit source, or ambient process.env when omitted
  3. dotenv files, later entries beating earlier ones
  4. compiled JSON/YAML defaults
  5. inline declaration defaults

It selects the highest-precedence defined candidate before coercion, so an invalid lower-precedence source cannot break a valid higher-precedence one. See source precedence.

Secret-aware diagnostics

A leaf marked @secret omits its received value and type-specific detail — such as the allowed enum members — from Typespun's own error output. Errors still tell you which field failed and which environment key it came from.

This is redaction in diagnostics: not encryption, not a secret store, and it cannot scrub your application's logs. See validation and redaction.

A deterministic artifact that CI can check

Generation is byte-stable for the same declaration, settings, defaults, and generator version. typespun check never writes and exits nonzero when output is stale, so configuration drift fails the build instead of production. See generated code.

The landscape

For context on the category Typespun is entering. Adoption figures were read from the npm and GitHub APIs on 2026-09-22.

PackageWeekly downloadsStarsShape
zod213.9M43,984General schema validation; commonly pointed at process.env.
dotenv131.4M20,538Loads .env into process.env. No validation.
@t3-oss/env-core3.06M4,004Env validation with a server/client split. Accepts any Standard Schema validator.
node-config1.23MHierarchical config files per NODE_ENV.
convict883.5k2,374Schema plus config files and env, with a nested format.
envalid509.0k1,592Focused env validation with its own validator vocabulary.
typia241.9k5,911Not configuration — but the same technique: TypeScript types compiled into runtime code.

Typespun is pre-release and new, with adoption far below every row above. Pin your versions and review generated diffs during early adoption.

When to choose something else

Plain Zod — when you already depend on Zod and your needs stop at "parse process.env, fail loudly." Three lines and a schema beat any tool that adds a build step. One sharp edge t3-env documents well: applying transforms makes the inferred type describe the transformed value while process.env still holds the original string.

t3-env — when environment variables must be split between server and client, the case Next.js and Nuxt force on you, or when you want to keep your existing validator. It accepts any Standard Schema implementation, so you are not locked into one library.

envalid — when you want focused environment validation with a small built-in validator vocabulary and good error messages, without adopting a general schema library.

node-config or convict — when your configuration genuinely lives in layered files per environment and that layering matters more than static typing.

A runtime schema library — when you need anything Typespun deliberately does not do: custom transforms, refinements, async or dynamic schemas, provider plugins, or validation of data that is not configuration.

What Typespun does not do

The limits are the most useful thing to know up front:

  • No async or plugin sources. No AWS Secrets Manager, Vault, or custom providers. Fetch the value yourself and pass a typed override.
  • No runtime JSON/YAML loading. Defaults files are validated and embedded at generation time.
  • A narrow type model. Strings, finite numbers, booleans, string enums and literal unions, arrays of those three primitives, and nested objects. Nullable unions, tuples, records and index signatures, dates, maps, sets, methods, computed fields, and recursive shapes all fail generation.
  • No transforms or refinements.
  • One configuration root per project.
  • A build step and committed output. If generated code in version control is a dealbreaker, Typespun is the wrong shape for you.
  • Two packages to install, where every alternative here needs one.

Is the codegen step worth it?

It depends on how much the duplication actually costs you. If your configuration is eight flat environment variables, a Zod schema is simpler and you should use that.

The trade becomes worth making when the configuration contract is large enough that keeping a type, a parser, and a schema in sync is real work — and when having that contract visible in a diff and enforced in CI has value to your team.