Skip to main content

Build an ERP integration for Shopify Collective

Shopify 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 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 and webhook basics before continuing.


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.


Anchor to Understanding your integration's roleUnderstanding 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.

RoleWhat your integration doesWhat Collective handles automatically
SupplierPublishes products to the Supplier channel, receives and fulfills orders from retailersPrice list sharing, inventory sync across stores, order routing, payment processing
RetailerIdentifies Collective orders, tracks fulfillment statusProduct importing, inventory sync, order splitting, supplier payments

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

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
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.


Anchor to How Collective order routing worksHow Collective order routing works

Collective automatically handles order routing between retailers and suppliers. Understanding this workflow helps you process orders correctly in your ERP.

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.

Anchor to What your integration handlesWhat 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.

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.


Anchor to Step 1: Configure Collective identifiersStep 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.

Anchor to Required Shopify IDsRequired Shopify IDs

Add these Shopify IDs to your integration's database:

Your database fieldStores this Shopify IDExample value
shopifyProductIdProduct id from API responsesgid://shopify/Product/8234567890
shopifyVariantIdVariant id from API responsesgid://shopify/ProductVariant/4567890123
shopifyOrderIdOrder id from webhooksgid://shopify/Order/1234567890
shopifyLineItemIdLine item id from webhooksgid://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.

Anchor to Collective-specific trackingCollective-specific tracking

To identify Collective orders and track the supplier-retailer relationship, also add these fields to your database:

FieldPurposeHow to get it
tagsIdentifies Collective ordersFrom order webhooks: orders have "Shopify Collective" tag
retailerNameFor suppliers: which retailer placed this orderFrom order tags: {$retailer_name} tag
supplierNameFor retailers: which supplier fulfills this orderFrom order tags: {$supplier_name} tag

Learn more about Collective order tags and workflows.


Anchor to Step 2: Publish products to Collective (suppliers only)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.

Anchor to Create the API clientCreate the API client

Create a lib/shopify.server.ts file with a GraphQL client:

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',
},
}
);

Anchor to Get the Collective publication IDGet the Collective publication ID

Add a getCollectivePublicationId() function to find the Collective publication. As described in the product sharing guide, you retrieve it by listing all store publications and finding the one with a catalog.title of "Collective (Supplier)":

export async function getCollectivePublicationId() {
const query = `
query {
publications(first: 10) {
nodes {
id
catalog {
title
}
}
}
}
`;

const response = await client.request<any>(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.

Anchor to Publish products to CollectivePublish 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:

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<any>(mutation, {
id: productId,
input: [{ publicationId }],
});

if (response.publishablePublish.userErrors.length > 0) {
throw new Error(
response.publishablePublish.userErrors
.map((e: any) => e.message)
.join(', ')
);
}
}

Anchor to When to call publishToCollectiveWhen to call publishToCollective

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.


Anchor to Step 3: Filter and handle Collective ordersStep 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.

Anchor to Identify Collective ordersIdentify 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.

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.

Anchor to Subscribe to order webhooksSubscribe to order webhooks

Subscribe to these webhooks to receive Collective orders:

  • orders/create - New orders from retailers
  • orders/updated - Order status updates (payment, fulfillment)

Learn more about subscribing to webhooks.

Anchor to Add a webhook endpointAdd a webhook endpoint

Add a route to handle incoming webhooks:

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.

Anchor to Check order fulfillment statusCheck order fulfillment status

Query the order's displayFulfillmentStatus to check whether the supplier has fulfilled it:

const query = `
query OrderStatus($orderId: ID!) {
order(id: $orderId) {
displayFulfillmentStatus
tags
}
}
`;

const response = await client.request<any>(query, {
orderId: 'gid://shopify/Order/1234567890'
});

// displayFulfillmentStatus values: UNFULFILLED, PARTIALLY_FULFILLED, FULFILLED


Anchor to Additional resourcesAdditional resources

If you need to implement general Shopify integration features alongside Collective:


Was this page helpful?