In Odoo 19, KPI (Key Performance Indicator) indicators are real-time metric widgets displayed on module dashboards. They aggregate live data from the database and present it as simple numbers, trends, or comparisons — giving managers an at-a-glance view of business performance.
Odoo 19 dashboards use KPI cards for metrics like:
- Total confirmed sales orders this month
- Number of pending delivery orders
- Outstanding receivables
- Monthly payroll cost
How KPI Data Flows in Odoo 19
PostgreSQL DB > Python ORM (read_group) > JSON-RPC > OWL Component > Dashboard Card
- Python: Aggregates records using read_group() or search_count()
- Controller: Exposes computed data via a JSON-RPC route
- OWL Component: Fetches and renders the KPI on the dashboard
Building a Custom KPI Dashboard — Full Example
Let's build a simple Sales KPI Dashboard that shows:
- Total confirmed sales orders
- Total sales revenue this month
Step 1: Python Model Method
In your custom module, add a method to the sale.order that returns KPI data:
# models/sale_dashboard.py
from odoo import models, fields, api
from datetime import date
class SaleDashboard(models.Model):
_inherit = 'sale.order'
@api.model
def get_kpi_data(self):
today = date.today()
first_day = today.replace(day=1)
# Count confirmed orders this month
confirmed_orders = self.search_count([
('state', 'in', ['sale', 'done']),
('date_order', '>=', first_day),
])
# Sum revenue of confirmed orders this month
result = self.read_group(
domain=[
('state', 'in', ['sale', 'done']),
('date_order', '>=', first_day),
],
fields=['amount_total:sum'],
groupby=[],
)
total_revenue = result[0]['amount_total'] if result else 0.0
return {
'confirmed_orders': confirmed_orders,
'total_revenue': total_revenue,
}
Step 2: Register a Client Action (XML)
<!-- views/dashboard_action.xml -->
<odoo>
<record id="action_sales_kpi_dashboard" model="ir.actions.client">
<field name="name">Sales KPI Dashboard</field>
<field name="tag">sales_kpi_dashboard</field>
</record>
<menuitem
id="menu_sales_kpi_dashboard"
name="KPI Dashboard"
parent="sale.sale_menu_root"
action="action_sales_kpi_dashboard"
sequence="5"/>
</odoo>
Step 3: OWL Component (JavaScript)
// static/src/js/sales_kpi_dashboard.js
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { Component, onWillStart, useState } from "@odoo/owl";
import { loadBundle } from "@web/core/assets";
export class SalesKpiDashboard extends Component {
static template = "sales_module.SalesKpiDashboard";
setup() {
this.orm = useService("orm");
this.state = useState({
confirmedOrders: 0,
totalRevenue: 0.0,
});
onWillStart(async () => {
const data = await this.orm.call(
"sale.order",
"get_kpi_data",
[],
{}
);
this.state.confirmedOrders = data.confirmed_orders;
this.state.totalRevenue = data.total_revenue.toFixed(2);
});
}
}
registry.category("actions").add("sales_kpi_dashboard", SalesKpiDashboard);
Step 4: OWL Template (XML)
<!-- static/src/xml/sales_kpi_dashboard.xml -->
<templates>
<t t-name="sales_module.SalesKpiDashboard">
<div class="o_dashboard">
<div class="o_dashboard_header">
<h2>Sales KPI Dashboard</h2>
</div>
<div class="o_kpi_card_wrapper d-flex gap-4 mt-4">
<!-- KPI Card 1 -->
<div class="o_kpi_card card p-4 shadow-sm text-center">
<h6 class="text-muted">Confirmed Orders (This Month)</h6>
<h2 class="text-primary fw-bold">
<t t-esc="state.confirmedOrders"/>
</h2>
</div>
<!-- KPI Card 2 -->
<div class="o_kpi_card card p-4 shadow-sm text-center">
<h6 class="text-muted">Total Revenue (This Month)</h6>
<h2 class="text-success fw-bold">
$<t t-esc="state.totalRevenue"/>
</h2>
</div>
</div>
</div>
</t>
</templates>
Step 5: Register Assets in __manifest__.py
# __manifest__.py
{
'name': 'Sales KPI Dashboard',
'version': '19.0.1.0.0',
'depends': ['sale_management'],
'data': [
'views/dashboard_action.xml',
],
'assets': {
'web.assets_backend': [
'sales_module/static/src/js/sales_kpi_dashboard.js',
'sales_module/static/src/xml/sales_kpi_dashboard.xml',
],
},
}Building KPI indicators in Odoo 19 follows a clean three-step pattern:
- Python > Write a model method using search_count() or read_group() to aggregate data
- OWL JS > Create a component that calls the method via orm.call() and stores results in useState()
- OWL XML > Bind the state to HTML template using t-esc directives
KPI dashboards in Odoo help track key data in real time, making it easier to understand performance and take quick decisions.
To read more about How to Configure a Dashboard with Odoo 19, refer to our blog How to Configure a Dashboard with Odoo 19.