Module Directory Layout
A well-structured Odoo module follows a predictable layout. Here is the full directory tree of a real-world module, library_management, with comments explaining each entry:
library_management/ ← module root (must match technical name)
├── __init__.py ← makes it a Python package
├── __manifest__.py ← module metadata and file lists
├── models/
│ ├── __init__.py
│ └── library_book.py ← model definitions
├── views/
│ ├── book_views.xml ← form, list, kanban, search views
│ └── menus.xml ← menu items and window actions
├── security/
│ ├── ir.model.access.csv ← CRUD access rights per group
│ └── record_rules.xml ← row-level access filters
├── data/
│ └── book_sequence.xml ← default data (sequences, config)
├── demo/
│ └── demo_books.xml ← sample data for development
├── report/
│ └── book_report.xml ← QWeb PDF report templates
├── wizard/
│ ├── __init__.py
│ └── borrow_wizard.py ← transient models for multi-step flows
├── static/
│ ├── description/
│ │ └── icon.png ← module icon shown in Apps list
│ └── src/
│ ├── js/ ← custom OWL components / JS
│ ├── xml/ ← OWL component templates
│ └── scss/ ← custom styles
└── controllers/
├── __init__.py
└── portal.py ← HTTP routes for portal/website
Required Files (Minimum Module)
The absolute minimum to get Odoo to recognise a module is two files:
my_module/
├── __init__.py ← can be empty but must exist
└── __manifest__.py ← must exist with name and version keys
# __init__.py — empty or imports subpackages
from . import models
# __manifest__.py — minimum valid manifest
{
'name': 'My Module',
'version': '19.0.1.0.0',
}
Odoo scans all directories in the addons path. A directory is treated as a module if and only if it contains __manifest__.py.
models/ Directory
Each Python file in models/ defines one or more model classes. The models/__init__.py must import every file so Odoo loads them:
# models/__init__.py
from . import library_book
from . import library_member
from . import res_partner # extending a base model
Naming convention: the file name should match the model's _name with dots replaced by underscores — library.book → library_book.py.
The module's root __init__.py imports the models package (and any other Python packages present):
# __init__.py (module root)
from . import models
from . import controllers # if you have controllers
from . import wizard # if you have wizards
views/ Directory
The views/ directory holds all XML view definitions, window actions, and menu items. Conventions to follow:
- One file per model —
book_views.xml,member_views.xml - Keep menus in a separate
menus.xmloractions.xmlso they load after the views they reference - All files must be listed in
__manifest__.pyunder the'data'key, in the correct load order
security/ Directory
Two files live here in almost every module:
ir.model.access.csv— controls which user groups can Create, Read, Write, and Delete each modelrecord_rules.xml— row-level filters (for example, users may only see their own records)
Important: the security CSV must be listed first in the manifest data list — before views. Models must be registered before access rules can reference them.
CSV format:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
static/ Directory
Everything under static/ is served directly by the web server — no Python processing occurs. Key subdirectories:
static/description/icon.png— 128×128 px module icon shown in the Apps menustatic/src/js/— OWL JavaScript componentsstatic/src/xml/— OWL component XML templatesstatic/src/scss/— custom SCSS stylesheets
Asset files must be registered in __manifest__.py under the 'assets' key (Odoo 14+):
'assets': {
'web.assets_backend': [
'my_module/static/src/scss/my_module.scss',
'my_module/static/src/js/my_component.js',
'my_module/static/src/xml/my_component.xml',
],
},
Naming Conventions
Following community naming conventions keeps your module consistent with the Odoo ecosystem and easier to navigate:
| Item | Convention | Example |
|---|---|---|
| Module directory | snake_case | library_management |
| Python model file | matches _name with dots→underscores | library_book.py |
| View XML file | {model}_views.xml | book_views.xml |
XML record id | view_{model}_{type} | view_library_book_form |
| Python class name | CamelCase | LibraryBook |
Model _name | dot.notation | library.book |
| Module technical name | snake_case, start with company/project prefix | algorid_library |
Prefix your module name with your company or project identifier (e.g. algorid_library, not just library). Odoo has thousands of community modules — generic names like pos_custom or sale_extension are very likely to conflict with an existing module in a production environment.
📋 Key Points
- A module is a directory with
__init__.py+__manifest__.py— Odoo recognises it if and only if__manifest__.pyis present. - Standard subdirectories:
models/,views/,security/,data/,demo/,report/,wizard/,static/,controllers/. - Every subdirectory containing Python files needs its own
__init__.py. - Root
__init__.pyimports:from . import models(pluscontrollers,wizardif present). models/__init__.pymust import every model file so Odoo loads them all.static/is served directly by the web server; JS/SCSS assets must be registered in the'assets'section of__manifest__.py.
FAQ
Yes, but it is bad practice for large models. Small utility models that are tightly related to a main model are sometimes grouped in a single file, but in general one file per model is easier to navigate and review.
__init__.py? +Every directory containing Python files that Odoo should import needs an __init__.py. The views/, security/, data/, demo/, report/, and static/ directories don't contain Python — they don't need __init__.py.
data/ and views/ directories? +Convention only — both contain XML files loaded from __manifest__.py. Typically: views/ holds UI views, actions, and menus. data/ holds non-UI records like sequences, email templates, and default configuration. Odoo doesn't enforce this split.
Add the parent directory of your module to the addons_path in odoo.conf. For example, if your module is at ~/custom_addons/library_management/, add ~/custom_addons to addons_path. Then restart Odoo and click "Update Apps List" in Settings.