Shopify Idempotency Keys: Retry Admin API Mutations Safely
Shopify idempotency keys are required for some mutations since Admin GraphQL API version 2026-04, sent with the @idempotent directive. The key lets you retry a request after a dropped connection without running the change twice. This guide explains which mutations are affected, how to add the directive, how to build a retry loop that reuses the key, and where we would test before trusting it. Checked against Shopify’s developer documentation in October 2026.

What Shopify Idempotency Keys Do
An idempotency key is a unique string your app generates. If a request is disrupted and you never see a response, you cannot tell whether Shopify processed it. Send the same request again with the same key, and Shopify executes it only once, no matter how many retries follow. Shopify recommends a randomly generated UUID to avoid collisions. The idempotent requests reference has the details.
Which Mutations Need Idempotency Keys
Shopify announced that idempotency became mandatory for inventory adjustment and refund mutations. The reference pages for individual mutations show the schedule. For inventoryMoveQuantities, inventoryShipmentReceive and inventorySetScheduledChanges, the key is optional as of 2026-01 and required as of 2026-04, passed with the @idempotent directive.
Not every mutation uses a directive. Some take the key as an argument. subscriptionBillingAttemptCreate, for example, accepts an idempotencyKey in its input.
Most update and delete mutations are idempotent by nature and need no key. Some are not, because they perform other operations on the side. inventoryTransferSetItems is Shopify’s example, and it requires a key. The reliable check is the mutation’s own page. If it carries an idempotency caution, you need a key.
Add the Directive
Attach @idempotent to the mutation field and pass the key as a variable.
mutation Adjust($input: InventoryAdjustQuantitiesInput!, $idempotencyKey: String!) {
inventoryAdjustQuantities(input: $input) @idempotent(key: $idempotencyKey) {
inventoryAdjustmentGroup {
id
}
userErrors {
field
message
}
}
}Retry Without Duplicating
The key belongs to the operation, not to the attempt. Create it once, then reuse it on every retry of that same operation. If you generate a new key inside the loop, each retry looks like a brand new request.
import { randomUUID } from 'node:crypto';
const ADJUST = `#graphql
mutation Adjust($input: InventoryAdjustQuantitiesInput!, $idempotencyKey: String!) {
inventoryAdjustQuantities(input: $input) @idempotent(key: $idempotencyKey) {
inventoryAdjustmentGroup { id }
userErrors { field message }
}
}`;
export async function adjustWithRetry(admin, input, key = randomUUID()) {
for (let attempt = 1; attempt <= 3; attempt++) {
try {
const res = await admin.graphql(ADJUST, {
variables: { input, idempotencyKey: key }
});
return await res.json();
} catch (err) {
if (attempt === 3) throw err;
await new Promise((r) => setTimeout(r, 500 * attempt));
}
}
}Pass your own key into the function when the operation comes from a job or queue. That way a crash and restart still reuses the same key.
Bulk Operations
You can use @idempotent with bulk mutation operations too. Put the key in the JSONL input file as a variable next to the other mutation arguments. Idempotency applies per row, not to the whole bulk operation. Give every row its own key, derived from something stable such as your source record ID. For the surrounding mechanics, see Shopify GraphQL Pagination: Cursors, Limits and Bulk Exports.
Our Analysis: Making Idempotency Keys Safe in Practice
Derive the key from your own record. A key built from a job ID or outbox row survives restarts. A key held only in memory does not. Store it with the job before the first attempt.
Retry only what can safely repeat. A key protects the mutation, not your surrounding logic. If your code also writes to a database or sends an email, make those steps safe to repeat as well.
Test deduplication on a development store. One developer on the Shopify community forum reported that repeating an inventory adjustment three times with the same key on 2026-04 still produced three adjustments. We cannot say why, and it may be a setup detail. It is still a good reason to confirm the behavior yourself before you rely on it for stock or refunds.
Treat the upgrade as a checklist item. Before moving to 2026-04 or later, search your code for the mutations named in the changelog and add the directive. Shopify’s docs say the key is required there, so do not assume a missing directive will be harmless.
Do not confuse this with webhook deduplication. Idempotency keys protect outgoing mutations. Incoming webhook deliveries need their own handling, covered in Shopify Webhooks: Set Up, Verify, and Debug Them.
FAQ about Shopify Idempotency Keys
What format should the key use? Any unique string works. Shopify recommends a random UUID.
Do I need a key for every mutation? No. Most update and delete mutations are idempotent by nature. Check the mutation’s reference page for an idempotency caution.
Should I create a new key for each retry? No. Reuse the same key for every retry of one operation, and use a new key only for a genuinely new operation.
Related: Receiving changes, not sending them? See Shopify Events vs Webhooks.


