Skip to Content

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.py

Follow 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.

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 call