Schema validation with $jsonSchema
Enforce required fields, types, ranges and enumerations on the server, and roll validation out safely with validationLevel and validationAction.
- Write a $jsonSchema validator
- Add validation to an existing collection with collMod
- Choose validationLevel (strict, moderate) and validationAction (error, warn)
- Read validation error details
conceptThe concept
db.createCollection("payments", { validator: { $jsonSchema: {
bsonType: "object",
required: ["orderId", "amount", "currency", "status"],
properties: {
amount: { bsonType: ["double", "decimal", "int"], minimum: 0 },
currency: { enum: ["INR", "USD", "EUR"] },
status: { enum: ["created", "captured", "refunded"] },
card: { bsonType: "object", properties: { last4: { bsonType: "string", pattern: "^[0-9]{4}$" } } }
},
additionalProperties: true
} }, validationLevel: "strict", validationAction: "error" })
validationLevel: "strict"(default) validates all inserts and updates;"moderate"skips updates to documents that were already invalid, which helps when introducing rules on existing data.validationAction: "error"rejects with code 121 (DocumentValidationFailure) and explains which rule failed;"warn"accepts the write and logs a warning.- Validators can also use query operators:
{ $or: [{ phone: { $type: "string" } }, { email: { $regex: /@/ } }] }. - MongoDB 9.0+
validationLevel: "constraint"goes further thanstrict: MongoDB guarantees every document in the collection satisfies the rules, so readers can trust old data too. The simulator in this course is 8.0 and does not accept it.
whyWhy it matters
Flexible schemas still need guarantees: a payment without an amount is a bug, not flexibility. Server-side validation catches errors from every client, script and service, not just the one with good application-level checks.
Use caseReal-world scenario
A team adds validation to a 5-year-old collection. Step 1: validationAction: "warn" and watch logs for a week. Step 2: fix the producers and clean the data. Step 3: switch to "error" with validationLevel: "moderate", and later "strict".
Try itExamples
Run and edit these in the interactive lesson.
db.lab_payments.drop()
db.createCollection("lab_payments", { validator: { $jsonSchema: {
bsonType: "object",
required: ["orderNo", "amount", "currency", "status"],
properties: {
amount: { bsonType: ["double", "int", "decimal"], minimum: 0 },
currency: { enum: ["INR", "USD", "EUR"] },
status: { enum: ["created", "captured", "refunded"] }
} } } })
db.lab_payments.insertOne({ orderNo: "ORD-1", amount: 99.5, currency: "INR", status: "created" })db.lab_payments.insertOne({ orderNo: "ORD-2", amount: -5, currency: "GBP" })db.runCommand({ collMod: "customers", validator: { $jsonSchema: { required: ["email", "tier"], properties: { tier: { enum: ["bronze", "silver", "gold", "platinum"] } } } }, validationLevel: "moderate", validationAction: "warn" })
db.customers.deleteOne({ _id: 9001 })
db.customers.insertOne({ _id: 9001, name: "No email" })
db.getCollectionInfos({ name: "customers" })[0].optionsinternalsUnder the hood
The validator is stored in collection options and evaluated on the primary during each write. It does not apply to existing documents until they are updated. bypassDocumentValidation lets privileged users skip validation for migrations. Validation is cheap compared with the write itself, but very large schemas with many patterns add CPU per write.
Avoid / preferDo this, not that
db.runCommand({ collMod: "customers", validator: { $jsonSchema: schema },
validationAction: "error" }) // on day one, on old datadb.runCommand({ collMod: "customers", validator: { $jsonSchema: schema },
validationAction: "warn" })
db.customers.countDocuments({ $nor: [{ $jsonSchema: schema }] }) // fix these, then "error"Switching straight to error breaks every update to an existing invalid document. Warn first, find the offenders with $nor, fix them, then enforce.
{ $jsonSchema: { properties: { email: { bsonType: "string" } } } }{ $jsonSchema: { required: ["email"],
properties: { email: { bsonType: "string", pattern: "^[^@]+@[^@]+$" } } } }properties only checks fields that are present, so a document without email passes. List mandatory fields in required.
PitfallsCommon mistakes
- Turning on strict/error validation on a collection with existing invalid documents and breaking every update to them.
- Over-specifying (
additionalProperties: falseeverywhere) and blocking legitimate schema evolution. - Forgetting that numbers from JavaScript are doubles:
bsonType: "int"rejects5sent as a double by some drivers. - Relying only on validation for business rules that span documents (it validates one document at a time).
LimitsWhen not to use it
Validators check one document at a time. Uniqueness needs a unique index, and references to other collections need application logic. Very complex regex patterns cost CPU on every write. Users with the bypassDocumentValidation privilege can skip validation entirely.
SecuritySecurity notes
Do not grant bypassDocumentValidation to application roles. On collections that hold privileges or money, add additionalProperties: false so unexpected fields such as isAdmin are rejected instead of stored.
ChallengeTry it yourself
Create a scratch collection members_lab that requires email (a string containing @) and tier (bronze, silver, gold or platinum). Insert one valid document and show that an invalid one is rejected.
Show one solution
db.members_lab.drop()
db.createCollection("members_lab", { validator: { $jsonSchema: {
bsonType: "object", required: ["email", "tier"],
properties: { email: { bsonType: "string", pattern: "@" }, tier: { enum: ["bronze", "silver", "gold", "platinum"] } }
} } })
db.members_lab.insertOne({ email: "a@x.com", tier: "gold" })
try { db.members_lab.insertOne({ email: "nope", tier: "diamond" }) } catch (e) { print("rejected:", e.code) }
db.members_lab.countDocuments()InterviewQuestions you should be able to answer
How do you introduce schema validation on a large existing collection?
Write the validator, run it as a query to count non-conforming documents (find({ $nor: [validator] })), set validationAction: "warn" to observe, fix producers and backfill data in batches, then switch to error with validationLevel: "moderate", and finally strict once all documents conform.
QuizCheck your understanding
4 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.