Zod 02 🛡️ Installation — npm install zod & Strict Mode Requirements
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
| Item | Value |
|---|---|
| Command | npm install zod |
| Dependency type | Runtime (not dev) |
| Runtime dependencies | None |
| Deno import | npm:zod@latest |
| Bun command | bun add zod |
TypeScript Requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| TypeScript version | 4.5+ | 5.5+ |
strict flag | true | true |
strictNullChecks | true | true |
noImplicitAny | true | true |
Inferred Type Comparison
| Schema | Strict Mode On | Strict Mode Off |
|---|---|---|
z.string() | string | string | undefined |
z.string().optional() | string | undefined | string |
z.number() | number | number | 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
| Pitfall | Why It Happens | Fix |
|---|---|---|
| All fields inferred as optional | strictNullChecks disabled | Enable strict: true |
any bypasses validation | noImplicitAny disabled | Enable strict: true |
| Strict flag set but inference still wrong | Explicit strictNullChecks: false override | Remove the override |
| Older TypeScript version | Project scaffolded before 5.5 | Upgrade TypeScript |
| Zod in devDependencies | Confused with type-only libraries | Move 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
| Item | Value |
|---|---|
| Install command | npm install zod |
| Dependency type | dependencies |
| TypeScript minimum | 4.5+, tested on 5.5+ |
| Required tsconfig | "strict": true |
| Minimum checks | strictNullChecks and noImplicitAny |
| Without strict: required field | string | undefined (wrong) |
| Without strict: optional field | string (wrong) |
| Override warning | Explicit strictNullChecks: false breaks inference |
Key takeaways:
- Installation is one command with no runtime dependencies.
npm install zodis 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 | undefinedwhile the runtime rejectsundefined. strictNullChecksandnoImplicitAnyare the two checks that matter. Fullstrict: trueenables both, but an explicitstrictNullChecks: falseoverrides 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.
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!