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

> Learn how ERPNext perpetual inventory synchronizes stock with General Ledger entries in real-time. This guide shows you how to enable perpetual inventory per company for accurate financial tracking.

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

---

**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`](https://github.com/frappe/erpnext/blob/main/erpnext/__init__.py) (lines 78-90). This function uses local caching to avoid repeated database hits:

```python
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`](https://github.com/frappe/erpnext/blob/main/erpnext/stock/stock_ledger.py) (line 1010), the system checks the flag before calculating valuation:

```python
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`](https://github.com/frappe/erpnext/blob/main/erpnext/stock/doctype/stock_entry/stock_entry.py) (line 751):

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

```javascript
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`](https://github.com/frappe/erpnext/blob/main/erpnext/setup/doctype/company/company.py) (lines 94-95) as:

```python
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:

```python
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`](https://github.com/frappe/erpnext/blob/main/erpnext/setup/setup_wizard/operations/company_setup.py) (line 25) and [`erpnext/setup/setup_wizard/operations/install_fixtures.py`](https://github.com/frappe/erpnext/blob/main/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:

```python
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:

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