ADSX
JUNE 20, 2026 // UPDATED JUN 20, 2026

Shopify Catalog GraphQL Query Cookbook (2026)

Copy-paste Shopify Admin GraphQL queries & mutations for the product catalog: reads, productSet writes, bulk exports, product feeds, price lists, and webhooks.

AUTHOR
AE
AdsX Engineering
SHOPIFY API & COMMERCE ENGINEERING
READ TIME
5 MIN
SUMMARY

Copy-paste Shopify Admin GraphQL queries & mutations for the product catalog: reads, productSet writes, bulk exports, product feeds, price lists, and webhooks.

This is a working reference of copy-paste Shopify Admin GraphQL snippets for product catalog work. For the conceptual overview of how these APIs fit together — Product objects, productSet, the Product Feed API, and the Catalog API — start with the Shopify Product Catalog API guide.

Every snippet targets the GraphQL Admin API. Pin an explicit version in your endpoint (e.g. /admin/api/2025-07/graphql.json) and request only the fields you need — GraphQL bills by calculated query cost.

Reads

Get a single product by handle

query ProductByHandle($handle: String!) {
  productByIdentifier(identifier: { handle: $handle }) {
    id
    title
    descriptionHtml
    status
    variants(first: 100) { nodes { id sku barcode price } }
  }
}

Paginate the full product list

query Products($cursor: String) {
  products(first: 100, after: $cursor, sortKey: UPDATED_AT) {
    pageInfo { hasNextPage endCursor }
    nodes { id title handle status updatedAt }
  }
}

Filter with a search query (status, type, tag)

query ActiveHats($cursor: String) {
  products(first: 50, after: $cursor, query: "status:active AND product_type:Hats") {
    pageInfo { hasNextPage endCursor }
    nodes { id title productType }
  }
}

Products in a collection

query CollectionProducts($id: ID!, $cursor: String) {
  collection(id: $id) {
    products(first: 100, after: $cursor) {
      pageInfo { hasNextPage endCursor }
      nodes { id title }
    }
  }
}

Variants low on inventory (feed hygiene)

query LowStock {
  productVariants(first: 100, query: "inventory_quantity:<5") {
    nodes { id sku displayName inventoryQuantity product { title } }
  }
}

Pull every feed-critical field for one product

query FeedFields($id: ID!) {
  product(id: $id) {
    id title descriptionHtml productType vendor tags
    featuredMedia { ... on MediaImage { image { url altText } } }
    variants(first: 100) {
      nodes { sku barcode price compareAtPrice inventoryQuantity selectedOptions { name value } }
    }
    metafields(first: 20) { nodes { namespace key value } }
  }
}

Writes

Create or update a product declaratively (productSet)

mutation UpsertProduct($input: ProductSetInput!) {
  productSet(input: $input) {
    product { id title }
    userErrors { field message }
  }
}

Patch a single field (productUpdate)

mutation Retitle($id: ID!, $title: String!) {
  productUpdate(product: { id: $id, title: $title }) {
    product { id title }
    userErrors { field message }
  }
}

Bulk-update variant prices

mutation Reprice($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
  productVariantsBulkUpdate(productId: $productId, variants: $variants) {
    productVariants { id price }
    userErrors { field message }
  }
}

Attach an image to a product

mutation AddImage($productId: ID!, $media: [CreateMediaInput!]!) {
  productCreateMedia(productId: $productId, media: $media) {
    media { ... on MediaImage { id image { url } } }
    mediaUserErrors { field message }
  }
}

Set a metafield on a product

mutation SetMetafield($metafields: [MetafieldsSetInput!]!) {
  metafieldsSet(metafields: $metafields) {
    metafields { id namespace key value }
    userErrors { field message }
  }
}

Bulk export the whole catalog

Start a bulk query

mutation BulkExport {
  bulkOperationRunQuery(
    query: """
    { products { edges { node { id title
        variants { edges { node { id sku barcode price inventoryQuantity } } } } } }
    """
  ) {
    bulkOperation { id status }
    userErrors { field message }
  }
}

Poll for completion, then download

query BulkStatus {
  currentBulkOperation {
    id
    status
    objectCount
    url
  }
}

When status is COMPLETED, stream the JSONL file at url. Each line is one object; child nodes (variants) reference their parent by __parentId.

Product feeds (channel distribution)

List existing feeds

query Feeds {
  productFeeds(first: 20) {
    nodes { id status country language }
  }
}

Create a feed and trigger a full sync

mutation CreateFeed($input: ProductFeedInput!) {
  productFeedCreate(input: $input) {
    productFeed { id status }
    userErrors { field message }
  }
}

mutation FullSync($id: ID!) {
  productFullSync(id: $id) {
    userErrors { field message }
  }
}

Catalogs and price lists (B2B / Markets)

List company-location catalogs

query Catalogs {
  catalogs(first: 20, type: COMPANY_LOCATION) {
    nodes { title status priceList { name currency } }
  }
}

Read a price list's fixed prices

query PriceListPrices($id: ID!) {
  priceList(id: $id) {
    name
    currency
    prices(first: 100) {
      nodes { variant { sku } price { amount currencyCode } }
    }
  }
}

Publish a product to a sales channel

mutation Publish($id: ID!, $input: [PublicationInput!]!) {
  publishablePublish(id: $id, input: $input) {
    publishable { availablePublicationsCount { count } }
    userErrors { field message }
  }
}

Webhooks (keep feeds fresh)

Subscribe to product updates

mutation SubscribeProductUpdate($callbackUrl: URL!) {
  webhookSubscriptionCreate(
    topic: PRODUCTS_UPDATE
    webhookSubscription: { callbackUrl: $callbackUrl, format: JSON }
  ) {
    webhookSubscription { id topic }
    userErrors { field message }
  }
}

Subscribe to PRODUCTS_CREATE, PRODUCTS_UPDATE, PRODUCTS_DELETE, and INVENTORY_LEVELS_UPDATE the same way. Process events idempotently and debounce bursts.

Handling rate limits

The GraphQL Admin API uses a calculated, cost-based limit. Every response includes extensions.cost with requestedQueryCost, actualQueryCost, and a throttleStatus bucket. Read it and back off when currentlyAvailable runs low:

{
  "extensions": {
    "cost": {
      "requestedQueryCost": 112,
      "actualQueryCost": 51,
      "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1949, "restoreRate": 100 }
    }
  }
}

For anything that would burn a large share of the bucket, use Bulk Operations instead. For OAuth, scopes, and the deeper rate-limit model, see the Shopify Admin API guide; for the strategy of turning this catalog data into ad and AI-shopping performance, see the Shopify Product Catalog API guide.

ABOUT THE AUTHOR
AE
AdsX Engineering
SHOPIFY API & COMMERCE ENGINEERING

The AdsX engineering team builds the data pipelines that turn a Shopify product catalog into high-performing ad feeds across Google, Meta, and AI shopping agents. We work hands-on with the Shopify Admin GraphQL API, the Product Feed and Catalog APIs, metafields, and bulk operations every day, and these guides document the patterns we use in production.

MORE BY ADSX ENGINEERING

Ready to Dominate AI Search?

Get your free AI visibility audit and see how your brand appears across ChatGPT, Claude, and more.

Get Your Free Audit