npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@zod-utils/core

v2.0.2

Published

Pure TypeScript utilities for Zod schema manipulation and default extraction

Readme

@zod-utils/core

npm version npm downloads Bundle Size License: MIT TypeScript CI codecov

Pure TypeScript utilities for Zod schema manipulation and default extraction. No React dependencies.

Installation

npm install @zod-utils/core zod

Related Packages

  • @zod-utils/react-hook-form - React Hook Form integration with automatic type transformation. Uses this package internally and re-exports all utilities for convenience.

Features

  • 🎯 Extract defaults - Get default values from Zod schemas
  • Check validation requirements - Determine if fields will error on empty input
  • 🔍 Extract validation checks - Get all validation constraints (min/max, formats, patterns, etc.)
  • 🔧 Schema utilities - Unwrap and manipulate schema types
  • 📦 Zero dependencies - Only requires Zod as a peer dependency
  • 🌐 Universal - Works in Node.js, browsers, and any TypeScript project

API Reference

getSchemaDefaults(schema)

Extract all default values from a Zod object schema. Only extracts fields that explicitly have .default() on them.

Transform support: Works with schemas that have .transform() - extracts defaults from the input type.

import { getSchemaDefaults } from "@zod-utils/core";
import { z } from "zod";

const schema = z.object({
  name: z.string().default("John Doe"),
  age: z.number().default(25),
  email: z.string().email(), // no default - skipped
  settings: z
    .object({
      theme: z.string().default("light"),
      notifications: z.boolean().default(true),
    })
    .default({}), // must have explicit .default() to be extracted
  tags: z.array(z.string()).default([]),
});

const defaults = getSchemaDefaults(schema);
// {
//   name: 'John Doe',
//   age: 25,
//   settings: {},
//   tags: []
// }

Important: Only fields with explicit .default() are extracted. Nested object fields without an explicit default on the parent field are not extracted, even if they contain defaults internally.

Handles:

  • Optional fields with defaults: .optional().default(value)
  • Nullable fields with defaults: .nullable().default(value)
  • Arrays with defaults: .array().default([])
  • Objects with defaults: .object({...}).default({})
  • Skips fields without explicit defaults

requiresValidInput(field)

Determines if a field will show validation errors when the user submits empty or invalid input. Useful for form UIs to show which fields need valid user input (asterisks, validation indicators).

Key insight: Defaults are just initial values - they don't prevent validation errors if the user clears the field.

Real-world example:

// Marital status with default but validation rules
const maritalStatus = z.string().min(1).default('single');

// What happens in the form:
// 1. Initial: field shows "single" (from default)
// 2. User deletes the value → empty string
// 3. User submits form → validation fails (.min(1) rejects empty)
// 4. requiresValidInput(maritalStatus) → true (show *, show error)

How it works:

  1. Removes .default() wrappers (defaults ≠ validation rules)
  2. Tests if underlying schema accepts empty/invalid input:
    • undefined (via .optional())
    • null (via .nullable())
    • Empty string (plain z.string())
    • Empty array (plain z.array())
  3. Returns true if validation will fail on empty input

Examples:

import { requiresValidInput } from "@zod-utils/core";
import { z } from "zod";

// User name - required, no default
const userName = z.string().min(1);
requiresValidInput(userName); // true - will error if empty

// Marital status - required WITH default
const maritalStatus = z.string().min(1).default('single');
requiresValidInput(maritalStatus); // true - will error if user clears it

// Age with default - requires valid input
const age = z.number().default(0);
requiresValidInput(age); // true - numbers reject empty strings

// Optional bio - doesn't require input
const bio = z.string().optional();
requiresValidInput(bio); // false - user can leave empty

// Notes with default but NO validation
const notes = z.string().default('N/A');
requiresValidInput(notes); // false - plain z.string() accepts empty

// Nullable middle name
const middleName = z.string().nullable();
requiresValidInput(middleName); // false - user can leave null

getPrimitiveType(field)

Get the primitive type of a Zod field by unwrapping optional/nullable/transform wrappers. Stops at arrays without unwrapping them.

Transform support: Automatically unwraps .transform() to get the underlying input type.

import { getPrimitiveType } from "@zod-utils/core";
import { z } from "zod";

const field = z.string().optional().nullable();
const primitive = getPrimitiveType(field);
// Returns the underlying string schema

const arrayField = z.array(z.string()).optional();
const arrayPrimitive = getPrimitiveType(arrayField);
// Returns the ZodArray (stops at arrays)

// Transform support
const transformed = z.string().transform((val) => val.toUpperCase());
const primitiveFromTransform = getPrimitiveType(transformed);
// Returns the underlying ZodString (unwraps the transform)

removeDefault(field)

Remove default values from a Zod field.

import { removeDefault } from "@zod-utils/core";
import { z } from "zod";

const withDefault = z.string().default("hello");
const withoutDefault = removeDefault(withDefault);

withDefault.parse(undefined); // 'hello'
withoutDefault.parse(undefined); // throws error

extractDefaultValue(field)

Extract the default value from a Zod field (recursively unwraps optional/nullable/union/transform layers).

Union handling: For union types, extracts the default from the first option. If the first option has no default, returns undefined (defaults in other union options are not checked).

Transform support: Automatically unwraps .transform() to get the input type's default value.

import { extractDefaultValue } from "@zod-utils/core";
import { z } from "zod";

// Basic usage
const field = z.string().optional().default("hello");
extractDefaultValue(field); // 'hello'

const noDefault = z.string();
extractDefaultValue(noDefault); // undefined

// Union with default in first option
const unionField = z.union([z.string().default('hello'), z.number()]);
extractDefaultValue(unionField); // 'hello'

// Union with default in second option (only checks first)
const unionField2 = z.union([z.string(), z.number().default(42)]);
extractDefaultValue(unionField2); // undefined

// Union wrapped in optional
const wrappedUnion = z.union([z.string().default('test'), z.number()]).optional();
extractDefaultValue(wrappedUnion); // 'test'

// Transform support - extracts input default, not output
const transformed = z.string().default('hello').transform((val) => val.toUpperCase());
extractDefaultValue(transformed); // 'hello' (not 'HELLO')

getFieldChecks(field)

Extract all validation check definitions from a Zod schema field. Returns Zod's raw check definition objects directly, including all properties like check, minimum, maximum, value, inclusive, format, pattern, etc.

Automatically unwraps: optional, nullable, and default layers. For unions, checks only the first option.

Supported check types: Returns any of 21 check types:

  • Length checks: min_length, max_length, length_equals (strings, arrays)
  • Size checks: min_size, max_size, size_equals (files, sets, maps)
  • Numeric checks: greater_than, less_than, multiple_of
  • Format checks: number_format, bigint_format, string_format (email, url, uuid, etc.)
  • String pattern checks: regex, lowercase, uppercase, includes, starts_with, ends_with
  • Other checks: property, mime_type, overwrite
import { getFieldChecks } from "@zod-utils/core";
import { z } from "zod";

// String with length constraints
const username = z.string().min(3).max(20);
const checks = getFieldChecks(username);
// [
//   { check: 'min_length', minimum: 3, when: [Function], ... },
//   { check: 'max_length', maximum: 20, when: [Function], ... }
// ]

// Number with range constraints
const age = z.number().min(18).max(120);
const checks = getFieldChecks(age);
// [
//   { check: 'greater_than', value: 18, inclusive: true, ... },
//   { check: 'less_than', value: 120, inclusive: true, ... }
// ]

// Array with item count constraints
const tags = z.array(z.string()).min(1).max(5);
const checks = getFieldChecks(tags);
// [
//   { check: 'min_length', minimum: 1, ... },
//   { check: 'max_length', maximum: 5, ... }
// ]

// String with format validation
const email = z.string().email();
const checks = getFieldChecks(email);
// [{ check: 'string_format', format: 'email', ... }]

// Unwrapping optional/nullable/default layers
const bio = z.string().min(10).max(500).optional();
const checks = getFieldChecks(bio);
// [
//   { check: 'min_length', minimum: 10, ... },
//   { check: 'max_length', maximum: 500, ... }
// ]

// No checks
const plainString = z.string();
getFieldChecks(plainString); // []

Type: The return type is ZodUnionCheck[], a union of all 21 Zod check definition types. You can also import the ZodUnionCheck type:

import { getFieldChecks, type ZodUnionCheck } from "@zod-utils/core";

extractDiscriminatedSchema(schema, key, value)

Extract a specific variant from a discriminated union schema based on the discriminator field and value.

import { extractDiscriminatedSchema } from "@zod-utils/core";
import { z } from "zod";

const userSchema = z.discriminatedUnion('mode', [
  z.object({
    mode: z.literal('create'),
    name: z.string(),
    age: z.number().optional(),
  }),
  z.object({
    mode: z.literal('edit'),
    id: z.number(),
    name: z.string().optional(),
    bio: z.string().optional(),
  }),
]);

// Extract the 'create' variant
const createSchema = extractDiscriminatedSchema({
  schema: userSchema,
  key: 'mode',
  value: 'create',
});
// Returns: z.ZodObject with { mode, name, age }

// Extract the 'edit' variant
const editSchema = extractDiscriminatedSchema({
  schema: userSchema,
  key: 'mode',
  value: 'edit',
});
// Returns: z.ZodObject with { mode, id, name, bio }

Use with discriminated unions: This is essential when working with z.discriminatedUnion() schemas, as it extracts the correct variant schema based on the discriminator value.


extractFieldFromSchema(schema, name, discriminator?)

Extract a single field from a Zod object or discriminated union schema.

Transform support: Works with schemas that have .transform() - extracts fields from the input type.

import { extractFieldFromSchema } from "@zod-utils/core";
import { z } from "zod";

// Simple object schema
const userSchema = z.object({
  name: z.string(),
  age: z.number(),
  email: z.string().email(),
});

const nameField = extractFieldFromSchema({
  schema: userSchema,
  name: 'name',
});
// Returns: ZodString

// Discriminated union schema
const formSchema = z.discriminatedUnion('mode', [
  z.object({
    mode: z.literal('create'),
    name: z.string(),
    age: z.number().optional(),
  }),
  z.object({
    mode: z.literal('edit'),
    id: z.number(),
    name: z.string().optional(),
  }),
]);

// Extract field from specific variant
const idField = extractFieldFromSchema({
  schema: formSchema,
  name: 'id',
  discriminator: {
    key: 'mode',
    value: 'edit',
  },
});
// Returns: ZodNumber

// Without discriminator on discriminated union, returns undefined
const fieldWithoutDiscriminator = extractFieldFromSchema({
  schema: formSchema,
  name: 'name',
});
// Returns: undefined (need discriminator to know which variant)

// Works with transforms - extracts from input type
const transformedSchema = z
  .object({
    name: z.string(),
    age: z.number(),
  })
  .transform((data) => ({ ...data, computed: true }));

const nameFromTransformed = extractFieldFromSchema({
  schema: transformedSchema,
  name: 'name',
});
// Returns: ZodString (from the input type, not affected by transform)

Discriminated union support: When extracting fields from discriminated unions, you must provide the discriminator option with key and value to specify which variant to use.


extendWithMeta(field, transform)

Extends a Zod field with a transformation while preserving its metadata.

This is useful when you want to add validations or transformations to a field but keep the original metadata (like translationKey) intact.

import { extendWithMeta } from "@zod-utils/core";
import { z } from "zod";

// Base field with metadata
const baseField = z.string().meta({ translationKey: 'user.field.name' });

// Extend with validation while keeping metadata
const extendedField = extendWithMeta(baseField, (f) => f.min(3).max(100));

extendedField.meta(); // { translationKey: 'user.field.name' }

// Validation still works
extendedField.parse('ab');     // throws - too short
extendedField.parse('abc');    // 'abc' - valid

Use case: When building forms with shared field definitions, you may want to reuse a base field with metadata across multiple schemas while adding schema-specific validations:

// Shared field definitions with i18n metadata
const fields = {
  name: z.string().meta({ translationKey: 'user.field.name' }),
  email: z.string().email().meta({ translationKey: 'user.field.email' }),
};

// Create form uses base fields with additional constraints
const createFormSchema = z.object({
  name: extendWithMeta(fields.name, (f) => f.min(3).max(50)),
  email: extendWithMeta(fields.email, (f) => f.min(5)),
});

// Edit form uses same fields with different constraints
const editFormSchema = z.object({
  name: extendWithMeta(fields.name, (f) => f.optional()),
  email: fields.email, // no extension needed
});

Note: If the original field has no metadata, the transformed field is returned as-is without calling .meta().


getSchemaDefaults(schema, discriminator?)

Updated: Now supports discriminated union schemas with the discriminator option.

import { getSchemaDefaults } from "@zod-utils/core";
import { z } from "zod";

// Discriminated union with defaults
const formSchema = z.discriminatedUnion('mode', [
  z.object({
    mode: z.literal('create'),
    name: z.string(),
    age: z.number().default(18),
  }),
  z.object({
    mode: z.literal('edit'),
    id: z.number().default(1),
    name: z.string().optional(),
    bio: z.string().default('bio goes here'),
  }),
]);

// Get defaults for 'create' mode
const createDefaults = getSchemaDefaults(formSchema, {
  discriminator: {
    key: 'mode',
    value: 'create',
  },
});
// Returns: { age: 18 }

// Get defaults for 'edit' mode
const editDefaults = getSchemaDefaults(formSchema, {
  discriminator: {
    key: 'mode',
    value: 'edit',
  },
});
// Returns: { id: 1, bio: 'bio goes here' }

// Without discriminator, returns empty object
const noDefaults = getSchemaDefaults(formSchema);
// Returns: {}

Discriminator types: The discriminator value can be a string, number, or boolean literal that matches the discriminator field type.


Type Utilities

Simplify<T>

Simplify complex types for better IDE hints.

import type { Simplify } from "@zod-utils/core";

type Complex = { a: string } & { b: number };
type Simple = Simplify<Complex>;
// { a: string; b: number }

DiscriminatedInput<TSchema, TDiscriminatorKey, TDiscriminatorValue>

Extracts the input type from a discriminated union variant. For discriminated unions, narrows to the variant matching the discriminator value and returns its input type. For regular schemas, returns the full input type.

import type { DiscriminatedInput } from "@zod-utils/core";
import { z } from "zod";

const schema = z.discriminatedUnion("mode", [
  z.object({ mode: z.literal("create"), name: z.string() }),
  z.object({ mode: z.literal("edit"), id: z.number() }),
]);

type CreateInput = DiscriminatedInput<typeof schema, "mode", "create">;
// { mode: 'create'; name: string }

type EditInput = DiscriminatedInput<typeof schema, "mode", "edit">;
// { mode: 'edit'; id: number }

ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>

Generates all valid dot-notation paths for a schema. For discriminated unions, narrows to the specific variant first.

import type { ValidPaths } from "@zod-utils/core";
import { z } from "zod";

const schema = z.discriminatedUnion("mode", [
  z.object({ mode: z.literal("create"), name: z.string() }),
  z.object({ mode: z.literal("edit"), id: z.number() }),
]);

type CreatePaths = ValidPaths<typeof schema, "mode", "create">;
// "mode" | "name"

type EditPaths = ValidPaths<typeof schema, "mode", "edit">;
// "mode" | "id"

ValidPathsOfType<TSchema, TValueConstraint, TDiscriminatorKey?, TDiscriminatorValue?>

Extracts field paths from a schema where the field value type matches a constraint. Filters schema keys to only those whose input type (with nullish stripped) extends the given TValueConstraint.

import type { ValidPathsOfType } from "@zod-utils/core";
import { z } from "zod";

const schema = z.object({
  name: z.string(),
  age: z.number(),
  email: z.string().optional(),
  count: z.number().nullable(),
  active: z.boolean(),
});

// Get all string field paths
type StringPaths = ValidPathsOfType<typeof schema, string>;
// "name" | "email"

// Get all number field paths
type NumberPaths = ValidPathsOfType<typeof schema, number>;
// "age" | "count"

// Get all boolean field paths
type BooleanPaths = ValidPathsOfType<typeof schema, boolean>;
// "active"

With discriminated unions:

const formSchema = z.discriminatedUnion("mode", [
  z.object({ mode: z.literal("create"), name: z.string(), age: z.number() }),
  z.object({ mode: z.literal("edit"), id: z.number(), title: z.string() }),
]);

// Get number paths for 'edit' variant only
type EditNumberPaths = ValidPathsOfType<
  typeof formSchema,
  number,
  "mode",
  "edit"
>;
// "id"

// Get string paths for 'create' variant
type CreateStringPaths = ValidPathsOfType<
  typeof formSchema,
  string,
  "mode",
  "create"
>;
// "name"

Array and object filtering:

const schema = z.object({
  tags: z.array(z.string()),
  scores: z.array(z.number()),
  profile: z.object({ bio: z.string() }),
});

type StringArrayPaths = ValidPathsOfType<typeof schema, string[]>;
// "tags"

type ProfilePaths = ValidPathsOfType<typeof schema, { bio: string }>;
// "profile"

License

MIT