If you've worked with Odoo for a while, you've probably noticed the shift. The clunky, jQuery-heavy views of older Odoo versions are giving way to something faster, cleaner, and more responsive.
That's OWL — the Odoo Web Library.
OWL is Odoo's homegrown JavaScript framework for building reactive user interfaces. If you're developing custom Odoo modules and still relying on legacy JS patterns, you're making your life harder than it needs to be.
What is OWL?
OWL (Odoo Web Library) is a lightweight, component-based JavaScript framework built specifically for Odoo. It was designed to replace the older QWeb + jQuery approach with a modern reactive system similar to Vue or React — but lighter and purpose-built for Odoo's architecture.
Key features:
- Reactive components — UI updates automatically when data changes
- Single-file components — template, logic, and style in one file
- ~20KB gzipped, no heavy dependencies
- Odoo-native — built to work seamlessly with Odoo's server-side framework
Why OWL Matters for Odoo Developers
1. Better User Experience
OWL components render faster and update without full page reloads. Your users get a smoother, app-like experience.
2. Cleaner Code
Instead of mixing jQuery selectors with QWeb templates, OWL lets you write self-contained components. Your code becomes easier to maintain, test, and extend.
3. Modern Development Patterns
If you already know Vue or React, OWL will feel familiar. Components, props, slots, lifecycles — the concepts translate directly.
Code Examples
1. Basic Component — Dashboard Widget
A reactive dashboard widget that fetches data from the server and displays it:
const { Component, useState, onWillStart } = owl;
const { xml } = owl.tags;
class DashboardWidget extends Component {
setup() {
this.state = useState({
totalSales: 0,
ordersToday: 0
});
onWillStart(async () => {
await this.loadData();
});
}
async loadData() {
const data = await this.env.services.rpc({
route: '/my_module/dashboard_data',
});
this.state.totalSales = data.total_sales;
this.state.ordersToday = data.orders_count;
}
}
DashboardWidget.template = xml`
`;
2. Component with Props — Reusable Button
A reusable button component with props validation and click tracking:
const { Component, useState } = owl;
class ActionButton extends Component {
static props = {
label: String,
icon: { type: String, optional: true },
color: { type: String, optional: true },
onAction: Function,
};
setup() {
this.clickCount = useState({ count: 0 });
}
handleClick() {
this.clickCount.count++;
this.props.onAction();
}
}
ActionButton.template = xml`
`;
3. Integrating Server Data with RPC
Fetching and displaying employee data from the server:
const { Component, onWillStart } = owl;
class EmployeeList extends Component {
setup() {
this.state = useState({ employees: [], loading: true });
onWillStart(async () => {
this.state.employees = await this.env.services.rpc({
route: '/hr/get_employees',
params: { active_only: true },
});
this.state.loading = false;
});
}
getFormattedSalary(emp) {
return new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP'
}).format(emp.salary);
}
}
EmployeeList.template = xml`
Loading employees...
Name
Department
Salary
`;
4. Form Widget — Real-Time Search with Debounce
A live search widget that debounces input (300ms) before querying the server:
const { Component, useState } = owl;
class LiveSearch extends Component {
static props = {
model: String,
fields: Array,
onSelect: { type: Function, optional: true },
};
setup() {
this.state = useState({ query: '', results: [], timer: null });
}
onSearch(ev) {
this.state.query = ev.target.value;
if (this.state.timer) clearTimeout(this.state.timer);
this.state.timer = setTimeout(async () => {
if (this.state.query.length < 2) {
this.state.results = [];
return;
}
this.state.results = await this.env.services.rpc({
route: '/web/dataset/search_read',
params: {
model: this.props.model,
fields: this.props.fields,
domain: [['name', 'ilike', this.state.query]],
limit: 10,
},
});
}, 300);
}
selectItem(item) {
this.state.query = item.name;
this.state.results = [];
if (this.props.onSelect) this.props.onSelect(item);
}
}
LiveSearch.template = xml`
-
`;
5. Registering a Custom Field Widget
Register a color picker as an Odoo form field widget:
const { Registry } = require('@web/core/registry');
const { Component, useState } = owl;
class ColorPickerField extends Component {
setup() {
this.state = useState({ color: this.props.value || '#000000' });
}
setColor(color) {
this.state.color = color;
this.props.update(color);
}
}
ColorPickerField.template = xml`
`;
Registry.category('fields').add('color_picker', ColorPickerField);
How to Add These to Your Module
Each component needs to be:
- Defined in your JS file (as shown above)
- Registered in Odoo's component registry
- Mounted via XML — in a form view, dashboard, or standalone page
Example XML to mount the dashboard widget:
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="dashboard_view" inherit_id="web.basic_layout">
<xpath expr="//div[hasclass('o_content')]" position="inside">
<DashboardWidget/>
</xpath>
</template>
</odoo>
When Should You Use OWL?
Use OWL when you need:
- Custom dashboards or analytics views
- Complex form widgets with real-time updates
- Interactive kanban or list views
- Any UI that needs to update without reloading the page
Stick with standard Odoo views (tree, form, kanban) for basic CRUD. Reach for OWL when you need something that standard views can't deliver.
Getting Started
OWL is already included in Odoo 16 and above. To start building with it:
- Create your custom module
- Import OWL in your JS files:
const { Component } = require('owl'); - Define your component and template
- Register it in Odoo's component registry
- Add it to your view via XML
Final Thoughts
OWL isn't just another framework — it's Odoo's bet on the future of its UI. If you're building custom modules for clients or your own business, learning OWL is one of the best investments you can make.
The days of fighting with jQuery selectors in Odoo are over. Welcome to reactive UIs.
Need help building custom OWL components for your Odoo instance? Logiz Information Technology Solutions specializes in Odoo development — from custom modules to full ERP implementations. Contact us at sales@logiz.cloud to learn more.