The gap between “it works on my laptop” and “it’s fine in production” on an Odoo module is rarely the business logic — that part usually gets tested. It’s the surrounding scaffolding: whether the manifest lists every real dependency, whether the right groups can actually see the right records, whether an upgrade six months from now silently wipes out data nobody meant to be temporary. None of it is hard. All of it is easy to forget under deadline pressure.
The short version: work through this checklist roughly in the order below — manifest and dependencies first (nothing else matters if the install itself is fragile), then security, then the UI surface (views/reports), then the things that only matter once real users and real upgrades are involved.
1. Manifest and dependencies
- Every module actually imported or referenced (
ref="other_module.xyz"in any XML/CSV) is listed independs— not just present by coincidence in your dev database. See The manifest.py File, Key by Key for the full key-by-key reference and why this specific one is the easiest to get away with skipping in development and only fail on a clean install. -
versionfollows the<odoo-series>.x.x.xconvention (e.g.18.0.1.0.0) — not enforced by Odoo itself, but relied on by real tooling and by whoever inherits this module later. -
license,author, andcategoryare actually filled in, not left as placeholder defaults — irrelevant to functionality, but the first thing that makes a module look unmaintained to the next developer who opens it. - Files in
dataare listed in an order that respects what references what: security before views, views before anything (menus, reports) that points at them.
2. Security
- Every new model has an
ir.model.access.csvrow for every group that needs it — including read-only access for groups that only need to see the data, not edit it. A model with zero access rows is invisible even to an administrator’s menu, not just “read-only.” - Record rules exist wherever “can see the model” and “should see this specific record” are genuinely different questions — and if there’s more than one rule on the same model, the group-specific-OR / global-AND combination logic has actually been worked through, not assumed. Why Can’t This User See a Record They Should? covers exactly this, including the multi-company global-rule trap that produces “I can’t see anything at all” tickets after a user’s company assignment changes.
- Sensitive fields (cost prices, personal data, anything a specific group shouldn’t see) carry their own
groupsattribute at the field or view-element level — a model-level access right doesn’t hide individual fields from an otherwise-authorized group. - External IDs for every access-rights row and record rule follow a naming convention someone else could guess — the External ID / XML ID Helper suggests one from the model/group name rather than inventing an ad hoc one per module.
3. Views and reports
- Every user-facing view has been opened as a user in the least privileged group it’s meant to support, not just as an administrator — the single most common way a “should be hidden” field or button ships visible anyway.
- Any inherited view uses an xpath expression targeting something structurally stable (an existing
name=orid=attribute), not a fragile positional path (//div[3]/table) that silently breaks the moment the parent view’s layout changes in a future Odoo upgrade or another module’s own inheritance. - Any custom QWeb report has been checked for the classic pagination failures — a table row splitting awkwardly across two pages, a repeated header not actually repeating — before a client discovers them on a real printed document.
- Field widgets on report templates degrade sensibly for genuinely empty values (an unset
many2one, an emptyhtmlfield) rather than printing a blank gap with no indication anything was expected there.
4. Data and upgrades
- Anything meant to be user-editable configuration (not fixed reference data) is not marked
noupdate="1"— data undernoupdateis loaded once on install and never touched again on upgrade, which is correct for a starting default a user might have since customized, and actively wrong for something the module needs to keep in sync going forward. - Conversely, anything the module needs to be able to correct in a future version update — a fixed lookup table, a report template with a genuine bug — is not accidentally left without
noupdate, or a user’s local customization to it gets silently overwritten on the next upgrade. -
demodata (if any) contains nothing a production install would ever load by accident — verify by installing with demo data disabled and confirming nothing is missing that shouldn’t be.
5. Before it actually goes live
- The module installs cleanly on a fresh database, not just upgrades cleanly on a dev database that’s accumulated months of manual fixes no fresh install would ever apply.
- Scheduled actions the module ships (if any) have been confirmed to actually fire in the target environment — server-level cron capacity is a deployment concern entirely separate from the job’s own configuration; see Why Odoo Scheduled Actions Don’t Run At All if anything doesn’t run as expected after deploy.
- A rollback plan exists — a database backup taken immediately before the install/upgrade, confirmed restorable, not just assumed to exist because backups run nightly.
Quick reference: what breaks if you skip each section
| Section skipped | What actually happens |
|---|---|
| Manifest/dependencies | Works in dev by accident, fails on a clean install or on someone else’s database |
| Security | Either an access-denied error for a group that needs the data, or a group seeing data it shouldn’t |
| Views/reports | UI looks fine to an admin, breaks or leaks data for a real, less-privileged user |
| Data/upgrades | A future upgrade either silently reverts a user’s customization or fails to ship a real bug fix |
| Go-live prep | An install that “works” in a dev database fails, with no way back, on the one that matters |
Frequently asked questions
Is this checklist specific to one Odoo version?
No — every item here is about module structure and configuration discipline, not a specific version's API. The underlying mechanisms (manifest keys, access rights, record rules, noupdate) have been stable across many Odoo versions.
Do I need all of this for a tiny internal module?
Scale the depth, not the categories — a five-model internal tool still needs correct depends, at least a basic access-rights row per group, and a rollback plan. What shrinks for a small module is the amount of *time* each item takes, not whether it applies.
What's the single most common item on this list that actually gets skipped?
Testing views and reports as a genuinely less-privileged user rather than only as an administrator. It's the one that requires actually switching context (a second test user, a different login) rather than just re-reading a file, which is exactly why it's the one deadline pressure eats first.
Should this run as an automated CI check instead of a manual checklist?
Parts of it can be (a fresh-database install test is a natural CI job), but the security and views/reports sections genuinely need a human looking at the result as a specific user role — an automated install test confirms the module installs, not that the right people see the right things.
Does this apply the same way to an OCA or other community-maintained module?
Yes, and OCA's own contribution guidelines already enforce several of these mechanically (a required readme structure, a `pre-commit` lint pass, and a CI matrix that installs every module fresh) — treat this checklist as the superset that still applies even when tooling already catches some of it automatically.
Further reading
- Module Manifests — official Odoo 18.0 developer documentation
- Security in Odoo — official Odoo 18.0 developer documentation
And the three other tools this checklist leans on directly: the Domain Filter Builder & Explainer for testing any record rule’s domain, the QWeb Report Snippet Generator for the report-pagination items in section 3, and the Report Builder for building or auditing the report itself end to end.