Defining Documents
A MongoEngine document is a Python class: each declared field becomes a document key, each instance becomes a MongoDB document, and the class itself defines validation, defaults, and which collection it lives in. This chapter is the core pattern of the library - defining a document, instantiating it, and understanding exactly what gets sent to the database when you call save().
The Basic Document
from mongoengine import Document, StringField, IntField, DateTimeField
import datetime
class Article(Document):
title = StringField(required=True, max_length=300)
body = StringField()
views = IntField(default=0)
published_at = DateTimeField()
meta = {
"collection": "articles",
"indexes": ["title", "published_at"],
}
def __str__(self):
return f"Article: {self.title}"
Four things happen by convention. First, the collection name defaults to the lowercased class name - "Article" becomes "articles" - and meta["collection"] overrides it. Second, fields with no value are simply absent from the stored document (MongoDB stores only what is set). Third, required=True and the other constraints are checked on save(). Fourth, every document automatically gets an _id field, an ObjectId by default, exposed as article.id.
Instantiating and Saving
article = Article(title="First post", body="Hello world")
print(article.id) # an ObjectId is assigned at instantiation
print(article.views) # 0 - the default was applied
article.save() # validates, then upserts into the collection
# a modified document saves the same way
article.views = 1
article.save() # updates the existing document (same _id)
Calling save() on a document with an existing _id performs an update, not an insert - the _id is what makes a document "the same row". Passing insert=True forces an insert (and fails if the _id already exists), which is how you catch accidental double-creation.
What MongoDB Actually Stores
article.save()
raw = Article.objects.with_id(article.id).first().to_mongo()
print(raw) # {'_id': ObjectId(...), 'title': 'First post',
# 'body': 'Hello world', 'views': 0}
to_mongo() is the audit view: it shows the exact BSON the driver sends, which is how you verify that a field's type, name, and presence match what the rest of the system (other services, MongoDB queries, exports) expects. Two habits follow: keep field names in stable snake_case, and use to_mongo() in tests to pin the wire format of your models.
The Useful Meta Options
| Option | Effect |
|---|---|
collection | target collection name (default: lowercase class name) |
db | named connection to use (see the connecting chapter) |
indexes | index specs created by ensure_indexes() |
allow_inheritance | enables subclass documents (inheritance chapter) |
ordering | default queryset ordering |
strict | reject undeclared fields on save |
strict is the production-relevant one: with strict = True, assigning a key the class does not declare raises instead of silently storing an unmodeled field - a cheap guarantee that the Python model and the stored documents stay in sync.
Summary
Define a model as a Document subclass with typed fields and a meta block; instantiate with keyword arguments; and call save(), which validates and upserts by _id. Use to_mongo() to see exactly what the driver stores, set strict = True in production so the model is the schema, and remember that the _id is the identity everything else in the tutorial - updates, references, lookups - hangs on.