The Hidden Mechanics of patch() and super in Odoo 19 (OWL Framework)

March 18, 2026 by
The Hidden Mechanics of patch() and super in Odoo 19 (OWL Framework)
Fazri Muhammad Yazid
| No comments yet

Overview

If you've migrated from older versions of Odoo, you know that the frontend has undergone a massive transformation with the introduction of OWL (Odoo Web Library). In Odoo 19, the legacy Class.include({}) is completely dead. Everything from components to services to core models is customized using the @web/core/utils/patch utility.

But while most tutorials stop at a basic "Hello World" modification, enterprise-level customization requires a much deeper understanding of how patch() behaves under the hood.

Here are the advanced concepts, common traps regarding patch and super in Odoo 19 that you won't easily find in the official documentation.


1. The Anatomy of a Patch

Unlike Python's multi-inheritance, JavaScript ES6 classes don't natively support dynamic inheritance in the same way. What Odoo's patch() actually does is directly modify the prototype of the target class at runtime.

The Foundation: How to correctly call super

When you patch a method, you almost always want to preserve the original behavior. This is done by calling super.methodName().

import { patch } from "@web/core/utils/patch";
import { FormController } from "@web/views/form/form_controller";

patch(FormController.prototype, {
    async saveRecord() {
        console.log("Interceptor: Before saving...");
       
        const result = await super.saveRecord(...arguments);
       
        console.log("Interceptor: Record saved");
        return result;
    }
});

Always use ...arguments (or the specific parameters) when calling super. Odoo 19 heavily utilizes contextual arguments in frontend methods. If you blindly call super.saveRecord(), you will silently drop parameters, causing bizarre UI bugs down the line.


2. Patching the setup() Lifecycle Hook

In OWL, setup() is the only place where you can initialize hooks (like useState, useEffect, or useService). If you need to inject a new service (like an RPC call or a notification) into an existing component (like the POS Screen or a Form View), you must patch setup().

However, the execution order is violently strict.

The Bad Way (Will crash the UI)

patch(FormController.prototype, {
    setup() {
        // CRASH: Using a hook before super.setup() ❌
        this.notification = useService("notification");
       
        super.setup(...arguments);
    }
});


The Correct Way

import { useService } from "@web/core/utils/hooks";

patch(FormController.prototype, {
    setup() {
        // ALWAYS call super.setup() as the absolute first line ✅
        super.setup(...arguments);
       
        // Only now is the component context ready for hooks ✅
        this.myCustomService = useService("my_custom_integration");
    }
});

Because setup() binds the component's environment, attempting to use any OWL hook before the original setup() has finished executing will result in a fatal Cannot use hook outside of a component error.


3. Advanced Secret: Unpatching (The Reversible Modification)

Did you know that patch() isn't permanent?

When you call patch(), it actually returns a teardown function. Running this returned function instantly removes your patch and restores the component's prototype to its exact original state.

Why is this useful?

  1. Writing Frontend Tests (QUnit/Tours): You want to patch a behavior only for the duration of a single test.
  2. Dynamic SPA States: In modules like Point of Sale, you might want a component patched only while a specific hardware scanner is connected, and unpatched when disconnected.
import { patch } from "@web/core/utils/patch";
import { PosStore } from "@point_of_sale/app/store/pos_store";

// 1. Apply the patch and capture the unpatch function
const removeMyPatch = patch(PosStore.prototype, {
    pay() {
        console.log("Mock Payment Triggered");
        return super.pay(...arguments);
    }
});

// ... Later in your code (e.g., during test teardown or component destruction) ...

// 2. Completely eliminate the patch!
removeMyPatch();

This trick is deeply buried in Odoo's core testing framework, but it is a massive lifesaver when debugging memory leaks or overlapping patches from multiple installed custom modules.


Summary

When working with the Odoo 19 OWL Frontend:

  1. Never drop arguments: Use super.methodName(...arguments).
  2. Respect setup(): Call super.setup(...arguments) before writing a single line of custom logic.
  3. Remember unpatch(): Store the return value of patch() if your modification is strictly temporary or based on session state.
The Hidden Mechanics of patch() and super in Odoo 19 (OWL Framework)
Fazri Muhammad Yazid March 18, 2026
Share this post
Archive
Sign in to leave a comment