SQLAlchemy default DateTime
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
SQLAlchemy provides two ways to set default datetime values on columns: default (sets the value in Python when creating the object) and server_default (adds a DEFAULT clause in the SQL DDL so the database generates the value). For "created at" timestamps, use server_default=func.now() so the database clock is authoritative. For "updated at" timestamps, use onupdate=func.now() which sets the value on every UPDATE. A common mistake is using default=datetime.utcnow() with parentheses, which evaluates once at import time and gives every row the same timestamp.
The Classic Mistake
default=datetime.utcnow (no parentheses) passes the function as a callable. SQLAlchemy calls it each time a new object is created. default=datetime.utcnow() (with parentheses) evaluates immediately and stores the resulting fixed timestamp.
Python-Side Default (default)
default is a Python-side feature. The value is set when the ORM creates the object, before it is sent to the database. The database column has no DEFAULT clause.
Server-Side Default (server_default)
server_default adds a DEFAULT clause to the column in the database schema. The value is generated by the database server, ensuring consistency even for raw SQL inserts outside SQLAlchemy.
Updated At with onupdate
onupdate calls the function every time an UPDATE statement is issued for the row via SQLAlchemy. It is Python-side — the database does not enforce it.
Server-Side onupdate (MySQL)
MySQL supports ON UPDATE CURRENT_TIMESTAMP natively. This works for all updates, including raw SQL outside SQLAlchemy.
SQLAlchemy 2.0 Style (Mapped Columns)
SQLAlchemy 2.0 uses Mapped type annotations and mapped_column() instead of Column(). The default, server_default, and onupdate parameters work the same way.
Common Pitfalls
- Using
default=datetime.utcnow()with parentheses: This evaluatesutcnow()once at class definition time. Every row gets the same timestamp. Remove the parentheses:default=datetime.utcnowpasses the function itself, which is called for each new row. - Using
defaultinstead ofserver_defaultfor timestamps:defaultonly works for inserts made through SQLAlchemy. Direct SQL inserts, database migrations, or other applications inserting into the same table will get NULL. Useserver_default=func.now()so the database sets the value. - Expecting
onupdateto work for raw SQL updates:onupdateis a Python-side feature that only triggers when SQLAlchemy's ORM issues an UPDATE. Raw SQL, bulk updates, or other applications bypassing SQLAlchemy will not trigger it. Use database triggers or MySQL'sON UPDATE CURRENT_TIMESTAMPfor database-enforced behavior. - Using
datetime.utcnow(deprecated in Python 3.12):datetime.utcnow()returns a naive datetime and is deprecated. Usedatetime.now(timezone.utc)for timezone-aware UTC timestamps:default=lambda: datetime.now(timezone.utc). - Not using
DateTime(timezone=True)for timezone-aware columns: Withouttimezone=True, the column stores naive datetimes. If different parts of your application assume different timezones, this causes silent data corruption. Always store timezone-aware timestamps.
Summary
- Use
default=datetime.utcnow(no parentheses) for Python-side defaults called per insert - Use
server_default=func.now()for database-enforced defaults that work with raw SQL - Use
onupdate=func.now()for "updated at" timestamps via SQLAlchemy ORM - Never use
default=datetime.utcnow()with parentheses — it evaluates once at import time - Use
DateTime(timezone=True)anddatetime.now(timezone.utc)for timezone-aware timestamps - For database-enforced "updated at", use MySQL's
ON UPDATE CURRENT_TIMESTAMPor database triggers
Related reading
- SQLAlchemy engine, connection and session difference
- SQLAlchemy IN clause
- SQLAlchemy ORDER BY DESCENDING?
- SQLAlchemy print the actual query
- sqlalchemy unique across multiple columns
- SQLAlchemy What's the difference between flush and commit?
- sqlalchemy.exc.NoSuchModuleError Can't load plugin sqlalchemy.dialectspostgres
- SqlCommand Close and Dispose - which to call?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.