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

The General Ledger injection point occurs immediately before the make_gl_entries(gl_map, …) function is invoked in 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 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, 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:

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:

The helper that builds individual GL dictionaries resides in 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.


# 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 to inject data that later controllers can append to gl_map.


# my_custom/hooks.py

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

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


# 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.
  • DocType controllers build the gl_map list using add_gl_entry() from 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 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, 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. 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.

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 →