Techbot

Contact

Author

Complete Developer’s Manual: Building Custom Modules in Odoo

Complete Developer’s Manual: Building Custom Modules in Odoo Blogs September 8, 2026 Shaan Jose Tech Analyst at Techbot ERP About This Guide This guide provides a beginner-friendly, step-by-step introduction to building custom applications in Odoo from scratch. Using a hospital management system as a practical example, it walks you through structuring your app, storing custom data, creating user-friendly screens, and making updates safely—all structured so anyone can follow along and build custom features easily. What You Will Learn App Organization: How to set up clean folder structures and manage how your app files load together. Data & Screens: How to create custom fields to store information, build clean lists and detailed views, and manage record statuses. Security & Access: How to control who can view, edit, or create records within your app. App Updates: How to properly install, update, and apply changes to your custom Odoo apps. Prerequisites: Finding Your Odoo Server & Custom Addons Directory Before writing code, you need to know where Odoo lives on your computer and where to place your custom code so Odoo can discover it. Step 1: Locate the custom-addons Directory in File Explorer If you are running Odoo locally on Windows: Open File Explorer by pressing Ctrl + E. Navigate to your installation path (typically C:Program FilesOdoo 19.0serverodooaddons or custom-addons). You can paste your folder path directly into the File Explorer address bar. Crucial Rule on Folder Naming: Pick one clean, lowercase name for your custom module folder using underscores instead of spaces or hyphens (for example: custom_hospital_management or real_estate). Throughout this guide, we will strictly use custom_hospital_management across all code files, paths, and configurations. Step 2: Create the Module Directory Structure Inside your custom-addons directory, create a main folder named custom_hospital_management. Inside that main folder, create three sub-folders: models/ — Holds all Python files that construct database tables and business logic. views/ — Holds all XML files that render screens, forms, tables, and top navigation menus. security/ — Holds CSV files that grant users access permissions to your database tables. Chapter 1: The Odoo Module Architecture Every Odoo module is a self-contained directory containing Python code, XML layouts, and security rules. Complete Directory Layout When viewed inside Visual Studio Code or File Explorer, your module structure must look like this: Plaintext custom_hospital_management/ │ ├── __init__.py                  <– Root Python file initializing the module package ├── __manifest__.py              <– Module configuration, metadata, and loading sequence │ ├── models/                      <– Python database layer │   ├── __init__.py              <– Registers all model Python files │   └── patient.py               <– Patient database schema & logic │ ├── views/                       <– XML user interface layer │   ├── patient_views.xml        <– Form and list (tree) view definitions │   └── menu_views.xml           <– Top menu navigation links │ └── security/                    <– Access control security layer     └── ir.model.access.csv      <– Security rights (Read, Write, Create, Delete) Chapter 2: Module Registration (__manifest__.py) The __manifest__.py file tells Odoo what your app does, what base Odoo apps it depends on, and which data files to load into PostgreSQL. When viewed inside Visual Studio Code or File Explorer, your module structure must look like this: Manifest Configuration Code File location: custom_hospital_management/__manifest__.py Python {     ‘name’: ‘Hospital Management’,     ‘version’: ‘1.0.0’,     ‘summary’: ‘Manage patient records, appointments, and medical histories’,     ‘category’: ‘Healthcare’,     ‘author’: ‘Your Company Name’,     ‘license’: ‘LGPL-3’,     # Dependencies: List official Odoo apps required before this module can be installed     ‘depends’: [‘base’],     # Data Files: Must be listed in STRICT sequential dependency order     ‘data’: [         ‘security/ir.model.access.csv’,  # 1. Security MUST load first         ‘views/patient_views.xml’,       # 2. Views and window actions MUST load second         ‘views/menu_views.xml’,          # 3. Menus referencing actions MUST load last     ],     ‘installable’: True,     ‘application’: True,  # Setting to True places this app on the main Odoo dashboard }  Core Concept: Why Manifest Order Matters Odoo executes files listed inside ‘data’: […] strictly from top to bottom: If views/menu_views.xml is placed above views/patient_views.xml, Odoo will crash with an External ID not found error because the menu will try to link to an action that hasn’t been created yet. Security files (ir.model.access.csv) must always sit at the top so permissions are active before screens are drawn. Chapter 3: Creating Database Tables (Models & ORM Fields) In Odoo, you do not write raw SQL statements (like CREATE TABLE). Instead, you define Python classes inheriting from models.Model. Odoo’s Object-Relational Mapping (ORM) creates and updates the underlying PostgreSQL database automatically. Step 1: Register the Models Directory File location: custom_hospital_management/__init__.py Python from . import models File location: custom_hospital_management/models/__init__.py Python from . import patient Step 2: Define the Model & Data Fields File location: custom_hospital_management/models/patient.py Python from odoo import models, fields, api   class HospitalPatient(models.Model):     # Technical database table identifier created in PostgreSQL: “hospital_patient”     _name = ‘hospital.patient’     _description = ‘Hospital Patient Record’       # Standard Database Fields     name = fields.Char(string=’Full Name’, required=True)     age = fields.Integer(string=’Age’)     gender = fields.Selection([         (‘male’, ‘Male’),         (‘female’, ‘Female’),         (‘other’, ‘Other’),     ], string=’Gender’, default=’male’)     note = fields.Text(string=’Medical History’)     active = fields.Boolean(string=’Active’, default=True)       # State tracking field for record lifecycles     state = fields.Selection([         (‘draft’, ‘Draft’),         (‘confirmed’, ‘Confirmed’),         (‘done’, ‘Done’),         (‘cancel’, ‘Cancelled’),     ], string=’Status’, default=’draft’, required=True)       # Computed Field: Calculates values dynamically on the fly     is_minor = fields.Boolean(string=’Is Minor’, compute=’_compute_is_minor’)       @api.depends(‘age’)     def _compute_is_minor(self):         # ALWAYS loop over ‘self’ because Odoo handles records in batches (recordsets)         for record in self:             if record.age and record.age < 18:                 record.is_minor = True             else:                 record.is_minor = False       # Workflow Action Methods (Connected to UI Buttons)     def action_confirm(self):         for record in self:             record.state = ‘confirmed’       def action_done(self):         for record in self:             record.state = ‘done’       def action_cancel(self):         for record in self:             record.state