Monkey Patching in ERPNext

Monkey Patching in ERPNext (Frappe) — The Safe & Production-Ready Way

Sometimes in ERPNext, you need to change core behavior where no hook or override API exists.
A common example is overriding internal functions like make_packing_list.

This article explains the safest, production-ready way to do monkey patching in Frappe — without breaking:

  • Redis cache

  • bench clear-cache

  • Scheduler

  • Multi-site setups

  • Future clones or upgrades

We’ll use make_packing_list as an example and add a simple msgprint to prove the override works.


:warning: Why NOT to monkey patch in hooks.py

Many developers try to monkey patch directly inside hooks.py.
This works temporarily, but causes serious issues later:

  • Redis serialization errors (can't pickle module objects)

  • Bench command failures

  • Random crashes during cache rebuild

  • Clone / restore instability

Rule:

hooks.py is for declaring hooks — not for executing logic.


:white_check_mark: The Correct Pattern (Overview)

We will use three files:

yourapp/
├── __init__.py              ← guarded loader
├── core_overrides.py        ← monkey patches
└── overrides/
    └── packed_item.py       ← custom logic


Step 1: Create the override function

File:

yourapp/overrides/packed_item.py

import frappe

def make_packing_list(doc):
    frappe.msgprint("Custom make_packing_list override executed")

For this article, we only show a msgprint to keep the example simple.


Step 2: Create a central override loader

File:

yourapp/core_overrides.py

def apply():
    import frappe

    frappe.logger("yourapp").info("YourApp core overrides applied")

    import erpnext.stock.doctype.packed_item.packed_item as original
    from yourapp.overrides.packed_item import make_packing_list

    original.make_packing_list = make_packing_list

Why this file exists

  • Keeps all monkey patches in one place

  • Easy to audit and debug

  • No side effects during app discovery


Step 3: Safely apply overrides from __init__.py

File:

yourapp/__init__.py

from __future__ import unicode_literals
__version__ = "0.0.1"

def _apply_overrides():
    from yourapp.core_overrides import apply
    apply()

try:
    import frappe
    # Apply overrides only when site context exists
    if getattr(frappe.local, "site", None):
        _apply_overrides()
except Exception:
    # Never break bench commands
    pass


:locked_with_key: Why this guard is CRITICAL

Frappe loads apps in multiple contexts:

Context Site Available
Web requests :white_check_mark:
Scheduler :white_check_mark:
Workers :white_check_mark:
bench clear-cache :cross_mark:
bench migrate :cross_mark:

Without the frappe.local.site guard:

  • Bench commands can crash

  • Redis cache may break

  • App discovery can fail

This guard makes the override bench-safe and production-safe.


Step 4: Restart and verify

bench restart

Now trigger any action that normally calls make_packing_list.

You should see:

Custom make_packing_list override executed

And in logs:

tail -f logs/frappe.log

YourApp core overrides applied


:white_check_mark: Final Result

  • :white_check_mark: Core function overridden safely

  • :white_check_mark: No hooks abuse

  • :white_check_mark: No Redis issues

  • :white_check_mark: Bench commands work

  • :white_check_mark: Clone & upgrade friendly


:brain: Best-Practice Summary

DO

  • Monkey patch in a dedicated file

  • Apply from __init__.py

  • Guard with frappe.local.site

  • Log when overrides load

DON’T

  • Monkey patch in hooks.py

  • Patch during hook loading

  • Patch without a site context check


:chequered_flag: Conclusion

Monkey patching is a last-resort tool in ERPNext — but when needed, this pattern is:

  • Clean

  • Predictable

  • Safe

  • Production-ready

If you must override core logic, this is the correct way to do it.

2 Likes

In frappe monkey patching is not allowed.

NOTE: Don’t Write any code in init.py file.

Fappe cloud will not able to update the app if you have any import code in init.py file.

you can still override the function which is not whitelisted or not in any doctype class from hooks.py

Thank you for the clarification and sorry for the wrong information that i have posted.

To safely apply monkey patch overrides (for non-whitelisted functions like make_packing_list or controller class overrides) without using __init__.py, could you please confirm which hook is recommended?

Is app_include_after_boot in hooks.py the correct and supported hook for applying such global overrides? or just directly adding the patching the code by importing the core code in custom app?

Best practice is to use a doctype-wise override. There are still some portions of the codebase that aren’t reachable that should probably be available as “business logic”. Monkey patching should generally a tool of last resort. Overrides that use the inheritance pattern are the favored approach.

the thing you were doing in init.py file to override the method. just do that same thing in hook.py. it will work and override the method for which you were doing monkey patch

2 Likes

It will not work if that codes runs in a queue or the scheduler because hooks.py isn’t called ahead of time. IT also has a small performance tax in the it has to run every time hooks is access for any reason. Executable code in hooks.py is a code smell and is not a best practice.

yes, you are right, this is one of the drawback.