Schema Validation with $jsonSchema

Intermediate
12 min

Schema Validation with $jsonSchema

"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.

How Validation Works

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.

Writing a $jsonSchema

$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:

javascript
address: { bsonType: "object", required: ["city", "country"], properties: { city: { bsonType: "string" }, country: { bsonType: "string", pattern: "^[A-Z]{2}$", description: "ISO 3166-1 alpha-2 code" } } }

Reading a Validation Error

javascript
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.

Adding Validation to an Existing Collection

Use collMod, and check first how many current documents would fail:

javascript
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 rules

A safe rollout is: warn + moderate, watch the server log, fix or migrate offending documents, then switch to error + strict.

Beyond JSON Schema: Query-Operator Rules

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:

javascript
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.

Common Mistakes

  • Using bsonType: "number" when an integer is meant. "number" accepts doubles and decimals too.
  • Assuming required rejects null. A field set to null is present; add a bsonType to exclude it.
  • Forgetting _id with additionalProperties: false, which rejects every insert.
  • Adding strict + error rules to a collection with legacy documents, breaking updates to the old data.
Quick Quiz
Question 1 of 3

Which setting lets you deploy a validator that only logs violations?

Key Takeaways

  • A collection 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.
  • Errors since 5.0 list every failing rule; use description to make them readable.
  • Combine $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.

Schema Validation with $jsonSchema - MongoDB | CodeYourCraft | CodeYourCraft