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

> Master the ERPNext regional override system to implement country-specific customizations. Learn to use @erpnext.allow_regional and regional_overrides hook without touching core code.

- Repository: [Frappe/erpnext](https://github.com/frappe/erpnext)
- Tags: how-to-guide
- Published: 2026-05-20

---

**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`](https://github.com/frappe/erpnext/blob/main/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`](https://github.com/frappe/erpnext/blob/main/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`](https://github.com/frappe/erpnext/blob/main/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 country** – `get_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`](https://github.com/frappe/erpnext/blob/main/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`](https://github.com/frappe/erpnext/blob/main/erpnext/hooks.py) maps these to their corresponding core function paths:

```python
"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`](https://github.com/frappe/erpnext/blob/main/__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`](https://github.com/frappe/erpnext/blob/main/hooks.py), add to `regional_overrides`:

   ```python
   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`](https://github.com/frappe/erpnext/blob/main/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:

```python

# 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:

```python

# 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:

```python

# 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:

```python

# 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`](https://github.com/frappe/erpnext/blob/main/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`](https://github.com/frappe/erpnext/blob/main/hooks.py).
- Test regional logic using patterns from [`erpnext/tests/test_regional.py`](https://github.com/frappe/erpnext/blob/main/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`](https://github.com/frappe/erpnext/blob/main/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`](https://github.com/frappe/erpnext/blob/main/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`](https://github.com/frappe/erpnext/blob/main/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.