The ERPNext Medusa Integration extends standard ERPNext behaviour through Frappe’sDocumentation 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.
doc_events system, custom JavaScript injections, and Python class overrides. This page documents every hook registered in hooks.py, the conditions under which each fires, and what it does. Understanding these hooks is essential when debugging data-flow issues or extending the integration with your own logic.
Document event hooks
The table below summarises all registered hooks. Detailed descriptions follow.| Doctype | Event | Handler |
|---|---|---|
| Item Price | validate | create_medusa_price_list |
| Website Item | validate | website_item_validate |
| Website Item | on_trash | delete_medusa_item |
| Quotation | on_update | export_quotation_on_update |
| Sales Order | before_insert | validate_medusa_order_id |
| Sales Order | on_submit | export_sales_order_on_update |
| Sales Order | on_update | export_sales_order_on_update |
| Sales Order | on_update_after_submit | export_sales_order_on_update |
| Sales Invoice | before_insert | set_ecommerce_details_from_sales_order |
| Sales Invoice | on_submit | export_sales_invoice_on_update |
| Delivery Note | before_insert | set_ecommerce_details_from_sales_order |
| Delivery Note | on_submit | export_delivery_note_on_update |
| Payment Entry | after_insert | handle_payment_entry |
| Payment Entry | on_update | handle_payment_entry |
| Payment Entry | on_submit | handle_payment_entry |
Item Price
validate → create_medusa_price_list
validate → create_medusa_price_list
- The
price_listfield must equal"Standard Selling". - The
customerfield must be empty (standard price, not a customer-specific negotiated price).
medusa_id yet, a new Medusa price list is created via a POST /admin/price-lists request and the returned IDs are written back to medusa_id and medusa_price_id on the record. If medusa_id is already set, the existing price list is updated in place.customer is set) are deliberately skipped. They are used internally for negotiated pricing and are served to the storefront through the quotation workflow rather than via Medusa price lists.Website Item
validate → website_item_validate
validate → website_item_validate
medusa_id is already present:- No
medusa_id: callsexport_website_item(), which creates a new product in Medusa (POST /admin/products), creates a default product option, creates a default variant, and writes the returnedmedusa_idandmedusa_variant_idback to the Website Item. It also creates an initial price list entry. medusa_idpresent: callsupdate_website_item(), which sends aPOST /admin/products/{medusa_id}request to update title, description, collection, status, specifications, and other metadata.
custom_skip_update_hook flag on Website Item can be set to 1 to skip the update path for a single save cycle (used internally when updating reviews and wishlists to prevent re-triggering the sync).on_trash → delete_medusa_item
on_trash → delete_medusa_item
Quotation
on_update → export_quotation_on_update
on_update → export_quotation_on_update
doc.workflow_state == "Ready for Customer Review"doc.from_ecommerce == 1
export_quotation(), which posts the full quotation — including line items, pricing, taxes, and totals — to the Medusa storefront endpoint POST /store/quotation-update. It then sends a notification email to the customer’s email address informing them their quote is ready for review.Sales Order
before_insert → validate_medusa_order_id
before_insert → validate_medusa_order_id
from_ecommerce = 1 but no medusa_order_id, the handler looks up the source Quotation (via items[0].prevdoc_docname) and copies the medusa_order_id from it. This ensures the Sales Order always carries the Medusa order reference even when it is created programmatically from a quotation.on_submit / on_update / on_update_after_submit → export_sales_order_on_update
on_submit / on_update / on_update_after_submit → export_sales_order_on_update
doc.from_ecommerce == 1 before proceeding. When the condition is met, it calls export_sales_order(), which:- Resolves the customer’s
medusa_id. - Queries any linked Sales Invoice to determine the current
payment_status. - Posts the order status and payment status to
POST /store/order-update?order_id={medusa_order_id}.
Sales Invoice
before_insert → set_ecommerce_details_from_sales_order
before_insert → set_ecommerce_details_from_sales_order
medusa_order_id and from_ecommerce flag available without needing to traverse document links.on_submit → export_sales_invoice_on_update
on_submit → export_sales_invoice_on_update
export_sales_order() so that Medusa’s order record reflects the updated payment status. Medusa is not sent the invoice itself; instead, the Sales Order’s payment status (derived from the invoice’s status field) is what gets synchronised.Delivery Note
before_insert → set_ecommerce_details_from_sales_order
before_insert → set_ecommerce_details_from_sales_order
medusa_order_id and from_ecommerce from the originating Sales Order into the new Delivery Note record.on_submit → export_delivery_note_on_update
on_submit → export_delivery_note_on_update
Delivery Note Item.against_sales_order and calls export_sales_order(), pushing the updated order status (which may now reflect “Completed” or a shipped state) to Medusa.Payment Entry
after_insert / on_update / on_submit → handle_payment_entry
after_insert / on_update / on_submit → handle_payment_entry
Payment Entry Reference for any linked Sales Invoices. For each invoice that has a medusa_order_id, it calls export_sales_invoice_on_update(), which in turn calls export_sales_order(). The chain ensures that as soon as a payment is recorded — whether on creation, update, or submission — the corresponding Medusa order reflects the new payment status.Custom JavaScript (doctype_js)
The integration injects custom JavaScript into two standard ERPNext forms:
| Doctype | Script file | What it adds |
|---|---|---|
| Customer | public/js/customer.js | Additional toolbar button(s) for Medusa-specific actions, such as linking an existing Medusa Lead to the Customer record |
| Website Item | public/js/website_item.js | Additional toolbar button(s) for manual sync operations directly from the Website Item form |
Doctype class overrides (override_doctype_class)
The integration replaces three standard ERPNext controller classes with custom subclasses:
| Doctype | Custom class | Module |
|---|---|---|
| Sales Order | CustomSalesOrder | medusa_integration.custom_sales_order |
| Sales Invoice | CustomSalesInvoice | medusa_integration.custom_sales_invoice |
| Delivery Note | CustomDeliveryNote | medusa_integration.custom_delivery_note |
validate_selling_price() method. The original ERPNext implementation raises a validation error when the net rate of any item falls below its last purchase rate or valuation rate. The custom implementation adds one check at the very start of the method:
from_ecommerce is truthy, validation is skipped entirely. This is necessary because ecommerce orders often carry negotiated or promotional rates that are legitimately lower than the standard valuation rate, and blocking submission would break the order fulfilment workflow.