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.
| Option | Type | Default | What it does |
|---|---|---|---|
collection | a string | required | The name of the collection. See Models. |
db | the driver's Db, or a function that returns one | required | The database, or a function called on every operation. See Models. |
schema | a z.object, or a z.discriminatedUnion of objects, of Zod or Zod Mini | required | The document as stored. See Schemas and Document types. |
timestamps | true, or { createdAt?, updatedAt? } with a field name or false each | off | Sets createdAt on insert and updatedAt on every write; a field the object leaves out keeps its default name. See Timestamps. |
indexes | a function that takes index and returns a list of index(key, options) calls | none | The indexes sync() creates, or the autoIndex plugin on the first operation. See Sync. |
jsonSchema | true, or { validationLevel?, validationAction? } | not applied | A $jsonSchema generated from the schema, which sync() applies. See Sync. |
references | an object whose keys are paths and whose values are targets | none | The fields that populate replaces with a document. See Populate. |
virtuals | an object whose values are Zod schemas | none | Fields that reads compute from others. See Virtuals. |
plugins | an 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.objector az.discriminatedUnionof objects, such as a.transform()of the whole document; a field name has a.or starts with$; or the schema stores az.set()or az.map(), which the driver doesn't read back. timestampsnames a field that isn't in every type of document, or doesn't take aDate.- An index has no key, or one of its keys or
weightsisn'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()| Property | What it is |
|---|---|
schema | The schema of each document. |
name | The name of the collection. |
indexes | The indexes the model declares, as the driver's IndexDescriptions, which sync() and autoIndex create. |
db | The driver's Db: the one the db option returns now, when it is a function. |
collection | The driver's Collection of that database, typed with the output of the schema. It skips the validation, the plugins and timestamps. |
virtuals | The virtuals option, or {}. |
references | The 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 declareOperations
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 } | nullfindOneAndDelete()
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)
}| Member | What it does |
|---|---|
await cursor | Reads every document into an array, like toArray(). |
for await | Reads 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 }>| Method | What 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 })| Key | What it is |
|---|---|
id | Identifies the plugin in error messages. Required. |
fields | A Zod shape with the fields the schema of the model must declare, with a compatible type. |
fills | The names of the fields that its before hooks fill on inserts and replacements, which these then take as optional. |
options | A z.object per group with the options the plugin adds to its operations. |
context | A z.object per group with what each call must pass in context. |
hooks | before and after hooks per operation or per group. See OperationHooks. |
middleware | A function that wraps the whole operation. See MiddlewareInput. |
methods | Methods 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.
| Type | The 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:
| Group | Operations |
|---|---|
read | find, findOne, countDocuments, distinct |
insert | insertOne, insertMany |
update | updateOne, updateMany, replaceOne, findOneAndUpdate, findOneAndReplace |
delete | deleteOne, deleteMany, findOneAndDelete |
bulk | bulkWrite |
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.