Overview
Icon for Shopify Sync

Shopify Sync

Sync customers, orders, and products from Shopify.

@rx-ventures/medusa-plugin-shopify-sync

Medusa v2 plugin that syncs customers, orders, products, and discounts from a Shopify store into Medusa, with manual triggers (admin UI buttons) and inbound webhook receivers.

Compatibility: Medusa v2.13.x, Node >= 20.

What it does

Domain Manual sync Webhook receivers Customers ✅ paginated ✅ Copy to clipboardcustomers/{create,update,delete,enable,disable} Orders ✅ paginated ✅ Copy to clipboardorders/{create,updated,cancelled,paid,fulfilled,partially_fulfilled,edited} + Copy to clipboardrefunds/create Products ✅ images downloaded from Shopify CDN and re-uploaded through Medusa's File module ✅ Copy to clipboardproducts/{create,update,delete} Discounts ✅ paginated manual only by design Inventory — ✅ Copy to clipboardinventory_levels/{update,connect,disconnect} Fulfillments — ✅ Copy to clipboardfulfillments/{create,update} — runs reconcile on parent order, writes fulfillment chain Draft orders — ⚠️ logged only

Every webhook delivery — successful or failed — writes a row to Copy to clipboardshopify_webhook_log. The admin Shopify webhook logs page (top-level, separate from Settings) shows them with filters by entity / topic / action / status code, auto-refreshes every 5 seconds, and renders clickable chips that open the affected Medusa entity.

Install

yarn add @rx-ventures/medusa-plugin-shopify-sync

Generate an encryption key (used to encrypt the Shopify access token at rest with AES-256-GCM):

openssl rand -hex 32

Add it to your Medusa application's environment:

SHOPIFY_SYNC_ENCRYPTION_KEY=<the 64-char hex you just generated>

⚠️ Use the same value across deploys. If it changes, the saved Shopify token in your DB becomes undecryptable and you'll need to re-paste it once via the admin UI.

Register the plugin in your Copy to clipboardmedusa-config.ts:

import { defineConfig } from "@medusajs/framework/utils"
module.exports = defineConfig({
// ...
plugins: [
{
resolve: "@rx-ventures/medusa-plugin-shopify-sync",
options: {
encryption_key: process.env.SHOPIFY_SYNC_ENCRYPTION_KEY,
// shopify_api_version: "2026-01" // override Shopify Admin GraphQL version
// webhook_base_url: process.env.MEDUSA_BACKEND_URL // for webhook auto-registration
},
},
],
})

Run migrations:

yarn medusa db:migrate

This creates two tables in your Medusa DB:

  • Copy to clipboardshopify_config — singleton row holding the (encrypted) Shopify credentials, webhook secret, and last-sync timestamps.
  • Copy to clipboardshopify_webhook_log — one row per inbound webhook delivery for the admin log page.

Usage

Start your Medusa app and open the admin dashboard.

1. Configure the connection

Navigate to Settings → Shopify Sync.

  • Paste your store URL (bare domain or full URL — both work).
  • Paste your Shopify Admin API access token. Required scopes: Copy to clipboardread_customers, write_customers, read_orders, write_orders, read_products, write_products, read_discounts, write_webhooks.
  • Click Save, then Test connection to verify.

2. Run the manual sync

From the same Settings page, run each entity in this recommended order:

  1. Customers — populates Medusa customers from Shopify (email-dedup, address sync, metafield flatten).
  2. Products — downloads images from Shopify and re-uploads to your Medusa file service. Variant matching by SKU.
  3. Discounts — creates Medusa promotions under a single Copy to clipboardshopify_discounts_sync campaign.
  4. Orders — full historical import. Customers, orders' addresses, line items, summary, payment chain, and discount line-item adjustments. Uses direct SQL writes for the order tables since Medusa has no public Admin API for "import historical order with payments + adjustments".

For Customers and Orders the section exposes batch controls (max batches + items per batch) so you can dry-run on a small slice before a full import.

3. Register webhooks

In Settings → Shopify Sync → Webhook subscriptions on Shopify, paste your public Medusa URL (your production URL or an ngrok tunnel for local dev) and click Register all defaults. The plugin appends each topic's path automatically:

Topic family URL appended Copy to clipboardCUSTOMERS_* Copy to clipboard/hooks/shopify/customers Copy to clipboardORDERS_* + Copy to clipboardREFUNDS_CREATE Copy to clipboard/hooks/shopify/orders Copy to clipboardPRODUCTS_* Copy to clipboard/hooks/shopify/products Copy to clipboardFULFILLMENTS_* Copy to clipboard/hooks/shopify/fulfillments Copy to clipboardINVENTORY_LEVELS_* Copy to clipboard/hooks/shopify/inventory Copy to clipboardDRAFT_ORDERS_* Copy to clipboard/hooks/shopify/draft-orders

This is additive only — never deletes existing subscriptions on the same store, so other integrations are safe.

4. Watch webhook activity

Open Shopify webhook logs (top-level admin page). Auto-refreshes every 5 s. Filter by:

  • Entity (customers / orders / products / fulfillments / inventory / draft_orders)
  • Topic (every supported topic)
  • Action (created / updated / deleted / skipped / errored / unauthorized)
  • Status code (200 / 400 / 401 / 404 / 500)

Each row shows the topic, action, duration, payload summary, error message, and clickable chips that open the affected Medusa entity in admin (Copy to clipboardOpen order ord_…, Copy to clipboardOpen customer cus_…, etc.). Fulfillment log rows link the parent order.

Configuration reference

Plugin options

Option Type Default Purpose Copy to clipboardencryption_key Copy to clipboardstring Copy to clipboardprocess.env.SHOPIFY_SYNC_ENCRYPTION_KEY 64-char hex (32 bytes) AES-256-GCM key for the at-rest Shopify access token. Required (one of plugin option or env). Copy to clipboardshopify_api_version Copy to clipboardstring Copy to clipboard"2026-01" Override Shopify Admin GraphQL API version. Copy to clipboardwebhook_base_url Copy to clipboardstring Copy to clipboardMEDUSA_BACKEND_URL env Public URL Shopify can reach Copy to clipboard/hooks/shopify/... at. Used for webhook auto-registration.

Required env vars in your Medusa application

Var Required Purpose Copy to clipboardSHOPIFY_SYNC_ENCRYPTION_KEY yes The 64-char hex key. Same value across deploys. Copy to clipboardMEDUSA_BACKEND_URL recommended Public URL — used as the default base for webhook registration.

The store URL and Shopify access token are saved through the admin UI (encrypted at rest) — not via env vars — so you can rotate without redeploying.

Admin API surface

Method Path Purpose Copy to clipboardGET Copy to clipboard/admin/shopify-sync/config Redacted config (no plaintext token) Copy to clipboardPOST Copy to clipboard/admin/shopify-sync/config Save store URL / token / enabled / rotate webhook secret Copy to clipboardPOST Copy to clipboard/admin/shopify-sync/connection-check Hits Shopify Copy to clipboard{ shop { name } } Copy to clipboardGET Copy to clipboard/admin/shopify-sync/webhooks Current Shopify subscriptions + curated topic list Copy to clipboardPOST Copy to clipboard/admin/shopify-sync/webhooks Create one subscription Copy to clipboardDELETE Copy to clipboard/admin/shopify-sync/webhooks/:numericId Delete one Copy to clipboardPOST Copy to clipboard/admin/shopify-sync/webhooks/register-all Bulk register the default set (additive only) Copy to clipboardPOST Copy to clipboard/admin/shopify-sync/customers/sync Manual sync — body Copy to clipboard{ cursor?, batchSize? } Copy to clipboardPOST Copy to clipboard/admin/shopify-sync/orders/sync Same shape Copy to clipboardPOST Copy to clipboard/admin/shopify-sync/products/sync Same shape Copy to clipboardPOST Copy to clipboard/admin/shopify-sync/discounts/sync Same shape Copy to clipboardGET Copy to clipboard/admin/shopify-sync/webhook-logs Paginated, filterable by Copy to clipboardentity / topic / action / status_code / limit / offset

Public webhook surface

These routes are public (no admin auth) and are what you register in Shopify:

POST /hooks/shopify/customers
POST /hooks/shopify/orders
POST /hooks/shopify/products
POST /hooks/shopify/fulfillments
POST /hooks/shopify/inventory
POST /hooks/shopify/draft-orders

Each route routes by the Copy to clipboardX-Shopify-Topic header, so the same URL accepts every topic in its family.

Database compatibility

The plugin's order import path uses raw SQL (necessary because Medusa has no public Admin API for historical order import). It works on any Postgres compatible with Medusa, including managed services. SSL is auto-enabled when the connection string contains Copy to clipboardsslmode=require, Copy to clipboardneon.tech, or Copy to clipboardsupabase.co.

⚠️ Schema-tied: re-validate after any major Medusa version upgrade. Minor / patch upgrades have been stable.

License

MIT — see LICENSE.

Repository

github.com/Rx-Ventures/medusa-plugin-shopify-sync

You may also like

Browse all integrations

Build your own

Develop your own custom integration

Build your own integration with our API to speed up your processes. Make your integration available via npm for it to be shared in our Library with the broader Medusa community.

gift card interface

Ready to build your custom commerce setup?