Ad – 728×90
⚙️ Module Development

Odoo Module Structure – Anatomy of an Odoo Addon

Every feature in Odoo is packaged as a module (also called an addon). A module is simply a directory with a specific file structure that Odoo's module loader recognises. This page walks through every file and directory in an Odoo module, what each one does, and how to create a minimal working module from scratch.

⏱️ 20 min 🎯 Beginner 📅 Updated 2026

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:

Text
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:

Text
my_module/
├── __init__.py      ← can be empty but must exist
└── __manifest__.py  ← must exist with name and version keys
Python
# __init__.py — empty or imports subpackages
from . import models
Python
# __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.

Ad – 336×280

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:

Python
# 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.booklibrary_book.py.

The module's root __init__.py imports the models package (and any other Python packages present):

Python
# __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.xml or actions.xml so they load after the views they reference
  • All files must be listed in __manifest__.py under 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 model
  • record_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:

Text
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 menu
  • static/src/js/ — OWL JavaScript components
  • static/src/xml/ — OWL component XML templates
  • static/src/scss/ — custom SCSS stylesheets

Asset files must be registered in __manifest__.py under the 'assets' key (Odoo 14+):

Python
'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:

ItemConventionExample
Module directorysnake_caselibrary_management
Python model filematches _name with dots→underscoreslibrary_book.py
View XML file{model}_views.xmlbook_views.xml
XML record idview_{model}_{type}view_library_book_form
Python class nameCamelCaseLibraryBook
Model _namedot.notationlibrary.book
Module technical namesnake_case, start with company/project prefixalgorid_library
💡
Always prefix your module name

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__.py is present.
  • Standard subdirectories: models/, views/, security/, data/, demo/, report/, wizard/, static/, controllers/.
  • Every subdirectory containing Python files needs its own __init__.py.
  • Root __init__.py imports: from . import models (plus controllers, wizard if present).
  • models/__init__.py must 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

Can I have multiple models in one Python file? +

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.

Does every directory need an __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.

What is the difference between 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.

How do I add a module to Odoo's addons path? +

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.