Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/aerele/medusa_integration/llms.txt

Use this file to discover all available pages before exploring further.

The ERPNext Medusa Integration synchronizes stock availability from ERPNext warehouses to Medusa on a daily schedule. This page explains how the sync works, how Medusa variant inventory settings are configured, how the custom_in_stock flag is used for storefront filtering, and how backorders are handled.

How inventory is managed in Medusa

When a product variant is created in Medusa via create_medusa_variant(), it is configured with:
  • manage_inventory: true — Medusa tracks inventory for this variant and can enforce stock limits.
  • allow_backorder — set based on the Website Item’s on_backorder field. When true, customers can place orders even when stock is zero.
payload = {
    "title": "Default",
    "inventory_quantity": 0,
    "manage_inventory": True,
    "allow_backorder": True if backorder else False,
    # ...
}
# POST /admin/products/{product_id}/variants
The inventory_quantity is initialized to 0 at creation time. Actual stock levels are not pushed to Medusa directly; instead, the custom_in_stock field on the Website Item is used by the storefront to filter available products.

Scheduled stock sync: update_webitem_stock()

The update_webitem_stock() function runs as a daily_long scheduled task. It queries actual stock quantities from tabBin for every published Website Item and updates the custom_in_stock flag accordingly.
def update_webitem_stock():
    from erpnext.stock.get_item_details import get_bin_details

    items = frappe.get_all(
        "Website Item",
        filters={"published": 1},
        fields=["name", "website_warehouse", "item_code", "custom_threshold_qty"]
    )

    for item in items:
        bin_details = get_bin_details(
            item.item_code,
            item.website_warehouse,
            include_child_warehouses=1
        )
        if bin_details["actual_qty"]:
            if bin_details["actual_qty"] > item.custom_threshold_qty:
                frappe.db.set_value("Website Item", item.name, "custom_in_stock", 1)
            else:
                frappe.db.set_value("Website Item", item.name, "custom_in_stock", 1)
        else:
            frappe.db.set_value("Website Item", item.name, "custom_in_stock", 0)
The logic is:
  • If actual_qty > 0custom_in_stock = 1 (in stock).
  • If actual_qty = 0custom_in_stock = 0 (out of stock).
The custom_threshold_qty field is read but the current implementation sets custom_in_stock = 1 for any positive quantity regardless of whether it meets the threshold. The threshold check branch currently maps to the same outcome as the positive-quantity branch. If threshold-based filtering is needed, the function should be updated to set custom_in_stock = 0 when actual_qty <= custom_threshold_qty.

Scheduled task registration

update_webitem_stock() is registered as a daily_long task in hooks.py:
scheduler_events = {
    "daily_long": [
        "medusa_integration.api.update_webitem_stock"
    ]
}
daily_long tasks are run once per day and are allowed to take longer than standard scheduler slots, making them appropriate for iterating over large product catalogs.

The custom_in_stock flag

The custom_in_stock field is a boolean field on the Website Item doctype. It acts as a pre-computed availability flag that the storefront queries instead of hitting tabBin on every product listing request. When the storefront requests products with the availability filter set, get_website_items() applies this filter directly:
if availability:
    filters["custom_in_stock"] = ["=", 1]
This pattern keeps product listing queries fast — the expensive warehouse lookup happens once per day in the background, not on each storefront request.

Backorder configuration

To allow customers to order an item even when stock is zero, enable the on_backorder field on the Website Item. This value is passed to create_medusa_variant() when the product is first exported:
medusa_var_id = create_medusa_variant(
    self.medusa_id,
    self.on_backorder,   # True = allow_backorder in Medusa
    country_code
)
Changing on_backorder on an existing Website Item after the Medusa variant has been created does not automatically update the variant’s allow_backorder setting in Medusa. You would need to update the variant directly via the Medusa admin API.

Checking stock sync status

To verify that stock levels are being synced correctly:
1

Check the custom_in_stock field

Open a Website Item in ERPNext and look at the custom_in_stock field. If it is 1, the item was found to have positive stock in the last daily sync. If 0, no stock was found.
2

Check the Medusa Request Log

The Medusa Request Log doctype records every request sent to the Medusa API. Filter by voucher_type = Website Item to see recent product-related requests and check for failures.
3

Run the sync manually

To trigger update_webitem_stock() immediately without waiting for the daily schedule, call it from the Frappe console:
from medusa_integration.api import update_webitem_stock
update_webitem_stock()
4

Check tabBin directly

To see the raw stock data that the sync reads:
SELECT item_code, warehouse, actual_qty
FROM `tabBin`
WHERE item_code = 'YOUR-ITEM-CODE'
AND warehouse IN (
    SELECT name FROM `tabWarehouse`
    WHERE company = 'YOUR COMPANY NAME'
)

Warehouse scope

get_bin_details() is called with include_child_warehouses=1, which means all child warehouses under the item’s website_warehouse contribute to the actual_qty figure. Set the website_warehouse field on the Website Item to the appropriate parent warehouse to control which stock locations are considered.

Build docs developers (and LLMs) love