v3.3.1
Playground
Guides

Validate Documents

Run Zod refinements during Mongoose validation and understand when transforms affect stored values.

Validate Documents

toMongooseSchema() maps supported Zod checks to Mongoose options. It also runs the Zod schema in a post('validate') hook by default, so refinements that Mongoose cannot represent still reject invalid documents.

Add a refinement

booking.ts
import mongoose from 'mongoose';
import {z} from 'zod/v4';
import {toMongooseSchema} from '@nullix/zod-mongoose';

const BookingZodSchema = z.object({
  startsAt: z.date(),
  endsAt: z.date(),
}).refine(({startsAt, endsAt}) => endsAt > startsAt, {
  message: 'End must be after start',
  path: ['endsAt'],
});

const Booking = mongoose.model('Booking', toMongooseSchema(BookingZodSchema));
const booking = new Booking({
  startsAt: new Date('2026-01-02'),
  endsAt: new Date('2026-01-01'),
});

await booking.validate(); // Rejects the document; no database connection is needed.

The hook parses document.toObject() after Mongoose validation. It checks Zod refinements, but it does not copy Zod's parsed output back into the Mongoose document. If you use .transform() to normalize input, parse that input before constructing or updating a Mongoose document.

For z.strictObject(), z.looseObject(), and .catchall(), Mongoose must also keep or reject unknown fields while constructing the document. See Object Modes and Unknown Keys for their storage behavior and the difference between document saves and query updates.

normalize-input.ts
import {z} from 'zod/v4';

const NameInput = z.object({name: z.string().trim().toLowerCase()});
const normalized = NameInput.parse({name: '  ADA  '});
// normalized.name === 'ada'; pass normalized to your Mongoose model.

Disable the extra Zod validation

schema-options.ts
const mongooseSchema = toMongooseSchema(BookingZodSchema, {
  validateBeforeSave: false,
});

This option skips the package's Zod validation hook. Mongoose's own validation still runs. You can also set validateBeforeSave: false in top-level withMongoose metadata. See Core Functions for the option signature.