"Schemaless" describes what MongoDB permits, not what a production system should allow. Once several services write to the same collection, one typo — emial, a price stored as a string, a missing createdAt — becomes a bug that surfaces weeks later in a report. Schema validation lets the server enforce structure on every insert and update. After this lesson you will be able to write $jsonSchema rules, read validation errors, add rules to existing collections safely, and combine JSON Schema with query-operator checks.
A collection can carry a validator: a query document that every inserted or updated document must match. It is checked on the server, regardless of which driver or shell wrote the data. Two settings control its behaviour:
| Option | Values | Meaning |
|---|---|---|
| validationLevel | strict (default), moderate, off | moderate exempts updates to documents that already violate the rules |
| validationAction | error (default), warn | warn logs violations instead of rejecting, so you can observe before enforcing |
Existing documents are never re-checked when you add a validator; only new writes are.
$jsonSchema supports the JSON Schema draft 4 vocabulary with BSON extensions. The keywords you will use most:
| Keyword | Purpose |
|---|---|
| bsonType | BSON type: "string", "int", "long", "double", "decimal", "number", "date", "objectId", "array", "object", "bool" |
| required | array of field names that must be present |
| properties | per-field rules |
| enum | allowed values |
| minimum / maximum, minLength / maxLength, pattern | numeric, string and regex constraints |
| items, minItems, maxItems, uniqueItems | array element rules |
| additionalProperties: false | reject unknown fields (list _id in properties if you use this) |
| description | text returned in the error for that rule |
Nested documents are validated by giving the property bsonType: "object" and its own properties:
address: {
bsonType: "object",
required: ["city", "country"],
properties: {
city: { bsonType: "string" },
country: { bsonType: "string", pattern: "^[A-Z]{2}$", description: "ISO 3166-1 alpha-2 code" }
}
}db.users.insertOne({ email: "not-an-email", name: "Ada", createdAt: "2026-03-01" })
// MongoServerError: Document failed validation
// errInfo: { failingDocumentId: ObjectId('...'), details: { operatorName: '$jsonSchema',
// schemaRulesNotSatisfied: [ { operatorName: 'properties', propertiesNotSatisfied: [
// { propertyName: 'email', details: [ { operatorName: 'pattern', ... } ] },
// { propertyName: 'createdAt', details: [ { operatorName: 'bsonType', ... } ] } ] } ] } }Since MongoDB 5.0 the error lists every failing rule (drivers expose it as error.errInfo), so a description on each rule pays off.
Use collMod, and check first how many current documents would fail:
const schema = { bsonType: "object", required: ["email"], properties: { email: { bsonType: "string" } } };
db.users.countDocuments({ $nor: [{ $jsonSchema: schema }] }) // documents that would fail
db.runCommand({
collMod: "users",
validator: { $jsonSchema: schema },
validationLevel: "moderate",
validationAction: "warn"
})
db.getCollectionInfos({ name: "users" })[0].options.validator // inspect the current rulesA safe rollout is: warn + moderate, watch the server log, fix or migrate offending documents, then switch to error + strict.
Because the validator is an ordinary query document, any query operator can join the rules. This is how you express cross-field constraints, which JSON Schema cannot:
validator: {
$and: [
{ $jsonSchema: { required: ["price", "discount"] } },
{ $expr: { $lte: ["$discount", "$price"] } },
{ status: { $in: ["draft", "published", "archived"] } }
]
}Server validation complements, rather than replaces, application-level checks that give users friendlier messages: it is the last line of defence that catches every writer, including scripts. Privileged repairs can skip it with bypassDocumentValidation.
bsonType: "number" when an integer is meant. "number" accepts doubles and decimals too.required rejects null. A field set to null is present; add a bsonType to exclude it._id with additionalProperties: false, which rejects every insert.strict + error rules to a collection with legacy documents, breaking updates to the old data.Which setting lets you deploy a validator that only logs violations?
validator is enforced on the server for every insert and update from any client.$jsonSchema covers types (bsonType), required fields, enums, ranges, patterns, arrays and nested objects.validationAction: "warn" and validationLevel: "moderate" enable a safe, gradual rollout with collMod.description to make them readable.$jsonSchema with query operators such as $expr for cross-field rules.Next lesson: Multi-Document Transactions — make several writes succeed or fail together with ACID guarantees.