Skip to main content

Custom Type Handlers

Handle complex Zod types that require custom generation logic.

Handler Interface

type ZodTypeHandler = (
schema: ZodType,
generator: ZodSchemaGenerator,
currentDepth: number,
) => unknown;

Required Handlers

These Zod types require custom handlers:

Functions

const schema = z.object({
id: z.string(),
processor: z.function().args(z.string()).returns(z.string()),
});

const factory = new ZodFactory(schema).withTypeHandler(
'ZodFunction',
() => (input: string) => `processed: ${input}`,
);

Promises

const schema = z.object({
id: z.string(),
data: z.promise(z.object({ content: z.string() })),
});

const factory = new ZodFactory(schema).withTypeHandler(
'ZodPromise',
(schema, generator) =>
Promise.resolve({
content: generator.factory.lorem.paragraph(),
}),
);

// Use buildAsync for promise-containing schemas
const data = await factory.buildAsync();

Custom Types

interface CustomMetadata {
version: string;
tags: string[];
}

const schema = z.object({
id: z.string(),
metadata: z.custom<CustomMetadata>(),
});

const factory = new ZodFactory(schema).withTypeHandler(
'ZodCustom',
(schema, generator) => ({
version: generator.factory.system.semver(),
tags: generator.factory.helpers.arrayElements(['tag1', 'tag2']),
}),
);

Multiple Handlers

Register multiple handlers at once:

const factory = new ZodFactory(schema).withTypeHandlers({
ZodFunction: () => () => 'mock result',
ZodPromise: (schema, generator) =>
Promise.resolve(generator.factory.lorem.word()),
ZodCustom: (schema, generator) => ({
timestamp: new Date(),
data: generator.factory.datatype.json(),
}),
});

Advanced Handlers

File Upload Handler

const fileSchema = z.object({
file: z.instanceof(File),
metadata: z.object({
size: z.number(),
type: z.string(),
}),
});

const factory = new ZodFactory(fileSchema).withTypeHandler(
'ZodType', // instanceof uses ZodType
(schema, generator) => {
const content = generator.factory.lorem.paragraphs(3);
return new File([content], 'test.txt', { type: 'text/plain' });
},
);

Context-Aware Handler

const factory = new ZodFactory(recursiveSchema).withTypeHandler(
'ZodLazy',
(schema, generator, currentDepth) => {
// Depth-aware generation
const shouldNest = currentDepth < 2;

return {
id: generator.factory.string.uuid(),
level: currentDepth,
children: shouldNest
? [factory.build(), factory.build()]
: undefined,
};
},
);

Async Handler

const factory = new ZodFactory(schema).withTypeHandler(
'ZodPromise',
async (schema, generator) => {
// Simulate async operation
await new Promise((resolve) => setTimeout(resolve, 10));

return {
result: generator.factory.lorem.word(),
timestamp: new Date(),
};
},
);

Built-in Handlers

These types have built-in handlers:

TypeGenerated Value
ZodBigIntBigInt(0 to 1,000,000)
ZodNaNNumber.NaN
ZodUnknownRandom word
ZodVoidundefined

Error Handling

Missing handlers throw clear errors:

const schema = z.object({
fn: z.function(),
});

try {
new ZodFactory(schema).build();
} catch (error) {
console.log(error.message);
// "No custom type handler registered for ZodFunction..."
}

Testing with Handlers

describe('Custom Handlers', () => {
const mockProcessor = jest.fn().mockReturnValue('processed');

const factory = new ZodFactory(schema).withTypeHandlers({
ZodFunction: () => mockProcessor,
});

it('should use custom handlers', () => {
const data = factory.build();

expect(data.processor).toBe(mockProcessor);

// Test the mock function
const result = data.processor('input');
expect(result).toBe('processed');
expect(mockProcessor).toHaveBeenCalledWith('input');
});
});

Performance Tips

// ❌ Recreates handlers on each build
function generateData() {
return new ZodFactory(schema)
.withTypeHandler('ZodFunction', expensiveHandler)
.build();
}

// ✅ Reuse factory with handlers
const factory = new ZodFactory(schema).withTypeHandler(
'ZodFunction',
expensiveHandler,
);

function generateData() {
return factory.build();
}

Real-World Example

// E-commerce order schema
const orderSchema = z.object({
id: z.string().uuid(),
processor: z
.function()
.args(
z.object({
amount: z.number(),
currency: z.string(),
}),
)
.returns(
z.promise(
z.object({
success: z.boolean(),
transactionId: z.string().optional(),
}),
),
),
validate: z.custom<(order: any) => boolean>(),
});

const factory = new ZodFactory(orderSchema).withTypeHandlers({
ZodFunction: () => async (payment) => ({
success: Math.random() > 0.1, // 90% success rate
transactionId: Math.random().toString(36),
}),
ZodCustom: () => (order) => {
return order.amount > 0 && order.currency?.length === 3;
},
});

const order = factory.build();
const result = await order.processor({ amount: 100, currency: 'USD' });