| | |

Zod 02 🛡️ Installation — npm install zod & Strict Mode Requirements

⏱️ Estimated read: 9 min | |

Zod installs with a single command and has no runtime dependencies. The installation itself is trivial; the configuration that matters is TypeScript’s strict mode. Zod’s type inference depends on strictNullChecks and noImplicitAny to distinguish undefined from a value and to prevent any from bypassing validation . Without strict mode, a required field in a schema may be inferred as optional, and the compiler will not catch the error .

This chapter covers the install command, the minimum TypeScript version, the exact tsconfig.json settings Zod requires, and the failure modes that appear when strict mode is disabled. The sections that follow walk through the installation, the strict mode requirement, and the migration path for projects that cannot enable full strict mode immediately.

Key point: npm install zod is the entire installation, but Zod’s type inference is only correct when TypeScript’s strict mode (or at minimum strictNullChecks and noImplicitAny) is enabled in tsconfig.json .


Why strict mode requirements exist

The optional-field inference problem. Zod’s default is that every field in a z.object is required. But when strictNullChecks is off, TypeScript treats undefined as assignable to every type, so the inferred type of a required field becomes string | undefined even though the schema says z.string() . The runtime validator still rejects undefined, but the static type says the field might be missing. Code that trusts the type will pass undefined through without a compiler error, and the failure surfaces at runtime where Zod cannot help—inside the application logic that assumed the field existed.

The any bypass problem. Without noImplicitAny, a function parameter that receives parsed data can be implicitly any. Zod’s parse returns the inferred type, but if that return value flows into an any context, the type safety is erased at the next function call . Strict mode closes this path by requiring explicit types at every boundary.

The version floor. Zod is tested against TypeScript 5.5 and later . Older versions may work but are not officially supported. The version floor and the strict mode requirement are separate constraints: TypeScript 5.5 without strict mode still produces incorrect inference, and strict mode on TypeScript 4.5 may work but is untested against current Zod releases.


a. Installation and version verification

Zod installs as a regular dependency, not a dev dependency, because validation runs at runtime in both client and server code.

# Install Zod
npm install zod

# Verify the installed version
npm ls zod

Zod has no peer dependencies and no runtime dependencies. It works in Node.js, Deno, Bun, and browsers without additional configuration . For Deno, the import path is npm:zod@latest; for Bun, bun add zod is the equivalent command .

// Verify the installation with a minimal schema
import { z } from "zod";

const Schema = z.object({ name: z.string() });

const result = Schema.safeParse({ name: "Ana" });
if (result.success) {
  console.log(result.data.name); // "Ana"
}

b. The tsconfig.json strict mode requirement

Zod’s official installation documentation states: “You must enable strict mode in your tsconfig.json. This is a best practice for all TypeScript projects” . The strict flag enables a family of checks, and two of them are load-bearing for Zod: strictNullChecks and noImplicitAny.

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler"
  }
}

If enabling full strict mode is not possible, the minimum acceptable configuration is the two checks that Zod depends on :

{
  "compilerOptions": {
    "strictNullChecks": true,
    "noImplicitAny": true
  }
}

The strict: true flag sets both of these to true along with other checks. An explicit "strictNullChecks": false anywhere in the configuration overrides the strict flag for that one check, and Zod’s optional-field inference breaks even when strict: true is present . Check for explicit overrides before assuming strict mode is active.


c. Failure modes without strict mode

The difference is observable in the inferred type of a simple schema. With strict mode enabled, z.string() infers as string. With strictNullChecks disabled, it infers as string | undefined .

import { z } from "zod";

const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

// With strict mode:    { name: string; email: string }
// Without strict mode: { name: string | undefined; email: string | undefined }

The consequences compound at call sites. A function that accepts User and calls user.name.toUpperCase() compiles without error in both configurations, but the non-strict inferred type permits undefined to reach the call, causing a runtime TypeError that Zod’s validation already prevented at the boundary . The validator worked; the type system lied about what it returned.

The same problem affects z.optional(). With strict mode, z.string().optional() infers as string | undefined. Without strictNullChecks, it infers as string, hiding the possibility that the value is missing .


Complete Example Session

// ============================================
// PART 1: INSTALL AND VERIFY VERSION
// ============================================
// npm install zod
// npm ls zod
// Expected: zod@4.x.x (or current release)

// ============================================
// PART 2: TSCONFIG WITH STRICT MODE
// ============================================
// tsconfig.json:
// {
//   "compilerOptions": {
//     "strict": true,
//     "target": "ES2022",
//     "module": "ESNext"
//   }
// }

// ============================================
// PART 3: SCHEMA WITH REQUIRED AND OPTIONAL FIELDS
// ============================================
import { z } from "zod";

const ProfileSchema = z.object({
  name: z.string(),                    // required
  bio: z.string().optional(),          // string | undefined
  age: z.number().int().positive(),    // required
});

// ============================================
// PART 4: THE INFERRED TYPE (STRICT MODE ON)
// ============================================
type Profile = z.infer<typeof ProfileSchema>;
// {
//   name: string;           // NOT string | undefined
//   bio?: string;           // string | undefined
//   age: number;
// }

// ============================================
// PART 5: THE FAILURE WITHOUT STRICT MODE
// ============================================
// With strictNullChecks disabled:
// type Profile = {
//   name: string | undefined;   // WRONG
//   bio?: string;
//   age: number | undefined;    // WRONG
// }
//
// function greet(p: Profile) {
//   return p.name.toUpperCase(); // compiles, may throw at runtime
// }

// ============================================
// PART 6: PARSING WITH CORRECT TYPES
// ============================================
const result = ProfileSchema.safeParse({
  name: "Ana",
  age: 30,
});

if (result.success) {
  // result.data.name is string, not string | undefined
  console.log(result.data.name.toUpperCase());
  // result.data.bio is string | undefined, handled explicitly
  console.log(result.data.bio ?? "No bio");
}

The parts covered installation, the tsconfig.json configuration, a schema with required and optional fields, the correct inferred type under strict mode, the incorrect inference without it, and a parse call that relies on the correct type.


Quick Reference

Installation

ItemValue
Commandnpm install zod
Dependency typeRuntime (not dev)
Runtime dependenciesNone
Deno importnpm:zod@latest
Bun commandbun add zod

TypeScript Requirements

RequirementMinimumRecommended
TypeScript version4.5+5.5+
strict flagtruetrue
strictNullCheckstruetrue
noImplicitAnytruetrue

Inferred Type Comparison

SchemaStrict Mode OnStrict Mode Off
z.string()stringstring | undefined
z.string().optional()string | undefinedstring
z.number()numbernumber | undefined
z.object({ name: z.string() }){ name: string }{ name: string | undefined }

Best Practices

✅ Do This:

// Enable full strict mode
{ "compilerOptions": { "strict": true } }                    // ✅
// Verify inferred types match schema expectations
type User = z.infer<typeof UserSchema>;                      // ✅
// Hover in editor: name should be string, not string | undefined
# Verify installed version
npm ls zod                                                   // ✅

❌ Don’t Do This:

// Disable strictNullChecks explicitly
{ "compilerOptions": { "strict": true, "strictNullChecks": false } } // ❌
// Trust the type without checking inference
function greet(u: User) { return u.name.toUpperCase(); }    // ❌ (if strict off)
# Install as dev dependency
npm install -D zod                                           // ❌ (runtime validation)

Common Pitfalls

PitfallWhy It HappensFix
All fields inferred as optionalstrictNullChecks disabledEnable strict: true
any bypasses validationnoImplicitAny disabledEnable strict: true
Strict flag set but inference still wrongExplicit strictNullChecks: false overrideRemove the override
Older TypeScript versionProject scaffolded before 5.5Upgrade TypeScript
Zod in devDependenciesConfused with type-only librariesMove to dependencies

Real-World Examples

1. Minimal strict tsconfig

{ "compilerOptions": { "strict": true } }

2. Minimum Zod-compatible config

{ "compilerOptions": { "strictNullChecks": true, "noImplicitAny": true } }

3. Verify inferred type in editor

const _check: string = result.data.name; // error if string | undefined

4. Deno import

import * as z from "npm:zod@latest";

5. Bun install

bun add zod

6. Check for strictNullChecks override

grep -r "strictNullChecks" tsconfig*.json

7. Optional field with strict mode

z.string().optional(); // correctly infers string | undefined

8. Required field with strict mode

z.string(); // correctly infers string, not string | undefined

9. TypeScript version check

npx tsc --version

10. Parse with typed result

const data: z.infer<typeof Schema> = Schema.parse(input);

Visual

Strict Mode and Zod Inference

┌─────────────────────────────────────────────────────────┐
│  STRICT MODE ON                                         │
│                                                         │
│  z.object({ name: z.string() })                         │
│         │                                               │
│         ▼                                               │
│  z.infer<typeof Schema>                                 │
│         │                                               │
│         ▼                                               │
│  { name: string }                                       │
│         │                                               │
│         ▼                                               │
│  Compiler knows name exists                             │
│  Runtime validator agrees                               │
│  No gap                                                 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│  STRICT MODE OFF                                        │
│                                                         │
│  z.object({ name: z.string() })                         │
│         │                                               │
│         ▼                                               │
│  z.infer<typeof Schema>                                 │
│         │                                               │
│         ▼                                               │
│  { name: string | undefined }                           │
│         │                                               │
│         ▼                                               │
│  Compiler permits undefined                             │
│  Runtime validator rejects undefined                    │
│  Gap: type says maybe, runtime says no                  │
│  Runtime crash possible                                 │
└─────────────────────────────────────────────────────────┘

The Two Required Checks

┌─────────────────────────────────────────────────────────┐
│  STRICT: true ENABLES                                   │
│                                                         │
│  strictNullChecks                                       │
│  ┌──────────────────────────────────────────┐           │
│  │ Distinguishes undefined from value       │           │
│  │ Required field → string                  │           │
│  │ Optional field → string | undefined      │           │
│  └──────────────────────────────────────────┘           │
│                                                         │
│  noImplicitAny                                          │
│  ┌──────────────────────────────────────────┐           │
│  │ Rejects implicit any at boundaries       │           │
│  │ Parsed data flows with types intact      │           │
│  └──────────────────────────────────────────┘           │
│                                                         │
│  Both required for Zod's inference to be correct        │
└─────────────────────────────────────────────────────────┘

Summary

ItemValue
Install commandnpm install zod
Dependency typedependencies
TypeScript minimum4.5+, tested on 5.5+
Required tsconfig"strict": true
Minimum checksstrictNullChecks and noImplicitAny
Without strict: required fieldstring | undefined (wrong)
Without strict: optional fieldstring (wrong)
Override warningExplicit strictNullChecks: false breaks inference

Key takeaways:

  • Installation is one command with no runtime dependencies. npm install zod is sufficient; there is no peer dependency to configure and no build step .
  • Strict mode is not optional for correct inference. Zod’s documentation states it as a requirement, and the failure mode is silent: types infer as string | undefined while the runtime rejects undefined .
  • strictNullChecks and noImplicitAny are the two checks that matter. Full strict: true enables both, but an explicit strictNullChecks: false overrides the flag and breaks inference anyway .
  • The gap is between static type and runtime validator. With strict mode off, the type permits values the validator rejects. Code that trusts the type will crash where Zod already did its job.
  • TypeScript 5.5+ is the tested floor. Older versions may work but are unsupported; upgrade before debugging inference issues .

Remember: Zod’s installation is two steps: install the package and enable strict mode in TypeScript. The first step takes seconds. The second step is the one that determines whether the type Zod infers is the type your code actually gets. Without strictNullChecks, every required field becomes maybe-undefined in the type system while the runtime still rejects undefined. The validator and the compiler disagree, and the compiler wins by default because it runs first. Enable strict mode before writing the first schema.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!