MongoDB has a flexible schema, and $jsonSchema validation is how you make it firm once you have decided on a shape. You attach a rule set to the collection, and MongoDB rejects any write that does not match. It is the difference between a schema your documents happen to share and one the database will not let them break. Here is a real validator, how to roll it out without locking yourself out of your own data, and the feature everyone expects that does not exist.
A validator that actually does something
Define the required fields and their types, and attach it at creation:
db.createCollection("users", {
validator: { $jsonSchema: {
bsonType: "object",
required: ["email", "createdAt", "role"],
properties: {
email: { bsonType: "string", pattern: "^.+@.+$" },
createdAt: { bsonType: "date" },
role: { enum: ["admin", "member", "guest"] },
age: { bsonType: "int", minimum: 0 }
}
} }
})
Now an insert without an email, or with role: "superuser", or with age as the string "30", is rejected before it lands. The schema validation docs list every keyword, but bsonType, required, enum, pattern, and minimum cover most of what you will reach for.
Roll it out without breaking production
Turning on strict validation over a collection full of legacy data is how you cause the incident you were trying to prevent. Two knobs make the rollout safe.
validationLevel decides which documents are checked. strict (the default) checks every insert and update. moderate only checks new documents and updates to documents that were already valid, so your messy historical records are left alone until something touches them.
validationAction decides what happens on a miss. error rejects the write. warn allows it but logs a warning. The safe sequence is: start with warn, watch the logs, clean up what fails, then switch to error.
db.runCommand({
collMod: "users",
validator: { $jsonSchema: { /* ... */ } },
validationLevel: "moderate",
validationAction: "warn" // observe first, enforce later
})
collMod is also how you add validation to a collection that already exists, no recreate required.
The one it does not do: default values
Here is the expectation that trips people, and the reason "json schema default value" is a search at all. JSON Schema as a spec has a default keyword. MongoDB's $jsonSchema ignores it. Validation checks the shape of a document; it does not fill in missing fields. Put a default in your validator and nothing populates, MongoDB simply does not use it.
Defaults live somewhere else:
- In your application code, when you build the document.
- In your ODM (Mongoose and friends apply schema defaults before the write).
- In an upsert, with
$setOnInsert, so a field is set only when the document is first created.
db.users.updateOne(
{ email: "sam@example.com" },
{ $setOnInsert: { role: "member", createdAt: new Date() } },
{ upsert: true }
)
Validation enforces that role is present and valid. Setting role when it is missing is a separate job, and it is yours.
Validate the shape, then keep watching it
Validation stops the next bad write, but it does not fix the documents already in the collection, and it does not catch drift in fields you did not think to constrain. That is the other half of schema work: analyzing your schema to find the type splits and missing fields that predate the validator, and designing the shape in the first place is MongoDB schema design.
mongoui's Schema doctor samples a collection and reports its real field shapes and type drift, which is how you decide what the validator should require and confirm nothing already violates it before you switch to error. It runs on your machine, so your documents never leave your network. Define the shape, roll it out with warn then error, and set your defaults where defaults actually live.
