Skip to content
ansezz.

▸ Free tool

Shopify GID Decoder.

Paste a Shopify global ID and get the resource type and legacy numeric ID back. Paste a numeric ID and get a GID. Base64, admin URLs and newline-separated lists all work.

▸ Read this first

A GID is meant to be opaque. This tool takes it apart because you are debugging — production code should read GIDs from the API, not build them with string concatenation. Nothing you paste leaves the page.

▸ Parsed

Input form what you pasted
Namespace always shopify for Shopify's own IDs
Resource type the GraphQL type, PascalCase
Legacy ID what REST, Liquid and admin URLs use
Query params metafields and inventory levels carry these

▸ Build a GID

Numeric ID in, GID out. The query field is for the parameters a few resources need — namespace=custom&key=fit_guide for a metafield, inventory_item_id=445566 for an inventory level.

▸ Bulk convert

One value per line — GIDs, base64 GIDs, admin URLs and bare numbers can be mixed. Lines that fail come back blank so row order still matches your spreadsheet, and the count below names what broke.

Why Shopify moved from numeric IDs to GIDs

REST gave every resource a plain 64-bit integer. 1234567890 is a fine primary key and a terrible identifier: on its own it does not tell you whether it points at a product, an order or a customer, so the type has to travel next to it in a separate field, a separate column, or somebody's head. GraphQL cannot work that way. A single field can return several types, and a client-side cache needs one string it can use as a key.

So Shopify adopted Relay's global object identification pattern: gid://shopify/Product/1234567890. Scheme, namespace, type, legacy ID. One string that is unique across the whole API, carries its own type, and can be handed to node(id:) or nodes(ids: [...]) to fetch anything without knowing up front what it is. That is also why Apollo and urql can normalise a Shopify response without configuration — the id field is already globally unique.

When you still need the legacy numeric ID

GIDs did not replace the numeric IDs; they wrap them. The number is still what half of the platform speaks, and you will keep converting between the two for as long as you work on Shopify.

  Numeric ID (REST / Liquid) GID (GraphQL)
Shape 1234567890 gid://shopify/Product/1234567890
Type is part of the ID
Where you get it Admin URLs, webhook id, Liquid, CSV and report exports, REST responses GraphQL responses, bulk operation JSONL, webhook admin_graphql_api_id
Fetching one by ID A different REST endpoint per resource node(id:) and nodes(ids:) for anything
Safe to assemble yourself n/a — it is just an integer No — treat it as opaque, read it from the API

The places it bites: webhook payloads are REST-shaped, so id is the number — but Shopify also ships admin_graphql_api_id in the same object, so reach for that instead of concatenating. Liquid gives you product.id as an integer. Admin URLs are all numeric, which is why this tool accepts one and pulls the ID out. CSV and report exports that include an Id column give you the number. And any ERP, 3PL or legacy database you integrated with in 2019 has a column full of integers. Going the other way, the GraphQL field you want is legacyResourceId — it returns the numeric ID as a string, and it means you never parse a GID yourself.

The base64 era, and why encoding assumptions rot

For years the Storefront API base64-encoded every ID it returned, so gid://shopify/Product/1234567890 came back as Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzEyMzQ1Njc4OTA=. The Admin API never did. Shopify dropped the encoding in API version 2022-04, and both forms are still accepted as input — which is a friendly API and a nasty source of bugs.

The failure mode is comparison, not decoding. Two rows hold the same product, one written by an old storefront and one by a new webhook handler, and a === b is false. Deduplication silently doubles the catalogue. A cache keyed on the encoded string never hits. If you store IDs, normalise on write: decode base64 if it decodes to something starting with gid://, keep the plain form, and never assume the shape of what the next API version hands you. This tool decodes with atob plus TextDecoder in fatal mode, so a string that merely looks like base64 fails loudly instead of producing mojibake.

A GID is opaque — including the one this tool just built you

Shopify's position is that a GID is an opaque token: read it from a response, pass it back, do not manufacture it. The builder above ignores that on purpose, because when you are staring at a support ticket with a numeric order ID in it you need a GID in ten seconds, not a query. Shipping that pattern into production is a different matter.

Three concrete reasons it breaks. The type name has to be exactly right and exactly PascalCase — gid://shopify/product/1 is not a typo the API forgives, and MediaImage, ProductImage and Image are three different things. Some resources need query parameters as part of the identifier: an inventory level is the pair of a location and an inventory item, so its GID carries ?inventory_item_id=…. And some IDs are not numbers at all — a Storefront cart ID is an opaque token, so any code doing "gid://shopify/Cart/" + parseInt(x) is already broken. When a bulk migration really does force your hand, build the strings, then validate them through nodes(ids: [...]) and treat every null in the response as a mapping you got wrong.

What this page accepts

Parsing is a string split, not a regex guess: everything after gid:// is cut at the first ?, the path must be exactly three segments, and the query is read with URLSearchParams so repeated and empty values survive. Anything that fails names the reason — one slash instead of two, a stray fragment, a space in the middle, four segments because a URL got pasted in. Inputs that are not GIDs are tried in order: a Shopify admin URL (the last /segment/12345 pair wins, so /products/1/variants/2 resolves to the variant), a bare integer, then base64. The notes panel flags the things that parse but still look wrong — a non-shopify namespace, a lowercase type, a leading zero, an InventoryLevel missing its parameter. All of it runs locally; the page works with the network unplugged.

Questions people ask

How do I get the numeric ID from a Shopify GID?

Take the last path segment: gid://shopify/Product/1234567890 gives 1234567890. In GraphQL you should not parse it yourself — most Admin API objects expose legacyResourceId, which returns that same number as a string. In webhook payloads the numeric id is already the top-level id field, sitting next to admin_graphql_api_id which holds the full GID.

What does gid://shopify/Product/1234567890 mean?

It is a Relay-style global ID. gid:// is the scheme, shopify is the namespace, Product is the GraphQL type, and 1234567890 is the legacy numeric ID that REST, Liquid and admin URLs use. The type is baked into the identifier, which is what lets a single node(id:) query fetch any object without knowing in advance what it is.

Why are some Shopify IDs base64-encoded?

The Storefront API used to return every ID base64-encoded, so gid://shopify/Product/123 arrived as Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzEyMw==. Shopify stopped doing that in API version 2022-04, and the Admin API never did it at all. Old code, cached rows and copy-pasted snippets still carry the encoded form, which is why this tool detects and decodes it — do not assume either encoding in your own code.

Can I build a Shopify GID by hand from a numeric ID?

It usually works and Shopify still tells you not to rely on it. GIDs are opaque: the type name must be exactly right and PascalCase, some resources need a query parameter (an InventoryLevel GID carries ?inventory_item_id=...), and some IDs are not numeric at all — a Storefront cart ID is an opaque string. Build them here to debug, and in production read the GID from the API.

Does this Shopify GID decoder send my IDs anywhere?

No. Parsing, base64 decoding and the bulk conversion all run in your browser with plain string operations, atob and TextDecoder. There is no API call, no logging and no storage — load the page, go offline, and it still works. GIDs are not secrets, but customer and order IDs still do not belong in a random web form.

What is admin_graphql_api_id in a Shopify webhook?

Webhook payloads are REST-shaped, so their id field is the legacy number. Shopify adds admin_graphql_api_id alongside it holding the full GID, so you can pass it straight into a GraphQL query with no string concatenation. If you are matching webhook events against records you stored from GraphQL, that is the field to key on.

Related

Keep reading