Field Types
Every field on a document declares both a BSON type and a set of constraints - required, choices, length and value bounds, regex, uniqueness, defaults. The type system is the ODM's main value: it is where the data model lives, where validation is enforced, and where the documentation of your schema is written. This chapter catalogs the field types you will actually use and the constraint vocabulary they share.
Scalar Types
| Field | BSON type | Typical use |
|---|---|---|
StringField | string | names, codes, short text; max_length, regex |
IntField | int32/64 | counts, ages; min_value/max_value |
FloatField | double | measurements, scores - not money |
DecimalField | decimal128 | money and exact values; precision, rounding |
BooleanField | bool | flags |
DateTimeField | date | timestamps; store UTC, render locally |
ObjectIdField | objectId | manual identity values (default _id) |
BinaryField | binary | small blobs; large files belong in GridFS or object storage |
EnumField | string | closed sets stored as strings |
from mongoengine import (Document, StringField, IntField, DecimalField,
DateTimeField, BooleanField, EnumField)
import datetime
class Product(Document):
sku = StringField(required=True, unique=True, regex=r"^[A-Z0-9-]{6,20}$")
name = StringField(required=True, max_length=200)
price = DecimalField(required=True, min_value=0, precision=2)
in_stock = BooleanField(default=True)
category = EnumField(["electronics", "books", "toys"], default="books")
created_at = DateTimeField(default=datetime.datetime.now(datetime.timezone.utc))
Note the money detail: DecimalField maps to BSON decimal128, which preserves exact decimal values - FloatField would silently turn 0.10 + 0.20 into 0.30000000000000004. For anything a human will audit as a number, the decimal type is the correct one.
Container Types
from mongoengine import ListField, DictField, EmbeddedDocumentField, EmbeddedDocument
class Tag(EmbeddedDocument):
label = StringField(required=True)
weight = IntField(default=1)
class Post(Document):
tags = ListField(StringField(max_length=40))
metadata = DictField(default=dict)
reviewers = ListField(EmbeddedDocumentField(Tag))
ListField(inner_type) stores a homogeneous array - the inner type is validated per element. DictField stores a free-form subdocument (weak typing by design; use an EmbeddedDocument when the sub-structure is stable and worth validating). ListField(EmbeddedDocumentField(...)) is the standard "array of typed subdocuments" shape, and it is the main alternative to a separate collection with references (the embedded documents chapter covers when each wins).
The Shared Constraint Vocabulary
required=True- the field must be present and non-None on save.default=...- a value or a zero-argument callable (callables are evaluated at instantiation, sodefault=datetime.datetime.utcnowworks butdefault=datetime.datetime.now()does not).choices=[...]/EnumField- a closed value set.min_length/max_length(strings),min_value/max_value(numbers) - bounds checked on save.regex- a pattern the value must match.unique=True- enforced with a unique index (created viaensure_indexes()).db_field- store under a different key than the Python attribute.
One subtlety worth internalizing: these constraints are enforced by the ODM in Python at save() time, not by the server (except unique, which needs its index). Writes that bypass MongoEngine - raw pymongo, another service, a fix in the shell - skip the checks, which is why production systems also want a database-level validation schema for the collections that matter.
Summary
Model data with the matching field type - DecimalField for money, DateTimeField in UTC, EnumField for closed sets - and declare the constraints where the field is declared, not in scattered if checks. Use ListField for homogeneous arrays and EmbeddedDocument for stable sub-structures, remember that default must be a callable when it should be evaluated per instance, and pair unique=True with ensure_indexes() so the database enforces what the model promises.