Skip to content
SyncMyShop

Shopify and ERPNext

Shopify ERPNext integration

Shopify orders into ERPNext as submitted sales orders, invoices and delivery notes. Items, prices and stock per warehouse back to Shopify through the GraphQL API. Built on the Frappe REST API and your own doctypes, then hosted and run for you.

What moves between Shopify and ERPNext

Shopify takes the order and the payment. ERPNext holds items, warehouses, prices, customers and the books. Every record on the ERPNext side is a doctype, reachable at /api/resource/<doctype>.

Flow Direction Shopify ERPNext doctype
Orders to ERPNext Order Sales Order, submitted
Payments to ERPNext transactions Sales Invoice and Payment Entry
Fulfillments to Shopify, or to ERPNext when Shopify ships fulfillment with tracking Delivery Note
Refunds to ERPNext refund credit note: Sales Invoice with is_return
Customers to ERPNext customer, addresses Customer, Address, Contact
Items to Shopify product, variants Item with has_variants, variants with variant_of
Prices to Shopify price, catalog prices Item Price in a Price List, Pricing Rule
Stock to Shopify inventory level per location Bin per item and warehouse

Which system owns what

Data System of record Direction Sync behavior
Items and variants ERPNext ERPNext to Shopify changed items, matched to existing variants
Titles, images, SEO Shopify stays in Shopify not overwritten from ERPNext
Prices for web orders Shopify at checkout Shopify to ERPNext line price as charged
B2B prices ERPNext ERPNext to Shopify Item Prices and Pricing Rules to catalogs
Stock per warehouse ERPNext ERPNext to Shopify changed quantities, 250 per call
Orders Shopify until import Shopify to ERPNext webhook plus reconciliation
Customers Shopify for web buyers, ERPNext for accounts both ways matched on email
Taxes charged Shopify Shopify to ERPNext charge type Actual per account head

What Shopify and ERPNext store differently

Submitted documents

A sales order in ERPNext has a docstatus: 0 draft, 1 submitted, 2 cancelled. Only a submitted order reserves stock and can be delivered and invoiced, and a submitted order cannot be edited. A change means cancel and amend: the new document points back through amended_from and, by default, gets a numbered suffix on its name. An order edited in Shopify after import therefore becomes a cancel-and-amend in ERPNext, not an update. The integration does this for changes before delivery and holds anything later for a person to decide.

ERPNext names documents by naming series, such as SAL-ORD-.YYYY.-, which gives SAL-ORD-2026-00412. The Shopify order name, #1042, is stored in a custom field and in po_no, and the integration looks it up there before it creates anything, so a retried webhook cannot create a second order.

Variants and items

An ERPNext template item has has_variants set and its variants point to it with variant_of, each with its own item code and attribute values. A Shopify product has up to three options and 2,048 variants. The link between the two is a map of Shopify variant ID to ERPNext item code. A sales order also needs a delivery date on every line, and ERPNext rejects one earlier than the order date; Shopify has no such field, so the integration sets it by rule, for example the order date plus your dispatch time.

Stock

ERPNext projected quantity against the quantity Shopify may sell An ERPNext Bin for one item in one warehouse holds actual_qty 40, reserved_qty 12 for open sales orders and ordered_qty 30 on a purchase order. projected_qty is 40 plus 30 minus 12, which is 58. The quantity Shopify may sell is actual minus reserved, 28. projected_qty: 40 actual + 30 ordered - 12 reserved 58 Free on the shelf 28 On a purchase order 30 Written to Shopify, this oversells by 30 actual_qty - reserved_qty: what Shopify may sell 28 Free on the shelf 28 Shopify available at this location 28
ERPNext's projected quantity counts purchase orders that have not arrived. Shopify gets actual minus reserved.

ERPNext keeps stock in a Bin per item and warehouse: actual_qty, reserved_qty, ordered_qty, planned_qty, projected_qty and more. projected_qty adds stock on purchase orders and planned production, which is the number purchasing works with but not the number a shop may sell. The integration sends actual_qty minus reserved_qty for each warehouse that ships web orders, minus any buffer, to the matching Shopify location. ERPNext quantities are decimals and Shopify's are whole numbers: 12.5 meters in ERPNext is 12 in Shopify. Background in which stock counts.

Prices and taxes

ERPNext prices come from Item Price records in a Price List and from Pricing Rule records that apply by customer, customer group or territory, with min_qty, max_qty, valid_from and valid_upto. For B2B those become Shopify catalogs with price lists and quantity breaks. Web orders go the other way: the line price is what Shopify charged, and the sales order is written with pricing rules switched off for it, or ERPNext would reprice the line. Taxes go into Sales Taxes and Charges with the charge type Actual and one account head per Shopify tax title.

The technical reality of this pair

Limit Shopify ERPNext
Unit query cost points per second seconds of request time per window, when set
Default 100 points per second, 1,000 on Plus no limit unless rate_limit is in the site config
Over the limit THROTTLED inside an HTTP 200 HTTP 429 with Retry-After
Page size up to 250 per connection 20 by default, limit_page_length to raise
Authentication app access token Authorization: token api_key:api_secret

The ERPNext side. Frappe's rate limiter is off until the site config sets rate_limit with a limit and a window. When it is on, it does not count requests: it adds up how long each request ran and rejects the next one once the window's budget of seconds is spent. One heavy report query costs as much as fifty light reads. On a self-hosted bench the real limit is the number of web workers. A list call returns 20 records and only their name unless fields and limit_page_length say otherwise:

GET /api/resource/Bin?fields=["item_code","warehouse","actual_qty","reserved_qty"]&filters=[["warehouse","=","Main - BIA"]]&limit_page_length=500
Authorization: token 4f1c2a9e0b7d3e1:8a2c6f0d9e4b7a1

Stock, worked through. 5,000 variants in one warehouse. Read from ERPNext: 10 calls of 500 Bin rows. Written to Shopify with inventorySetQuantities, 250 quantities per call: 20 calls. The REST route that writes one inventory level per call and looks up each variant first is 10,000 calls; at the REST limit of 2 requests per second on a standard plan that is 5,000 seconds, 1 hour 23 minutes, for one full stock run. A normal run sends only the quantities that changed since the last one.

Versions. Shopify releases an API version every quarter and supports each for at least 12 months; a request to a retired version is answered by the oldest version still supported. The REST Admin API has been legacy since October 2024. An integration pinned to an old version keeps running until a field it relies on behaves differently. We move to each new version before the old one is retired. The Shopify side is in Shopify API rate limits.

Where standard connectors stop

The connector most ERPNext users meet first is the Shopify app that Frappe publishes as open source. Its code shows its design:

  • API version. It pins Shopify API version 2024-01 and uses the REST resources, so Shopify serves it whatever version is the oldest still supported.
  • Stock. It reads each variant and then sets its inventory level, two REST calls per variant and warehouse, in batches of 50, with actual_qty minus reserved_qty cut to a whole number.
  • Items. An order line for an unknown product creates ERPNext items. Variants get the Shopify variant ID as item code; only products without variants are matched to an existing item by SKU.
  • Prices. Sales orders are written with a placeholder price list and pricing rules ignored, so ERPNext prices play no part in web orders.
  • Events. It subscribes to order creation, payment, fulfillment, partial fulfillment and cancellation. Refunds are not among them.

A built integration starts from your existing items, price lists and warehouses, and runs on the GraphQL API. Taking over from the app is done in parallel first: the new integration reads the same orders and logs what it would write, without writing, until both agree. See replacing an integration.

When it goes wrong

What fails What the integration does What you see
ERPNext answers 429 waits for Retry-After, continues nothing
Sales order fails validation, such as a missing account head for a tax holds the order, continues with the rest the order and ERPNext's message
Variant without an ERPNext item holds the order the order and the variant
Order changed in Shopify after submit cancel and amend before delivery, otherwise held a line in the run diff
Shopify answers THROTTLED waits for the budget to refill nothing
API key revoked or user disabled stops writing, keeps the queue an alert
ERPNext upgrade or bench restart queue fills, drains afterwards queue length on the dashboard

An order that still fails after its retries is kept with its original payload and sent again once the cause is fixed. Before go-live the integration runs as a dry run that writes nothing and shows the sales orders, customers and stock levels it would write; after that every run leaves a diff per record. This is part of every e-commerce ERP integration we build. The Shopify side is on the Shopify page and in Shopify ERP integration; sibling pages are Shopify QuickBooks integration and Shopify Acumatica integration. Tell us how your ERPNext is set up.

Frequently asked questions

ERPNext has an open-source Shopify app. What does a built integration change?

Frappe's app covers the basic flows and its source is public, so its limits can be read: it pins Shopify API version 2024-01 and the REST API, writes stock with two REST calls per variant, matches existing items by SKU only for products without variants, and subscribes to no refund webhook. A built integration runs on the GraphQL API, maps variants to your existing items, books refunds, and is monitored when either side changes.

Does the integration need to be installed on our ERPNext server?

No. It runs outside ERPNext and uses the Frappe REST API with an API key and secret of a dedicated user, whose roles limit what it can read and write. That works the same on Frappe Cloud and on a self-hosted bench, and an ERPNext upgrade does not touch integration code.

Our items in ERPNext already exist. Will Shopify create new ones?

Not with a built integration. Before go-live the integration matches every Shopify variant to an existing ERPNext item, by SKU against item code or by a mapping table, and reports variants without a match. Orders for an unmatched variant are held instead of creating an item named after a Shopify ID.

Who decides the price on the sales order, Shopify or ERPNext?

Shopify, for web orders: the customer paid what checkout showed. The integration writes each line at the price Shopify charged and keeps ERPNext pricing rules from recalculating it. For B2B prices that start in ERPNext, Item Prices and Pricing Rules are the source and go to Shopify catalogs.

How are Shopify taxes booked in ERPNext?

As lines in Sales Taxes and Charges with the charge type Actual, so ERPNext keeps the amount Shopify charged instead of calculating its own. Each Shopify tax title, such as a state or county tax, maps to an account head. A new tax title without a mapping is reported before the order is written.

Tell us what you need

Tell us which systems you use and what needs to work better. We reply personally within one working day. We help you define the work, then agree the price and timing before you commit.

  • You know the price and the timeline before you commit to anything
  • We work out the connection that takes the work off your hands
  • We handle the build, hosting, monitoring and maintenance
  • Your team has less information to retype and fewer discrepancies to chase

Suppliers, products, orders per day, a rough estimate is fine.

Or email directly: carsten@syncmyshop.com