k-sync
Back to blog

Shopify admin API for WooCommerce developers (2026)

How WooCommerce developers use the Shopify Admin API — GraphQL vs REST, product mutations, inventory management, order queries, webhooks, and porting WooCommerce REST API logic to Shopify.

·By k-sync
6 min read · 1,279 words

WooCommerce developers building custom integrations used the WooCommerce REST API v3 — a REST interface for products, orders, customers, and coupons. Shopify offers two parallel Admin APIs: GraphQL (recommended) and REST (legacy). This guide bridges WooCommerce REST API patterns to their Shopify Admin API equivalents, covering the mutations and queries you'll use most when building migrations, integrations, and backend tools.

WooCommerce REST API vs Shopify Admin API

OperationWooCommerce REST v3Shopify Admin API (GraphQL)
List productsGET /wp-json/wc/v3/productsquery { products(first: 50) }
Create productPOST /wp-json/wc/v3/productsmutation productCreate
Update productPUT /wp-json/wc/v3/products/{id}mutation productUpdate
Delete productDELETE /wp-json/wc/v3/products/{id}mutation productDelete
List ordersGET /wp-json/wc/v3/ordersquery { orders(first: 50) }
Create orderPOST /wp-json/wc/v3/ordersmutation orderCreate
Update inventoryPUT /products/{id} stock_quantitymutation inventorySetQuantities
List customersGET /wp-json/wc/v3/customersquery { customers(first: 50) }
Set metafieldsPOST /wp-json/wc/v3/products/{id} meta_datamutation metafieldsSet
WebhooksWooCommerce webhook managermutation webhookSubscriptionCreate

Authentication

The Admin API requires a Private App access token (not the Storefront Access Token):

const SHOP_DOMAIN = 'yourstore.myshopify.com';
const ACCESS_TOKEN = process.env.SHOPIFY_ADMIN_ACCESS_TOKEN;

const endpoint = 'https://' + SHOP_DOMAIN + '/admin/api/2024-01/graphql.json';

async function adminQuery(query, variables = {}) {
  const response = await fetch(endpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Shopify-Access-Token': ACCESS_TOKEN,
    },
    body: JSON.stringify({ query, variables }),
  });
  const data = await response.json();
  if (data.errors) throw new Error(JSON.stringify(data.errors));
  return data;
}

Product mutations

Create a product (productCreate)

const CREATE_PRODUCT = `
  mutation productCreate($input: ProductInput!) {
    productCreate(input: $input) {
      product {
        id
        title
        handle
        variants(first: 10) {
          edges { node { id sku price } }
        }
      }
      userErrors { field message }
    }
  }
`;

const result = await adminQuery(CREATE_PRODUCT, {
  input: {
    title: "Classic leather wallet",
    bodyHtml: "<p>Full grain leather, 6 card slots</p>",
    vendor: "Artisan Co",
    productType: "Accessories",
    tags: ["leather", "wallet", "accessories"],
    status: "ACTIVE",
    variants: [{
      price: "49.99",
      sku: "WALLET-BLK-001",
      inventoryManagement: "SHOPIFY",
      inventoryQuantities: [{
        availableQuantity: 50,
        locationId: "gid://shopify/Location/12345"
      }]
    }]
  }
});

Update a product

const UPDATE_PRODUCT = `
  mutation productUpdate($input: ProductInput!) {
    productUpdate(input: $input) {
      product { id title }
      userErrors { field message }
    }
  }
`;

await adminQuery(UPDATE_PRODUCT, {
  input: {
    id: "gid://shopify/Product/12345",
    title: "Updated title",
    tags: ["leather", "wallet", "new-arrival"]
  }
});

Variable products: create with options and variants

const CREATE_VARIABLE_PRODUCT = `
  mutation productCreate($input: ProductInput!) {
    productCreate(input: $input) {
      product { id title variants(first: 50) { edges { node { id title sku } } } }
      userErrors { field message }
    }
  }
`;

await adminQuery(CREATE_VARIABLE_PRODUCT, {
  input: {
    title: "Classic t-shirt",
    options: ["Color", "Size"],
    variants: [
      { options: ["Black", "S"], price: "29.99", sku: "TSHIRT-BLK-S", inventoryQuantities: [{ availableQuantity: 10, locationId: "gid://shopify/Location/12345" }] },
      { options: ["Black", "M"], price: "29.99", sku: "TSHIRT-BLK-M", inventoryQuantities: [{ availableQuantity: 15, locationId: "gid://shopify/Location/12345" }] },
      { options: ["White", "S"], price: "29.99", sku: "TSHIRT-WHT-S", inventoryQuantities: [{ availableQuantity: 8, locationId: "gid://shopify/Location/12345" }] },
      { options: ["White", "M"], price: "29.99", sku: "TSHIRT-WHT-M", inventoryQuantities: [{ availableQuantity: 12, locationId: "gid://shopify/Location/12345" }] }
    ]
  }
});

Metafields (replaces WooCommerce meta_data)

WooCommerce stored custom data as meta_data arrays on products. Shopify uses structured metafields with namespace + key + type:

const SET_METAFIELDS = `
  mutation metafieldsSet($metafields: [MetafieldsSetInput!]!) {
    metafieldsSet(metafields: $metafields) {
      metafields { namespace key value type }
      userErrors { field message }
    }
  }
`;

await adminQuery(SET_METAFIELDS, {
  metafields: [
    {
      ownerId: "gid://shopify/Product/12345",
      namespace: "product_info",
      key: "material",
      value: "Full grain leather",
      type: "single_line_text_field"
    },
    {
      ownerId: "gid://shopify/Product/12345",
      namespace: "product_info",
      key: "weight_g",
      value: "85",
      type: "number_integer"
    }
  ]
});

Inventory management

WooCommerce stores stock_quantity on the product. Shopify separates inventory into InventoryItem (the item) and InventoryLevel (quantity per location):

// Get location ID first
const LOCATIONS_QUERY = `
  query { locations(first: 10) { edges { node { id name } } } }
`;

// Set inventory quantity
const SET_INVENTORY = `
  mutation inventorySetQuantities($input: InventorySetQuantitiesInput!) {
    inventorySetQuantities(input: $input) {
      inventoryAdjustmentGroup { id }
      userErrors { field message }
    }
  }
`;

await adminQuery(SET_INVENTORY, {
  input: {
    name: "available",
    reason: "correction",
    quantities: [{
      inventoryItemId: "gid://shopify/InventoryItem/67890",
      locationId: "gid://shopify/Location/12345",
      quantity: 25
    }]
  }
});

Order queries

const ORDERS_QUERY = `
  query getOrders($first: Int!, $after: String) {
    orders(first: $first, after: $after, sortKey: CREATED_AT, reverse: true) {
      pageInfo { hasNextPage endCursor }
      edges {
        node {
          id
          name
          createdAt
          totalPriceSet { shopMoney { amount currencyCode } }
          displayFinancialStatus
          displayFulfillmentStatus
          customer { id email firstName lastName }
          lineItems(first: 20) {
            edges {
              node {
                id title quantity
                variant { sku price }
              }
            }
          }
        }
      }
    }
  }
`;

let allOrders = [];
let cursor = null;
let hasMore = true;

while (hasMore) {
  const result = await adminQuery(ORDERS_QUERY, { first: 50, after: cursor });
  const { orders } = result.data;
  allOrders = allOrders.concat(orders.edges.map(e => e.node));
  hasMore = orders.pageInfo.hasNextPage;
  cursor = orders.pageInfo.endCursor;
}

Webhooks (replaces WooCommerce hooks)

WooCommerce used action hooks (woocommerce_order_status_changed, woocommerce_new_order). Shopify has HTTP webhooks:

const CREATE_WEBHOOK = `
  mutation webhookSubscriptionCreate($topic: WebhookSubscriptionTopic!, $webhookSubscription: WebhookSubscriptionInput!) {
    webhookSubscriptionCreate(topic: $topic, webhookSubscription: $webhookSubscription) {
      webhookSubscription { id callbackUrl topic }
      userErrors { field message }
    }
  }
`;

// Subscribe to new orders
await adminQuery(CREATE_WEBHOOK, {
  topic: "ORDERS_CREATE",
  webhookSubscription: {
    callbackUrl: "https://yourapp.com/webhooks/shopify/orders",
    format: "JSON"
  }
});

// Available topics: PRODUCTS_CREATE, PRODUCTS_UPDATE, ORDERS_PAID,
// ORDERS_FULFILLED, CUSTOMERS_CREATE, INVENTORY_LEVELS_UPDATE, etc.

Rate limits

APIRate limit typeLimitRestore rate
GraphQL Admin APICost-based throttling1000 points/bucket50 points/second
REST Admin APILeaky bucket40 requests2 requests/second
Storefront APICost-based throttling1000 points/bucket50 points/second

GraphQL response includes cost information in extensions:

// Check cost in response
const result = await adminQuery(PRODUCTS_QUERY, { first: 50 });
const { actualQueryCost, throttleStatus } = result.extensions.cost;
// throttleStatus: { maximumAvailable, currentlyAvailable, restoreRate }
// If currentlyAvailable is low, add delay: await new Promise(r => setTimeout(r, 1000))

Bulk operations (large catalog migrations)

For migrating catalogs with 1000+ products, use Shopify Bulk Operations — asynchronous operations that return a JSONL file:

// 1. Start bulk operation query
const BULK_QUERY = `
  mutation {
    bulkOperationRunQuery(
      query: """
      {
        products {
          edges {
            node {
              id title handle
              variants(first: 100) { edges { node { id sku price inventoryQuantity } } }
            }
          }
        }
      }
      """
    ) {
      bulkOperation { id status }
      userErrors { field message }
    }
  }
`;

// 2. Poll for completion
const POLL_BULK = `
  query {
    currentBulkOperation {
      id status errorCode
      createdAt completedAt
      objectCount url
    }
  }
`;

// 3. When status === "COMPLETED", download the JSONL from url
// Each line is one product node as JSON

Developer migration checklist

The most common mistake WooCommerce developers make on the Shopify Admin API is ignoring userErrors in mutation responses. Unlike WooCommerce REST API which returns 4xx HTTP status codes for validation failures, Shopify GraphQL mutations always return HTTP 200 — validation errors are in the userErrors array on the mutation result. A product that failed to create still returns 200 with an empty product and populated userErrors. Always check userErrors on every mutation or you'll silently lose products during migration.

Migrate your store with k-sync

Connect your WooCommerce store, validate your products, and push to Shopify in minutes. Free for up to 50 products.

Get started free

Related reading

Browse all migration guides