ERPNext Regional Override System: How to Implement Country-Specific Customizations

The ERPNext regional override system lets developers replace or extend core functionality for specific countries using the @erpnext.allow_regional decorator and the regional_overrides hook, without modifying core source code.

The ERPNext regional override system provides a robust mechanism for tailoring ERP functionality to local tax laws, accounting standards, and business regulations. As implemented in the frappe/erpnext repository, this architecture allows developers to inject country-specific logic while keeping the core codebase clean and upgradable. Understanding how to leverage get_region(), the allow_regional decorator, and the hooks system is essential for building compliant multi-national ERP deployments.

Core Components of the Regional Override System

The architecture consists of three interconnected pieces that work together to route function calls to country-specific implementations.

get_region() Function

Located in erpnext/__init__.py at lines 20-32, the get_region() function determines the active country by checking three sources in order of priority:

  1. frappe.flags.country (typically set by the UI)
  2. The country associated with the current user's company via frappe.local.flags.company
  3. The global system setting frappe.get_system_settings("country")

The allow_regional Decorator

Found in erpnext/__init__.py at lines 35-53, this decorator wraps functions to enable dynamic dispatch. At runtime, it intercepts calls to check whether the current country has a registered override in frappe.get_hooks("regional_overrides"). If a match exists, it executes the override; otherwise, it falls back to the original implementation.

regional_overrides Hook

Defined in erpnext/hooks.py at lines 20-33, this dictionary maps country names to dictionaries of original function path → overriding function path. The system prioritizes overrides from the last installed app (lines 45-52), allowing layered customizations where later apps take precedence over earlier ones.

How the Regional Override System Works

When a function decorated with @erpnext.allow_regional is called, ERPNext executes the following resolution process:

  1. Determine the countryget_region() resolves the active country using the hierarchy of flags and settings.
  2. Lookup overrides – The decorator queries frappe.get_hooks("regional_overrides") and retrieves the sub-dictionary for the resolved country.
  3. Match the function – The fully-qualified path of the decorated function (e.g., erpnext.controllers.taxes_and_totals.update_itemised_tax_data) serves as the lookup key.
  4. Execute the override – If a matching key exists, the call forwards to the dotted path specified in the hook value; otherwise, the original implementation runs.
  5. Apply app priority – When multiple apps provide overrides for the same country, the one installed last wins, enabling progressive customization layers.

Where Regional Code Lives in ERPNext

All country-specific logic resides under erpnext/regional/<country>/. For example, the United Arab Emirates implementation at erpnext/regional/united_arab_emirates/utils.py defines utilities such as update_itemised_tax_data and make_regional_gl_entries. The regional_overrides hook in erpnext/hooks.py maps these to their corresponding core function paths:

"United Arab Emirates": {
    "erpnext.controllers.taxes_and_totals.update_itemised_tax_data":
        "erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data",
    "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries":
        "erpnext.regional.united_arab_emirates.utils.make_regional_gl_entries",
}

Step-by-Step Implementation Guide

Follow these steps to implement country-specific customizations using the regional override system:

  1. Create a new app (optional) – Scaffold a custom Frappe app to isolate your customizations from core ERPNext code.

  2. Add a regional module – Inside your app, create regional/<your_country>/ with an __init__.py and any helper modules.

  3. Write the override function – Implement the new behavior with the same signature as the original function you intend to replace.

  4. Expose the function via a hook – In your app's hooks.py, add to regional_overrides:

    regional_overrides = {
        "My Country": {
            "erpnext.controllers.accounts_controller.validate_regional":
                "my_customizations.regional.my_country.utils.validate_regional"
        }
    }
  5. Verify the original function is decorated – Ensure the core function is already decorated with @erpnext.allow_regional. Most ERPNext core functions already are; if writing new core functions, add the decorator yourself.

  6. Install the app – Install your custom app after ERPNext (or after any other app that may provide competing overrides) to ensure your implementation takes precedence.

  7. Write tests – Create unit tests following the pattern in erpnext/tests/test_regional.py, setting frappe.flags.country to your target country and asserting that the overridden function executes.

Practical Code Examples

Decorating a Core Function

Most ERPNext core functions already include the decorator:


# In erpnext/controllers/accounts_controller.py (or similar core module)

import erpnext

@erpnext.allow_regional
def validate_regional(self):
    """Default validation logic."""
    pass

Implementing a Country-Specific Override

Create your replacement logic in your custom app:


# my_customizations/regional/my_country/utils.py

import frappe

def validate_regional(self):
    """Custom validation logic for My Country."""
    if self.tax_id and not self.tax_id.startswith("MC"):
        frappe.throw("Tax ID must start with 'MC' for My Country regulations")

Registering the Override in hooks.py

Connect your function to the core system:


# my_customizations/hooks.py

regional_overrides = {
    "My Country": {
        "erpnext.controllers.accounts_controller.validate_regional":
            "my_customizations.regional.my_country.utils.validate_regional"
    }
}

Testing the Regional Override

Verify your implementation using ERPNext's testing utilities:


# my_customizations/tests/test_my_country.py

import frappe
import erpnext
from erpnext.tests.utils import ERPNextTestSuite

def dummy_core_function():
    return "core"

class TestMyCountryOverrides(ERPNextTestSuite):
    def test_override_execution(self):
        frappe.flags.country = "My Country"
        # If "My Country" has a registered override for dummy_core_function,

        # this will return the custom value instead of "core"

        result = dummy_core_function()
        self.assertEqual(result, "custom")

Summary

  • The regional override system enables country-specific customizations without core code modification.
  • Three core components power the system: get_region() in erpnext/__init__.py, the @erpnext.allow_regional decorator, and the regional_overrides hook dictionary.
  • The system resolves the active country from flags, company settings, or global settings, then routes calls to the appropriate regional implementation.
  • Later installed apps take precedence, allowing layered customization stacks.
  • Place regional code in regional/<country>/ directories and register overrides in your app's hooks.py.
  • Test regional logic using patterns from erpnext/tests/test_regional.py by setting frappe.flags.country.

Frequently Asked Questions

How does ERPNext determine which country's override to use?

ERPNext calls get_region() from erpnext/__init__.py (lines 20-32), which checks three sources in sequence: first frappe.flags.country (often set during document processing or tests), then the country associated with the current user's company via frappe.local.flags.company, and finally the global system setting. This determined country is used as the lookup key in the regional_overrides hook.

Can multiple apps override the same function for the same country?

Yes. When multiple apps register overrides for identical function paths within the same country, the system prioritizes the last installed app. This behavior is defined in erpnext/__init__.py at lines 45-52, where the code iterates through installed apps and the final match wins. Install your customization app after ERPNext and any other dependencies to ensure your override takes effect.

Do I need to modify ERPNext core files to implement regional customizations?

No. The system is designed specifically to avoid core modifications. You create a separate Frappe app containing your regional code in a regional/<country>/ directory, register the mappings in your app's hooks.py using the regional_overrides dictionary, and install the app alongside ERPNext. The @erpnext.allow_regional decorator on core functions (already present in ERPNext's codebase) handles the routing automatically.

What happens if a country has no override registered for a decorated function?

If get_region() returns a country that has no entry in frappe.get_hooks("regional_overrides"), or if the entry exists but does not contain a key matching the decorated function's fully-qualified path, the allow_regional decorator simply executes the original function. This fallback behavior ensures that core functionality remains intact for countries without specific customizations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →