Conversion Mapping
Conversion Mapping
@nullix/zod-mongoose maps supported Zod types and checks to Mongoose SchemaType options. Zod transforms are parsed during validation, but their output is not assigned to the stored document; see Validate Documents.
For a stored date, use z.date() or z.coerce.date(). For an ISO string you want to keep as a string, use z.iso.datetime() or z.iso.date(). Avoid z.string().datetime() and z.string().date() for persisted fields: they map to Mongoose Date, then fail the default Zod validation because Mongoose casts the value to a Date while Zod expects a string.
Automatic Validation Mapping
The following Zod checks are directly converted to Mongoose SchemaType options:
| Zod Check | Mongoose Option | Target Types |
|---|---|---|
.min(n) | minlength: n | z.string() |
.max(n) | maxlength: n | z.string() |
.length(n) | minlength: n, maxlength: n | z.string() |
.regex(re) | match: re | z.string() |
.trim() | trim: true | z.string() |
.toLowerCase() | lowercase: true | z.string() |
.toUpperCase() | uppercase: true | z.string() |
.min(n) / .positive() | min: n | z.number(), z.date() |
.max(n) / .negative() | max: n | z.number(), z.date() |
.uuid() | UUID format validation; normally String | z.string() |
.datetime() | type: Date; see date caveat above | z.string() |
.date() | type: Date; see date caveat above | z.string() |
.time() | type: String | z.string() |
Type Conversion Table
The following table shows how Zod types are mapped to Mongoose types by default.
| Zod Type | Mongoose Type | Notes |
|---|---|---|
z.string() | String | Required by default unless .optional() is used. |
z.uuid() / z.string().uuid() | String by default | UUID format is still checked by Zod. A configured global Mongoose instance may select its native UUID type. |
z.iso.datetime() / z.iso.date() | String | Keeps the ISO text and passes default post-validation. |
z.string().datetime() / z.string().date() | Date | Mongoose casts to Date, so default post-validation fails; use one of the alternatives above. |
z.number() | Number | |
z.boolean() | Boolean | |
z.date() | Date | |
z.bigint() | BigInt | Fallback to Number if BigInt is not supported. Use withMongoose with type: 'Long' for 64-bit integers. |
z.enum() | String | Includes Mongoose enum validation. |
z.nativeEnum() | String | Includes Mongoose enum validation. |
z.string().trim() | String | Automatically sets trim: true. |
z.string().toLowerCase() | String | Automatically sets lowercase: true. |
z.string().min(5) | String | Automatically sets minlength: 5. |
z.array() | [] | Mapped to a Mongoose array of the inner type. |
z.set() | [] | Mapped to a Mongoose array of the value type. |
z.tuple() | [] | Mapped to a Mongoose array of the first item's type. |
z.record() | Object | Mapped to a Mongoose nested object (POJO) with dynamic keys. |
z.map() | Map | Mapped to a Mongoose Map with of type. |
z.object() | Subdocument | Unknown keys are stripped. Nested objects have their own _id by default. See Nested Object IDs. |
z.strictObject() | Subdocument with strict: 'throw' | Rejects unknown keys; Mongoose-generated IDs and version keys are excluded from Zod document validation unless declared. |
z.looseObject() | Subdocument with strict: false | Preserves unknown keys. |
.catchall(valueSchema) | Subdocument with strict: false | Preserves unknown keys and validates values in the document validation hook. Dynamic keys do not get individual Mongoose paths. |
.required() / exactOptional() fields | Underlying field type | Required fields use Mongoose required where compatible; exact optional fields reject explicit undefined on document assignment. |
z.intersection() | Subdocument | Merges the definitions of both branches into a single subschema. |
z.union() | Schema.Types.Union (primitives) or Nested Object (objects) | Mapped to Union for primitives, merged into an object for z.object() unions. Others fallback to Mixed. |
z.xor() | mongoose.Schema.Types.Mixed | Non-inclusive union. Maps to Mixed with a custom Zod-based validator to enforce mutual exclusivity. |
z.discriminatedUnion() | Mongoose Discriminator | Maps to native Mongoose discriminators. Common fields are automatically moved to the base schema. |
zObjectId() | mongoose.Schema.Types.ObjectId | Specialized helper for ObjectIds. By default, it is omitted from the generated Mongoose schema to let Mongoose handle its auto-generation. |
zBuffer() | mongoose.Schema.Types.Buffer | Specialized helper for Buffers. |
zPoint() (coming in v3.2) | GeoJSON Point subdocument | Two numeric coordinates; no nested _id by default. See GeoJSON helpers. |
zPolygon() (coming in v3.2) | GeoJSON Polygon subdocument | One or more closed rings; no nested _id by default. |
zRef() | mongoose.Schema.Types.ObjectId | Helper for fields that can be either an ObjectId (unpopulated) or a full object (populated). During Zod validation, it accepts both but always outputs an ObjectId. |
z.instanceof(Buffer) | mongoose.Schema.Types.Buffer | |
z.instanceof(ObjectId) | mongoose.Schema.Types.ObjectId | |
z.literal() | String / Number / Boolean | Mapped to the literal's type with a Mongoose enum constraint. |
z.any() / z.unknown() | mongoose.Schema.Types.Mixed | Fallback for unhandled types. |
Unhandled and Unsupported Zod Types
The following Zod types are currently not explicitly handled or are unsupported by nature and will fall back to Mongoose.Schema.Types.Mixed unless a custom type is provided via withMongoose.
| Zod Type | Current Status | Recommended Alternative |
|---|---|---|
z.readonly() | Base Type | Automatically unwrapped, applies readOnly: true metadata. |
z.promise() | Unsupported | Not applicable for database schemas |
z.function() | Unsupported | Not applicable for database schemas |
z.void() / z.never() | Unsupported |
Note: Types like
z.branded(),z.readonly(),z.pipeline(),z.preprocess(), andz.transform()are automatically unwrapped to their underlying base type during conversion.
See Object Modes and Unknown Keys for nested objects, option overrides, and query-update caveats.