Ad – 728×90
🦉 OWL in Odoo

OWL in Odoo – Using the Web Library in Backend and POS

OWL (Odoo Web Library) is Odoo's own frontend framework, used for the backend UI (/web), the Point of Sale, and increasingly for the website editor. Understanding how Odoo loads, organizes, and boots OWL code is key to writing reliable frontend customizations.

⏱️ 22 min 🎯 Intermediate 📅 Updated 2026
What you'll learn:
  • Where OWL is used in Odoo (backend, POS, website editor)
  • How asset bundles work and how to add your files
  • The @odoo-module directive and ES module system
  • The service and registry pattern
  • How the Odoo web client boots

Where OWL is Used in Odoo

AreaOWL usageMain asset bundle
Backend (/web)All views, dialogs, widgets, action managerweb.assets_backend
Point of SaleFull POS app — screens, components, popupspoint_of_sale.assets
Website editorEditor toolbar, snippet options, overlaywebsite.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:

Python
'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:

JavaScript
/** @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.

Ad – 728×90

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:

JavaScript
/** @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:

CategoryPurpose
fieldsField widgets for form/list views
viewsView types (form, list, kanban, etc.)
servicesSingleton services injected into components
actionsClient-side action handlers
systrayItems in the top-right system tray

Services

Services are singletons that provide shared functionality to all components. Access them via useService():

JavaScript
/** @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' });
    }
}
Key takeaways:
  • 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.