Shopify duplicate products cleanup
You have the same product listed twice. Maybe you spot it in search results on your own storefront — two rows, same photo, same name — or a customer buys the one with the wrong stock count. Somewhere along the way a second record for one product got created, and now your catalogue is carrying both.
That is a different problem from a product with a lot of variants, and it is worth being sure which you have before you delete anything. One product in five colours is one record with five variants — that is correct, and deleting four of them would be the mistake. A duplicate is two separate product records that describe the same thing: two entries in Products, two /products/... URLs, two inventory numbers. This article is for the second case: finding those, deciding which record to keep, and removing the other without leaving a dead link behind.
Duplicates rarely announce themselves — you notice one by accident and assume it's the only one. So before deciding anything, find all of them.
Key takeaways
- List your likely duplicates first, in one pass. In admin, Products, sort or search so same-named items sit next to each other — two rows with the same title are your first candidates. For the whole catalogue at once,
https://your-store.com/products.json?limit=250returns every product's title and handle with no login; the script in the next section flags every title that appears more than once. The exact steps are below. - A shared title is a candidate, not a verdict. Two records with the same name might be a genuine duplicate, or they might be two legitimately different products that happen to share a name. Open both before you touch either — the point of the list is to know where to look, not what to delete.
- It is common enough to be worth checking. Across 20,604 products on 41 live storefronts, 293 (1.4%) shared an identical title with at least one other product in the same store, and 21 of the 41 stores had at least one such pair.
- Removing a duplicate means redirecting its URL, not just deleting it. Deleting a product removes its page; if that page was ever indexed or linked, its URL now 404s. The safe removal is: pick the record to keep, then add a URL redirect from the deleted one's path to the survivor's (Online Store → Navigation → URL Redirects). That is the "without breaking SEO" part.
Sample: 41 live Shopify stores drawn at random from a public list of storefronts whose owners posted their URLs on the Shopify Community's Store Feedback board asking for critique. Not a cross-section of Shopify. Method is at the end. "Duplicate" here is inferred from the public catalogue — products sharing an identical title within one store — so it flags candidates to check, not confirmed duplicates.
Find your likely duplicates first
Before any deletion, get the candidate list. There is no storefront symptom that flags a duplicate reliably — the two records look like two normal products, each on its own page.
In the admin. Go to Products. Sort the list by product title (or use the search box on a name you suspect), so any two records with the same name sit next to each other. Same title, same photo, and — the tell — often one has stock and one reads zero, because you kept selling from the original and the copy never got its inventory. If you have a few dozen products, scanning the sorted list is the whole job.
For the whole catalogue at once. Shopify exposes every product's title and handle on a public endpoint — no app, no login. Paste this in a terminal, swap in your domain:
# save as dupes.py, then: python3 dupes.py
import json, urllib.request
from collections import Counter, defaultdict
STORE = "your-store.com" # swap in your own domain
titles, page = defaultdict(list), 1
while page <= 40:
url = f"https://{STORE}/products.json?limit=250&page={page}"
prods = json.load(urllib.request.urlopen(url))["products"]
if not prods:
break # stop on an EMPTY page, not a short one
for p in prods:
key = (p["title"] or "").strip().lower()
titles[key].append(p["handle"])
page += 1
dupes = {t: hs for t, hs in titles.items() if len(hs) > 1}
print(f"{len(dupes)} titles appear more than once:")
for t, hs in dupes.items():
print(f" {t!r}")
for h in hs:
print(f" /products/{h}")
/products.json returns 250 products per request, so the script walks the pages and stops on the first empty page (a short page mid-list is normal — stop only when a page comes back with nothing). It groups products by a trimmed, lower-cased title and prints every group where two or more products share one — with each one's /products/handle link, so you can open both side by side. Nothing this script does can change your store; it only reads. Treat the output as a shortlist to inspect: a repeated title is where duplicates hide, but two products can share a name and still be different things.
One number is worth having before you start, so you know whether you're looking at a stray or a pattern. Across the 41 stores we read, 293 of 20,604 products (1.4%) shared a title with at least one sibling, and 21 of the 41 stores carried at least one such pair. Most stores had a couple; the median store with any at all had just two. A store with one repeated title has a slip. A store with dozens has a workflow that keeps producing them — which is worth naming, because it tells you where they came from.
How duplicate products get created in the first place
Knowing the cause helps you tell a real duplicate from a false match, and helps you stop making more.
- The "Duplicate product" action, left as a draft that got published. Shopify's product page has a Duplicate action that creates a new draft product — a copy you're meant to edit into a different product, or to stage a change safely. Its handle gets
-copyappended. If that draft gets published without being edited or deleted, you now have two near-identical live products. In the catalogues we read, this left a visible fingerprint: see the note at the end of this section. - CSV or app imports run more than once, or without a matching key. Re-importing a product feed can create fresh records instead of updating existing ones when the import isn't keyed on handle or SKU. A dropshipping or supplier-sync app that pulls the same catalogue twice does the same thing.
- Migration from another platform. Moving a catalogue into Shopify, especially in more than one pass, is a classic source — the same product comes in twice under slightly different handles.
- Two people, or one person twice. A product added manually that was already imported, or added on two different days because nobody checked first.
A modest, illustrative signal for the first cause: of the 41 stores, 5 had products whose handle ends in -copy while the original handle still exists as another live product — Shopify's Duplicate action appends exactly that suffix, so it's a plausible marker of a copy that never got cleaned up. That accounted for 49 products in this sample. Read it as a hint of where the "Duplicate" button left something behind, not a precise duplicate count — a handful of stores also use -copy deliberately, and plenty of real duplicates carry no such marker at all.
Deciding which record to keep
Once you've confirmed a pair is a genuine duplicate — same product, two records — the cleanup is a decision, not an edit. Keep exactly one, and the choice matters because the survivor inherits everything.
Prefer the record that has more going for it, in roughly this order: the one that is already getting traffic or has orders against it (deleting the one people find and buy from is the expensive mistake); the one whose URL is linked or indexed (its handle is the one search engines and your own menus already know); and the one with the complete, correct data — description, images, current inventory, collections, tags. Often these point at the same record. When they don't — say the better-written copy is the one nobody links to — the safe move is usually to keep the record with the established URL and copy the better content into it, rather than switch the URL you'd have to redirect anyway.
Before you delete the loser, carry over anything only it has: inventory it was still holding, images, its place in any collection, and — if it's the one people were reaching — note its URL, because that's what you'll redirect next.
Removing a duplicate without breaking its URL
This is the step people skip, and it's the one the phrase "without breaking SEO" is about.
Deleting a product in Shopify removes its page. If that product's URL was ever linked from anywhere — a search engine's index, another site, an old email, your own navigation — that link now lands on a 404. You've cleaned up the catalogue and quietly created a dead end.
The safe removal is two moves, in order:
- Add a URL redirect from the deleted product's path to the survivor's, before or right after deleting it. In admin: Online Store → Navigation → URL Redirects → Create URL redirect. Redirect from
/products/the-duplicate-handleto/products/the-one-you-kept. Shopify sets this up as a permanent (301) redirect, which is what passes the old page's accumulated value to the survivor and sends anyone on the old link to the right place. - Then delete the duplicate. With the redirect in place, the old URL no longer 404s — it lands on the record you kept.
Do this per duplicate. If several duplicates all resolve to one survivor, each of their URLs gets its own redirect to that survivor. Check your theme and navigation too: if a menu or a "featured product" block pointed at the deleted record, update it to the survivor so you're not relying on the redirect to paper over an internal link you control.
One thing to be clear about: there is no "merge duplicates" button in Shopify that fuses two products into one and moves the orders and reviews across. Merging is manual — you pick a survivor, move what's worth keeping into it, redirect the other's URL, and delete. If an app in your admin offers a merge, check exactly what it does before trusting it with order history.
Why duplicate products are worth clearing, not ignoring
A duplicate isn't just untidy — it splits things that are supposed to be whole:
- Inventory splits. Two records means two stock counts for one physical product. You oversell (both show stock, you only have one lot) or you look sold out (the copy customers land on reads zero while the original still has stock).
- Search signals split. Two URLs for the same product means a search engine sees two competing pages and has to guess which to rank — often it ranks neither well, or picks the weaker one. Consolidating to a single canonical URL is the point of the redirect.
- The shopping experience splits. A customer who finds both doesn't know which is "real," and reviews, Q&A, and any social proof get spread across two pages instead of stacking on one.
So the goal per duplicate is specific: confirm it's really a duplicate, keep the stronger record, redirect the other's URL, delete it. Not "tidy the catalogue" — four concrete moves, in that order.
When it stops being a five-minute job
With three duplicates you've confirmed, work through the steps above by hand and you're done in ten minutes: pick a survivor, move the good data over, add a redirect, delete.
The point where that stops being true arrives with scale and with churn. The list of candidates is cheap to generate, but every candidate needs a human judgement — is this actually a duplicate, or two products that share a name? — and every confirmed one needs a survivor chosen and a redirect written by hand. On a catalogue of a few thousand products with dozens of shared-title groups, that's an afternoon of opening pairs, comparing them, and clicking through URL Redirects one at a time. And a store that produced duplicates once — an import that double-ran, a migration, a Duplicate action habit — usually keeps producing them, so it's not a one-time pass.
That deciding is what Arvio does before anything changes. It reads your live store, groups the products that look like duplicates, and for each group works out which record is the stronger one to keep — by traffic, links, and completeness — then drafts the plan: what to keep, what to move across, which URL to redirect where, and what to delete. Nothing is applied until you approve it. It doesn't merge in the background or delete on a hunch; it reads, decides, drafts the fix, and holds for your sign-off — because with product records, the wrong delete is the one you can't take back.
Disclosure: Arvio is our own product, and it is paid — plans start at $9.90 per 30 days with a 5-day trial. If you have three duplicates and a spare afternoon, the steps above need nothing from us. Arvio is for the store where duplicates arrive faster than you can work through them, and the job isn't deleting any one record but going through all of them and deciding, safely, which to keep.
FAQ
How do I find duplicate products in Shopify?
In the admin, open Products and sort by title (or search a name you suspect) so same-named records sit next to each other — same title and photo, often one with stock and one without. To scan the whole catalogue at once, https://your-store.com/products.json?limit=250 returns every product's title and handle with no login; the short script in the first section groups them and prints every title that appears more than once. Treat the output as candidates — open both records before deciding, since two products can legitimately share a name.
Does Shopify have a "merge duplicate products" feature?
No. Shopify has a Duplicate action that creates a copy of a product (as a new draft), but there is no built-in action that merges two existing products into one and carries orders or reviews across. Merging is manual: pick which record to keep, move over any data only the other has, add a URL redirect from the duplicate's path to the survivor, then delete the duplicate. If an app offers a merge, verify exactly what it does with order history before relying on it.
If I delete a duplicate product, will its links break?
They will, unless you add a redirect. Deleting a product removes its page, so any link to its URL — from a search index, another site, or your own menu — will 404. Before or right after deleting, create a URL redirect (Online Store → Navigation → URL Redirects) from the deleted product's /products/... path to the one you kept. Shopify makes it a permanent (301) redirect, which sends visitors and the page's search value to the survivor.
Which of two duplicate products should I keep?
Keep the record that has the most invested in it: prefer the one already getting traffic or holding orders, then the one whose URL is linked or indexed, then the one with the more complete and correct data. These often point at the same record. When they don't, it's usually safest to keep the record with the established URL and copy the better content into it, since that URL is the one you'd otherwise have to redirect anyway. Move any images, inventory, and collection memberships off the loser before you delete it.
Are duplicate products bad for SEO?
They can be. Two URLs for the same product means a search engine sees two competing pages and has to choose which to rank — often ranking neither well. Consolidating to one URL, with a 301 redirect from the removed duplicate to the survivor, points all the signals at a single page. It also fixes the shopper-side problem of reviews and traffic being split across two pages instead of stacking on one.
Is a product with many variants the same as a duplicate?
No, and it's the mistake to avoid. One product sold in several sizes or colours should be a single product record with multiple variants — that's correct, and deleting variants or splitting them into separate products would create the very problem you're trying to fix. A duplicate is two separate product records (two entries in Products, two /products/... URLs) describing the same item. Open both suspects: if they're one record with variants, leave it; if they're two records, that's the duplicate.
Method
Sample. 41 live Shopify storefronts, catalogues read 2026-08-26. Every figure comes from /products.json, an endpoint any browser can fetch — nothing authenticated, and nothing installed on those stores. A "duplicate candidate" is inferred, not confirmed: it is a product whose title, trimmed and lower-cased, is identical to at least one other product's title in the same store. That flags where duplicates are likely; it cannot on its own distinguish a true duplicate from two genuinely different products that share a name.
How the frame was built, and how the 41 came out of it. Owners had posted their own store URL on the Shopify Community's Store Feedback board asking for feedback: a public list of candidate hostnames, from which those still serving a catalogue were verified. From the verified set we drew a sample at random — a fixed seed, so anyone can redraw the same stores — excluding stores that hit our read ceiling and those holding fewer than 10 products, leaving 41.
The largest bias is survivorship, and it is worth stating: more than half the stores on that public list are gone — no longer resolving, returning 404, frozen for non-payment, password-protected, or no longer on Shopify. Stores that post asking for feedback also skew newer and smaller — the median store here holds around 115 products. So read every share here as a prompt to check your own store, not as a platform-wide rate.
What we counted. 20,604 products across the 41 stores. Grouping by trimmed, lower-cased title, 293 products (1.4%) shared a title with at least one sibling in the same store, and 21 of the 41 stores had at least one such pair; the worst single store had 58 such products, an outlier well above the median (stores with any at all typically had two), so we anchor to the percentage, not the maximum. Separately, as an illustrative marker of the "Duplicate" action left uncleaned, we counted products whose handle ends in -copy while the base handle still exists as another live product in the same store: 49 products across 5 stores — a hint, not a duplicate count, since -copy is sometimes used deliberately and most real duplicates carry no such marker.
Written by Adot Technologies Inc, the team behind Arvio: AI Store Operator — it reads your live store, groups the products that look like duplicates, works out which record to keep, and drafts the redirect-and-remove plan for your approval.
