technical

How Odoo's patch() Function Works Under the Hood

Published on 06/17/2026
odoo-patch-function

1. What is patch()?

The patch() utility (located at @web/core/utils/patch) is Odoo's mechanism for modifying existing classes and objects at runtime without changing their original source code. It is the backbone of Odoo's extensibility model, allowing enterprise and custom modules to add or override behavior in community components.

Before patch(), Odoo used a legacy include() system tied to its custom Class mechanism. With the move to native ES6 classes and the OWL framework, patch() was introduced as a modern, standards-compliant replacement that supports the native super keyword.

2. Why Use patch()?

patch() solves a critical problem in modular software: how to let one module safely extend the behavior of another module's class without creating tight coupling or requiring source modification.

Approach When to Use
patch() Modify existing behavior in a class you don't own (core, community)
Registry Add a new pluggable item (view, field, service) to a list
JS Inheritance Create a new variant of a class for your own use
Hook (useX) Share stateful logic across multiple components
Service Provide cross-component logic via the environment
Rule of Thumb

Use patch() as a last resort. Prefer registries, hooks, and services. Patch only when you must modify behavior in an existing class that has no declared extension point.

3. Internal Mechanics of patch()

3.1 The Prototype Chain Model

JavaScript class instances resolve method calls by walking up the prototype chain until a matching method is found. patch() works by extending this chain dynamically  each patch becomes part of the method resolution order.

Conceptually, after two patches are applied, method resolution flows like this:

Base Class Patch 1 Layer Patch 2 Layer Runtime Instance

Each patch layer in the chain is a bridge: it holds the extension's methods and ensures is a transparent bridge object. It holds the method descriptors from the previous layer, ensuring super resolves correctly to the previous implementation. This resolution is performed entirely by the JavaScript engine's native prototype lookup  it is standard JS behaviour, not Odoo-specific logic.

Key Concept

patch() creates a prototype chain layer between the original object and its extensions, ensuring that the native JavaScript super keyword resolves to the previous implementation whether that is the original class or an earlier patch.

3.2 Internal Metadata Tracking

Odoo internally tracks patch state using private data structures (an implementation detail). This allows it to manage the prototype chain correctly as multiple patches accumulate, and to support the unpatch function. The exact internal structure is not part of the public API and should not be relied upon.

Clarification

Two important nuances: patch() extends method resolution  it does not replace methods. The original implementation remains reachable via super. super is resolved entirely by the JavaScript engine's native prototype chain lookup. It is standard JavaScript behaviour, not an Odoo-specific mechanism.

3.3 Simplified Implementation

The core logic of patch() stripped of error handling — looks like this:

server/addons/web/static/src/core/utils/patch.js
Your JavaScript code goes here...
Important

The return value of patch() is an unpatch function. In unit tests, always call this function in teardown to prevent patches from leaking between tests.

4. How to Use patch()

4.1 Basic Syntax

Basic patch() example
import { patch } from '@web/core/utils/patch';
import { MyComponent } from '@some_module/path/my_component';

patch(MyComponent.prototype, {
    myMethod() {
        // Your custom logic BEFORE the original
        super.myMethod();  // Call the original (or previous patch)
        // Your custom logic AFTER the original
    }
});

4.2 Patching setup() in OWL Components

The setup() method is the OWL lifecycle entry point. Patching it is the standard way to add new hooks, state, or services to an existing component.

setup() patch example
import { Spreadsheet } from '@odoo/o-spreadsheet';
import { patch } from '@web/core/utils/patch';
import { useSpreadsheetCommandPalette } from './command_palette_hook';

patch(Spreadsheet.prototype, {
    setup() {
        super.setup();  // ALWAYS call this first
        // Now add your own logic
        useSpreadsheetCommandPalette();
    },
});
Warning

Always call super.setup() unless you deliberately want to suppress ALL base logic. Forgetting super.setup() is the single most common cause of patch-related bugs in Odoo development.

4.3 Adding State with useState in a Patch

You can call useState() inside a patched setup(). OWL's reactivity engine tracks the state proxy on the component instance regardless of which method initialized it.

useState() example
import { useState } from '@odoo/owl';

patch(MyComponent.prototype, {
    setup() {
        super.setup();
        this.myState = useState({ isOpen: false, count: 0 });
    },
    togglePanel() {
        this.myState.isOpen = !this.myState.isOpen;
    }
});

5. Patch Lifecycle & Execution Order

5.1 When Patches Are Applied

Patches are applied during Phase III of the Odoo frontend initialization lifecycle — after module-level code executes and registries are populated, but before the OWL component tree is mounted.

Phase Stage What Happens Patch Relevance
I JS Load Browser loads web.assets_backend bundle
II Registry Init Modules register views, fields, services
III Patch Application patch() calls execute and extend prototypes Patches must be applied before first instantiation of the target
IV Service Bootstrap makeEnv() and startServices() run Patches to eagerly-used services should be done before this phase
V OWL Mount WebClient mounts, setup() lifecycles begin Patches now active for all instances
VI Runtime Loop User interactions trigger re-renders Patched methods execute normally

5.2 Execution Order Between Multiple Patches

When multiple modules patch the same method, the execution order is determined by the module dependency graph — not by alphabetical order or any other implicit rule.

  • Declared dependency order is the only reliable guarantee. If Module B declares Module A in its __manifest__.py depends list, Module A's patch is applied first.
  • For unrelated modules with no declared dependency, execution order is not guaranteed and depends on asset bundle composition.
  • The last-applied patch is the outermost layer  it executes first when the method is called.
  • super.method() resolves to the next layer inward via the JavaScript engine's native prototype lookup, ultimately reaching the original.

Example with two patches on the same component:

// Module A patch (applied first, inner layer):

patch(MyComp.prototype, {

    doWork() { console.log('A before'); super.doWork(); console.log('A after'); }

});



// Module B patch (applied second, outer layer):

patch(MyComp.prototype, {

    doWork() { console.log('B before'); super.doWork(); console.log('B after'); }

});



// Output when doWork() is called:

// B before → A before → [Original] → A after → B after

5.3 Execution Order Inside setup()

Code placed before super.setup() runs first (outermost patch wins). Code placed after super.setup() runs last (innermost / original runs first).

patch(MyComp.prototype, {

    setup() {

        // Runs BEFORE original setup

        this.earlyState = useState({ ready: false });

        super.setup();

        // Runs AFTER original setup

        onWillStart(() => this.loadData());

    }

});

6. Real-World Examples from Odoo Core

6.1 Spreadsheet Command Palette

Odoo extends the spreadsheet application by integrating the global command palette without modifying the original o-spreadsheet library. Instead, it patches the Spreadsheet component during setup(), demonstrating the recommended approach for adding framework-specific functionality to third-party components.

Preserve Third-Party Code

The original @odoo/o-spreadsheet library remains completely untouched, making future upgrades much safer.

Clean Extension

Odoo-specific functionality is added using patch() instead of modifying the original component.

Easier Maintenance

Since the base implementation remains unchanged, updates and long-term maintenance become significantly easier.

import { Spreadsheet } from '@odoo/o-spreadsheet';
import { patch } from '@web/core/utils/patch';
import { useSpreadsheetCommandPalette } from './hooks';

patch(Spreadsheet.prototype, {
    setup() {
        super.setup();
        useSpreadsheetCommandPalette();
    },
});

6.2 Mail Messaging Menu

The Mail module is designed to remain independent of any specific platform. The web client uses patch() to introduce browser-specific capabilities, such as desktop notifications, without modifying the reusable core module.

Architecture Pattern

A generic core module exposes extension points, while environment-specific modules (Web, Desktop, Mobile) enhance its behavior using patches.

6.3 POS Service Patching

The Point of Sale application patches shared services, such as actionService, to customize navigation and workflow specifically for the POS interface while still reusing the same core service implementation.

Important Note

Most Odoo services are instantiated lazily. Therefore, the patch must simply be loaded before the service is used for the first time, rather than during a particular lifecycle phase.

7. include() vs Modern patch()

Feature Details
Legacy: include() Part of Odoo's legacy JavaScript class system. It relied on this._super(), a manually managed reference to the previous implementation. The implementation resided in /web/static/src/legacy/js/core/class.js.
Modern: patch() Designed for native ES6 classes and uses JavaScript's standard super keyword. Available from @web/core/utils/patch, it offers better performance, cleaner debugging, and standards compliance.
Why the change? Odoo 17+ adopted native ES Modules and OWL 2. These technologies are built around standard JavaScript classes, making the old include() mechanism incompatible with the modern frontend architecture.
Odoo 19 Behavior patch() fully leverages native ES Modules, the JavaScript prototype chain, and the standard super keyword. The old this._super() pattern is no longer used.
Best Practices

8. Do's & Don'ts

Follow these practical guidelines when extending Odoo using patch(). They help keep your code maintainable, upgrade-safe, and consistent with Odoo's frontend architecture.

Do

Recommended

Don't

Avoid
Always call super()

Preserve the original implementation and previously applied patches to avoid unexpected behavior.

Never skip super()

Skipping it replaces the original logic entirely and may break other patches.

Patch the prototype

Patch .prototype when extending class instance methods.

Patch private methods

Internal APIs are implementation details and can change between Odoo versions.

Use unpatch() in tests

Remove patches during teardown to keep test cases isolated.

Over-patch components

If many methods require changes, prefer subclassing or registries instead.

Declare module dependencies

Ensure dependent modules are listed in __manifest__.py.

Ignore registries

Use field, service, or view registries whenever an official extension point already exists.

9. Debugging Patches

Inspect Prototype

Browser DevTools

Expand __owl__.component inside your browser's DevTools. Every [[Prototype]] layer represents either a patch or the original component, allowing you to verify the complete execution chain.

Tip

Expand each prototype level to confirm the order in which patches are applied.

Using Breakpoints

Runtime Debugging

patch(MyComp.prototype, {
    setup() {
        debugger;
        super.setup();
    }
});

Pause execution and step into super.setup() to inspect which patch or base implementation executes next.

Pro Tip

Use the Call Stack panel to verify the complete execution flow across multiple patches.

Asset Debug Mode

Development Environment

Enable asset debugging to load unminified JavaScript files. This makes every module visible inside the browser's Sources panel, allowing you to set breakpoints directly in your patch files.

URL Parameter
?debug=assets

Troubleshooting Guide

Problem Likely Cause Recommended Fix
Core feature stops working after applying a patch Missing super() Call super.method() before or after your custom logic, depending on the desired execution order.
Only one of multiple patches appears to execute Patch overwrite Verify that every patch in the chain calls super().
Patch loads successfully but has no visible effect Wrong patch target Patch MyClass.prototype instead of a live component instance.
Patches execute in an unexpected order Missing dependency Declare the required module inside __manifest__.py to guarantee loading order.
Tests pass individually but fail when executed together Patch leakage Store the returned unpatch() function and call it during test teardown.

10. Version Evolution

Feature Odoo 16 Odoo 17 / 18 Odoo 19
Module System odoo.define()
Legacy AMD modules
@odoo-module
ES Module transition
Native ESM
Transpiled & optimized
Patching API include() + early patch() Modern patch() patch() + Native super
Super Mechanism this._super()
Custom implementation
Native JavaScript super Native super
Full ESM support
Services Registry-based Registry-based Reactive Proxies
Registry-powered
OWL Version OWL 1.0 / 2.0 OWL 2.0 OWL 2.0
Performance improvements

11. Performance Considerations

Every patch introduces an additional function call in the execution chain. For most business applications, the overhead is negligible. However, performance-sensitive components should be patched carefully.

Deep Patch Chains

Avoid stacking too many patches on the same component. Long super() chains increase execution time, especially in methods executed repeatedly.

Rendering

Avoid expensive logic inside render(). Rendering happens frequently, so heavy computations directly affect UI responsiveness.

Reactivity

Prevent unnecessary useState() updates inside shared components such as the NavBar to reduce unnecessary application-wide re-renders.

Performance Tip

For List and Kanban views, prefer registry extensions over patching row renderers. Registry-based widgets execute only where needed, reducing unnecessary rendering and improving overall UI responsiveness, especially when working with large datasets.

Quick Reference

Everything you should remember about patch() at a glance.

Golden Rule

Always choose the right extension point.

Prefer Registries, then Hooks, then Services. Use patch() only when an existing class provides no official extension point.

Import

Import Path

@web/core/utils/patch
Target

Patch MyClass.prototype, never an instance.

Multiple Patches

Last applied patch executes first through super().

setup()

Always call super.setup().

Debug

Enable ?debug=assets for readable JS files.

Unpatch

Call the returned function during test teardown.

Frequently Asked Questions

Common questions developers ask about patch() in Odoo.

What is patch() in Odoo?

patch() is Odoo's core utility located at @web/core/utils/patch. It lets you extend or modify existing classes and objects at runtime without changing their original source code, making it the foundation of Odoo's frontend extensibility.

How is patch() different from include()?

include() belonged to Odoo's legacy class system and relied on this._super(). Modern patch() works with native ES6 classes and uses JavaScript's native super keyword, making it cleaner, faster, and easier to debug.

When should I use patch() instead of a Registry?

Registries should always be your first choice for adding views, field widgets, or services. Use patch() only when no official extension point exists.

Registry → Hooks → Services → patch()

How does super work with patch()?

super is resolved entirely by JavaScript's native prototype chain. Each call to patch() creates another prototype layer, allowing super to automatically call the previous implementation, whether it belongs to the original class or another patch.

Consult Expertise

Engage with our core laboratory engineering leads to optimize your infrastructural frameworks.