ADSX
JULY 1, 2026 // UPDATED JUL 1, 2026

The Shopify Product Data Model: A Field Reference

A field-level reference for Shopify's product data model — Product, ProductVariant, options, media, inventory, and metafields — with relationships.

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

A field-level reference for Shopify's product data model — Product, ProductVariant, options, media, inventory, and metafields — with relationships.

The Shopify product data model is a hierarchy: a Product groups shared marketing content, one or more ProductVariants are the purchasable SKUs, ProductOptions define how variants differ, Media holds images and video, and InventoryItem/InventoryLevel track stock per location. Metafields and Metaobjects extend any of these with typed custom data. Get the relationships right and every feed, sync, and integration downstream gets easier.

This is the expanded, field-level reference that the Shopify Product Catalog API guide links to — the pillar has a six-row summary; this page is the full schema with a table per object. To extract these objects at scale, see fetch your entire catalog with GraphQL. Everything below uses the GraphQL Admin API, where new catalog fields ship first (Shopify: API versioning).

The hierarchy at a glance

Product ─────────────────────────────────────────────
  ├── title, descriptionHtml, productType, vendor, status, tags
  ├── options[] ── ProductOption
  │                  └── optionValues[] ── ProductOptionValue
  ├── media[] ─── Media (MediaImage | Video | Model3d | ExternalVideo)
  │                  └── MediaImage.image ── Image (url, altText, dimensions)
  ├── variants[] ─ ProductVariant
  │                  ├── price, compareAtPrice, sku, barcode
  │                  ├── selectedOptions[] (option name → value)
  │                  └── inventoryItem ── InventoryItem
  │                                        └── inventoryLevels[] ── InventoryLevel
  │                                                                   └── location, quantities
  └── metafields[] ── Metafield ──(metaobject_reference)──▶ Metaobject

Read it top-down: a Product owns options, media, variants, and metafields. Each variant points at exactly one InventoryItem, which fans out to one InventoryLevel per location. Metafields can point sideways to Metaobjects for reusable records. The two rules that trip people up: purchasable data lives on the variant, never the product, and inventory counts live two hops down on InventoryLevel, never on the variant itself. Keep those straight and the rest of the schema follows.

Product

The Product is the parent resource shoppers browse. It carries all content shared across variants — title, description, imagery, and taxonomy — but nothing purchasable. You cannot buy a Product; you buy one of its variants.

FieldTypeNotes
idIDGlobal GID, e.g. gid://shopify/Product/123
titleStringShopper-facing name; the single biggest driver of feed and ad relevance
descriptionHtmlHTMLRich body copy; description returns plain text
handleStringURL slug, unique per store
productTypeStringFree-text merchant category
vendorStringBrand/manufacturer
statusProductStatusACTIVE, DRAFT, or ARCHIVED — only ACTIVE is buyable
tags[String]Free-form labels used for collections and filtering
options[ProductOption]Variation axes (see below)
variantsProductVariantConnectionThe purchasable SKUs
mediaMediaConnectionImages, video, 3D models
categoryTaxonomyCategoryShopify's standard product taxonomy node

Gotcha: status gates visibility everywhere. A DRAFT product silently drops out of feeds and the Storefront API. When products go missing downstream, check status first (Shopify: Product).

ProductVariant

A ProductVariant is the atomic sellable unit. Price, SKU, barcode, and the inventory link all live here. Every product has at least one variant — even a single-SKU product ships with one default variant named Default Title.

FieldTypeNotes
idIDVariant GID
titleStringAuto-composed from option values, e.g. Large / Blue
priceMoneyCurrent selling price
compareAtPriceMoneyOriginal price for showing a markdown
skuStringYour stock-keeping unit; not enforced unique by Shopify
barcodeStringGTIN/UPC/EAN — the key field for Google & Meta feeds
selectedOptions[SelectedOption]Name/value pairs resolving this variant's options
inventoryItemInventoryItemLink to the stock-tracking record
inventoryQuantityIntTotal across all locations (read-only)
availableForSaleBooleanDerived from inventory + policy
query VariantDetail($id: ID!) {
  productVariant(id: $id) {
    title
    price
    barcode
    selectedOptions { name value }
    inventoryItem { id tracked }
  }
}

Gotcha: sku is not unique-enforced by Shopify — duplicates are allowed and common after imports. If your integration keys on SKU, deduplicate yourself. And barcode, not sku, is what maps to gtin in ad feeds (Shopify: ProductVariant).

ProductOption & ProductOptionValue

ProductOptions are the axes of variation. Each option (Size, Color, Material) owns a list of ProductOptionValues. A variant is the intersection of exactly one value from each option, which is why total variant count is the product of the value counts.

FieldTypeNotes
ProductOption.nameStringe.g. Size
ProductOption.positionInt1-indexed display order
ProductOption.optionValues[ProductOptionValue]The allowed values
ProductOptionValue.nameStringe.g. Large
ProductOptionValue.linkedMetafieldValueStringSet when the option is backed by a metaobject (e.g. a color swatch)

Gotcha: standard plans cap a product at 3 options and 100 variants; the expanded limit raises variants to 2,048 but options stay at 3. Options backed by metaobjects (linked options) power swatches and rich pickers (Shopify: ProductOption).

Media, MediaImage & Image

The Media connection holds all visual assets. Media is an interface implemented by MediaImage, Video, ExternalVideo, and Model3d. For images, the actual URL and dimensions live one level deeper on the Image object.

FieldTypeNotes
Media.mediaContentTypeMediaContentTypeIMAGE, VIDEO, MODEL_3D, EXTERNAL_VIDEO
Media.statusMediaStatusREADY, PROCESSING, FAILED
MediaImage.imageImageThe renderable asset
Image.urlURLSupports transform args (width, height)
Image.altTextStringAlt text — feeds and accessibility read this
MediaImage.mediaContentTypeMediaContentTypeAlways IMAGE
query ProductMedia($id: ID!) {
  product(id: $id) {
    media(first: 10) {
      nodes {
        mediaContentType
        ... on MediaImage { image { url altText } }
      }
    }
  }
}

Gotcha: newly uploaded media returns status: PROCESSING with a null image.url until Shopify finishes ingesting it. Poll or subscribe before you export, or images silently drop from feeds (Shopify: Media).

InventoryItem & InventoryLevel

Stock is a two-object subtree. Each variant links to one InventoryItem (cost, tracking, country of origin), and each item fans out to one InventoryLevel per location. Quantities live on the level, broken into named states.

FieldTypeNotes
InventoryItem.trackedBooleanWhether Shopify counts stock
InventoryItem.unitCostMoneyCost of goods (COGS)
InventoryItem.countryCodeOfOriginCountryCodeCustoms/feed attribute
InventoryLevel.locationLocationThe warehouse/store this level belongs to
InventoryLevel.quantities[InventoryQuantity]available, on_hand, committed, incoming

Gotcha: as of the 2024+ API, quantities are queried by name through the quantities(names: [...]) argument rather than a single available scalar — a common source of "why is my count null" bugs. One InventoryItem, many InventoryLevels: sum them for a store-wide total (Shopify: InventoryLevel).

Metafield & Metaobject

Metafields attach typed custom data to a Product, ProductVariant, or nearly any resource. Metaobjects are standalone, reusable custom records that products reference via a metafield of type metaobject_reference. Together they model anything Shopify's core schema omits.

FieldTypeNotes
Metafield.namespaceStringGroups keys, e.g. custom
Metafield.keyStringField name within the namespace
Metafield.typeStringTyped: single_line_text_field, number_decimal, metaobject_reference, etc.
Metafield.valueStringAlways serialized as a string; parse per type
Metaobject.typeStringThe metaobject definition, e.g. designer
Metaobject.fields[MetaobjectField]The record's typed fields

Gotcha: value is always a string even for numbers and JSON — deserialize based on type. And metafields are only exposed to the Storefront API when their definition is flagged storefront-visible. The full patterns live in catalog metafields & metaobjects (Shopify: Metafield).

Putting it together

The model is deliberately layered: content on the Product, commerce on the Variant, physical stock on Inventory, and everything custom on Metafields. When a feed underperforms, the cause is almost always a specific node in this tree — a missing barcode, a DRAFT status, an image still PROCESSING, or a null inventory quantity. Knowing exactly which object owns which field is what turns a vague "the feed is broken" into a one-line fix.

That data-quality ceiling is the work AdsX does for Shopify brands: the completeness of these objects caps how well ads and AI shopping feeds can ever perform. To see which fields your own catalog is missing, run the free feed-readiness audit.

Next steps

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