▸ 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
- —
▸ Notes
▸ 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
- ▸ Blog Agentic commerce on Shopify Where GIDs show up when an agent is the thing calling the Admin API instead of a person.
- ▸ Blog Shopify Liquid vs headless Liquid hands you numeric IDs, the Storefront API hands you GIDs. Picking a stack means picking which one you live with.
- ▸ Blog Shopify UCP quick start Getting a catalog and its identifiers out of Shopify and into something a model can use.
- ▸ Tool Shopify HMAC Verifier The other half of webhook handling — check the signature before you trust the IDs inside the payload.
Keep reading
-
▸ Tool
Shopify Webhook HMAC Verifier
The other Shopify thing that fails silently: webhook signature verification.
-
▸ Post
/blog/agentic-commerce-shopify/
Why structured, machine-readable product IDs suddenly matter a lot more.
-
▸ Post
/blog/shopify-liquid-vs-headless/
Which ID format you get depends on which storefront stack you picked.
-
▸ Tool
Base64 Encoder / Decoder
Older Shopify APIs hand back base64-wrapped GIDs. Here's the decoder.