# General Ledger Injection Point in ERPNext: How to Customize GL Entries

> Discover the ERPNext General Ledger injection point and learn to customize GL entries by modifying the gl_map before database commitment. Unlock powerful accounting adjustments for your ERP.

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

---

**The General Ledger injection point occurs immediately before the `make_gl_entries(gl_map, …)` function is invoked in [`erpnext/accounts/general_ledger.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/general_ledger.py), enabling developers to modify the `gl_map` list to inject, alter, or suppress GL entries before they commit to the database.**

The General Ledger injection point represents the exact moment in ERPNext's accounting workflow where transaction-level data transforms into permanent financial records. Located within the Frappe Framework's ERPNext repository, this architectural hook allows developers to customize how Sales Invoices, Purchase Invoices, and Stock Entries generate double-entry bookkeeping lines without touching core ledger logic.

## Understanding the Two-Step GL Entry Flow

ERPNext builds General Ledger entries through a strict two-phase process that separates business logic from accounting persistence.

### Phase 1: DocType-Level Collection

Each transaction document (such as a **Sales Invoice** or **Stock Entry**) collects its financial impact into a Python list named `gl_map`. Inside the DocType's `make_gl_entries` method, controllers append dictionaries representing individual debit or credit lines using the `add_gl_entry()` helper found in [`erpnext/controllers/stock_controller.py`](https://github.com/frappe/erpnext/blob/main/erpnext/controllers/stock_controller.py) at lines 1777-1807.

### Phase 2: Central Posting Routine

Once the DocType has assembled the complete `gl_map`, it hands control to the **core ledger engine** by calling `make_gl_entries(gl_map, …)` from `erpnext.accounts.general_ledger`. This function, located at lines 28-35 in [`erpnext/accounts/general_ledger.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/general_ledger.py), validates entries, merges similar accounts, creates round-off rows, and writes the final `GL Entry` documents to the database.

The **General Ledger injection point** sits precisely between these two phases—immediately before the call to the central engine.

## Locating the Injection Point in Source Code

To customize GL entries, you must intercept the `gl_map` variable after the DocType populates it but before the following code executes:

```python
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(self.gl_map, from_repost=from_repost)

```

This call appears in transaction controllers such as:

- [`erpnext/accounts/doctype/sales_invoice/sales_invoice.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/doctype/sales_invoice/sales_invoice.py) at lines 1537-1553
- [`erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py) at lines 781-806

The helper that builds individual GL dictionaries resides in [`erpnext/controllers/stock_controller.py`](https://github.com/frappe/erpnext/blob/main/erpnext/controllers/stock_controller.py) at lines 1777-1807, making it another viable target for customization.

## Methods to Customize GL Entries

You can manipulate the General Ledger injection point through several architectural approaches depending on whether you need transaction-specific logic or site-wide behavior.

### Override the DocType Controller

For **transaction-specific** customizations (such as adding a surcharge line to every Sales Invoice), subclass the original DocType in your custom app and manipulate `self.gl_map` before calling the central engine.

```python

# my_custom/doctype/sales_invoice/sales_invoice.py

from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
from erpnext.accounts.general_ledger import make_gl_entries

class CustomSalesInvoice(SalesInvoice):
    def make_gl_entries(self, gl_entries=None, from_repost=False):
        # Build default GL map via parent logic

        super().make_gl_entries(gl_entries, from_repost)
        
        # Injection point: append custom entry

        surcharge = self.base_grand_total * 0.015
        custom_entry = self.get_gl_dict({
            "account": "Surcharge Income - %s" % self.company_abbr,
            "debit": 0,
            "credit": surcharge,
            "remarks": "15% fast-track service surcharge"
        })
        self.gl_map.append(custom_entry)
        
        # Post the extended map to the central engine

        make_gl_entries(self.gl_map, from_repost=from_repost)

```

### Use Document Hooks for Site-Wide Changes

For **global modifications** that apply across multiple DocTypes, implement Frappe's `doc_events` hooks in your custom app's [`hooks.py`](https://github.com/frappe/erpnext/blob/main/hooks.py) to inject data that later controllers can append to `gl_map`.

```python

# my_custom/hooks.py

doc_events = {
    "Purchase Invoice": {
        "on_submit": "my_custom.utils.add_procurement_cost"
    }
}

```

```python

# my_custom/utils.py

def add_procurement_cost(doc, method):
    if doc.docstatus != 1:
        return
    # Store temporary attribute for later pickup by controller override

    doc.custom_gl_append = {
        "account": "Custom Procurement Cost",
        "debit": doc.base_total * 0.01,
        "credit": 0,
        "remarks": "1% procurement surcharge"
    }

```

### Monkey-Patch Stock Controller Methods

To inject entries into **all stock-related transactions** (Stock Entry, Delivery Note, etc.), patch the `add_gl_entry` method in `StockController` from your custom app's startup code.

```python

# my_custom/patches/inject_stock_gl.py

from erpnext.controllers.stock_controller import StockController as OriginalSC

def patched_add_gl_entry(self, **kwargs):
    # Execute original logic

    OriginalSC.add_gl_entry(self, **kwargs)
    
    # Inject rounding adjustment

    rounding = round(kwargs.get("stock_value_diff", 0) * 0.0005, 2)
    if rounding:
        extra = self.get_gl_dict({
            "account": "Rounding Loss",
            "debit": rounding if rounding > 0 else 0,
            "credit": -rounding if rounding < 0 else 0,
            "remarks": "Auto-rounding on stock entry"
        })
        self.gl_map.append(extra)

# Apply patch during app initialization

def apply_monkey_patch():
    StockController.add_gl_entry = patched_add_gl_entry

```

## Summary

- The **General Ledger injection point** occurs immediately before `make_gl_entries(gl_map, …)` is called in [`erpnext/accounts/general_ledger.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/general_ledger.py).
- **DocType controllers** build the `gl_map` list using `add_gl_entry()` from [`erpnext/controllers/stock_controller.py`](https://github.com/frappe/erpnext/blob/main/erpnext/controllers/stock_controller.py).
- To customize entries, **override the DocType's `make_gl_entries`** method and modify `self.gl_map` before passing it to the central engine.
- For stock transactions, **patch `StockController.add_gl_entry`** to inject entries across all inventory movements.
- Always use `get_gl_dict()` to ensure proper formatting of GL entry dictionaries.

## Frequently Asked Questions

### What is the General Ledger injection point in ERPNext?

The General Ledger injection point is the specific code location immediately before the `make_gl_entries()` function call in [`erpnext/accounts/general_ledger.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/general_ledger.py) where the `gl_map` list is finalized. At this point, developers can modify, add, or remove dictionary entries representing debit and credit lines before they are validated and written to the `GL Entry` table.

### Which source file contains the central GL posting engine?

The central engine resides in [`erpnext/accounts/general_ledger.py`](https://github.com/frappe/erpnext/blob/main/erpnext/accounts/general_ledger.py), specifically within the `make_gl_entries()` function at lines 28-35. This function handles validation, merging of duplicate accounts, creation of round-off entries, and the actual database insertion of GL records.

### How do I add a custom GL entry to every Sales Invoice?

Subclass the `SalesInvoice` class in your custom app, override the `make_gl_entries` method to call the parent logic, then append your custom dictionary to `self.gl_map` using `self.get_gl_dict()`. Finally, invoke `make_gl_entries(self.gl_map, from_repost=from_repost)` from `erpnext.accounts.general_ledger` to post the extended map.

### Can I modify GL entries for stock transactions without touching individual DocTypes?

Yes, by monkey-patching the `add_gl_entry` method in [`erpnext/controllers/stock_controller.py`](https://github.com/frappe/erpnext/blob/main/erpnext/controllers/stock_controller.py). Since Stock Entry, Delivery Note, and other inventory documents inherit from `StockController`, patching this shared method allows you to inject custom GL logic across all stock-related transactions from a single location.