What Is OWL?
OWL (Odoo Web Library) is Odoo's reactive JavaScript framework, used since Odoo 14. It is similar to React/Vue but designed specifically for Odoo. All frontend components in Odoo — form views, list views, kanban cards, buttons, dialogs — are OWL components.
OWL Component Basics
/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
class EquipmentDashboard extends Component {
static template = "equipment_tracking.Dashboard";
static props = {};
setup() {
this.state = useState({
count: 0,
items: [],
loading: true,
});
this.loadData();
}
async loadData() {
const orm = this.env.services.orm;
this.state.items = await orm.searchRead(
"equipment.item",
[],
["name", "status", "assigned_to"],
{ limit: 50 }
);
this.state.count = this.state.items.length;
this.state.loading = false;
}
onClickRefresh() {
this.state.loading = true;
this.loadData();
}
}
registry.category("actions").add("equipment_dashboard", EquipmentDashboard);OWL Template (XML)
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="equipment_tracking.Dashboard">
<div class="o_equipment_dashboard p-3">
<h2>Equipment Dashboard</h2>
<button class="btn btn-primary" t-on-click="onClickRefresh">
Refresh
</button>
<p>Total: <t t-esc="state.count"/> items</p>
<t t-if="state.loading">
<div class="text-muted">Loading...</div>
</t>
<t t-else="">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th>Assigned To</th>
</tr>
</thead>
<tbody>
<t t-foreach="state.items" t-as="item" t-key="item.id">
<tr>
<td><t t-esc="item.name"/></td>
<td>
<span t-attf-class="badge #{item.status === 'available' ? 'bg-success' : 'bg-info'}">
<t t-esc="item.status"/>
</span>
</td>
<td><t t-esc="item.assigned_to[1] or '-'"/></td>
</tr>
</t>
</tbody>
</table>
</t>
</div>
</t>
</templates>Key OWL Concepts
| Concept | Description | Example |
|---|---|---|
useState | Reactive state object | this.state = useState({ count: 0 }) |
useRef | DOM element reference | this.inputRef = useRef("input") |
useService | Access Odoo services | this.orm = useService("orm") |
useEffect | Side effects on state change | useEffect(() => { ... }, () => [this.state.x]) |
onWillStart | Async setup before render | onWillStart(async () => { ... }) |
onMounted | After first render | onMounted(() => { ... }) |
Using Odoo Services
import { useService } from "@web/core/utils/hooks";
class MyComponent extends Component {
setup() {
// ORM service (call Python models)
this.orm = useService("orm");
// RPC service (call controllers)
this.rpc = useService("rpc");
// Notification service
this.notification = useService("notification");
// Action service (navigate)
this.action = useService("action");
// Dialog service
this.dialog = useService("dialog");
}
async fetchData() {
// ORM call
const records = await this.orm.searchRead("res.partner", [], ["name"]);
// Controller call
const result = await this.rpc("/my/api/endpoint", { param: "value" });
// Show notification
this.notification.add("Data loaded!", { type: "success" });
// Navigate to record
this.action.doAction({
type: "ir.actions.act_window",
res_model: "res.partner",
res_id: 1,
views: [[false, "form"]],
});
}
}Registering Components
// As a client action (full-page component)
registry.category("actions").add("my_dashboard", MyDashboard);
// Then reference in XML:
// <record id="action_dashboard" model="ir.actions.client">
// <field name="name">Dashboard</field>
// <field name="tag">my_dashboard</field>
// </record>
// As a field widget
registry.category("fields").add("my_widget", { component: MyWidget });
// As a view component
registry.category("views").add("my_view", MyView);Common Mistakes
- Forgetting @odoo-module — Without the magic comment, Odoo does not load the JS file
- Mutating state directly — Always use
useState()for reactive state - Missing t-key in loops — Required for efficient DOM updates
- Not using services — Direct XHR instead of
useService('rpc')bypasses Odoo's auth and error handling
AI + OWL
Claude Code generates OWL components from descriptions. Describe the dashboard, widget, or interaction you need, and it creates the component class, template, and registration code with correct Odoo 19 syntax.