How ERPNext Perpetual Inventory Works: Complete Guide to Enabling It Per Company

ERPNext perpetual inventory automatically synchronizes stock transactions with General Ledger entries in real-time when enabled per company via the "Enable Perpetual Inventory" checkbox in the Company master.

ERPNext supports both periodic and perpetual inventory accounting methods, but the perpetual inventory system is the recommended approach for real-time financial accuracy. This feature is controlled at the company level through a specific configuration flag that determines whether stock movements automatically generate corresponding accounting entries. Understanding how this mechanism works in the ERPNext source code helps administrators implement proper inventory valuation and GL synchronization.

What Is ERPNext Perpetual Inventory?

Perpetual inventory in ERPNext is an accounting method where the system maintains continuous, real-time records of inventory valuation alongside the General Ledger. When enabled for a company, every stock transaction—whether a Purchase Receipt, Delivery Note, Stock Entry, or Stock Reconciliation—automatically creates the necessary GL entries to reflect the financial impact immediately.

The key characteristic of this system is its real-time synchronization between the Stock Ledger and the General Ledger. This eliminates the need for manual journal entries at period-end to account for cost of goods sold or inventory value changes, providing accurate financial data at any point in time.

How the Perpetual Inventory Flag Works

The entire workflow hinges on a single boolean field checked consistently across the codebase. When this flag is active, ERPNext validates, creates, and updates GL entries automatically; when disabled, the system treats inventory as periodic, requiring manual accounting entries.

The Central Helper Function

The core check is performed by is_perpetual_inventory_enabled defined in erpnext/__init__.py (lines 78-90). This function uses local caching to avoid repeated database hits:

def is_perpetual_inventory_enabled(company):
    if not company:
        company = "_Test Company" if frappe.in_test else get_default_company()

    if not hasattr(frappe.local, "enable_perpetual_inventory"):
        frappe.local.enable_perpetual_inventory = {}

    if company not in frappe.local.enable_perpetual_inventory:
        frappe.local.enable_perpetual_inventory[company] = (
            frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
        )
    return frappe.local.enable_perpetual_inventory[company]

This helper reads the enable_perpetual_inventory field from the Company DocType and caches the result in frappe.local, making the flag instantly available to all server-side code without repeated database queries.

Server-Side Implementation

The flag drives critical logic in the stock processing pipeline. In erpnext/stock/stock_ledger.py (line 1010), the system checks the flag before calculating valuation:

if not cint(erpnext.is_perpetual_inventory_enabled(sle.company)):
    return

Similarly, document-level validations in stock transactions guard their accounting logic with this check. In erpnext/stock/doctype/stock_entry/stock_entry.py (line 751):

if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
    return

When the flag returns 1 (enabled), the system proceeds to create GL entries for stock received, cost of goods sold, and inventory adjustments. When 0, these code paths return early, leaving the General Ledger unaffected by stock movements.

Client-Side Implementation

The frontend also respects this setting through a JavaScript helper in erpnext/public/js/utils.js (line 135). This allows forms to dynamically show or hide accounting-related fields based on the company's perpetual inventory status:

erpnext.is_perpetual_inventory_enabled = function(company) {
    // Implementation checks local cache or server
}

This ensures that users cannot accidentally attempt to trigger automatic accounting entries for companies where the feature is disabled.

How to Enable Perpetual Inventory Per Company

Via the User Interface

The toggle resides on the Company DocType. Navigate to Setup > Company, select your company, and check the "Enable Perpetual Inventory" field. This field is defined in erpnext/setup/doctype/company/company.py (lines 94-95) as:

enable_perpetual_inventory: DF.Check

When you save the company document, the value is stored in the database and the cached local value is cleared, making the change effective immediately for all subsequent transactions.

Via API or Script

You can enable perpetual inventory programmatically using the Frappe API:

import frappe
from erpnext import is_perpetual_inventory_enabled

company = "Acme Ltd."

# Enable perpetual inventory

frappe.db.set_value("Company", company, "enable_perpetual_inventory", 1)

# Clear the cached value to reflect changes immediately

if hasattr(frappe.local, "enable_perpetual_inventory"):
    frappe.local.enable_perpetual_inventory.pop(company, None)

# Verify the change

if is_perpetual_inventory_enabled(company):
    frappe.msgprint(f"Perpetual inventory is now enabled for {company}")

During the Setup Wizard, ERPNext automatically creates the default company with perpetual inventory enabled by default, as implemented in erpnext/setup/setup_wizard/operations/company_setup.py (line 25) and erpnext/setup/setup_wizard/operations/install_fixtures.py (line 467).

Real-World Impact: Transactions with Perpetual Inventory On vs Off

Understanding the practical difference helps determine when to use this feature:

  • Purchase Receipts: With the flag on, receiving stock automatically creates a "Stock Received But Not Billed" GL entry. With the flag off, only stock quantities update; you must manually create a Journal Entry for the liability.

  • Delivery Notes: When enabled, issuing stock automatically debits Cost of Goods Sold and credits Inventory. When disabled, the Stock Ledger updates but the General Ledger remains unchanged until you post manual periodic entries.

  • Stock Reconciliation: With perpetual inventory on, adjusting physical counts automatically generates GL entries to match book value with actual value. With the flag off, reconciliation only corrects stock quantities without financial impact.

  • Serial and Batch Valuation: The automatic recalculation of bundle rates depends on the perpetual inventory flag; without it, manual rate adjustments are required.

Practical Code Examples

Checking the Flag Server-Side

Use this pattern in custom server scripts or apps:

import frappe
from erpnext import is_perpetual_inventory_enabled

company = frappe.defaults.get_user_default("company")
if not is_perpetual_inventory_enabled(company):
    frappe.throw("Perpetual inventory must be enabled for automatic accounting")

Conditional Client-Side Logic

Make fields read-only or visible based on the inventory method:

frappe.ui.form.on('Stock Entry', {
    company: function(frm) {
        if (!erpnext.is_perpetual_inventory_enabled(frm.doc.company)) {
            frm.set_df_property('expense_account', 'hidden', 1);
        }
    }
});

Summary

  • ERPNext perpetual inventory is a per-company setting controlled by the enable_perpetual_inventory checkbox in the Company master.
  • The central helper function is_perpetual_inventory_enabled in erpnext/__init__.py caches the setting and drives all automatic accounting logic.
  • When enabled, stock transactions automatically generate General Ledger entries in real-time through the stock ledger processing pipeline.
  • When disabled, ERPNext operates in periodic inventory mode, requiring manual journal entries to reflect inventory value changes in accounting.
  • Enable the feature through the Company UI, via API using frappe.db.set_value, or automatically through the Setup Wizard.

Frequently Asked Questions

What happens if I disable perpetual inventory after using it?

Disabling the flag stops automatic GL entry creation for new transactions, but existing entries remain in the General Ledger. You should reconcile any open stock-related accounting entries before switching to periodic inventory mode. Future stock movements will update only the Stock Ledger until you manually post corresponding journal entries.

Can different companies in the same ERPNext instance use different inventory methods?

Yes. The enable_perpetual_inventory field is stored per Company document, allowing Company A to use perpetual inventory while Company B uses periodic inventory. The is_perpetual_inventory_enabled helper accepts a company parameter and maintains separate cache entries for each, ensuring proper isolation of accounting logic between companies.

Does enabling perpetual inventory affect historical transactions?

Enabling the flag does not retroactively create GL entries for past stock transactions. It only affects transactions created after the setting is enabled. If you need to apply perpetual inventory to historical data, you must use the "Repost Accounting Ledger" tool or create manual journal entries to bring the General Ledger in sync with the Stock Ledger.

Where is the perpetual inventory setting stored in the database?

The setting is stored in the tabCompany table in the column enable_perpetual_inventory. The system uses Frappe's cached value framework (frappe.get_cached_value) to read this field, storing active configurations in frappe.local.enable_perpetual_inventory to minimize database queries during high-volume stock processing.

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 →