v2.5.4
Playground
Getting Started

Usage

Learn how to use zod-mongoose in your application.

Usage

Basic Conversion

To convert a Zod schema into a Mongoose schema, use the toMongooseSchema function. This is the main entry point for using zod-mongoose in your backend application.

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

// Define a Zod schema
const UserZodSchema = z.object({
  username: z.string().min(3),
  email: z.string().email(),
  age: z.number().optional(),
});

// Convert to Mongoose Schema
const UserSchema = toMongooseSchema(UserZodSchema);

// Create a Mongoose Model
const UserModel = mongoose.model('User', UserSchema);

Passing Mongoose Options

The second parameter of toMongooseSchema allows you to specify standard Mongoose schema options, such as timestamps, collection, and versionKey.

const mongooseSchema = toMongooseSchema(UserZodSchema, {
  timestamps: true,
  collection: 'users',
});

Automatic Zod Validation

One of the most powerful features of zod-mongoose is the automatic Zod validation. When you convert a schema using toMongooseSchema, it automatically adds a Mongoose post('validate') hook that runs yourZodSchema.parse() on the document.

This ensures that validations that Mongoose doesn't natively support (like .refine(), .superRefine(), or .transform()) are still enforced.

Example: Using Refinements

const UserZodSchema = z.object({
  password: z.string(),
  confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
  message: "Passwords don't match",
  path: ["confirmPassword"],
});

const UserSchema = toMongooseSchema(UserZodSchema);
const UserModel = mongoose.model('User', UserSchema);

const user = new UserModel({ password: '123', confirmPassword: '456' });
await user.validate(); // Throws a Zod error because passwords don't match

Disabling Automatic Validation

If you want to handle validation manually or find that the automatic hook interferes with your logic, you can disable it by setting validateBeforeSave: false.

const UserSchema = toMongooseSchema(UserZodSchema, {
  validateBeforeSave: false
});

Extending Schemas

zod-mongoose makes it easy to extend your schemas with Mongoose plugins or custom logic using hooks.

Using Mongoose Plugins

You can apply standard Mongoose plugins directly during the conversion process.

import mongooseLeanVirtuals from 'mongoose-lean-virtuals';

const mongooseSchema = toMongooseSchema(zodSchema, {
  plugins: [mongooseLeanVirtuals]
});

Using Hooks

The schema:created hook allows you to modify the mongoose.Schema instance immediately after it is created.

import { hooks } from '@nullix/zod-mongoose';

hooks.hook('schema:created', ({ schema, zodSchema }) => {
  // Add a virtual field to all schemas that have a 'title' field
  if ('title' in zodSchema.shape) {
    schema.virtual('slug').get(function() {
      return this.title.toLowerCase().replace(/ /g, '-');
    });
  }
});

Advanced Type Handling

zod-mongoose provides specialized helpers for Mongoose-specific types.

ObjectIds and Buffers

Use zObjectId() and zBuffer() for fields that map to Mongoose ObjectId and Buffer types.

import { zObjectId, zBuffer } from '@nullix/zod-mongoose';

const ProductSchema = z.object({
  _id: zObjectId(), // Managed by Mongoose unless { includeId: true } is passed
  category: zObjectId({ ref: 'Category' }),
  imageData: zBuffer(),
});

Populated Fields

For fields that can be either an ObjectId (unpopulated) or a full object (populated), use zRef.

import { zRef } from '@nullix/zod-mongoose';

const PostSchema = z.object({
  author: zRef('User', UserZodSchema),
});

Adding Mongoose Metadata

Use withMongoose to add Mongoose-specific options like unique, index, or default.

import { withMongoose } from '@nullix/zod-mongoose';

const UserZodSchema = z.object({
  email: withMongoose(z.string().email(), { 
    unique: true, 
    index: true 
  }),
});

Usage with Type Inference

@nullix/zod-mongoose provides a specialized z object (and mz alias) that overrides the standard z.infer to automatically include the _id: mongoose.Types.ObjectId field in the inferred type. This ensures that your TypeScript types accurately reflect the structure of a Mongoose document.

import { z } from '@nullix/zod-mongoose';

const UserSchema = z.object({
  name: z.string(),
});

// User type will automatically include { _id: mongoose.Types.ObjectId }
type User = z.infer<typeof UserSchema>;

async function createUser(data: User) {
  // data._id is already typed as ObjectId
  const user = await UserModel.create(data);
  return user;
}

Isomorphic Support (Frontend Mode)

In modern applications, you often want to share your Zod schemas between your backend and your frontend. @nullix/zod-mongoose is designed to work automatically in both environments.

Automatic Detection

When you import from @nullix/zod-mongoose in a browser environment (detected via package.json exports by tools like Vite, Webpack, or Nuxt), the library automatically switches to "frontend mode":

  • zObjectId() falls back to a regex-validated string.
  • zBuffer() falls back to Uint8Array.
  • No mongoose dependency is required on the client.

Manual Configuration (Optional)

If automatic detection is not available in your environment, you can still manually enable frontend mode:

import { setFrontendMode } from '@nullix/zod-mongoose';

// Manually enable frontend mode
setFrontendMode(true);

Using with Nuxt 4 & Nitro

zod-mongoose is optimized for Nuxt 4. You can use your Zod schemas directly with readValidatedBody in your Nitro server routes.

// server/api/users.post.ts
import { UserZodSchema } from '~/schemas/user';

export default defineEventHandler(async (event) => {
  const body = await readValidatedBody(event, UserZodSchema.parse);
  
  // body is now fully typed and validated by Zod
  const user = await UserModel.create(body);
  return user;
});