We write a lot of code that pushes prices, SKUs and barcodes into Shopify from other systems, and the ID format is where most first attempts go wrong. So here's the short version we wish we'd had.

The format

Shopify's GraphQL APIs (Admin, Storefront and Customer Account) all use global IDs. The pattern is gid://shopify/{object}/{id}, so:

  • Variant: gid://shopify/ProductVariant/43729076
  • Product: gid://shopify/Product/20995642
  • Inventory item: gid://shopify/InventoryItem/123

Two things catch people out. The object name is case-sensitive and singular: ProductVariant, not productvariant or Variant. And it's a string, not a number, so don't let your language or your database helpfully turn it into one.

A few objects use a longer, parameterised form because they need a parent's ID too. Inventory levels are the one you'll meet, for example gid://shopify/InventoryLevel/123?inventory_item_id=456. Variants don't do this. A variant GID is always the simple form.

Converting between the REST ID and the GID

From numeric ID to GID you can just build the string. If you're reading from the REST Admin API, you don't even need to: every REST resource includes an admin_graphql_api_id property holding the GID for you.

From GID to numeric ID, the safe way is to ask for legacyResourceId, which Shopify describes as "the ID of the corresponding resource in the REST Admin API":

query GetProductVariant($id: ID!) {
  productVariant(id: $id) {
    id
    legacyResourceId
    sku
    price
  }
}

In practice most of us also strip the prefix in code when we already have the GID in hand. That's fine for variants, as long as you handle the parameterised IDs separately if you ever reuse the same helper elsewhere. Here's what we use in PHP:

function variant_gid($id) {
    $id = (string) $id;
    if (strpos($id, 'gid://shopify/ProductVariant/') === 0) {
        return $id;
    }
    if (!ctype_digit($id)) {
        throw new InvalidArgumentException("Not a variant ID: $id");
    }
    return 'gid://shopify/ProductVariant/' . $id;
}

function variant_numeric_id($gid) {
    if (!preg_match('~^gid://shopify/ProductVariant/(\d+)$~', $gid, $m)) {
        throw new InvalidArgumentException("Not a variant GID: $gid");
    }
    return $m[1]; // keep it as a string: these IDs outgrow 32-bit integers
}

And the same idea in JavaScript:

const variantGid = (id) =>
  String(id).startsWith('gid://shopify/ProductVariant/')
    ? String(id)
    : `gid://shopify/ProductVariant/${id}`;

const variantId = (gid) => gid.split('/').pop(); // a string, on purpose

That last comment matters. Variant IDs are 64-bit, and Shopify types legacyResourceId as an unsigned 64-bit integer. JavaScript numbers lose precision past about 9 quadrillion, and plenty of older PHP setups and spreadsheets mangle long numbers too. Keep them as strings end to end and you'll never chase a variant that's off by a few hundred.

Where you'll find the numeric ID

  • The REST Admin API, as id, with the GID alongside as admin_graphql_api_id.
  • The admin, as the number at the end of a variant's URL when you open it from the product page.
  • Liquid and the storefront, as variant.id and in links like ?variant=43729076.
  • The GraphQL Admin API, as legacyResourceId on the variant.

The Storefront API used to encode them

If you've got old code with base64_decode or atob wrapped round variant IDs, this is why. The Storefront API used to return its IDs Base64-encoded, so a variant came back as an unreadable string that decoded to the gid:// form.

Shopify stopped that from Storefront API version 2022-04, which was the first stable version to return plain GIDs. Encoded IDs as arguments were deprecated at the same time, and Shopify said support for them would be removed in 2023-07. Every supported version now uses the same plain gid://shopify/ProductVariant/... string as the Admin API. If you stored encoded IDs in a database years ago, decode them once and get rid of the encoding code.

Using the GID to update a variant today

This is where our old post came from. Shopify deprecated the single-variant mutations in favour of the bulk ones as of Admin API version 2024-10, with productVariantsBulkUpdate replacing productVariantUpdate. The old mutation isn't in the current reference at all (I checked 2026-10). If an integration started failing on variant updates, that's usually why.

The bulk mutation needs the parent product's GID as well as each variant's:

mutation UpdateVariants($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
  productVariantsBulkUpdate(productId: $productId, variants: $variants) {
    productVariants {
      id
      sku
      price
      compareAtPrice
    }
    userErrors {
      field
      message
    }
  }
}
{
  "productId": "gid://shopify/Product/20995642",
  "variants": [
    {
      "id": "gid://shopify/ProductVariant/43729076",
      "price": "49.00",
      "compareAtPrice": "65.00",
      "inventoryItem": { "sku": "TSHIRT-BLK-M" },
      "barcodes": [ { "value": "5012345678900" } ]
    }
  ]
}

A few things worth knowing from the 2026-10 reference:

  • SKU lives on the inventory item when you write it. You read sku straight off the variant, but you set it through inventoryItem: { sku: ... }.
  • Barcode has changed. The single barcode field is deprecated. Use barcodes, a list of up to 20 values, which replaces the whole set each time.
  • It's all or nothing by default. With allowPartialUpdates left at false, one bad variant stops the whole batch. Always read userErrors, because a request can come back with a 200 and still have changed nothing.
  • You need the write_products scope on your app.

The short version

Build the GID as gid://shopify/ProductVariant/{id}, get the number back with legacyResourceId, keep both as strings, forget about Base64, and send updates through productVariantsBulkUpdate with the product's GID alongside. That covers almost every variant ID problem we've seen.

If you've got a stock system, ERP or price feed that needs to talk to Shopify properly, that's a lot of what we do. Have a look at our custom Shopify development and API and integration work, or see how we built ShopFlow to run stock and orders on top of Shopify.

Cheers, Rob