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:
erpnext/accounts/doctype/sales_invoice/sales_invoice.pyat lines 1537-1553erpnext/accounts/doctype/purchase_invoice/purchase_invoice.pyat lines 781-806
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 inerpnext/accounts/general_ledger.py. - DocType controllers build the
gl_maplist usingadd_gl_entry()fromerpnext/controllers/stock_controller.py. - To customize entries, override the DocType's
make_gl_entriesmethod and modifyself.gl_mapbefore passing it to the central engine. - For stock transactions, patch
StockController.add_gl_entryto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →