Skip to content

Instantly share code, notes, and snippets.

@TonyGravagno
Last active January 2, 2026 12:38
Show Gist options
  • Select an option

  • Save TonyGravagno/2b744ceb99e415c4b53e8b35b309c29c to your computer and use it in GitHub Desktop.

Select an option

Save TonyGravagno/2b744ceb99e415c4b53e8b35b309c29c to your computer and use it in GitHub Desktop.
Create a default object from a Zod schema
import { z } from 'zod'
/**
* @summary Function returns default object from Zod schema
* @version 23.05.15.2
* @link https://gist.github.com/TonyGravagno/2b744ceb99e415c4b53e8b35b309c29c
* @author Jacob Weisenburger, Josh Andromidas, Thomas Moiluiavon, Tony Gravagno
* @param schema z.object schema definition
* @param options Optional object, see Example for details
* @returns Object of type schema with defaults for all fields
* @example
* const schema = z.object( { ... } )
* const default1 = defaultInstance<typeof schema>(schema)
* const default2 = defaultInstance<typeof schema>(
* schema,{ // toggle from these defaults if required
* defaultArrayEmpty: false,
* defaultDateEmpty: false,
* defaultDateUndefined: false,
* defaultDateNull: false,
* } )
*/
export function defaultInstance<T extends z.ZodTypeAny>(
schema: z.AnyZodObject | z.ZodEffects<any>,
options: object = {}
): z.infer<T> {
const defaultArrayEmpty = 'defaultArrayEmpty' in options ? options.defaultArrayEmpty : false
const defaultDateEmpty = 'defaultDateEmpty' in options ? options.defaultDateEmpty : false
const defaultDateUndefined = 'defaultDateUndefined' in options ? options.defaultDateUndefined : false
const defaultDateNull = 'defaultDateNull' in options ? options.defaultDateNull : false
function run(): z.infer<T> {
if (schema instanceof z.ZodEffects) {
if (schema.innerType() instanceof z.ZodEffects) {
return defaultInstance(schema.innerType(), options) // recursive ZodEffect
}
// return schema inner shape as a fresh zodObject
return defaultInstance(z.ZodObject.create(schema.innerType().shape), options)
}
if (schema instanceof z.ZodType) {
let the_shape = schema.shape as z.ZodAny // eliminates 'undefined' issue
let entries = Object.entries(the_shape)
let temp = entries.map(([key, value]) => {
let this_default =
value instanceof z.ZodEffects ? defaultInstance(value, options) : getDefaultValue(value)
return [key, this_default]
})
return Object.fromEntries(temp)
} else {
console.log(`Error: Unable to process this schema`)
return null // unknown or undefined here results in complications
}
function getDefaultValue(dschema: z.ZodTypeAny): any {
console.dir(dschema)
if (dschema instanceof z.ZodDefault) {
if (!('_def' in dschema)) return undefined // error
if (!('defaultValue' in dschema._def)) return undefined // error
return dschema._def.defaultValue()
}
if (dschema instanceof z.ZodArray) {
if (!('_def' in dschema)) return undefined // error
if (!('type' in dschema._def)) return undefined // error
// return empty array or array with one empty typed element
return defaultArrayEmpty ? [] : [getDefaultValue(dschema._def.type as z.ZodAny)]
}
if (dschema instanceof z.ZodString) return ''
if (dschema instanceof z.ZodNumber || dschema instanceof z.ZodBigInt) {
let value = dschema.minValue ?? 0
return value
}
if (dschema instanceof z.ZodDate) {
let value = defaultDateEmpty
? ''
: defaultDateNull
? null
: defaultDateUndefined
? undefined
: (dschema as z.ZodDate).minDate
return value
}
if (dschema instanceof z.ZodSymbol) return ''
if (dschema instanceof z.ZodBoolean) return false
if (dschema instanceof z.ZodNull) return null
if (dschema instanceof z.ZodPipeline) {
if (!('out' in dschema._def)) return undefined // error
return getDefaultValue(dschema._def.out)
}
if (dschema instanceof z.ZodObject) {
return defaultInstance(dschema, options)
}
if (dschema instanceof z.ZodAny && !('innerType' in dschema._def)) return undefined // error?
return getDefaultValue(dschema._def.innerType)
}
}
return run()
}
@yamatohagi
Copy link

Has this been resolved?

@man-trackunit
Copy link

Would it make sense/possible to abstract it to also work for .catch()?

@FondueBug
Copy link

Thanks for your contribution, I really appreciate it.

@TonyGravagno
Copy link
Author

Update of ... non updates. I'm sorry that I haven't had time to come back to enhance this code. I was hoping that by now there would be a solution implemented in Zod, and have been holding out on continuing updates here. I'll hold out a bit longer. I haven't been active in my apps that use Zod recently so I'm a bit rusty on all of this.

The code here isn't ideal. There are a number of faults in more complex use cases. Here is my suggested approach to addressing this topic:

  • Watch this gist, but don't count on it being updated or for generous submissions to be incorporated.
  • Watch colinhacks/zod#1953
  • Be aware that any system that provides a default value for general use cases may not be ideal for a large number of specific use cases.
  • Get lots of sleep. Drink lots of coffee. Be good to yourself and others.
  • Have a great day.

@mpint
Copy link

mpint commented Apr 21, 2025

Anyone have a chance to see if the new zod version improves this experience?

Update: Upgrading to zod@4 beta breaks this function and the new version doesn't appear to solve the default schema problem

@markovdigital
Copy link

For the veterans of this gist: zod v4 compatible edition

import { z } from 'zod';

/**
 * @summary Function returns default object from Zod schema (Zod v4 compatible)
 * @version 24.0.0
 * @link https://gist.github.com/TonyGravagno/2b744ceb99e415c4b53e8b35b309c29c
 * @author Jacob Weisenburger, Josh Andromidas, Thomas Moiluiavon, Tony Gravagno, Vladislav Markov
 * @param schema Zod schema definition
 * @param options Optional configuration object
 * @param options.defaultArrayEmpty If true, arrays return empty array; if false, array with one default element
 * @param options.defaultDateEmpty If true, dates return empty string
 * @param options.defaultDateUndefined If true, dates return undefined
 * @param options.defaultDateNull If true, dates return null
 * @returns Object of type schema with defaults for all fields
 * @example
 * const schema = z.object( { ... } )
 * const default1 = defaultInstance<typeof schema>(schema)
 * const default2 = defaultInstance<typeof schema>(
 *   schema,{ // toggle from these defaults if required
 *     defaultArrayEmpty: false,
 *     defaultDateEmpty: false,
 *     defaultDateUndefined: false,
 *     defaultDateNull: false,
 * } )
 */

export interface DefaultInstanceOptions {
	defaultArrayEmpty?: boolean;
	defaultDateEmpty?: boolean;
	defaultDateUndefined?: boolean;
	defaultDateNull?: boolean;
}

export function defaultInstance<T extends z.ZodType>(schema: z.ZodType, options: DefaultInstanceOptions = {}): z.infer<T> {
	const {
		defaultArrayEmpty = false,
		defaultDateEmpty = false,
		defaultDateUndefined = false,
		defaultDateNull = false,
	} = options;

	function isZodObject(s: z.ZodType): s is z.ZodObject<any> {
		return s instanceof z.ZodObject || (s && typeof s === 'object' && 'shape' in s && typeof (s as any).shape === 'object');
	}

	function run(): z.infer<T> {
		if (schema instanceof z.ZodTransform) {
			const def = (schema as any)._def;
			if (def && 'schema' in def) {
				const innerSchema = def.schema as z.ZodType;
				return defaultInstance(innerSchema, options);
			}
		}

		if (isZodObject(schema)) {
			const shape = schema.shape;
			if (!shape) {
				return {} as z.infer<T>;
			}
			return Object.fromEntries(
				Object.entries(shape).map(([key, value]) => {
					const val = value as z.ZodType;
					const this_default = val instanceof z.ZodTransform ? defaultInstance(val, options) : getDefaultValue(val);
					return [key, this_default];
				}),
			) as z.infer<T>;
		}

		return getDefaultValue(schema) as z.infer<T>;

		function getDefaultValue(dschema: z.ZodType): any {
			function extractDefaultValue(s: z.ZodDefault | z.ZodPrefault): any {
				const def = (s as any)._def;
				if (def && 'defaultValue' in def) {
					const defaultValue = def.defaultValue as unknown;
					return typeof defaultValue === 'function' ? (defaultValue as () => any)() : defaultValue;
				}
				return getDefaultValue(s.unwrap() as z.ZodType);
			}

			function getArrayElementType(arraySchema: z.ZodArray<any>): z.ZodType | null {
				if ((arraySchema as any).element) {
					return (arraySchema as any).element as z.ZodType;
				}
				const def = (arraySchema as any)._def;
				if (def && 'element' in def) {
					return def.element as z.ZodType;
				}
				return null;
			}

			if (isZodObject(dschema)) {
				return defaultInstance(dschema, options);
			}

			if (dschema instanceof z.ZodDefault) {
				return extractDefaultValue(dschema);
			}

			if (dschema instanceof z.ZodPrefault) {
				return extractDefaultValue(dschema);
			}

			if (dschema instanceof z.ZodOptional) {
				return getDefaultValue(dschema.unwrap() as z.ZodType);
			}

			if (dschema instanceof z.ZodNullable) {
				return getDefaultValue(dschema.unwrap() as z.ZodType);
			}

			if (dschema instanceof z.ZodArray) {
				const elementType = getArrayElementType(dschema);
				if (elementType) {
					return defaultArrayEmpty ? [] : [getDefaultValue(elementType)];
				}
				return [];
			}

			if (dschema instanceof z.ZodString) return '';
			if (dschema instanceof z.ZodNumber || dschema instanceof z.ZodBigInt) {
				return (dschema as any).minValue ?? 0;
			}
			if (dschema instanceof z.ZodDate) {
				return defaultDateEmpty
					? ''
					: defaultDateNull
						? null
						: defaultDateUndefined
							? undefined
							: (dschema as any).minDate;
			}
			if (dschema instanceof z.ZodSymbol) return '';
			if (dschema instanceof z.ZodBoolean) return false;
			if (dschema instanceof z.ZodNull) return null;

			if (dschema && typeof dschema === 'object' && '_def' in dschema && 'out' in (dschema as any)._def) {
				const pipeOut = (dschema as any)._def.out as z.ZodType;
				if (pipeOut) {
					return getDefaultValue(pipeOut);
				}
			}

			if (dschema instanceof z.ZodRecord) {
				return {};
			}

			if (dschema instanceof z.ZodAny || dschema instanceof z.ZodUnknown || dschema instanceof z.ZodCustom) {
				return undefined;
			}

			if (dschema && typeof dschema === 'object' && '_def' in dschema) {
				const def = (dschema as any)._def;
				if ('innerType' in def && def.innerType) {
					return getDefaultValue(def.innerType as z.ZodType);
				}
				if ('unwrap' in dschema && typeof dschema.unwrap === 'function') {
					try {
						return getDefaultValue(dschema.unwrap() as z.ZodType);
					} catch {
						// ignore
					}
				}
			}

			return undefined;
		}
	}

	return run();
}

@alexcroox
Copy link

alexcroox commented Jan 2, 2026

total: z.number() defaults to total: -Infinity

fix:

if (dschema instanceof z.ZodNumber || dschema instanceof z.ZodBigInt) {
  const minValue = (dschema as any).minValue

  return minValue && minValue !== -Infinity ? minValue : null
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment