As of Odoo 19, OWL remains the core UI layer. Whether you're building a custom widget, extending a view, or crafting a client action from scratch, you'll be writing OWL components. Let's walk through the key building blocks.
1. The Component Class
Every OWL component starts as a JavaScript class that extends Component from the OWL library. This class is where all your logic lives - state, methods, lifecycle hooks, and more.
import { Component } from "@odoo/owl";
class MyWidget extends Component {
setup() {
// initialization logic here
}
}
The setup() method is the heart of a component. It's called once when the component is created and is where you call hooks, initialize state, and wire up services.
2. The Template
OWL uses XML-based templates written in QWeb, Odoo's own templating language. Templates define what the component looks like. They live either in a separate .xml file or inline within the component.
<templates>
<t t-name="MyWidget">
<div class="my-widget">
<span>Hello, <t t-esc="props.name"/></span>
</div>
</t>
</templates>
The template name must match what you declare in the component class via a static template property. QWeb directives like t-if, t-foreach, and t-esc handle logic and rendering.
3. Props
Props are how a parent component passes data down to a child. They're read-only — a child should never modify its own props. In Odoo 19, you can define and validate props using a static props object on the class.
static props = {
name: { type: String },
count: { type: Number, optional: true },
};
If a required prop is missing or has the wrong type, OWL will warn you in development mode — a handy safety net.
4. State & Reactivity
For local, mutable data, OWL provides the useState hook. Any change to a reactive state object automatically triggers a re-render of the relevant parts of the template — no manual DOM manipulation needed.
import { Component, useState } from "@odoo/owl";
setup() {
this.state = useState({ count: 0 });
}
State is reactive by default. You don't call setState() — just mutate the object directly (this.state.count++) and OWL handles the rest.
5. Lifecycle hooks
OWL components expose lifecycle hooks you can tap into. These are called as regular functions inside setup(), keeping everything neatly in one place.
- onMounted
- onWillMount
- onWillUpdateProps
- onPatched
- onWillStart
For example, onMounted runs after the component is first inserted into the DOM — ideal for fetching data or integrating a third-party library. onWillUnmount is where you clean up event listeners or timers.
6. Services & the env object
Odoo injects a global env object into every component. This is how you access core Odoo services like the ORM, notifications, router, and more — without needing to import them manually each time.
Use the useService() hook inside setup() to grab a service by name. For instance, useService("orm") gives you direct access to Odoo's data layer.
Once you internalize this structure, reading and writing Odoo frontend code becomes much more natural. The pattern is consistent across every part of the Odoo 19 UI — from a simple button widget to a full custom view.
The best next step is to open any existing Odoo module's static/src/components folder and trace how these six pieces appear together in real code. The framework rewards curiosity.
