Ir al contenido

OWL en Odoo 19: primer componente custom

Estructura static/src, assets en manifest y registro en el backend
22 de junio de 2026 por
OWL en Odoo 19: primer componente custom
Atemi, Aitor Atencia

Odoo · OWL · Frontend

Odoo 19 usa OWL 2 para el backend web. Un componente custom bien registrado puede sustituir widgets XML limitados o pantallas compuestas. Esta guía monta el esqueleto mínimo: JS + XML + assets + acción cliente.

Logo Odoo JavaScript OWL
OWL vive en static/src; el manifest declara los assets.

Estructura

Archivos del componente

mi_modulo/
├── static/src/dashboard/
│   ├── mi_panel.js
│   ├── mi_panel.xml
│   └── mi_panel.scss      # opcional
├── views/mi_panel_action.xml
└── __manifest__.py
ArchivoResponsabilidad
*.jsClase OWL, servicios, registro en registry
*.xmlPlantilla QWeb del componente (t-name)
__manifest__.pyBloque assets apuntando al JS/XML
ir.actions.clientEntrada de menú con tag del registry

JavaScript

Componente OWL mínimo

/** @odoo-module **/

import { registry } from "@web/core/registry";
import { Component, onWillStart, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";

export class MiPanel extends Component {
    static template = "mi_modulo.MiPanel";
    static props = ["*"];

    setup() {
        this.orm = useService("orm");
        this.state = useState({ loading: true, items: [] });
        onWillStart(() => this.loadItems());
    }

    async loadItems() {
        this.state.items = await this.orm.call(
            "mi.model", "search_read", [[], ["name", "state"]]
        );
        this.state.loading = false;
    }
}

registry.category("actions").add("mi_modulo_panel", MiPanel);

@odoo-module activa el bundler ES modules. El tag del registry (mi_modulo_panel) debe coincidir con la acción cliente XML.

Plantilla

XML OWL (QWeb)

<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
    <t t-name="mi_modulo.MiPanel">
        <div class="o_mi_panel p-3">
            <t t-if="state.loading">
                <span>Loading…</span>
            </t>
            <t t-else="">
                <ul>
                    <t t-foreach="state.items" t-as="item" t-key="item.id">
                        <li><t t-out="item.name"/></li>
                    </t>
                </ul>
            </t>
        </div>
    </t>
</templates>

Manifest

Assets en __manifest__.py

'assets': {
    'web.assets_backend': [
        'mi_modulo/static/src/dashboard/mi_panel.js',
        'mi_modulo/static/src/dashboard/mi_panel.xml',
        'mi_modulo/static/src/dashboard/mi_panel.scss',
    ],
},

Sin esta declaración, el JS no se carga y verás «Could not get content for…» en consola. Tras cambiar assets: reinicia Odoo o activa modo debug assets.

Acción

Menú y ir.actions.client

<record id="action_mi_panel" model="ir.actions.client">
    <field name="name">Mi Panel</field>
    <field name="tag">mi_modulo_panel</field>
</record>

<menuitem id="menu_mi_panel"
          name="Mi Panel"
          action="action_mi_panel"
          parent="mi_modulo.menu_root"/>
Backend Odoo OWL
DevTools → Network: verifica que el bundle incluye tu JS tras -u.

Depuración

Errores frecuentes

SíntomaCausaSolución
Menú no abre nadatag ≠ registry keyAlinear action y .add("key", …)
Template not foundstatic template mal escritoDebe ser modulo.NombreTemplate
ORM AccessErrorMétodo sin permisosACL + método con @api.model acotado
Cambios JS no aparecenCache assets?debug=assets o reinicio

Resumen

Crea JS con @odoo-module, plantilla XML emparejada, declara assets en manifest y expón un ir.actions.client. Usa useService("orm") para datos y mantén la lógica de negocio en Python. OWL complementa el backend; no reemplaza ACL ni validaciones del servidor.

en Odoo
Vistas XML en Odoo: form, list, kanban y search
Convenciones de IDs, xpath inheritance y errores al actualizar módulos