--- title: Build an ERP integration for Shopify Collective description: >- Learn how to add Shopify Collective capabilities to your existing ERP integration, including publishing products to Collective and handling orders from retailers. source_url: html: 'https://shopify.dev/docs/apps/build/collective/erp-integration-tutorial' md: 'https://shopify.dev/docs/apps/build/collective/erp-integration-tutorial.md' --- # Build an ERP integration for Shopify Collective [Shopify Collective](https://www.shopify.com/collective) connects suppliers and retailers on Shopify. Suppliers share products through price lists, retailers import and sell those products, and when customers place orders, Collective automatically routes them to suppliers for fulfillment. Payments between retailers and suppliers are handled automatically. If you're new to Shopify Collective, review [About Shopify Collective](https://shopify.dev/docs/apps/build/collective) to understand key features and how it works before continuing. This tutorial shows you how to add Collective capabilities to an existing enterprise resource planning (ERP) system or external system integration. If your client uses an external system as their source of truth for products and orders but wants to leverage the Collective ecosystem to expand their retail distribution, then this integration enables them to: * **As a supplier**: Automatically publish products from their system to Collective and receive retailer orders directly in their ERP for fulfillment. * **As a retailer**: Identify which incoming orders contain Collective products and track supplier fulfillment status. **Info:** If you're new to building Shopify integrations, start with [product synchronization](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data) and [webhook basics](https://shopify.dev/docs/apps/build/webhooks/subscribe/get-started) before continuing. *** ## What you'll learn In this tutorial, you'll learn how to do the following Collective-specific tasks: * Publish products to the Collective (Supplier) channel so they can be shared via price lists. * Identify and filter orders that come from Collective using tags. * Understand the Collective order routing workflow and fulfillment responsibilities. * Configure Collective-specific identifiers for tracking supplier-retailer relationships. *** ## Requirements * You've created a [Partner account](https://partners.shopify.com/) and a development store. * Your app has the `write_products`, `read_products`, `write_orders`, `read_orders`, and `write_webhooks` [access scopes](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration#access_scopes). * You understand how GraphQL works. * You're familiar with [Shopify Collective concepts](https://shopify.dev/docs/apps/build/collective). * You have an existing integration that [syncs products](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data) and [handles orders](https://shopify.dev/docs/api/admin-graphql/latest/objects/Order) with Shopify. *** ## Understanding your integration's role Your integration sits between your external system (ERP, OMS, or other platform) and Shopify. It adds Collective capabilities on top of your existing product and order sync. Your responsibilities differ depending on whether your client is a supplier or a retailer. | Role | What your integration does | What Collective handles automatically | | - | - | - | | **Supplier** | Publishes products to the Supplier channel, receives and fulfills orders from retailers | Price list sharing, inventory sync across stores, order routing, payment processing | | **Retailer** | Identifies Collective orders, tracks fulfillment status | Product importing, inventory sync, order splitting, supplier payments | ### As a supplier Products flow from your external system into Shopify and are published to the Collective Supplier channel, making them available for retailers to import via price lists. When retailers place orders, Shopify sends them to your app via webhooks for fulfillment in your ERP. ![Supplier integration data flow diagram](https://shopify.dev/assets/assets/images/apps/collective/erp-supplier-data-flow-DvbyUjVj.png) ### As a retailer Collective automatically imports and syncs supplier products into your Shopify store. Your integration listens for incoming orders tagged with `"Shopify Collective"` via webhooks and routes them to your external system for tracking. ![Retailer integration data flow diagram](https://shopify.dev/assets/assets/images/apps/collective/erp-retailer-data-flow-CcjUsjjf.png) **Note:** Price lists are managed in the Shopify admin, not via API. After your integration publishes products to the Supplier channel, suppliers create and share price lists with retailers through the [Collective app](https://help.shopify.com/manual/online-sales-channels/shopify-collective/suppliers/pricelists). *** ## How Collective order routing works Collective automatically handles order routing between retailers and suppliers. Understanding this workflow helps you process orders correctly in your ERP. ### The order flow When a customer purchases Collective products on a retailer store: 1. **Order created on retailer store** with `"Shopify Collective"` tag. 2. **After payment and fraud checks** (2 minute delay), Collective sends fulfillment request to supplier. 3. **Order created on supplier store** with `"Shopify Collective"` and `"{$retailer_name}"` tags. 4. **Supplier fulfills order** directly to end customer (not to retailer). 5. **Payment processed** automatically between retailer and supplier. ### What your integration handles Your integration's responsibilities differ based on whether you're a supplier or retailer: **As a supplier:** * Receive `orders/create` webhook for orders tagged with `"Shopify Collective"` and `"{$retailer_name}"`. * Extract retailer name from tags to identify which retailer placed the order. * Process order through your existing fulfillment workflow. * Mark order as fulfilled in Shopify using [`fulfillmentCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/fulfillmentCreate). **As a retailer:** * Receive `orders/create` webhook for orders containing Collective products. * Identify which items are from suppliers using tags (`"Shopify Collective"` and `"{$supplier_name}"`). * Track fulfillment status (suppliers fulfill directly to customers). * Handle non-Collective items in your order through your normal fulfillment process. **Info:** Collective handles the complexity of order splitting (when an order contains products from multiple suppliers), payment routing, and shipping label generation. Your integration only needs to process orders and update fulfillment status. *** ## Step 1: Configure Collective identifiers Add Collective-specific identifiers to your integration's data model so you can track products and orders across Shopify and your external system. **Note:** How you store these values depends on your system. You might add columns to existing tables, use extension tables, or maintain a separate integration database. ### Required Shopify IDs Add these Shopify IDs to your integration's database: | Your database field | Stores this Shopify ID | Example value | | - | - | - | | `shopifyProductId` | Product `id` from API responses | `gid://shopify/Product/8234567890` | | `shopifyVariantId` | Variant `id` from API responses | `gid://shopify/ProductVariant/4567890123` | | `shopifyOrderId` | Order `id` from webhooks | `gid://shopify/Order/1234567890` | | `shopifyLineItemId` | Line item `id` from webhooks | `gid://shopify/LineItem/1234567890` | **Info:** These are your local database field names. You'll extract Shopify's `id` values from API responses and webhooks (shown in later steps) and store them in these fields. ### Collective-specific tracking To identify Collective orders and track the supplier-retailer relationship, also add these fields to your database: | Field | Purpose | How to get it | | - | - | - | | `tags` | Identifies Collective orders | From order webhooks: orders have `"Shopify Collective"` tag | | `retailerName` | For suppliers: which retailer placed this order | From order tags: `{$retailer_name}` tag | | `supplierName` | For retailers: which supplier fulfills this order | From order tags: `{$supplier_name}` tag | Learn more about [Collective order tags and workflows](https://shopify.dev/docs/apps/build/collective/orders). *** ## Step 2: Publish products to Collective (suppliers only) After creating or updating a product in Shopify, update your product handler to call `publishToCollective()` to make it available for price lists. **Note:** This step is only for suppliers who create products. Retailers import products from supplier price lists and don't need to publish products. ### Create the API client Create a `lib/shopify.server.ts` file with a GraphQL client: ```typescript import { GraphQLClient } from 'graphql-request'; const client = new GraphQLClient( `https://${process.env.SHOPIFY_STORE_DOMAIN}/admin/api/${process.env.SHOPIFY_API_VERSION}/graphql.json`, { headers: { 'X-Shopify-Access-Token': process.env.SHOPIFY_ACCESS_TOKEN!, 'Content-Type': 'application/json', }, } ); ``` ### Get the Collective publication ID Add a `getCollectivePublicationId()` function to find the Collective publication. As described in the [product sharing guide](https://shopify.dev/docs/apps/build/collective/products#create-price-lists-as-a-supplier), you retrieve it by listing all store publications and finding the one with a `catalog.title` of `"Collective (Supplier)"`: ```typescript export async function getCollectivePublicationId() { const query = ` query { publications(first: 10) { nodes { id catalog { title } } } } `; const response = await client.request(query); const collectivePublication = response.publications.nodes.find( (pub: any) => pub.catalog?.title === 'Collective (Supplier)' ); if (!collectivePublication) { throw new Error('Collective (Supplier) publication not found'); } return collectivePublication.id; } ``` Cache the Collective publication ID in your application. It doesn't change for a given store. ### Publish products to Collective Add a `publishToCollective()` function to your API client, then call it from your product creation and update handlers after every successful Shopify product operation: ```typescript export async function publishToCollective(productId: string) { const publicationId = await getCollectivePublicationId(); const mutation = ` mutation publishablePublish($id: ID!, $input: [PublicationInput!]!) { publishablePublish(id: $id, input: $input) { publishable { availablePublicationsCount { count } } userErrors { field message } } } `; const response = await client.request(mutation, { id: productId, input: [{ publicationId }], }); if (response.publishablePublish.userErrors.length > 0) { throw new Error( response.publishablePublish.userErrors .map((e: any) => e.message) .join(', ') ); } } ``` ### When to call publish​To​Collective Call `publishToCollective()` after: * Creating a new product in Shopify * Updating product availability or inventory **Note:** Publishing to the Supplier channel makes products available for inclusion in price lists. Suppliers create price lists in the Shopify admin and select which products to share with specific retailers. Learn more about [creating price lists](https://shopify.dev/docs/apps/build/collective/products#create-price-lists-as-a-supplier). *** ## Step 3: Filter and handle Collective orders Update your webhook handler to identify Collective orders by their tags and route them to your external system for processing. ### Identify Collective orders Update your `handleOrderCreate` function to check the tags and determine whether an order came from Collective. In the following example code, `storeOrderInERP` represents your existing order storage logic. Replace this with your actual database write, API call, or OMS integration. ```typescript export async function handleOrderCreate(payload: any) { // Check if order is from Collective const tags = payload.tags?.split(', ') || []; const isCollectiveOrder = tags.includes('Shopify Collective'); if (!isCollectiveOrder) { // Handle as regular order or ignore return; } // Find retailer name from tags (for suppliers) const retailerTag = tags.find((tag: string) => tag !== 'Shopify Collective' && !tag.startsWith('_') ); const retailerName = retailerTag || 'Unknown'; // Store the order in your external system. // The exact implementation depends on your database or OMS. await storeOrderInERP({ shopifyOrderId: payload.admin_graphql_api_id, orderName: payload.name, retailerName: retailerName, tags: payload.tags, }); console.log(`Collective order from ${retailerName}: ${payload.name}`); } ``` **Info:** For suppliers, Collective orders have two tags: `"Shopify Collective"` and `"{$retailer_name}"`. For retailers, orders have `"Shopify Collective"` and `"{$supplier_name}"`. Use these tags to identify the relationship. ### Subscribe to order webhooks Subscribe to these webhooks to receive Collective orders. Learn more about [subscribing to webhooks](https://shopify.dev/docs/apps/build/webhooks/subscribe/get-started): * `orders/create` - New orders from retailers * `orders/updated` - Order status updates (payment, fulfillment) ### Add a webhook endpoint Add a route to handle incoming webhooks: ```typescript import { ActionFunctionArgs } from '@remix-run/node'; export async function action({ request }: ActionFunctionArgs) { const topic = request.headers.get('X-Shopify-Topic'); const payload = await request.json(); if (topic === 'orders/create') { await handleOrderCreate(payload); } return new Response('OK', { status: 200 }); } ``` Always verify webhooks using HMAC validation before processing them. Learn more about [webhook verification](https://shopify.dev/docs/apps/build/webhooks/subscribe/https#step-2-verify-the-webhook). ### Check order fulfillment status Query the order's `displayFulfillmentStatus` to check whether the supplier has fulfilled it: ```typescript const query = ` query OrderStatus($orderId: ID!) { order(id: $orderId) { displayFulfillmentStatus tags } } `; const response = await client.request(query, { orderId: 'gid://shopify/Order/1234567890' }); // displayFulfillmentStatus values: UNFULFILLED, PARTIALLY_FULFILLED, FULFILLED ``` *** ## Next steps * Learn more about [Collective order fulfillment](https://shopify.dev/docs/apps/build/collective/orders). * Set up [order cancellations](https://shopify.dev/docs/apps/build/collective/cancellations) and [returns](https://shopify.dev/docs/apps/build/collective/returns). * Implement [shipping rate customization](https://shopify.dev/docs/apps/build/collective/shipping). *** ## Additional resources If you need to implement general Shopify integration features alongside Collective: * [Product synchronization](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/sync-data) - Creating and updating products * [Webhook setup](https://shopify.dev/docs/apps/build/webhooks/subscribe/get-started) - Subscribing to and verifying webhooks * [Order management](https://shopify.dev/docs/api/admin-graphql/latest/objects/Order) - Working with orders in the GraphQL API * [Bulk operations](https://shopify.dev/docs/api/usage/bulk-operations) - Syncing large datasets efficiently ***