The Shopify product catalog API is not one endpoint — it is a set of Admin GraphQL APIs that read, write, and distribute your products. The core pieces are the Product/ProductVariant objects (the canonical catalog), the productSet mutation and Bulk Operations API (write at scale), the ProductFeed API (sync to channels), and the Catalog API with price lists and publications (control what publishes where). This guide shows when to use each, with runnable code.
If you only take one thing away: use the GraphQL Admin API, not REST. REST is legacy as of 2024–2025, and every new catalog capability ships to GraphQL first. This guide assumes the GraphQL Admin API throughout. For authentication, OAuth, and rate-limit fundamentals, see the Shopify Admin API guide; for the read-side browsing API, see the Shopify Storefront API guide.
The Shopify catalog data model in one table
| Object | What it represents | Key fields |
|---|---|---|
Product | A sellable item | title, descriptionHtml, productType, vendor, status, tags |
ProductVariant | A specific SKU of a product | price, sku, barcode (GTIN), inventoryItem, selectedOptions |
ProductOption | An axis of variation | name (e.g. Size), values |
Media / MediaImage | Images and video | image.url, alt |
InventoryItem / InventoryLevel | Stock per location | available, location |
Metafield / Metaobject | Custom catalog data | namespace, key, value |
The hierarchy is Product → Variants → InventoryItems, with Media and Metafields hanging off the Product. Get this model right and every downstream feed becomes straightforward.
Reading the catalog: products and variants
A typical catalog read pulls products with their variants and the fields a feed needs. Request exactly what you need — GraphQL bills by query cost, so over-fetching wastes your rate-limit budget.
query CatalogPage($cursor: String) {
products(first: 50, after: $cursor) {
pageInfo { hasNextPage endCursor }
nodes {
id
title
descriptionHtml
productType
vendor
status
featuredMedia { ... on MediaImage { image { url altText } } }
variants(first: 100) {
nodes {
id
sku
barcode
price
inventoryQuantity
selectedOptions { name value }
}
}
}
}
}
Paginate with the endCursor until hasNextPage is false. For a handful of common reads — single product by handle, products by collection, low-inventory variants — grab them from the Shopify Catalog GraphQL Query Cookbook.
When the catalog is large: Bulk Operations
Synchronous pagination is fine for a few hundred products. For thousands, use the Bulk Operations API: submit one query, let Shopify run it asynchronously, then download a JSONL file with every product and variant flattened.
mutation {
bulkOperationRunQuery(
query: """
{ products { edges { node { id title
variants { edges { node { id sku barcode price } } } } } }
"""
) {
bulkOperation { id status }
userErrors { field message }
}
}
Poll currentBulkOperation until status is COMPLETED, then fetch the url and stream the JSONL. This is the right pattern for full-catalog exports that power Google Merchant Center, Meta catalogs, or AI shopping feeds — it sidesteps the rate limit entirely.
Writing the catalog at scale: productSet
The modern way to create or update products is the productSet mutation. It is declarative: you describe the desired end state of a product — its variants, options, and media — and Shopify reconciles it. That makes it ideal for syncing from an external source of truth (a PIM, an ERP, a spreadsheet) because you do not have to diff and orchestrate individual create/update/delete calls.
mutation UpsertProduct($input: ProductSetInput!) {
productSet(input: $input) {
product { id title }
userErrors { field message }
}
}
{
"input": {
"title": "Merino Wool Beanie",
"productType": "Hats",
"vendor": "Northbound",
"status": "ACTIVE",
"productOptions": [{ "name": "Color", "values": [{ "name": "Charcoal" }, { "name": "Rust" }] }],
"variants": [
{ "optionValues": [{ "optionName": "Color", "name": "Charcoal" }], "sku": "BEANIE-CHAR", "barcode": "0080000000017", "price": "32.00" },
{ "optionValues": [{ "optionName": "Color", "name": "Rust" }], "sku": "BEANIE-RUST", "barcode": "0080000000024", "price": "32.00" }
]
}
}
Use productSet over the older productCreate/productUpdate + productVariantsBulkUpdate dance whenever you are syncing from an authoritative external catalog. Reach for the granular mutations only when you need to patch a single field without describing the whole product.
Distributing the catalog: the Product Feed API
The ProductFeed API generates channel-specific product feeds — the bridge between your catalog and the places people (and AI agents) actually buy. You create a feed for a publication, then run a full sync to populate it.
mutation CreateFeed($input: ProductFeedInput!) {
productFeedCreate(input: $input) {
productFeed { id status country language }
userErrors { field message }
}
}
After creating the feed, call productFullSync to push the catalog, and listen for the product_feeds/full_sync_finish webhook to know when it is done. Reading feeds requires the read_product_listings scope. This API is what makes your catalog available to sales and marketing channels in a structured, channel-aware way rather than a brittle CSV export.
Controlling what publishes where: the Catalog API
For B2B and international selling, the Catalog API controls which products and what prices are visible to which context. A Catalog ties together a Publication (which products are available) and a PriceList (what they cost) for a specific company location (B2B) or market.
query Catalogs {
catalogs(first: 20, type: COMPANY_LOCATION) {
nodes {
title
status
priceList { name currency }
publication { id }
}
}
}
Use this when one store serves multiple audiences at different prices — wholesale buyers, regional markets, or member tiers — without duplicating products. To manage per-sales-channel availability for the standard storefront, the publishablePublish mutation is the lighter-weight tool.
Extending the catalog: metafields and metaobjects
Native fields rarely cover everything a rich feed needs — material, care instructions, certifications, model dimensions, or AI-readable attributes. Metafields attach custom data to products and variants; metaobjects model structured, reusable entities (a size chart, a fabric definition) you reference from many products. Both are fully API-driven. For creating and displaying them, see the Shopify metafields guide.
Well-structured metafields are also one of the highest-leverage things you can do for discovery: they are exactly the attributes AI shopping agents look for. See preparing your product feed for AI agents for the structuring patterns.
Keeping feeds in sync: webhooks
A feed is only as good as its freshness. Subscribe to catalog webhooks so your downstream feeds update the moment the catalog changes:
products/create,products/update,products/delete— catalog changesinventory_levels/update— stock changes (critical for ads; out-of-stock products waste spend)product_feeds/full_sync_finish— feed sync completion
Process these idempotently and debounce bursts — a bulk edit in the Shopify admin can fire thousands of products/update events in seconds.
Why this matters for ads and AI shopping
Your catalog is the raw material of every ad. Google Shopping, Meta Advantage+ catalog ads, and AI shopping agents (ChatGPT, Perplexity, Google AI Overviews) all consume the same fields: titles, descriptions, images, prices, and identifiers like GTIN/barcode. Missing GTINs, thin descriptions, or absent structured attributes cap performance no matter how well campaigns are managed — clean catalog data is the cheapest ROAS lever most stores never pull.
This is the work AdsX does day to day: turning a Shopify catalog into high-performing feeds across paid and AI channels. If you want that handled end to end, see our Shopify AI advertising guide or how we work with stores.
Next steps
- Pull a full catalog export with Bulk Operations and audit it for missing GTINs, images, and descriptions.
- Move any external-source sync to the declarative
productSetmutation. - Stand up a ProductFeed for each channel and wire
inventory_levels/updatewebhooks. - Enrich with metafields for the attributes ads and AI agents need.
Bookmark the query cookbook for copy-paste GraphQL as you build.