Back to Blog

June 21, 2026

Odoo Merges ACLs and Record Rules Into a Single ir.access Permission Layer — and Finally Makes Its ORM Cache Thread-Safe

Odoo 19.4 consolidates access control lists and record rules into a unified ir.access model, adds automatic thread-safe signaling to its ORM cache, enforces deterministic field update ordering, and begins modularizing its reporting engines.

Diagram showing Odoo merging ACLs and Record Rules into a single ir.access layer alongside thread-safe ORM cache with automatic signaling

Odoo’s security model has always had a split personality. Access Control Lists live in one place. Record Rules live in another. Both ultimately answer the same question — can this user do this thing to this record? — but they do it through separate mechanisms, separate database tables, and separate evaluation paths. In 19.4, that split is gone. A new ir.access model merges both concepts into a single permission layer.

Alongside that change, Odoo is making its ORM cache thread-safe with automatic invalidation signaling, enforcing deterministic ordering on field updates during create() and write(), and starting to break its monolithic reporting engine into modular, replaceable components. Four changes, one theme: the internal architecture is growing up.

One Permission Model to Replace Two

To understand why this matters, consider what happens when an Odoo administrator needs to debug why a user can’t access a specific record. Under the old system, you check ACLs first — does this user’s group have read/write/create/unlink permission on the model? Then you check record rules — does a domain filter onir.rulerestrict which records in that model the user can see? The two checks happen independently, are configured in different parts of the settings, and interact in ways that aren’t always obvious.

The new ir.access model collapses this into a single check. Model-level permissions and record-level filtering are defined in the same place, evaluated through the same code path, and debugged with the same tools. A permission entry in ir.accesscan express both “this group can read sale.order” and “but only orders belonging to their own company” in a single record.

For module developers, this simplifies security file definitions. No more maintaining a security/ir.model.access.csv file for ACLs and separate ir.rulerecords in XML data. For system administrators, it means one place to look when permissions aren’t working as expected. For the Odoo framework itself, it means a single, optimized permission check rather than two sequential evaluations that sometimes produce contradictory results.

Thread-Safe ORM Cache With Automatic Signaling

Odoo’s ORM cache — the ormcachedecorator that memoizes method results to avoid redundant database queries — has always had an awkward relationship with multi-threaded deployments. The cache itself was a dictionary shared across threads, and invalidation was manual: when data changed, something had to explicitly signal the cache to drop stale entries. Miss a signal, and one worker serves stale data while another has already moved on.

In 19.4, the ORM cache is thread-safe by design. Concurrent reads and writes to the cache no longer risk corrupted state or race conditions. More importantly, invalidation is now automatic. When a record changes, the cache entries that depend on it are invalidated without any developer intervention. The signaling mechanism handles cross-worker communication, so a change made by one Odoo worker propagates to every other worker’s cache.

This fixes a class of bugs that are notoriously hard to reproduce: intermittent stale data that only appears under load, when multiple workers are handling requests simultaneously. The kind of issue where a customer reports seeing outdated information, but by the time you check, the cache has already refreshed and everything looks fine. With automatic signaling, these phantom inconsistencies should disappear.

Field Updates Now Follow a Deterministic Order

When you call write()on a recordset with multiple field values, in what order do the fields get updated? Until 19.4, the answer was “it depends.” Dictionary ordering in Python is insertion-ordered, but the ORM could reorder field updates based on internal processing, and inverse methods fired in an order that wasn’t guaranteed across calls.

The 19.4 release introduces a write_sequenceattribute on fields. Combined with the field’s index position, this creates a fully deterministic ordering for field updates during bothcreate() and write(). Inverse methods — the code that runs when a computed field is written to — now execute in this same predictable sequence.

Why does this matter? Because field dependencies exist. Setting field A might trigger a recomputation that depends on field B already being set. Under non-deterministic ordering, this works sometimes and fails sometimes, depending on which field the ORM happens to process first. With write_sequence, developers can explicitly control the order, and even without explicit configuration, the default ordering is consistent across every invocation.

Reporting Engines Break Apart

The fourth change in this release is more about groundwork than immediate impact. Odoo is modularizing its reporting engines — the systems that generate PDFs, spreadsheets, and other document outputs from record data. The refactoring separates the rendering pipeline into distinct, replaceable components and prepares infrastructure for what the commit references call “paper-muncher,” presumably a new approach to document generation.

Today, Odoo’s reporting relies heavily on wkhtmltopdf for PDF generation, with QWeb templates providing the HTML source. This architecture has been a pain point for deployments: wkhtmltopdf is an aging project with its own set of rendering quirks, and running a headless browser to generate invoices feels heavier than it needs to be. While the 19.4 changes don’t replace wkhtmltopdf outright, the modularization creates the extension points that would make a swap possible without touching every module that generates reports.

The Architecture Is Getting Serious

Taken individually, each of these changes addresses a real, long-standing limitation. Together, they signal that Odoo’s core team is investing in the kind of foundational work that doesn’t ship features but makes every future feature more reliable.

Unified permissions mean fewer security bugs caused by ACL-record rule mismatches. Thread-safe caching means fewer stale-data ghosts in production. Deterministic field ordering means fewer write-order dependencies hiding in custom modules. Modular reporting means the PDF pipeline can evolve without a full rewrite.

For developers maintaining custom Odoo modules, the migration work is real but bounded. Permission definitions need to move to the new format. Code that relied on implicit field update ordering might need explicit write_sequence values. And any module that was manually managing cache invalidation should test whether automatic signaling handles it now.

For everyone else, these are the changes you never see but always benefit from. Your Odoo instance gets a little faster, a little more consistent, and a little harder to break. That’s what good infrastructure work looks like.

Ready to experience Odoo AI?

Join hundreds of teams using DearERP to customize Odoo in minutes, not weeks. Plans start at $29/month.