View Architecture Overview
Every screen in Odoo is defined by one or more ir.ui.view records. When the web client requests a view, Odoo resolves the base view, applies all inherited view extensions in priority order, and returns the combined XML. Understanding this process is essential for customizing Odoo without breaking existing functionality.
View Types
Odoo supports these view types, each rendered by a different frontend component:
form— single record editinglist(tree) — tabular record listingkanban— card-based grouped displaysearch— search bar, filters, group bypivot— pivot table for analysisgraph— charts (bar, line, pie)calendar— calendar displayactivity— activity planning viewgantt— Gantt chart (Enterprise)map— geographical map (Enterprise)
Base View Definition
<record id="view_ticket_form" model="ir.ui.view">
<field name="name">support.ticket.form</field>
<field name="model">support.ticket</field>
<field name="arch" type="xml">
<form>
<header>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<field name="name"/>
<field name="partner_id"/>
</group>
</sheet>
</form>
</field>
</record>
View Inheritance
The most powerful concept in Odoo views. Instead of replacing views, you extend them using inherit_id:
<record id="view_ticket_form_inherit" model="ir.ui.view">
<field name="name">support.ticket.form.inherit.my_module</field>
<field name="model">support.ticket</field>
<field name="inherit_id" ref="support.view_ticket_form"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="priority"/>
<field name="user_id"/>
</field>
</arch>
</record>
XPath Expressions
Use <xpath> for precise targeting when simple element matching is ambiguous:
<!-- Match by element + attribute -->
<xpath expr="//field[@name='partner_id']" position="after">
<field name="email"/>
</xpath>
<!-- Match by CSS class -->
<xpath expr="//div[hasclass('oe_title')]" position="inside">
<h3>Custom Title</h3>
</xpath>
<!-- Match notebook page by name -->
<xpath expr="//page[@name='details']" position="after">
<page string="Custom Tab" name="custom">
<field name="custom_field"/>
</page>
</xpath>
<!-- Match by position in parent -->
<xpath expr="//group[1]" position="inside">
<field name="new_field"/>
</xpath>
Position Attribute
Controls where the new content goes relative to the matched element:
after— insert after the matched elementbefore— insert before the matched elementinside— append inside the matched element (at the end)replace— replace the matched element entirelyattributes— modify attributes of the matched elementmove— move the matched element to a new location
<!-- Change attributes -->
<field name="name" position="attributes">
<attribute name="readonly">state == 'done'</attribute>
<attribute name="required">True</attribute>
</field>
<!-- Replace element -->
<field name="old_field" position="replace">
<field name="new_field"/>
</field>
<!-- Remove element (replace with nothing) -->
<field name="unwanted_field" position="replace"/>
View Priority and Resolution
When multiple views exist for the same model and type, Odoo uses these rules:
- The view with the lowest
priorityvalue (default: 16) becomes the base view - All inherited views are applied in priority order (lowest first)
- Within the same priority, views are applied in module dependency order
<record id="view_ticket_form_high_priority" model="ir.ui.view">
<field name="name">support.ticket.form.priority</field>
<field name="model">support.ticket</field>
<field name="priority">5</field>
<!-- Lower number = higher priority = processed first -->
<field name="arch" type="xml">
<form>...</form>
</field>
</record>
Primary vs Extension Views
Views are either primary (base) or extension (inherited):
- Primary views: have no
inherit_id. They define the base structure. - Extension views: have
inherit_id. They modify the primary view.
You can create a new primary view that inherits from another primary view. This creates a separate view that starts from the parent but diverges:
<record id="view_ticket_form_alternative" model="ir.ui.view">
<field name="name">support.ticket.form.alt</field>
<field name="model">support.ticket</field>
<field name="inherit_id" ref="support.view_ticket_form"/>
<field name="mode">primary</field>
<!-- mode='primary' makes this a standalone view -->
<field name="arch" type="xml">
<field name="name" position="replace">
<field name="display_name"/>
</field>
</field>
</record>
Linking Views to Actions
Actions reference views by type or explicitly:
<!-- Automatic: Odoo picks the lowest-priority view per type -->
<record id="action_tickets" model="ir.actions.act_window">
<field name="res_model">support.ticket</field>
<field name="view_mode">list,form,kanban</field>
</record>
<!-- Explicit: specify exactly which views to use -->
<record id="action_tickets" model="ir.actions.act_window">
<field name="res_model">support.ticket</field>
<field name="view_mode">list,form</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_ticket_list')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_ticket_form')}),
]"/>
</record>
Debugging View Issues
Common view problems and how to fix them:
- Field not appearing: Check the xpath expression matches the correct element. Use developer mode to inspect the combined view XML.
- View inheritance conflict: Two modules modifying the same element. Adjust priority or use more specific xpath.
- View not loading: Check the module dependency chain. The inheriting module must depend on the module that defines the base view.
- Wrong view being used: Check view priorities. Use explicit view_ids in the action to force a specific view.