Shopify Integration
Multi-store, GraphQL-first Shopify integration for ERPNext with real-time inventory and India GST support
- Author: tanishkothari9
- Repository: https://github.com/tanishkothari9/shopify-integration
- GitHub stars: 5
- Forks: 0
- License: GPL-3.0
- Category: Retail
- Maintenance: Actively Maintained
Install Shopify Integration
bench get-app https://github.com/tanishkothari9/shopify-integration
Tags
- ecommerce
- erpnext
- erpnext-app
- frappe
- frappe-app
- gst-india
- shopify
- shopify-api
Add the Frappe Gems badge to your README
Maintain Shopify Integration? Paste this into your README:
[](https://frappegems.com/gems/apps/tanishkothari9/shopify-integration)
About Shopify Integration
Shopify Integration for ERPNext
A Shopify ↔ ERPNext integration built around three things the existing options don't do:
Multi-store. Connect as many Shopify stores to one ERPNext site as you like. Every mapping, credential, queue row and throttle bucket is keyed by store, so two shops selling the same SKU stay independent — including when one of them is being rate-limited.
GraphQL-first. The Shopify REST Admin API has been legacy since 2024-10-01. This app speaks only the GraphQL Admin API, which means batched mutations (one call for fifty inventory levels, not fifty), bulk operations for catalogue-scale work, and cost-based throttling that adapts to the shop's plan without being told what it is.
Real-time inventory. Stock moves reach Shopify in seconds, not on a 5-to-60-minute timer. Every stock ledger entry and every sales order reservation enqueues a push, and the worker sends the current availability — so out-of-order events heal instead of corrupting.
Plus first-class refunds: a Shopify refund becomes a proper ERPNext Credit Note, with restocking, proportional tax and shipping, and a reversing Payment Entry.
Status
Working today:
Foundation
Shopify Store— multi-store configuration with encrypted credentials- GraphQL client with adaptive, plan-agnostic throttling and a three-layer error taxonomy
Shopify Sync Queue— the outbox: coalescing, claim semantics, backoff, stale recoveryShopify Event Log— every webhook, verified, deduplicated and replayable- Webhook receiver and automatic subscription registration
Catalogue
- Bulk catalogue import over Shopify's async JSONL export — streamed to disk, applied in batches, and resumable: a crash at product 60,000 continues from 60,000
Shopify Item Link— the mapping table, with theinventoryItemGID cached so inventory pushes cost one API call rather than two- Product webhooks (create / update / delete), with delete unlinking rather than deleting the ERPNext Item
- Multi-option products map to ERPNext variant templates; single-variant products map to plain Items
- Echo suppression, so a product arriving from Shopify never pushes itself back
Orders
orders/create→ a submitted Sales Order, idempotent on(store, order GID)so Shopify's retries cannot produce a second order for one saleorders/paid→ Sales Invoice plus a Payment Entry for what was actually received, not what was owed —orders/paiddoes not reliably mean paid in fullorders/fulfilledandorders/partially_fulfilled→ one Delivery Note per Shopify fulfilment, carrying only that fulfilment's quantitiesorders/cancelled→ cancels the linked documents, dependents first- Customers matched on Shopify GID, never duplicated; guest checkouts fall back to the store's default customer
- Tax lines mapped to account heads and consolidated; tax-inclusive pricing, shipping as either a charge or a line item, and multi-currency
- Discount codes are booked at the price Shopify charged — percentage, fixed-amount and free-shipping coupons alike. Tax follows the discounted amount, not the list price
- No document is submitted whose total disagrees with Shopify's. Every amount is a
Decimalfrom parse to post
Real-time inventory
- Every stock movement — POS, Delivery Note, Stock Entry, Purchase Receipt, Reconciliation —
queues a push the moment it is submitted, and so does a Sales Order raising
reserved_qty - Availability is
actual_qty - reserved_qty, floored.reserved_qtyis not optional: between a web order arriving and its Delivery Note being made, nothing physical has moved but those units are already sold - Pushes are batched into one
inventorySetQuantitiescall, withcompareQuantityfor optimistic concurrency, so a hand edit in Shopify admin is not silently clobbered - Twenty stock movements on one item produce one queue row, not twenty API calls
inventory_levels/updateis used for drift detection only. ERPNext is the master; stock is never written back from Shopify, because that loop has no stable fixed point
Refunds
refunds/create→ a return Sales Invoice against the original, carrying only the refunded lines and quantities, with tax and shipping taken from Shopify's own breakdown- Restocking follows Shopify's per-line
restockType, and how the stock returns depends on whether it ever left: a delivered order gets a return Delivery Note, an unfulfilled one gets none, because booking stock back that never shipped invents inventory - A reversing Payment Entry only when the gateway actually settled money — a store-credit exchange is recorded as a refund but moves nothing
- Idempotent on
(store, refund GID), so a redelivered webhook cannot credit twice
All twelve webhook topics Shopify sends for products, orders, refunds, inventory and customers are handled.
Prices
- Selling price changes on the store's own price list push to Shopify via
productVariantsBulkUpdate, grouped per product. Wholesale and cost lists are ignored, and an item with no price is skipped rather than zeroed on Shopify
India and GST
- Works with
india_compliance. CGST + SGST within your own state, IGST outside it, per line — a 5% saree and an 18% kurti in one order stay at 5% and 18% rather than being blended into an average that is neither - Item Tax Templates are created on demand from the rates Shopify actually charged, so the taxable value reported against each HSN code is the one the customer paid
- Place of supply derived from the buyer's state; company GSTIN on every document
- Tax-inclusive (MRP) pricing, which is how most Indian retail quotes a price: a ₹4,500 saree stays ₹4,500 on the invoice and the GST is taken out of it, not added on top
- Freight carries its own rate rather than inheriting the goods' — a delivery charge taxed at the saree's 5% instead of 18% is wrong by a few rupees on the order and wrong in the return
- Verified against GSTR-1: B2C(Small) reports each rate separately and the HSN summary files every product under its own code
Not built: buyer GSTIN for B2B (Shopify does not collect it), and e-Invoice / IRN / e-Way Bill.
Reconciliation and operations
- A nightly drift check compares every mapped variant's Shopify level against ERPNext's and enqueues corrections, and replays any order Shopify has that ERPNext does not
- A dashboard on each store: pending and failed counts, webhook errors, live API headroom, last successful sync, last reconciliation
- Retry on any event log, Requeue on any failed queue row
Documentation
| docs/prerequisites.md | Start here. What to set up in Shopify, then in ERPNext, in order — plus the traps, and what is not built |
| CONTRIBUTING.md | Running the tests, and what a good change looks like |
| SECURITY.md | The security model, and how to report a vulnerability |
The build specification records the original plan and the reasoning behind the architecture.
Importing a catalogue
Open the Shopify Store and click Import Catalogue. The import runs in the background and
reports progress on the form. It is safe to re-run: mappings are keyed by
(store, variant GID), so a second import updates rather than duplicates.
Requirements
| Frappe | v15 |
| ERPNext | v15 |
| Python | 3.10–3.13, 3.12 recommended (Frappe v15 does not support 3.14) |
| MariaDB | 10.6+ — FOR UPDATE SKIP LOCKED is required by the queue |
Install
bench get-app https://github.com/tanishkothari9/shopify-integration
bench --site your-site.localhost install-app shopify_integration
Configure a store
- In Shopify, create a custom app with the scopes listed under Access scopes and copy its Admin API access token and API secret key.
- In ERPNext, create a Shopify Store: shop domain, both credentials, company and warehouse defaults.
- Click Test Connection to confirm the token, domain and pinned API version agree.
- Tick Enabled. Webhook subscriptions register automatically — you never configure them by hand in Shopify.
API version
Each store pins its own Shopify API version. The default is 2026-01, which is the version
this app's GraphQL documents have been verified against. Shopify ships a new version
quarterly and sunsets each one after twelve months; when you move a store forward, diff the
documents in shopify_integration/api/queries/ as part of the upgrade.
Access scopes
| Scope | Why |
|---|---|
read_products, write_products |
Catalogue sync |
read_orders |
Orders → Sales Orders |
read_inventory, write_inventory |
Real-time inventory |
read_locations |
Mapping warehouses to Shopify locations |
read_customers |
Customer, Address and Contact creation |
write_merchant_managed_fulfillment_orders |
Marking orders fulfilled and pushing tracking |
Design
Two decisions account for most of the behaviour:
The outbox. ERPNext changes never call Shopify. They write a queue row; per-store workers drain it. That buys retry, coalescing, backpressure and observability, and it means nothing in a save path — including a POS transaction — ever waits on the network.
Absolute state, not deltas. A queue row records that an item needs pushing, never the value to push. Workers re-read current state at drain time, so duplicate and out-of-order events converge on the truth rather than compounding.
Development
The full suite needs a site:
bench --site your-site.localhost run-tests --app shopify_integration
Most of it is pure and needs no site, but those files have to be excluded by name and still need frappe importable — see the exact command in CONTRIBUTING.md.
CI runs the site-bound suite twice on the same site. Handlers commit deliberately, so a test that does not clean up after itself passes once and fails on every run after.
See CONTRIBUTING.md before opening a pull request, and SECURITY.md to report a vulnerability — please don't open a public issue for one.
License
GPLv3. This app adapts logic from
frappe/ecommerce_integrations,
also GPLv3.
Related Retail apps for Frappe & ERPNext
- Ury — URY - FOSS restaurant management system super powered by ERPNext.
- Ecommerce Integrations — Ecommerce integrations for ERPNext
- Webshop — Open Source eCommerce Platform
- Posnext — A modern, high-performance Point of Sale system for ERPNext with offline capabilities, real-time updates, and intuitive UI.
- Woocommerceconnector — Integration App for ERPNext to connect to WooCommerce
- Pos Awesome V15 — POS Awesome is an open-source Point of Sale for Erpnext using Vue.js and Vuetify
- Woocommerce Fusion — WooCommerce connector for ERPNext v15
- Go1 Webshop — Go1 Webshop is an Open Source eCommerce portal built on frappe builder.