Create View Widget Odoo 18
By Braincuber Team
Published on December 29, 2025
Development team building custom inventory dashboard discovers UI limitation: standard Odoo list views displaying basic field data unable showing contextual information without drilling multiple screens, warehouse manager clicking each product row viewing stock levels across 5 locations requiring 3 clicks per product wasting 20 minutes daily checking 200 products, sales representative needing quick customer order summary information viewing total order value line item count but standard views only showing single fields requiring opening full form view losing efficiency, project manager tracking task completion wanting instant visual indicators progress percentage team assignment but lacking clean compact way displaying multi-field summary data, and operations director requiring real-time KPI indicators list views showing computed metrics interactive popovers detailed breakdowns impossible standard field widgets—creating productivity loss excessive clicking poor UX designer frustration and inability creating information-dense interactive interfaces requiring custom view widgets popover functionality computed field integration and interactive UI components enhancing user efficiency data visibility operational effectiveness.
Odoo 18 custom view widgets enable interactive UI components through JavaScript OWL framework components defining widget behavior structure interactivity, XML templates providing widget layout popover content visual design, Python computed fields supplying dynamic calculated data widget display, usePopover hook creating interactive popups displaying detailed information on-click, registry integration registering widgets view_widgets category making available views, view inheritance extending existing list tree form views custom widget columns, event binding connecting click events popup triggers user interactions, template directives using t-on-click t-out Owl framework features, column invisible fields hiding computed data fields while keeping available widget logic, and reusable components creating modular widgets applicable multiple models views—reducing clicks required viewing data 80 percent through instant popovers improving information density displaying multi-field summaries single column enhancing user experience through interactive visual components achieving development efficiency through reusable widget patterns and supporting custom business requirements through flexible extensible UI framework enabling tailored user interfaces operational dashboards real-time analytics.
View Widget Features: JavaScript OWL components, XML templates, Python computed fields, usePopover hook, Registry integration, View inheritance, Event binding, Template directives, Column invisible fields, Reusable components
Understanding View Widgets
Customizing field display and behavior:
What are View Widgets:
View widgets in Odoo customize display behavior fields views enabling unique data presentation adding interactivity integrating additional functionality. Widgets enhance standard fields with custom rendering interactive features computed data visualization.
Key Capabilities:
- Display data in unique customized formats
- Add interactive elements popovers tooltips dropdowns
- Integrate computed fields calculated values
- Enhance user experience with visual indicators
- Create reusable UI components across models
Widget Architecture:
| Component | Purpose | Technology |
|---|---|---|
| JavaScript Component | Widget behavior and logic | OWL Framework |
| XML Template | Widget layout and structure | QWeb Templates |
| Python Model | Computed fields data | Python ORM |
| View Definition | Widget integration in views | XML Views |
Example: Order Line Count Widget
Complete implementation walkthrough:
Widget Functionality:
This example creates custom widget displaying icon in purchase order list view. When clicked icon shows popover with order line count total quantity information providing quick summary without opening full record.
User Experience:
- User sees small icon in each purchase order row
- Clicking icon triggers popover appearing above icon
- Popover displays: Order line count Number of product quantities
- Instant information without page navigation
- Close popover clicking anywhere outside
JavaScript Component Implementation
OWL framework component code:
Component Structure:
Odoo 18 uses OWL (Odoo Web Library) framework for building components. Component consists of popover content and main widget logic.
/* @odoo-module */
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { usePopover } from "@web/core/popover/popover_hook";
import { Component } from "@odoo/owl";
export class orderLineCountPopover extends Component {
setup() {
this.actionService = useService("action");
}
}
orderLineCountPopover.template = "your_module.OrderLineCountPopOver";
export class orderLineCountWidget extends Component {
setup() {
this.popover = usePopover(
this.constructor.components.Popover,
{ position: "top" }
);
this.calcData = {};
}
showPopup(ev) {
this.popover.open(ev.currentTarget, {
record: this.props.record,
calcData: this.calcData,
});
}
}
orderLineCountWidget.components = { Popover: orderLineCountPopover };
orderLineCountWidget.template = "your_module.orderLineCount";
export const OrderLineCountWidget = {
component: orderLineCountWidget,
};
registry.category("view_widgets").add(
"order_line_count_widget",
OrderLineCountWidget
);Code Explanation:
Imports:
registry: Registers widget in Odoo's view widget systemuseService: Hook for accessing Odoo servicesusePopover: Hook for creating popover functionalityComponent: Base OWL component class
orderLineCountPopover Class:
- Defines popover component displaying detailed information
setup(): Initializes action service for potential actionstemplate: Links to XML template defining popover layout
orderLineCountWidget Class:
- Main widget component handling user interactions
usePopover(): Initializes popover with "top" positioningshowPopup(ev): Method called on icon click opening popover passing record datacomponents: Declares popover as child component
Registry Registration:
registry.category("view_widgets").add(): Makes widget available views- Name: "order_line_count_widget" used in view definitions
XML Templates
Widget and popover layout:
Template Code:
<?xml version="1.0" encoding="UTF-8" ?>
<template id="template" xml:space="preserve">
<t t-name="your_module.orderLineCount">
<a t-on-click="showPopup" t-attf-class="fa fa-list"/>
</t>
<t t-name="your_module.OrderLineCountPopOver">
<div>
<table class="table table-borderless table-sm">
<br/>
<tr>
<td><h5>Order line count:</h5></td>
<td>
<h6>
<span t-out='props.record.data.order_line_count'/>
</h6>
</td>
</tr>
<tr>
<td><h5>Number of quantity:</h5></td>
<td>
<h6>
<span t-out='props.record.data.num_qty'/>
</h6>
</td>
</tr>
</table>
</div>
</t>
</template>Template Breakdown:
Widget Template (orderLineCount):
t-name: Links template to JavaScript component<a>: Creates clickable icon elementt-on-click="showPopup": Binds click event to showPopup methodfa fa-list: FontAwesome icon class displaying list icon
Popover Template (OrderLineCountPopOver):
<table>: Structured layout for data displayt-out: Safely renders field values from record dataprops.record.data.order_line_count: Accesses computed fieldprops.record.data.num_qty: Accesses quantity field- Bootstrap classes:
table table-borderless table-smfor styling
Python Model with Computed Fields
Backend data calculation:
Model Extension:
# -*- coding: utf-8 -*-
from odoo import fields, models
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
order_line_count = fields.Integer(
string='Order Line Count',
compute='compute_line_count',
store=True
)
num_qty = fields.Integer(
string='Number Of Quantity',
compute='compute_line_count',
store=True
)
def compute_line_count(self):
for rec in self:
rec.order_line_count = len(rec.order_line.mapped('id'))
rec.num_qty = sum(rec.order_line.mapped('product_qty'))Code Explanation:
Model Inheritance:
_inherit = 'purchase.order': Extends existing Purchase Order model- Adds custom fields without modifying core module
Computed Fields:
order_line_count: Integer field storing number order linesnum_qty: Integer field storing total product quantitycompute=: Specifies computation methodstore=True: Stores computed values database improving performance
Computation Method:
compute_line_count(): Calculates both field valueslen(rec.order_line.mapped('id')): Counts order line recordssum(rec.order_line.mapped('product_qty')): Sums product quantities- Iterates through recordset updating each record
View Integration
Adding widget to views:
View Inheritance:
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="purchase_order_view_tree" model="ir.ui.view">
<field name="name">purchase.order.tree.custom</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//list" position="inside">
<field name="order_line_count" column_invisible="1"/>
<field name="num_qty" column_invisible="1"/>
<widget name="order_line_count_widget"/>
</xpath>
</field>
</record>
</odoo>View Code Explanation:
inherit_id: References standard purchase order tree viewxpath expr="//list": Targets list element for modificationposition="inside": Adds content inside list elementcolumn_invisible="1": Hides computed fields from display while keeping data available widget<widget name="order_line_count_widget"/>: Adds custom widget column- Widget name matches registry registration name
Module Structure
Complete file organization:
Required Files:
Module Directory Structure:
your_module/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ └── purchase_order.py
├── static/
│ └── src/
│ ├── js/
│ │ └── order_line_count_widget.js
│ └── xml/
│ └── order_line_count_template.xml
└── views/
└── purchase_order_views.xmlManifest Configuration:
{
'name': 'Order Line Count Widget',
'version': '18.0.1.0.0',
'depends': ['purchase', 'web'],
'data': [
'views/purchase_order_views.xml',
],
'assets': {
'web.assets_backend': [
'your_module/static/src/js/order_line_count_widget.js',
'your_module/static/src/xml/order_line_count_template.xml',
],
},
}Testing the Widget
Verification steps:
Installation and Testing:
- Install Module:
- Apps menu → Update Apps List
- Search for module name
- Click Install
- Navigate to Purchase Orders:
- Purchase → Orders → Purchase Orders
- List view displays
- Verify Widget Appearance:
- Check for list icon in each row
- Icon should be clickable
- Test Popover:
- Click icon
- Popover appears above icon
- Displays order line count and total quantity
- Close by clicking outside popover
- Verify Data Accuracy:
- Compare popover data with actual order lines
- Ensure counts match
Best Practices
Use Stored Computed Fields for Performance Optimization: Computing values on-the-fly every render equals performance degradation slow list views. Optimization: store=True in computed field definitions caching calculated values database, compute methods efficient using mapped() instead loops, add depends decorator triggering recomputation when related fields change. Stored fields improve widget performance enabling fast list rendering supporting large datasets maintaining responsive user experience.
Implement Proper Error Handling in Widget Code: Unhandled errors breaking widgets equals poor user experience console errors. Error handling: Try-catch blocks protecting showPopup method, null checks before accessing props.record.data, default values preventing undefined display, graceful degradation showing simple fallback when data unavailable. Robust error handling ensures widgets work reliably edge cases maintaining professional user experience preventing JavaScript crashes.
Keep Popover Content Concise and Scannable: Information overload cluttered popovers equals cognitive burden reduced usability. Content design: Limit 2-5 key metrics per popover, use clear labels descriptive headings, format numbers appropriately (thousands separators currency symbols), leverage visual hierarchy through font sizes weights, maintain consistent styling matching Odoo design. Concise content enables quick information absorption supporting efficient workflows maintaining clean professional appearance.
Register Widgets with Descriptive Meaningful Names: Generic ambiguous widget names equals confusion maintenance difficulties. Naming conventions: Use descriptive names reflecting widget purpose order_line_count_widget, follow Odoo naming patterns consistency, prefix custom widgets module name avoiding conflicts, document widget purpose usage comments. Clear naming improves code maintainability enables easier debugging supports team collaboration facilitating long-term maintenance.
Conclusion
Odoo 18 custom view widgets enable interactive UI components through JavaScript OWL framework components XML templates Python computed fields usePopover hook registry integration view inheritance event binding template directives column invisible fields and reusable components. Reduce clicks required viewing data through instant popovers improving information density displaying multi-field summaries single column enhancing user experience through interactive visual components achieving development efficiency through reusable widget patterns supporting custom business requirements through flexible extensible UI framework enabling tailored user interfaces operational dashboards and achieving competitive advantage through superior user experience efficient workflows and modern professional interfaces supporting productivity operational excellence business success.
