How can I enforce unique SKU validation across all products/variants in Shopify?

Hi Shopify Developers,

We have a requirement to enforce SKU uniqueness across the entire Shopify store.

Currently, Shopify allows users to create or update products/variants with the same SKU. For example, SKU 54052 can be assigned to multiple products.

Our requirement is:

  • The SKU must be unique across all products and variants in the store.

  • When creating a new product/variant, Shopify should check whether the SKU already exists.

  • When editing an existing product/variant, it should also check for duplicate SKUs.

  • If the SKU already exists on another product/variant, the user should not be allowed to save the product.

  • An appropriate validation message should be displayed, for example:

    “This SKU already exists. Please enter a unique SKU.”

  • The validation must work across the entire store, not just within a product or collection.

Technical Question

We are considering building a custom Shopify app/Admin UI extension to implement this validation.

Could you please advise:

  1. Is there an official Shopify-supported way to block product/variant saving when a duplicate SKU is detected?
  2. Can an Admin UI extension intercept the product save action and prevent the save?
  3. Is there any Shopify Function, validation API, webhook, or Admin API mechanism that can enforce this at the time of product/variant creation or update?
  4. If Admin UI extension is not able to block the native Shopify save operation, what is the recommended architecture for implementing this requirement?
  5. Does the solution support Shopify Grow plan?
  6. Are there any Shopify platform limitations we should be aware of?

The main requirement is that duplicate SKUs must not be saved, rather than simply displaying a warning after the product has been saved.

Any guidance or recommended implementation approach would be greatly appreciated.

Thanks!

Hey @Josh_DSouza :waving_hand:

Just confirming what you’ve likely suspected: there isn’t an official way to block a native product/variant save based on a duplicate SKU. We do allow duplicate SKUs by design (some bundle and multi-location workflows rely on it), and the admin only surfaces a warning in the Inventory section rather than rejecting the save. Going through your questions:

  1. Admin UI extensions can’t intercept or cancel the native save. Blocks and actions run in a sandbox, and the save bar is controlled by the admin. A block on admin.product-details.block.render can show a duplicate warning in-context, but it can’t stop the merchant from saving.
  2. No Function or validation API runs at product write time. Functions cover cart/checkout validation, discounts, delivery/payment, etc. - nothing hooks product or variant mutations.
  3. Webhooks (products/create, products/update) fire after the save commits, so they can detect and remediate but not prevent.

Here’s what I’d recommend as a possible workaround though:

  • Hard block where you control the write path. If product creation/editing goes through your own app UI (or an admin action modal), have your backend check for an existing SKU before calling productSet / productVariantsBulkCreate, and reject with your “This SKU already exists” message. Two public options for the lookup: productVariants(first: 10, query: "sku:54052") (note sku: is a search filter with wildcard support, so re-verify exact matches on the returned nodes), or for an existing variant, productVariant { inventoryItem { duplicateSkuCount } } - that’s the same field the admin uses for its own warning. This is the the best current way to get “true” prevention.
  • Reactive guard for everything else (native product form, CSV import, other apps). Subscribe to the products/create + products/update webhooks, run the same lookup, and on a duplicate do something like clear the SKU, tag the product duplicate-sku, or set it to draft.
  • In-context visibility: a product-details admin block that shows a banner when the current SKU exists elsewhere, so merchants see it before they leave the page.
  • No-code option: Shopify Flow ships a template called “Send email notification when variant is added with duplicate SKU” that you can extend with an Update variant / Add tag action.

On plan support: all of the above should work on the Grow plan and higher - custom apps, webhooks, admin extensions, and Flow are available there. Limitations to be aware of: webhook delivery is asynchronous (typically seconds, so there’s a brief window where the duplicate exists), products/update fires for many unrelated changes so filter early, and bulk imports can create many duplicates at once - so I’d just make sure the remediation path is idempotent and rate-limit aware.

Hope this helps! Let me know if I can clarify anything on our end here.

Hi @Alan_G ,

In-context visibility: a product-details admin block that shows a banner when the current SKU exists elsewhere, so merchants see it before they leave the page.

Please explain this which banner to see in shopify admin product add page

Thank you for your updated

Hey @Josh_DSouza, no worries!

Good question!

The banner wouldn’t be a native Shopify one - the way I’d recommend implementing this would be card your app renders via an admin block extension on the admin.product-details.block.render target. Merchants add and pin it to their product page once, and after that it shows up as a card alongside the other product sections.

Two limitations that matter for your use case:

  • It only renders on existing (saved) products. The block is keyed off the product ID on the page, so on the Add product page there’s nothing for it to attach to - it won’t appear there at all.
  • It can’t see unsaved form values. Blocks run in a sandbox and only have access to committed data through the Admin API, so the banner reflects the SKUs as of the last save, not what the merchant is currently typing.

So in practice it’s a post-save heads-up for the edit flow - a merchant saves a product with a duplicate SKU, and the card flags it right there so they can fix it before moving on. For the create flow, the hard block through your own UI (an admin action modal or a page in your app) is still the way to actually prevent the save.

Rough sketch of what the block looks like:

import '@shopify/ui-extensions/preact';
import { render } from 'preact';
import { useEffect, useState } from 'preact/hooks';

export default async () => {
  render(<Extension />, document.body);
};

function Extension() {
  const [dupes, setDupes] = useState([]);

  useEffect(() => {
    const productId = shopify.data.selected[0].id;
    fetch('shopify:admin/api/graphql.json', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        query: `query ProductSkuDuplicates($id: ID!) {
          product(id: $id) {
            variants(first: 100) {
              nodes { title sku inventoryItem { duplicateSkuCount } }
            }
          }
        }`,
        variables: { id: productId },
      }),
    })
      .then((res) => res.json())
      .then(({ data }) => {
        const nodes = data?.product?.variants?.nodes ?? [];
        setDupes(nodes.filter((v) => v.inventoryItem?.duplicateSkuCount > 0));
      });
  }, []);

  return (
    <s-admin-block heading="SKU check">
      {dupes.length > 0 ? (
        <s-banner tone="warning" heading="Duplicate SKU">
          <s-paragraph>
            {dupes.map((v) => `${v.sku} (${v.title})`).join(', ')} already exists on another product.
          </s-paragraph>
        </s-banner>
      ) : (
        <s-paragraph>All SKUs on this product are unique.</s-paragraph>
      )}
    </s-admin-block>
  );
}

You’ll need read_products and read_inventory scopes for duplicateSkuCount. Scaffold with shopify app generate extension --template admin_block and set the target in the extension TOML. The block would also appear after the native Shopify blocks, which I realize isn’t ideal, but it would look something like this:

Hope this helps!