TL;DR
- Shopify serves every product at two URLs; most themes link to the collection-scoped version via the
withinfilter, which contradicts the canonical tag.- Google treats internal links as a canonicalization signal. When hundreds of links point to
/collections/name/products/handleand the tag declares/products/handle, the tag loses.- The fix is replacing
product.url | within: collectionwithproduct.urlin product card snippets, then aligning the sitemap and Merchant Center feed to the same URL.
When you audit a client's Shopify store in Search Console, you may find a pile of product URLs under "Duplicate, Google chose different canonical than user." The Google-selected canonical is /collections/name/products/handle, not the clean /products/handle the canonical tag declares.
This happens because Shopify's within Liquid filter (used in most theme product cards) generates collection-scoped URLs. Every collection page and every related-products block links to the scoped version, casting hundreds of internal-link votes against the canonical declaration. Google weighs internal links as a canonicalization signal, so the declaration loses.
This article covers why Shopify produces two URLs per product, how to confirm the issue in Search Console, the Liquid change that fixes it, and the sitemap/feed alignment needed to close the loop.
What does "Duplicate, Google chose different canonical than user" actually mean?
Two statuses describe similar situations and are worth keeping distinct. Google's page indexing documentation defines "Duplicate, Google chose different canonical than user" as a page "marked as canonical for a set of pages, but Google thinks another URL makes a better canonical" and has indexed that other URL instead. "Duplicate without user-selected canonical" is the variant where no canonical was declared, so Google picked freely.
A third status, "Alternate page with proper canonical tag," is the healthy outcome: the collection-scoped copy defers to the clean product URL and Google agrees. If your client's collection URLs sit there, nothing here needs fixing.
How do you confirm the issue on a client store?
- Open Search Console → Indexing → Pages, find "Duplicate, Google chose different canonical than user," and read the sample URLs. If they are
/products/..., the clean URLs are the ones losing. - Run one of those URLs through URL Inspection and compare User-declared canonical with Google-selected canonical. Google states plainly there is no guarantee it will choose yours.
- Search
site:client-store.com inurl:/collections/ inurl:/products/in Google. Every result is a collection-scoped product URL Google has kept in its index. - Open a collection page, click through to a product, and read the address bar. If it shows
/collections/.../products/..., that is the URL the theme is linking to. View source and check the<link rel="canonical">href: it should resolve to/products/handlewith no query string.
Why does Google override the canonical tag?
Internal links are a canonicalization signal. Google's canonicalization documentation calls rel="canonical" "a strong signal," a sitemap entry only "a weak signal," and then says to "link to the canonical URL rather than a duplicate URL." A store that declares one URL and links to another from every collection page has cast a vote against its own declaration at scale.
The within filter. Shopify's Liquid reference for within shows it turning /products/handle into /collections/name/products/handle, and warns that "a standard product page and a product page in the context of a collection have the same content on separate URLs." Most themes call within: collection in the product card snippet, so every collection page and every related-products block links to the scoped version.
A canonical carrying a query parameter. If the tag resolves to /products/handle?variant=123, each variant is declaring itself the canonical version of the product, which is the opposite of consolidating them.
Two canonical tags. Apps with theme access can inject their own <link rel="canonical"> into the <head>. Two contradictory declarations give Google permission to ignore both and choose freely.
How do you fix the canonical mismatch?
The goal is to stop the site contradicting itself. All four signals need to point to the same URL.
1. Internal links (highest impact)
In the theme's product card snippet and related-products snippets, replace:
{{ product.url | within: collection }}
with:
{{ product.url }}
This is the change that moves the needle, because it removes the volume of votes against the canonical declaration.
2. The canonical tag
One tag per page, resolving to /products/handle, with no query string. Check for apps that may be injecting a second tag by viewing page source and searching for rel="canonical" (there should be exactly one).
3. The sitemap
Open /sitemap_products_1.xml and confirm the URL form listed there matches what the canonical declares.
4. The Merchant Center feed
Google's specification for the link attribute requires the product's landing page on the verified domain. Submitting the collection-scoped URL while canonicalizing to the clean one adds another contradiction for Google to resolve. Align the feed's link value with the canonical.
After making these changes, run the clean URL through Search Console's URL Inspection tool and request indexing. Canonical selection is recalculated on Google's own schedule; expect the status to change over days.
Why does this issue keep coming back after a theme update?
The Liquid edit is small; keeping it is harder. A theme update or a new theme can reintroduce within: collection without any explicit decision. Any app with theme access can add a second canonical tag to the <head>. Every new collection creates a fresh set of duplicate URLs for products that were previously clean.
The same pattern shows up in related issues: a product can look healthy in the Shopify admin while the disagreement lives between the theme output, the feed, and Google's index. Something has to read the live pages and compare what they actually emit.
Have you run into cases where a theme update silently reintroduced the within filter after a fix? How do you catch it in your deployment workflow?
The full version of this article, with screenshots and ongoing updates, lives on the StoreCanary blog.
Top comments (0)