ZODB, a Python object database that stores objects as objects
Python object-oriented database
At a glance
- What is it?
- zopefoundation/ZODB is an ACID transactional database for Python with no mapping layer: you persist ordinary Python objects and the database keeps them as objects. It ships with FileStorage and a family of command line tools for inspecting and recovering it, and it is maintained by the Plone Foundation under the Zope Public License.
- Who is it for?
- Adopt ZODB if you want to store Python object graphs natively, with attribute access, references and transactions behaving the way they do in memory, and you are prepared to accept a storage model with no query language. Skip it if you need ad hoc reporting queries across many rows, since ZODB has no SQL and traversal queries are the alternative.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The core argument: no mapper, no seam
ZODB is a Python object-oriented database, and the README sets out the case in four bullets that are worth quoting in substance.
There is no separate language for database operations, so you do not learn a query dialect to work with it.
There is very little impact on your code to make objects persistent, which is the practical version of the transparency claim.
There is no database mapper that partially hides the database. The README puts the point more directly: using an object-relational mapping is not like using an object-oriented database.
And there is almost no seam between code and database.
That last pair is the distinction the project exists to make. With an ORM you write a class, describe how it maps to tables and columns, and the database layer becomes a place where your objects and your rows are two different things kept in sync by machinery you do not control. With ZODB the object you put in the database is the object your code uses, with the same attributes, the same references and the same behaviour.
The database is ACID transactional, which is worth stating plainly because a surprising number of NoSQL-adjacent stores are not. ZODB gives you transactions with commit and abort.
The project runs on Python 3.10 and above and also runs on PyPy. The classifiers in the package metadata confirm it, listing CPython and PyPy across Python 3.10 to 3.15, with the version currently at 6.5.dev0 and requires-python set to 3.10 or newer.
The documentation lives at zodb-docs.readthedocs.io, the project site is zodb.org, and the repository is maintained by the Plone Foundation and contributors under the Zope Public License, version 2.1. The author of the package metadata is Jim Fulton, which dates the project to the era of Zope, where the object database came from.
The dependencies and what they actually do for you
The dependency list in the package metadata is short and each entry has a clear job.
persistent provides the Persistent class and the PersistentMapping and PersistentList variants, which is what tells the database that an object should be stored when it is referenced from a root. zodbpickle provides the pickler used to serialise object state, replacing the standard library pickle with one that can handle the object graph relationships.
BTrees is the storage structure underneath the object graph, and it is the piece that makes ZODB scale in a way a plain dictionary does not. B-trees give you ordered, persistent mappings and sequences with efficient insertion and lookup at large sizes, and they store their data in pages that are individually loaded. That is what lets a ZODB database hold more than fits in memory.
transaction is the transaction manager, which is where commit and abort live and where the two-phase commit machinery for storages that need it is implemented. ZConfig is the configuration file parser, and it is what makes a ZODB configuration readable rather than a wall of constructor arguments.
zc.lockfile handles the file locking that keeps several processes from corrupting a storage file. zope.interface provides the interface declarations the codebase uses.
The whole set is small, which matters for a database layer, because every one of these is code you now have on your critical path. ZODB itself pulls in nothing heavy beyond that.
The package metadata also shows the script entry points, and they are the clearest statement of the operational tooling: fsdump, fsoids, fsrefs and fstail, plus repozo. The fs-prefixed ones inspect a FileStorage file, and repozo is the incremental backup tool, which is the piece you will care about most if you run this in production.
The FileStorage tooling and what it means for operations
ZODB ships five console scripts and they exist because a FileStorage database is a file you can lose, and being able to inspect and recover it is a real operational requirement rather than a nicety.
fsdump is the main one. It reads a FileStorage file and prints the transactions and the objects they wrote, which is how you answer the question of what is actually in the database when the application cannot tell you. fsoids prints object ids, fsrefs shows the references between objects, and fstail shows the tail of the file, which is what you want when a storage file has grown and you are trying to work out how to truncate it.
repozo is the backup tool, and its incremental model is the important part. A full backup followed by incremental backups that only record what changed since the last one means a backup schedule is cheap after the first run, and restoring a recent point in time means replaying a full backup plus the increments. For a database that stores whole object graphs, an incremental file-level backup that understands the format is much more practical than trying to snapshot the file while it is being written.
The consequence is that ZODB comes with a maintenance culture attached. A FileStorage file grows as objects are written, including overwritten versions of the same object, so it needs periodic compaction, and the tools in this package are the tools you use for that. That is a real operational cost that a SQL database pushes into its own implementation and ZODB leaves with you.
The repository layout reflects the project's age and its Python community roots. There is a src/ directory with the package, a docs/ directory, a tox.ini for the test matrix, a buildout.cfg from the zope.buildout era, setup.py and setup.cfg alongside a pyproject.toml, CHANGES.rst and HISTORY.rst, CONTRIBUTING.md and DEVELOPERS.rst for contributors, a .pre-commit-config.yaml, a .meta.toml for the zope.meta tooling, and a 3.11.txt file that names a Python version.
The pyproject.toml header says it was generated with zope.meta from the zopefoundation/meta repository, which tells you the packaging is generated rather than hand-written, and that the whole project family shares a metadata generation pipeline.
How persistence actually attaches to your objects
The mechanism that makes an ordinary class persistent is worth understanding, because it determines what you can store and what happens when you change a class.
You subclass Persistent, or use one of the persistent mapping and list variants. The Persistent base class tracks whether an object has been modified since it was loaded, by comparing a modification counter against the value it had when it was read from the database. When a transaction commits, only objects whose counter has changed are written back.
That counter is what makes ZODB efficient rather than rewriting the whole graph on every commit. It is also what makes the system less obvious than it looks, because a mutation that happens outside the object's own attributes is invisible to it. If a plain list is stored on a persistent object and you append to that list, the persistent object does not know, and the change is not saved. The persistent list and persistent mapping variants exist to close that gap, and using them consistently is the practical rule.
The other half of the mechanism is reference semantics. When one persistent object points at another, the database stores a reference, not a copy, so the two remain the same object as far as your code is concerned, and the second one is written only if it changed. Traversal from a root object is therefore how you navigate the graph, which is why the queries in a ZODB application are usually code walking the object tree rather than a query language.
Pickling is the boundary of all this. zodbpickle serialises the object state, which means the state has to be reconstructible from the class definition and the pickled data. That is why the classes you store need to be importable by the same code that will load them later, and why a schema migration in ZODB is a matter of migrating stored data rather than altering a table definition. The CHANGES.rst and HISTORY.rst files in the repository track the compatibility decisions over more than two decades, which is a fair proxy for how careful those changes have to be.
Where ZODb fits, and what it costs
The honest comparison is against an ORM on one side and against a document store on the other.
Against an ORM, ZODB wins on the object model and loses on everything else. You get references, inheritance and attribute access for free, and you get none of the query capability, none of the migrations that a schema change forces, and none of the ability to look at your data with a third-party tool. If your application is mostly loading and saving aggregates rather than running reports, that trade can be right. If reporting is a requirement, it is not.
Against a document store such as MongoDB, ZODB offers real ACID transactions and a coherent object graph, and it requires everything to be picklable and lives in one process-local connection model. Documents lose type information and references, and getting a coherent graph back out of them is your problem. ZODB does not lose it.
The costs that are specific to ZODB are worth naming. Pickling means your objects must be serialisable, which rules out open file handles and threads, and means a class change can make old data unreadable if you are not careful. The database is in-process, so a multi-process deployment needs care around which process writes. Storage files grow and need compaction. And the project version is a development version, 6.5.dev0, with no GitHub releases listed, so the versioning story is whatever PyPI carries.
None of that makes it wrong. It makes it a choice for a particular shape of application, and ZODB has been that choice for Plone for twenty years, which is a better track record than most persistence layers.
The niche it occupies is narrow and real: an application whose natural unit is a tree of Python objects that reference each other, where the team wants that structure to survive a restart unchanged, and where reporting needs are modest enough that traversal queries will do.
Editorial conclusion
Adopt ZODB if you want to store Python object graphs natively, with attribute access, references and transactions behaving the way they do in memory, and you are prepared to accept a storage model with no query language. Skip it if you need ad hoc reporting queries across many rows, since ZODB has no SQL and traversal queries are the alternative. Verify first by modelling one aggregate root with references into it, then reading it back in a fresh process, and by running fsdump over the resulting FileStorage file to see what the objects actually look like on disk.
Frequently asked questions
What does ZODB do differently from an object-relational mapper?
ZODB stores Python objects as objects with no mapping layer, so the object your code uses is the object in the database, with the same attributes and references. The README puts it directly: using an object-relational mapping is not like using an object-oriented database.
Is ZODB transactional?
Yes. The README states that ZODB is an ACID transactional database, with the transaction package providing the commit and abort machinery.
What Python versions does ZODB support?
Python 3.10 and above, and it also runs on PyPy. The package metadata requires Python 3.10 or newer and lists classifiers for CPython and PyPy across 3.10 through 3.15.
Which tools ship with ZODB for inspecting and backing up storage?
Five console scripts: fsdump, fsoids, fsrefs and fstail for inspecting a FileStorage file, and repozo for incremental backups. The repository also carries the fs prefixed tools as entry points in the package metadata.
What licence is ZODB released under?
The Zope Public License, version 2.1, with the license field in the package metadata set to ZPL-2.1 and the terms in LICENSE.txt and COPYRIGHT.txt. The project is maintained by the Plone Foundation and contributors.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zopefoundation-zodb)Was this useful?