Schema Validation
Learn how to enforce data quality in MongoDB using $jsonSchema validation rules — required fields, types, value ranges, and string patterns.
Schema Validation
MongoDB is schema-flexible by default — you can insert any document into any collection without defining a structure first. This is great for getting started quickly, but in a real application, you need to ensure data quality. A student document without a name, or an age stored as a string, or a grade value that does not match your expected values — these cause bugs that are hard to track down.
Schema validation lets you define rules at the database level. MongoDB enforces these rules on every insert and update. If a document does not follow the rules, MongoDB rejects it with a clear error.
How Validation Works
You define validation rules when creating a collection, or add them to an existing collection using collMod. Rules are written using $jsonSchema — a standard way to describe the shape and constraints of a document.
When a document is inserted or updated, MongoDB checks it against the schema. If it passes — the operation succeeds. If it fails — MongoDB rejects it and returns an error.
Creating a Collection with Validation
Use db.createCollection() with a validator option:
db.createCollection("students", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "age", "grade", "enrolled"],
properties: {
name: {
bsonType: "string",
description: "Student name is required and must be a string"
},
age: {
bsonType: "int",
minimum: 5,
maximum: 25,
description: "Age must be an integer between 5 and 25"
},
grade: {
bsonType: "string",
enum: ["9th", "10th", "11th", "12th"],
description: "Grade must be one of 9th, 10th, 11th, 12th"
},
enrolled: {
bsonType: "bool",
description: "Enrolled must be a boolean"
},
email: {
bsonType: "string",
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
description: "Email must be a valid email address"
}
}
}
}
})Now every insert into students must follow these rules.
$jsonSchema — Key Properties
bsonType
Specifies the expected BSON type of the field:
| bsonType value | Matches |
|---|---|
"string" | String values |
"int" | 32-bit integers |
"double" | Decimal numbers |
"bool" | Boolean true/false |
"date" | Date values |
"objectId" | ObjectId values |
"array" | Array values |
"object" | Embedded document |
"null" | Null values |
required
An array of field names that must be present in every document:
required: ["name", "age", "grade", "enrolled"]If any of these fields is missing, the insert is rejected.
properties
Defines rules for individual fields:
properties: {
fieldName: {
bsonType: "string",
// ... other rules
}
}minimum and maximum
For number fields — the allowed range of values:
age: {
bsonType: "int",
minimum: 5,
maximum: 25
}minLength and maxLength
For string fields — the allowed length range:
name: {
bsonType: "string",
minLength: 2,
maxLength: 100
}enum
A list of allowed values. Only these exact values are accepted:
grade: {
bsonType: "string",
enum: ["9th", "10th", "11th", "12th"]
}pattern
A regular expression the string must match:
email: {
bsonType: "string",
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
}Validating Embedded Documents
You can validate nested objects too:
db.createCollection("students", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "age", "grade"],
properties: {
name: { bsonType: "string" },
age: { bsonType: "int", minimum: 5, maximum: 25 },
grade: {
bsonType: "string",
enum: ["9th", "10th", "11th", "12th"]
},
// Validate the embedded address object
address: {
bsonType: "object",
required: ["city", "country"],
properties: {
city: { bsonType: "string" },
country: { bsonType: "string" }
}
},
// Validate the embedded guardian object
guardian: {
bsonType: "object",
required: ["name", "phone"],
properties: {
name: { bsonType: "string" },
phone: { bsonType: "string" },
relation: {
bsonType: "string",
enum: ["Father", "Mother", "Brother", "Sister", "Uncle", "Aunt", "Other"]
}
}
}
}
}
}
})Validating Arrays
Validate an array field and the type of its elements:
subjects: {
bsonType: "array",
minItems: 1,
maxItems: 10,
items: {
bsonType: "string"
},
description: "Subjects must be an array of 1 to 10 strings"
}Validate an array of objects:
grades: {
bsonType: "array",
items: {
bsonType: "object",
required: ["subject", "score"],
properties: {
subject: { bsonType: "string" },
score: {
bsonType: "int",
minimum: 0,
maximum: 100
},
semester: {
bsonType: "int",
enum: [1, 2]
}
}
}
}What Happens When Validation Fails
If you insert a document that violates the schema:
// Missing required field 'grade'
db.students.insertOne({
name: "Ali Hassan",
age: 16,
enrolled: true
})MongoDB throws:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId("..."),
details: {
operatorName: "$jsonSchema",
schemaRulesNotSatisfied: [
{
operatorName: "required",
specifiedAs: { required: ["name", "age", "grade", "enrolled"] },
missingProperties: ["grade"]
}
]
}
}The error tells you exactly which rule failed and why. Wrap inserts in try/catch in your Node.js code:
try {
await students.insertOne({
name: "Ali Hassan",
age: 16,
enrolled: true
// grade is missing
});
} catch (error) {
if (error.code === 121) {
console.error("Document failed validation:", error.errInfo.details);
}
}Error code 121 means document validation failure.
Adding Validation to an Existing Collection
If your collection already exists and has data, use collMod to add validation:
db.runCommand({
collMod: "students",
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "age", "grade"],
properties: {
name: { bsonType: "string" },
age: { bsonType: "int", minimum: 5, maximum: 25 },
grade: {
bsonType: "string",
enum: ["9th", "10th", "11th", "12th"]
},
enrolled: { bsonType: "bool" }
}
}
},
validationLevel: "moderate", // only validate new/updated docs
validationAction: "error" // reject invalid documents
})validationLevel
Controls which documents are validated:
| Level | Behavior |
|---|---|
"strict" | Validate all inserts and updates — default |
"moderate" | Validate inserts and updates to documents that already pass validation. Skip validation for documents that already fail. |
Use "moderate" when adding validation to an existing collection that may have some invalid documents. This way, old invalid documents are not blocked from being updated.
validationAction
Controls what happens when validation fails:
| Action | Behavior |
|---|---|
"error" | Reject the document — default |
"warn" | Allow the document but log a warning |
Use "warn" when you want to see what would fail before enforcing strict rules — useful during migration.
Viewing Validation Rules
To see the validation rules on a collection:
db.getCollectionInfos({ name: "students" })Output includes the full validator definition.
Removing Validation
To remove all validation rules from a collection:
db.runCommand({
collMod: "students",
validator: {},
validationLevel: "off"
})Complete School System Validation
Here is the full validation schema for our students collection:
db.createCollection("students", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "age", "grade", "enrolled", "enrollmentDate"],
properties: {
name: {
bsonType: "string",
minLength: 2,
maxLength: 100,
description: "Required. Student full name."
},
age: {
bsonType: "int",
minimum: 5,
maximum: 25,
description: "Required. Age between 5 and 25."
},
grade: {
bsonType: "string",
enum: ["9th", "10th", "11th", "12th"],
description: "Required. Must be a valid grade."
},
enrolled: {
bsonType: "bool",
description: "Required. Enrollment status."
},
enrollmentDate: {
bsonType: "date",
description: "Required. Must be a Date type."
},
email: {
bsonType: "string",
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
description: "Optional. Must be a valid email if provided."
},
address: {
bsonType: "object",
required: ["city", "country"],
properties: {
city: { bsonType: "string" },
country: { bsonType: "string" }
}
},
guardian: {
bsonType: "object",
required: ["name", "phone"],
properties: {
name: { bsonType: "string" },
phone: { bsonType: "string" },
relation: {
bsonType: "string",
enum: ["Father", "Mother", "Brother", "Sister", "Uncle", "Aunt", "Other"]
}
}
},
subjects: {
bsonType: "array",
minItems: 1,
maxItems: 10,
items: { bsonType: "string" }
},
grades: {
bsonType: "array",
items: {
bsonType: "object",
required: ["subject", "score"],
properties: {
subject: { bsonType: "string" },
score: {
bsonType: "int",
minimum: 0,
maximum: 100
},
semester: {
bsonType: "int",
enum: [1, 2]
}
}
}
}
}
}
},
validationAction: "error",
validationLevel: "strict"
})Quick Reference
| Rule | What it does | Example |
|---|---|---|
required | Fields that must exist | required: ["name", "age"] |
bsonType | Expected type of field | bsonType: "string" |
minimum / maximum | Number range | minimum: 0, maximum: 100 |
minLength / maxLength | String length range | minLength: 2, maxLength: 100 |
enum | Allowed values | enum: ["9th", "10th", "11th"] |
pattern | Regex the string must match | pattern: "^[a-z]+$" |
minItems / maxItems | Array size range | minItems: 1, maxItems: 10 |
items | Rules for array elements | items: { bsonType: "string" } |
properties | Rules for object fields | properties: { city: { bsonType: "string" } } |
Add validation rules gradually — start with just the required fields and critical type checks. Once those are working correctly, add more specific rules like enums, patterns, and ranges. Trying to write a perfect schema on day one usually leads to overly strict rules that break legitimate use cases.