MongoZod
API reference

mongozod

The reference of the `mongozod` entry point, with mongozod(), the model, its operations and cursors, definePlugin and the types.

The mongozod entry point exports mongozod(), which defines the model of a collection, the cursors its reads return, definePlugin and the types for services and plugins. The mz namespace is in mongozod/mzod, and the official plugins in Soft delete and Auto index. Each entry links to the guide that explains it.

The examples on this page use these models:

import type { CountDocumentsOptions } from 'mongodb'
import type {
  AnyModel,
  BulkWriteOf,
  DocOf,
  FilterMethod,
  FilterOf,
  FindOptionsOf,
  Input,
  InsertOf,
  PipelineOf,
  ReadOf,
  ReadOptionsArg,
  SortOf,
  UpdateOf,
  UpdateOptionsArg,
  UpdateOptionsOf
} from 'mongozod'

import { ObjectId } from 'mongodb'
import { definePlugin, mongozod } from 'mongozod'
import * as mz from 'mongozod/mzod'
import * as z from 'zod'

const userSchema = z.object({

  _id: mz.objectId()
    .default(() => new ObjectId()),

  name: z.string(),

  email: z.email()
})

const postSchema = z.object({

  _id: mz.objectId()
    .default(() => new ObjectId()),

  title: z.string()
    .min(1),

  body: z.string()
    .default(''),

  status: z.enum(['draft', 'published'])
    .default('draft'),

  tags: z.string()
    .array()
    .default([]),

  authorId: mz.objectId(),

  createdAt: mz.date(),

  updatedAt: mz.date()
})

export const UsersModel = mongozod({

  collection: 'users',

  db,

  schema: userSchema
})

export const PostsModel = mongozod({

  collection: 'posts',

  db,

  schema: postSchema,

  timestamps: true,

  indexes: (index) => [
    index({ status: 1, createdAt: -1 })
  ],

  references: {

    authorId: () => UsersModel
  }
})

Models

mongozod()

mongozod(options) defines the model of a collection and returns a Model. See Models.

OptionTypeDefaultWhat it does
collectiona stringrequiredThe name of the collection. See Models.
dbthe driver's Db, or a function that returns onerequiredThe database, or a function called on every operation. See Models.
schemaa z.object, or a z.discriminatedUnion of objects, of Zod or Zod MinirequiredThe document as stored. See Schemas and Document types.
timestampstrue, or { createdAt?, updatedAt? } with a field name or false eachoffSets createdAt on insert and updatedAt on every write; a field the object leaves out keeps its default name. See Timestamps.
indexesa function that takes index and returns a list of index(key, options) callsnoneThe indexes sync() creates, or the autoIndex plugin on the first operation. See Sync.
jsonSchematrue, or { validationLevel?, validationAction? }not appliedA $jsonSchema generated from the schema, which sync() applies. See Sync.
referencesan object whose keys are paths and whose values are targetsnoneThe fields that populate replaces with a document. See Populate.
virtualsan object whose values are Zod schemasnoneFields that reads compute from others. See Virtuals.
pluginsan array of plugins[]The plugins of the model, the first wrapping the others. See Plugins.

mongozod() throws an Error when the options don't fit, and the types reject most of these cases first:

  • The schema isn't a z.object or a z.discriminatedUnion of objects, such as a .transform() of the whole document; a field name has a . or starts with $; or the schema stores a z.set() or a z.map(), which the driver doesn't read back.
  • timestamps names a field that isn't in every type of document, or doesn't take a Date.
  • An index has no key, or one of its keys or weights isn't a path of the document.
  • A reference isn't a path of the schema, names an array index, goes through a record, or points to something that isn't a target.
  • A virtual is named like a field, doesn't take an object, or reads a field that a type of document doesn't have.
  • A plugin needs a field that isn't in every type of document, fills a field that isn't one of its fields, defines a method that another plugin or the model has, or declares an option of MongoZod or of another plugin.

A schema with a z.bigint() needs a client with useBigInt64: true, or every operation throws (see Schemas).

Model

Model is the type of what mongozod() returns: the driver's operations on one collection, typed and validated by the schema. You rarely write it: use typeof PostsModel with the helper types. See Models.

const changes = PostsModel.collection.watch()
PropertyWhat it is
schemaThe schema of each document.
nameThe name of the collection.
indexesThe indexes the model declares, as the driver's IndexDescriptions, which sync() and autoIndex create.
dbThe driver's Db: the one the db option returns now, when it is a function.
collectionThe driver's Collection of that database, typed with the output of the schema. It skips the validation, the plugins and timestamps.
virtualsThe virtuals option, or {}.
referencesThe references option, or {}.

Besides these, a model has the operations, sync() and the methods its plugins add. Two more properties, ~timestamps and ~plugins, exist only in its type, for the helper types.

sync()

sync() creates the indexes the model declares and, with jsonSchema, applies its $jsonSchema. It returns a SyncResult and never drops an index. See Sync.

const { indexes, extraIndexes } = await PostsModel.sync()
// indexes: ['status_1_createdAt_-1'], extraIndexes: the ones the model doesn't declare

Operations

Each operation wraps the driver's Collection method of the same name, with the same arguments, options and result. On top of the driver, it checks its filter against the schema, rejects an undefined value in it at runtime, validates what it writes, throwing Zod's ZodError, and runs the plugins. See Operations and Validation.

aggregate()

aggregate(pipeline?, options?) runs an aggregation pipeline and returns a ModelCursor. The schema option validates and types each result. See Aggregation.

const tagCountSchema = z.object({

  _id: z.string(),

  count: z.number()
})

const tagCounts = await PostsModel.aggregate(
  [
    { $match: { status: 'published' } },
    { $unwind: '$tags' },
    { $group: { _id: '$tags', count: { $sum: 1 } } }
  ],
  {
    schema: tagCountSchema
  }
)
// Array<{ _id: string; count: number }>

bulkWrite()

bulkWrite(operations, options?) runs several writes in one command and returns the driver's BulkWriteResult. Each operation is validated like the operation of the same name, all of them before anything is written. See Operations.

const { document: ada } = await UsersModel.insertOne({ name: 'Ada', email: 'ada@example.com' })

await PostsModel.bulkWrite([
  { insertOne: { document: { title: 'Indexes', authorId: ada._id } } },
  { updateMany: { filter: { status: 'draft' }, update: { $set: { status: 'published' } } } },
  { deleteOne: { filter: { title: 'Old news' } } }
])

countDocuments()

countDocuments(filter?, options?) counts the documents that match the filter. See Operations.

const drafts = await PostsModel.countDocuments({ status: 'draft' })

deleteMany()

deleteMany(filter?, options?) deletes every document that matches the filter, and returns the driver's DeleteResult. See Operations.

deleteOne()

deleteOne(filter?, options?) deletes the first document that matches the filter, and returns the driver's DeleteResult. See Operations.

await PostsModel.deleteOne({ title: 'Old news' })

distinct()

distinct(key, filter?, options?) returns the distinct values of a path, typed as the values of that path with arrays flattened. See Operations.

const tags = await PostsModel.distinct('tags', { status: 'published' })
// string[]

estimatedDocumentCount()

estimatedDocumentCount(options?) estimates the number of documents from the collection's metadata. It has no group, so it takes no plugin options and no context. See Operations.

find()

find(filter?, options?) finds the documents that match the filter, and returns a ModelFindCursor that you can await for the array. The projection narrows the type, and populate replaces references with their documents. See Operations.

const titles = await PostsModel.find(
  { status: 'published' },
  { projection: { title: 1 }, sort: { createdAt: -1 }, limit: 20 }
)
// Array<{ _id: ObjectId; title: string }>

findOne()

findOne(filter?, options?) finds the first document that matches the filter, or null, with the same options as find(). See Populate for populate.

const withAuthor = await PostsModel.findOne(
  { status: 'published' },
  { populate: { authorId: { projection: { name: 1 } } } }
)
// withAuthor?.authorId: { _id: ObjectId; name: string } | null

findOneAndDelete()

findOneAndDelete(filter, options?) deletes the first document that matches the filter and returns it, or null, or the driver's ModifyResult with includeResultMetadata: true. See Operations.

const deleted = await PostsModel.findOneAndDelete(
  { title: 'Old news' },
  { projection: { title: 1 } }
)

findOneAndReplace()

findOneAndReplace(filter, replacement, options?) replaces the first document that matches the filter, validated like replaceOne(), and returns it like findOneAndDelete(). See Operations.

findOneAndUpdate()

findOneAndUpdate(filter, update, options?) updates the first document that matches the filter, validated like updateOne(), and returns it like findOneAndDelete(). See Operations.

const published = await PostsModel.findOneAndUpdate(
  { title: 'Indexes' },
  { $set: { status: 'published' } },
  { returnDocument: 'after' }
)

insertMany()

insertMany(documents, options?) validates the documents with the schema and inserts them. It returns the driver's InsertManyResult plus documents, as stored. See Validation.

const { documents } = await PostsModel.insertMany([
  { title: 'Schemas', authorId: ada._id },
  { title: 'Models', authorId: ada._id }
])

insertOne()

insertOne(document, options?) validates the document with the schema and inserts it. It returns the driver's InsertOneResult plus document, as stored, with its defaults and its _id. See Validation.

const { document: post } = await PostsModel.insertOne({
  title: 'Hello, MongoDB',
  authorId: ada._id
})
// { _id: ObjectId('…'), title: 'Hello, MongoDB', body: '', status: 'draft', tags: [], authorId, … }

replaceOne()

replaceOne(filter, replacement, options?) replaces the first document that matches the filter, with the replacement validated like an insert, and returns the driver's UpdateResult. See Validation.

await PostsModel.replaceOne(
  { _id: post._id },
  { title: 'Hello again', authorId: ada._id, createdAt: post.createdAt }
)

updateMany()

updateMany(filter, update, options?) updates every document that matches the filter, validated like updateOne(), and returns the driver's UpdateResult. See Validation.

await PostsModel.updateMany({ status: 'draft' }, { $set: { status: 'published' } })

updateOne()

updateOne(filter, update, options?) updates the first document that matches the filter, with each value validated by the schema of its path, and returns the driver's UpdateResult. See Validation.

await PostsModel.updateOne({ _id: post._id }, { $addToSet: { tags: 'mongodb' } })

Cursors

find() and aggregate() return a cursor that starts on the first read. See Operations.

ModelCursor

ModelCursor<T> is the cursor of aggregate(), and the base of ModelFindCursor. Unlike the driver's cursors, it can be awaited.

for await (const draft of PostsModel.find({ status: 'draft' })) {
  console.log(draft.title)
}
MemberWhat it does
await cursorReads every document into an array, like toArray().
for awaitReads the documents batch by batch, as the server returns them. A loop that ends early closes it.
toArray()Reads every document. The cursor is exhausted afterwards.
close()Frees the cursor on the server before it is exhausted.

ModelFindCursor

ModelFindCursor is the cursor of find(): a ModelCursor that chains sort, limit, skip and project, like the driver's FindCursor. Called after the cursor starts, they throw an Error.

const latest = await PostsModel.find({ status: 'published' })
  .sort({ createdAt: -1 })
  .limit(10)
  .project({ title: 1, createdAt: 1 })
// Array<{ _id: ObjectId; title: string; createdAt: Date }>
MethodWhat it does
sort(sort)Sets the sort, with its keys checked against the schema.
limit(n)Sets the maximum number of documents.
skip(n)Sets the number of documents to skip.
project(projection)Replaces the projection of find, checked against the schema, and returns a cursor typed by it.

Plugins

definePlugin()

definePlugin(plugin) defines a plugin and returns it, typed by its definition. Write it inside a function that takes the plugin's configuration. See Writing plugins, and Plugins to use one.

/** Adds exists() to a model: true when a document matches the filter. */
export function exists() {
  return definePlugin({

    id: 'exists',

    methods: {

      async exists<M extends AnyModel, const F extends FilterOf<M, F>>(
        this: M,
        filter: F,
        ...options: ReadOptionsArg<M>
      ) {
        return (await this.countDocuments(filter, { ...options[0], limit: 1 })) > 0
      }
    }
  })
}

const commentSchema = z.object({

  postId: mz.objectId(),

  body: z.string()
})

const CommentsModel = mongozod({

  collection: 'comments',

  db,

  schema: commentSchema,

  plugins: [exists()]
})

const hasComments = await CommentsModel.exists({ postId: post._id })
KeyWhat it is
idIdentifies the plugin in error messages. Required.
fieldsA Zod shape with the fields the schema of the model must declare, with a compatible type.
fillsThe names of the fields that its before hooks fill on inserts and replacements, which these then take as optional.
optionsA z.object per group with the options the plugin adds to its operations.
contextA z.object per group with what each call must pass in context.
hooksbefore and after hooks per operation or per group. See OperationHooks.
middlewareA function that wraps the whole operation. See MiddlewareInput.
methodsMethods added to the model, each with this as a generic AnyModel.

Types for documents

See TypeScript for these types in services.

Input

Input<S> is the input of a schema: a document to write, before its defaults and transforms. For what an insert of a model takes, use InsertOf.

type NewUser = Input<typeof userSchema>
// { _id?: string | ObjectId; name: string; email: string }

Output

Output<S> is the output of a schema: a document as stored.

DocOf

DocOf<M> is a document of a model as stored, with its _id, without virtuals.

type Post = DocOf<typeof PostsModel>

ReadOf

ReadOf<M, O> is a document as a read of the model returns it, given the projection, the populate and the filter of O, which is optional.

type PostWithAuthor = ReadOf<
  typeof PostsModel,
  { projection: { title: 1; authorId: 1 }; populate: { authorId: { projection: { name: 1 } } } }
>
// { _id: ObjectId; title: string; authorId: { _id: ObjectId; name: string } | null }

InsertOf

InsertOf<M> is what an insert or a replacement of the model takes: the input of its schema, with the fields of timestamps and those its plugins fill optional.

function createPost(input: InsertOf<typeof PostsModel>) {
  return PostsModel.insertOne(input)
}

Types for queries and options

These types take the model as typeof. Declare a filter, an update or a sort as a const type parameter that extends its type, so it is checked key by key. See TypeScript.

FilterOf

FilterOf<M, F> is a filter on the documents of a model.

function listPosts<const F extends FilterOf<typeof PostsModel, F>>(filter: F) {
  return PostsModel.find(filter)
}

UpdateOf

UpdateOf<M, U, F, O> is an update of the documents of a model, given its filter F and, optionally, its options O. It rejects the fields of timestamps, as the model does.

function updatePosts<
  const F extends FilterOf<typeof PostsModel, F>,
  const U extends UpdateOf<typeof PostsModel, U, F>
>(filter: F, update: U) {
  return PostsModel.updateMany(filter, update)
}

UpdateOptionsOf

UpdateOptionsOf<M, U, O> is the options of an update U that a function passes to the model: their arrayFilters are checked against the identifiers of the update, and their sort, projection and populate against the schema. They cannot ask for includeResultMetadata, so a findOneAndUpdate that takes them returns the document.

function updatePostsWith<
  const F extends FilterOf<typeof PostsModel, F>,
  const U extends UpdateOf<typeof PostsModel, U, F, O>,
  const O extends UpdateOptionsOf<typeof PostsModel, U, O>
>(filter: F, update: U, options?: O) {
  return PostsModel.updateMany(filter, update, options)
}

await updatePostsWith({ status: 'draft' }, { $set: { status: 'published' } }, { upsert: false })

SortOf

SortOf<M, Sort> is a sort on the documents of a model, with each key of Sort checked against the schema.

function newestPosts<const Sort extends SortOf<typeof PostsModel, Sort>>(sort: Sort) {
  return PostsModel.find({}, { sort }).limit(10)
}

await newestPosts({ createdAt: -1 })

BulkWriteOf

BulkWriteOf<M, Ops, O> is the operations of a bulkWrite of a model, each checked like the model's, given the options O of the call.

function writePosts<const Ops extends BulkWriteOf<typeof PostsModel, Ops>>(operations: Ops) {
  return PostsModel.bulkWrite(operations)
}

await writePosts([{ deleteMany: { filter: { status: 'draft' } } }])

FindOptionsOf

FindOptionsOf<M> is the options of a find or a findOne that a service takes from a router: the driver's, a sort of the paths of the document and, on a model with references, a populate as mz.populate outputs it, plus the options and the context its plugins ask for. It leaves projection out, because a projection that is not a literal types every field as optional. It is not generic, so each reference it allows reads as its id, the projected document or null.

function listRecentPosts<const F extends FilterOf<typeof PostsModel, F>>(
  filter: F,
  options: FindOptionsOf<typeof PostsModel> = {}
) {
  const { sort = { createdAt: -1 }, ...rest } = options

  return PostsModel.find(filter, { ...rest, sort })
}

await listRecentPosts({ status: 'published' }, { limit: 20, populate: { authorId: { projection: { name: 1 } } } })

ReadOptionsArg

ReadOptionsArg<M> is the options argument of the reads of a model, as a tuple for a rest parameter: optional, or required with the context its plugins ask for. Plugin methods use it to pass the options on, as in definePlugin().

UpdateOptionsArg

UpdateOptionsArg<M> is the options argument of the updates of a model, like ReadOptionsArg.

const publishing = definePlugin({

  id: 'publishing',

  methods: {

    publish<M extends AnyModel, const F extends FilterOf<M, F>>(
      this: M,
      filter: F,
      ...options: UpdateOptionsArg<M>
    ) {
      return this.updateMany(filter, { $set: { status: 'published' } }, ...options)
    }
  }
})

PipelineOf

PipelineOf<M, P> is an aggregation pipeline on a model, whose $match stages at its start are checked like a filter.

Types for services

The type of a method of a service, given with satisfies: an arrow function takes the type parameters that check what it passes to the model, and its result is its own. See Repositories.

TypeThe method takes
FilterMethod<M, O>A filter and options O, the options of a find unless you give others.
UpdateMethod<M>A filter, an update and its options, checked like UpdateOptionsOf.
ReplaceMethod<M, O>A filter and the document that replaces the one it matches.
BulkWriteMethod<M>The operations of a bulkWrite and its options.
AggregateMethod<M>An aggregation pipeline and its options.
DistinctMethod<M>A path for a distinct and a filter.

FilterMethod

const recentPosts = {

  list: (filter, options = {}) => PostsModel.find(filter, options),

  count: (filter, options?) => PostsModel.countDocuments(filter, options)
} satisfies {

  list: FilterMethod<typeof PostsModel>

  count: FilterMethod<typeof PostsModel, CountDocumentsOptions>
}

await recentPosts.list({ status: 'published' }, { limit: 20 })

Driver options with a sort, such as FindOneAndDeleteOptions, take the model's: an object of the paths of the document, not a string or an array.

UpdateMethod

UpdateMethod<M> types a method that takes a filter, an update and the options of the update, with their arrayFilters checked against its identifiers, and can add options of its own, such as { ...options, returnDocument: 'after' }. UpdateMethod<M, O> types one that takes no options and passes its own, O, such as { upsert: true }, which the check of the update reads.

ReplaceMethod

ReplaceMethod<M, O> types a method that takes a filter and a replacement, typed as InsertOf, and the options of a replaceOne unless you give others, with the model's sort.

BulkWriteMethod

BulkWriteMethod<M> types a method that takes the operations of a bulkWrite, each checked like BulkWriteOf.

AggregateMethod

AggregateMethod<M> types a method that takes an aggregation pipeline, checked like PipelineOf.

DistinctMethod

DistinctMethod<M> types a method that takes a path and a filter for a distinct.

Types of the model

MongozodOptions

MongozodOptions is the type of the options of mongozod(), for a function that builds them.

ModelSchema

ModelSchema is what the schema option takes: a z.object, or a z.discriminatedUnion of objects or of other discriminated unions, up to three levels, of Zod or Zod Mini. See Document types.

SyncResult

SyncResult is what sync() returns: indexes, the names of the declared indexes, and extraIndexes, the driver's descriptions of the indexes the model doesn't declare. See Sync.

Types for plugins

These types describe what a plugin receives. See Writing plugins for how they fit together.

Plugin

Plugin<Id, Fields, Options, Context, Methods, Fills> is the type of a plugin, which definePlugin() infers. See Annotate a factory.

Hooks

Hooks<C> is the type of the hooks of a plugin: an OperationHooks per operation or per group, where C is the plugin's context.

OperationHooks

OperationHooks<O, C> is the before({ operation, args, context, model }) and the after({ operation, args, context, model, result }) of an operation or a group. before may return { args } or { result }, and after may return { result }. See Hooks.

Group

Group is a group of operations, which a plugin declares its options, its context and its hooks for once:

GroupOperations
readfind, findOne, countDocuments, distinct
insertinsertOne, insertMany
updateupdateOne, updateMany, replaceOne, findOneAndUpdate, findOneAndReplace
deletedeleteOne, deleteMany, findOneAndDelete
bulkbulkWrite

aggregate and estimatedDocumentCount have no group: only their own hooks and the middleware run on them.

Operation

Operation is the name of an operation that plugins see: every operation of the model. sync() and model.collection don't go through the plugins.

OperationArgs

OperationArgs maps each operation to its arguments as plugins see them, such as { filter, update, options } for updateOne, with documents typed as the driver's Document.

OperationResults

OperationResults maps each operation to its result as plugins see it. find and aggregate give one batch of documents at a time.

MiddlewareInput

MiddlewareInput<C> is what a middleware receives: { operation, args, context, model } and next(), which runs the operation and returns its result. See Middleware.

NextOptions

NextOptions is what next() takes in a middleware: { session? }, a ClientSession of the driver.

AnyModel

AnyModel is a model as plugins see it: db, name, schema, indexes, a collection with its createIndexes, and the operations, which take and return Documents and go through every plugin again. A plugin method declares this as a type parameter that extends it.

Command line

agents-md

npx mongozod agents-md [--output <file>] writes the MongoZod rules for coding agents to AGENTS.md, or to the file of --output (-o), such as CLAUDE.md. It creates the file when it is missing, replaces the block between its markers when the file has it, appends it otherwise, and leaves the rest of the file as it is. --help (-h) prints the usage. See AI agents.

Last updated on

MongoZod is an independent open-source project. It is not affiliated with, endorsed by or sponsored by MongoDB, Inc. MongoDB® is a registered trademark of MongoDB, Inc.

On this page