Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/odoo/documentation/llms.txt

Use this file to discover all available pages before exploring further.

Odoo supports three kinds of JavaScript files: plain scripts (for external libraries), native ES modules (the standard for all new code), and the legacy Odoo custom module system (for older code not yet converted). All JS files are bundled and served to the browser through the asset pipeline defined in __manifest__.py.

Three Module Flavours

Plain JS

External libraries or low-level scripts with no module system. Use IIFE style to avoid leaking globals. No import/export.

Native ES Modules

All new addon code. Uses standard import/export syntax. Transpiled by Odoo at serve time into Odoo modules.

Odoo Module System

Legacy odoo.define('name', ['dep'], function(require){}) pattern. Still valid; use for files not yet migrated.

Native JavaScript Modules

Writing a Module

Any file under /static/src or /static/tests is automatically transpiled. Use standard ES syntax:
// File: myaddon/static/src/utils/helpers.js
import { someFunction } from "./other_file";

export function myHelper(val) {
  return someFunction(val + 3);
}
After transpilation, this becomes an Odoo module named @myaddon/utils/helpers. The naming rule is:
some_addon/static/src/path/to/file.js@some_addon/path/to/file

Import Paths

Within the same addon, use relative paths:
import { something } from "./file_a";
Across addons, always use the full @addon_name/... path:
// Importing from the web addon
import { memoize } from "@web/core/utils/functions";
import { registry } from "@web/core/registry";
import { Component, useState } from "@odoo/owl";
Relative imports only work within the same Odoo addon. Importing from another addon with a relative path will silently fail at runtime. Always use @addon_name/... for cross-addon imports.

Opting Out of Transpilation

To prevent a file from being transpiled (e.g., an IIFE library wrapper):
/** @odoo-module ignore **/
(function () {
  const sum = (a, b) => a + b;
  console.log(sum(1, 2));
})();

Opting In (Outside /static/src)

For files outside the default transpiled directories, add the @odoo-module comment to opt in:
/** @odoo-module **/
export function sum(a, b) {
  return a + b;
}

Module Aliases

During migration from the legacy system, create an alias so old require('web.name') calls continue to work:
/** @odoo-module alias=web.someName **/
import { someFunction } from "./file_b";

export default function otherFunction(val) {
  return someFunction(val + 3);
}
By default, aliases re-export the default export. To alias the entire module namespace:
/** @odoo-module alias=web.someName default=0 **/
export function otherFunction(val) { ... }

Transpilation Limitations

The Odoo transpiler is not a full parser. Avoid these patterns:
// ❌ Not supported — import must not be preceded by a non-space character
var a = 1; import X from "xxx";

// ❌ Not supported — export object cannot contain comments
export {
  a as b, // this is a comment  ← breaks transpilation
  c,
}

// ✅ Supported — import/export on their own lines
import X from "xxx";
export { a as b, c };
Module names with a / are treated as file paths by the heuristic. Do not use / in symbolic module names.

Legacy Odoo Module System

The odoo.define pattern is still valid for code not yet migrated:
// file a.js
odoo.define("module.A", [], function (require) {
  "use strict";
  const A = { /* ... */ };
  return A;
});

// file b.js
odoo.define("module.B", ["module.A"], function (require) {
  "use strict";
  const A = require("module.A");
  const B = { /* uses A */ };
  return B;
});
odoo.define takes:
  1. moduleName — unique string, convention: addon_name.DescriptiveName
  2. dependencies — array of module name strings required before this module runs
  3. factory function — receives require, returns the module’s exported value
Circular dependencies are not supported and will prevent dependent modules from loading. Both the native and legacy systems can coexist within the same addon.

Asset Bundles

JavaScript (and SCSS) files are grouped into asset bundles declared in __manifest__.py. Odoo provides several standard bundles:
BundleUsed in
web.assets_backendAll backend (web client) pages
web.assets_frontendPublic website pages
web.assets_commonShared between backend and frontend
web.assets_testsTest tours (loaded in debug=tests mode)
web.assets_backend_lazyLoaded on demand in the backend

Adding Files to a Bundle

In __manifest__.py:
{
    "name": "My Addon",
    "version": "1.0",
    "depends": ["web"],
    "assets": {
        "web.assets_backend": [
            "myaddon/static/src/my_component/my_component.js",
            "myaddon/static/src/my_component/my_component.xml",
            "myaddon/static/src/my_component/my_component.scss",
        ],
        "web.assets_frontend": [
            "myaddon/static/src/website/my_widget.js",
        ],
    },
}
Asset paths support glob patterns. For example, "myaddon/static/src/**/*" includes all files recursively. However, for Odoo SaaS databases, list files individually — wildcard inclusion is not supported there.

Overriding Bundle Contents

You can prepend, append, or remove entries using special keys:
"assets": {
    "web.assets_backend": [
        # Prepend before existing entries
        ("prepend", "myaddon/static/src/early_loader.js"),
        # Remove an existing entry
        ("remove", "web/static/src/core/some_file.js"),
        # Replace an existing entry
        ("replace", "web/static/src/core/old.js", "myaddon/static/src/new.js"),
    ],
},

The Patching Mechanism

OWL components and plain objects can be patched (monkey-patched) without modifying source files. Use patch from @web/core/utils/patch:
import { patch } from "@web/core/utils/patch";
import { FormController } from "@web/views/form/form_controller";

patch(FormController.prototype, {
  setup() {
    super.setup();
    // additional initialization
    this.myExtraState = useState({ count: 0 });
  },

  async onSaveRecord() {
    await super.onSaveRecord();
    this.myExtraState.count++;
  },
});
Always call super when patching setup() and other lifecycle methods — failing to do so will break all other patches that rely on the original method. Note that super is only valid inside a method shorthand (fn() {}), not in a regular function (fn: function() {}) or an arrow function.
To unpatch (useful in tests):
import { patch } from "@web/core/utils/patch";

const unpatch = patch(SomeClass.prototype, { myMethod() { ... } });
// later:
unpatch();

Patching Static Methods

When patching a class directly (not .prototype), static properties are affected:
// patches static methods on the class itself
patch(MyClass, {
  myStaticFn() { ... },
});

// patches instance methods (the usual case)
patch(MyClass.prototype, {
  myPrototypeFn() { ... },
});

Build docs developers (and LLMs) love