Odoo Development
Odoo Module Structure: Every File and What It Does
An Odoo module is a directory, and only a few things in it are enforced by the framework. Knowing which parts Odoo genuinely requires and which are convention is what stops you cargo-culting a layout you cannot explain.
Part of our guide to Odoo Development
Only two files are actually required
Strip a module to the minimum Odoo will install and you are left with __manifest__.py and __init__.py. That is the whole hard requirement.
Everything else, the models/ folder, the views/ folder, the security/ folder, exists because the community settled on it, not because the framework looks for those names. Odoo finds your Python through ordinary imports starting at __init__.py, and it finds your data through the data list in the manifest. Neither mechanism cares what the directories are called.
This matters because it tells you where to look when something does not load. A model that never appears is an import problem. A view that never appears is a manifest problem. The directory tree is almost never the cause.
The conventional layout
my_module/
__init__.py
__manifest__.py
models/
__init__.py
my_model.py
views/
my_model_views.xml
menus.xml
security/
ir.model.access.csv
security.xml
data/
my_data.xml
demo/
demo_data.xml
wizard/
my_wizard.py
report/
my_report.xml
static/
description/
icon.png
src/
js/
i18n/
de.po
tests/
__init__.py
test_my_model.pyFollow it. Not because Odoo requires it, but because every Odoo developer who opens your module expects it, and matching that expectation is free.
What each part is for
__init__.py at the root imports the subpackages: from . import models. Each subfolder needs its own __init__.py importing its files. A model defined in a file nobody imports simply does not exist as far as Odoo is concerned, and this produces no error at all, which is why it is the classic first-day bug.
models/ holds the business objects. One file per model is the usual convention, named after the model.
security/ holds ir.model.access.csv and any record rules. Access first, always: a model without an access line is invisible to non-superusers.
views/ holds view definitions, actions and menus. Actions must be defined before the menus that reference them, which is a load-order constraint, not a folder constraint.
data/ and demo/ are both XML or CSV records, and the difference is when they load. data always loads. demo loads only when the database was created with demo data, and it is declared under a separate demo key in the manifest.
wizard/ holds transient models. Structurally these are just models, so this folder is purely for the reader's benefit.
static/ is the one name Odoo does treat specially: its contents are served over HTTP with no authentication. That makes it right for JavaScript, stylesheets, images and the description/icon.png shown in the Apps list, and wrong for anything you would not publish.
i18n/ holds translation files. The .pot template plus one .po per language.
tests/ holds test cases, and its __init__.py is imported by Odoo only when running with tests enabled.
Load order lives in the manifest, not the tree
This is the part that surprises people. Odoo loads data files strictly in the order the data list gives them:
'data': [
'security/ir.model.access.csv',
'security/security.xml',
'data/sequences.xml',
'views/my_model_views.xml',
'views/menus.xml',
],Reorder those lines and you change behaviour. A menu referencing an action defined later in the list fails to resolve. A record rule referencing a group created later fails the same way. The folder each file sits in is irrelevant to this entirely.
So when a fresh install fails but an upgrade of an existing database succeeds, suspect load order first. The upgrade found the referenced records already present from last time; the clean install did not.
Naming that pays for itself
Prefix the module directory consistently so your modules group together in the Apps list and are obviously yours in a long addons path. Name view files after the model they describe. Keep one model per Python file until a file genuinely cannot be split.
None of this is enforced. All of it is read by whoever inherits the module, which at some point is going to be somebody at an upgrade trying to work out what it does. The manifest is the first thing they will open, so it is worth getting right.
FAQ
Questions, answered.
What files are mandatory in an Odoo module?
Only two: __manifest__.py and __init__.py. Everything else, including the models, views and security folders, is convention. Odoo finds your code through the Python imports in __init__.py and your data through the data list in the manifest, not by scanning directory names.
Does the folder structure affect how Odoo loads a module?
No. Load order comes entirely from the order of paths in the manifest data list. A file in a security folder loads after one in a views folder if the manifest lists it later, which is why security is conventionally listed first.
Where do static assets go in an Odoo module?
Under static, which is the one folder name Odoo treats specially: it is served over HTTP without authentication. Anything under static is public, so it is the wrong place for anything sensitive, and it is where the description and icon for the Apps list live.
Keep reading
More on Odoo Development.
The full guide, plus the other articles in this cluster.
With CODEerts
Need something built properly?
We are certified Odoo partners. We build custom Odoo modules that extend the framework rather than fight it, and we publish our own apps on the Odoo Store.
See how we can helpBook a callReady to make Odoo work the way your business does?
Book a free callCODEerts is a team of certified Odoo partners and full-stack engineers. We implement, customise and support Odoo ERP, then build the software around it.