- Where OWL is used in Odoo (backend, POS, website editor)
- How asset bundles work and how to add your files
- The
@odoo-moduledirective and ES module system - The service and registry pattern
- How the Odoo web client boots
Where OWL is Used in Odoo
| Area | OWL usage | Main asset bundle |
|---|---|---|
| Backend (/web) | All views, dialogs, widgets, action manager | web.assets_backend |
| Point of Sale | Full POS app — screens, components, popups | point_of_sale.assets |
| Website editor | Editor toolbar, snippet options, overlay | website.assets_wysiwyg |
| Frontend (public pages) | Minimal — some widgets (chat, form) | web.assets_frontend |
Backend and POS customizations go into different bundles. Never put backend code in the POS bundle or vice versa — they boot separately and have different registries.
Asset Bundles
Asset bundles are named collections of JS, CSS, and XML files. Declare files in your module's __manifest__.py:
'assets': {
# Backend customizations
'web.assets_backend': [
'my_module/static/src/js/my_widget.js',
'my_module/static/src/xml/my_widget.xml',
'my_module/static/src/scss/my_widget.scss',
],
# POS customizations
'point_of_sale.assets': [
'my_module/static/src/pos/my_pos_component.js',
'my_module/static/src/pos/my_pos_component.xml',
],
# Public website
'web.assets_frontend': [
'my_module/static/src/js/website_widget.js',
],
}
Odoo builds bundles at startup (or from cache). In development mode (?debug=assets), bundles are rebuilt on every request.
@odoo-module Directive
Every JS file in an Odoo asset bundle must start with /** @odoo-module **/. This marks the file as an ES module in Odoo's module system and enables imports from other Odoo modules:
/** @odoo-module **/
// Import from Odoo core
import { Component, useState, onMounted } from '@odoo/owl';
import { registry } from '@web/core/registry';
import { useService } from '@web/core/utils/hooks';
// Import from another module in the same bundle
import { MyHelperClass } from 'my_module/static/src/js/helpers';
export class MyComponent extends Component {
static template = 'my_module.MyComponent';
setup() {
this.orm = useService('orm');
this.state = useState({ count: 0 });
}
}
The path in imports like '@web/core/registry' resolves to the module exported from web/static/src/core/registry.js in the Odoo source.
The Registry System
Registries are the primary mechanism for extending Odoo's UI without patching. A registry is a key→value store where you register components, services, or views under a string key:
/** @odoo-module **/
import { registry } from '@web/core/registry';
import { MyFieldWidget } from './my_field_widget';
// Register a custom field widget
registry.category('fields').add('my_widget', {
component: MyFieldWidget,
displayName: 'My Custom Widget',
supportedTypes: ['char', 'text'],
});
// Use it in a view XML: widget="my_widget"
Common registry categories:
| Category | Purpose |
|---|---|
| fields | Field widgets for form/list views |
| views | View types (form, list, kanban, etc.) |
| services | Singleton services injected into components |
| actions | Client-side action handlers |
| systray | Items in the top-right system tray |
Services
Services are singletons that provide shared functionality to all components. Access them via useService():
/** @odoo-module **/
import { Component } from '@odoo/owl';
import { useService } from '@web/core/utils/hooks';
export class MyComponent extends Component {
static template = 'my_module.MyComponent';
setup() {
this.orm = useService('orm'); // ORM calls
this.notification = useService('notification'); // Toast messages
this.dialog = useService('dialog'); // Dialog popups
this.router = useService('router'); // URL routing
this.user = useService('user'); // Current user info
}
async loadData() {
const records = await this.orm.searchRead(
'res.partner',
[['is_company', '=', true]],
['name', 'email'],
{ limit: 10 },
);
this.notification.add('Loaded!', { type: 'success' });
}
}
- Backend, POS, and website editor use separate OWL asset bundles — add files to the correct bundle
- Every JS file needs
/** @odoo-module **/at the top - Use registries to add field widgets, views, services, and systray items without patching
- Access shared functionality (ORM, notifications, dialogs) through services via
useService()
Frequently Asked Questions
What's the difference between a registry and a patch?
A registry registers a new entry under a key — it adds to the system. A patch modifies an existing component or class in-place. Use registries for adding new field widgets, services, and actions. Use patches for extending existing POS components or altering behavior of a specific existing component.
How do I debug OWL components in the browser?
Open DevTools and use the OWL Devtools browser extension (available for Chrome/Firefox). It shows the component tree, component state, props, and environment. In development mode (?debug=1), Odoo also adds extra error messages and disables caching.
Can I use external npm packages in Odoo OWL code?
Not directly via npm — Odoo's asset system doesn't use npm. You can include a pre-bundled JavaScript library by adding the file to your module's static/lib/ folder and registering it in the manifest. Alternatively, load it from a CDN via a <script> tag in a QWeb template, though this adds a network dependency.