SEO · TECHNICAL · CONTENT · 25 MARCH 2025 · 8 MIN READ
Faceted navigation without wrecking your crawl budget
Filters multiply into millions of near-identical URLs. The fix is not a plugin — it is deciding which handful of combinations deserve to be pages.
Sort every filter combination into three buckets and treat each differently. A small number have real search demand — "black walking boots" — and should be proper collections with their own copy, their own h1 and their own internal links. Most should be reachable by shoppers but kept out of the index. A few, chiefly sort orders and price bands, should not be crawled at all. Shopify gives you three levers for this: which filters exist at all, what your theme puts in the canonical tag, and robots.txt.liquid. Nothing here is automatic, and no app decides it for you, because the answer depends on what people actually search for in your category.
IN SHORT
- Filters are combinatorial, so the URL count grows multiplicatively: five filters with four values each, multi-select, is over a million reachable combinations from one collection.
- Shopify filter URLs follow the documented pattern `filter.<scope>.<attribute>=value`, where scope is `p` for product-level and `v` for variant-level.
- A merchant can create up to 25 filters, and a filter must exist in the admin before its URL parameter does anything.
- Three buckets: indexable collections for facets with search demand, crawlable-but-not-indexed for the rest, and blocked from crawling for sort orders and price bands.
- `noindex` does not save crawl budget — a crawler has to fetch the page to read it. Blocking in `robots.txt` saves the crawl but hides the canonical, so the two levers do different jobs.
- Most stores under a few thousand products should build ten good facet collections and stop worrying about crawl budget entirely.
Where the URLs come from, and how many there are
Shopify’s storefront filtering produces URLs in a documented shape: the namespace filter, a scope of either p for product-level or v for variant-level, the attribute, and the value. So filter.p.product_type=shoes&filter.v.option.color=red filters by product type and variant option together. Multiple values within one filter can be a comma-separated list or a repeated parameter — filter.v.option.color=red,blue and filter.v.option.color=red&filter.v.option.color=blue are both valid. Different filters combine with AND; values inside one filter combine with OR.
Now count. Take a collection with five filters — type, colour, size, brand, availability — each offering four values. If a shopper may pick at most one value per filter, every filter has five states (unset, or one of four), which is 5⁵ = 3,125 distinct URLs. Multi-select changes the arithmetic badly: each filter now has 2⁴ = 16 possible value subsets including empty, so five filters give 16⁵ = 1,048,576 reachable URLs. Add a sort parameter with six options and you have six million. From one collection.
That is the whole problem in one paragraph. Nobody chose to publish a million pages; the pages are an emergent property of a feature everybody wants. And most of them are near-duplicates of each other — the same twelve products in a different order, or a subset with no copy of its own.
It is worth being honest about scale, though. A store with 300 products and eight collections does not have a crawl budget problem, it has a tidiness problem, and a crawler will work through it regardless. Crawl budget starts to bite when the ratio of low-value URLs to real pages gets large enough that discovery of new products slows down. If you are not seeing that in Search Console, the rest of this post is a tuning exercise rather than an emergency.
The three buckets
Every filter combination belongs in exactly one of three buckets. Doing this properly is an afternoon with your Search Console query report and your category knowledge, and it is the only part of the job that cannot be delegated to a tool.
- Indexable: combinations with genuine search demand behind them. "Waterproof walking boots", "black leather sofa", "size 12 running shoes" — a category plus one qualifier. These earn a real collection with its own h1, its own opening paragraph and its own place in your internal linking.
- Crawlable, not indexed: everything a shopper might reasonably build that nobody searches for. Two or three stacked filters, brand plus size plus colour. Reachable, functional, and not competing with anything in the index.
- Not crawled at all: sort orders, pagination past a sensible depth, price sliders, and the parameters your theme or apps append for their own purposes. There is no version of a "sorted by price descending" URL that anybody wants to land on.
Why a facet with demand should be a collection, not a filtered URL
The temptation is to leave the demand facets as filtered URLs and simply allow them to be indexed. It is less work and it does not hold up.
A filtered URL has the parent collection’s h1, the parent’s copy, and the parent’s title tag. It ranks, if at all, as a slightly worse version of its parent for a phrase the parent is not targeting. It has no internal links pointing at it except the filter control itself. And it cannot carry the thing that actually wins the query — two or three sentences answering what this selection is and who it suits, which is the paragraph an answer engine lifts.
A real collection can carry all of that. It can also be merchandised: a hero, a short buying guide, links to the sibling facets, and the FAQs your sales team gets asked. That is a page, and it is the difference between appearing in a category search and owning it.
The reason most teams do not do this is production cost — each facet collection needs copy, an image and a layout, and ten of them is a project. That is a tooling problem rather than a strategy problem, and it is what a section library is for: composable blocks a merchandiser arranges without a developer, so the eleventh facet page costs an hour rather than a sprint.
One caveat from the filtering documentation worth knowing if you build these pages: when variant-level filters are applied, a product object’s featured_media and url update to reflect the first matching variant. That is usually the behaviour you want — filter to red and you see the red one — but it means the image on a filtered grid is not necessarily the image on the unfiltered one, which occasionally surprises people reviewing screenshots.
The levers Shopify actually gives you
There are three, and they do different jobs. Confusing them is the usual reason a faceted-navigation fix does not work.
Which filters exist. Filters are configured in the admin, and the documentation is explicit that a filter must be created there before its URL parameter applies. The documented ceiling is 25 filters. This is the bluntest and most underused lever: a filter you delete is a dimension removed from the multiplication. If a facet gets almost no use, it is costing you crawl surface for nothing, and usage is measurable.
The canonical tag. Your theme decides what goes in it, via the canonical_url object or your own logic. The Liquid documentation describes canonical_url as "the canonical URL for the current page" and does not spell out how it treats query parameters, so do not take anyone’s word for what your store emits — open a filtered collection URL, view source, and read the tag. Whatever you find, that is your baseline. Filtered URLs in the crawlable-not-indexed bucket should canonicalise to the unfiltered collection; the ones you have promoted to real collections should canonicalise to themselves.
`robots.txt.liquid`. Shopify generates a robots.txt that the documentation says "works for most stores", which is why the template is not in your theme by default. Add it and you can append rules — Disallow patterns for sort and price parameters are the standard use. Shopify’s guidance is to keep using the provided robots Liquid object rather than replacing the output with static text, so that platform updates to the default rules keep applying; and the template has to be robots.txt.liquid, not a JSON template. Take that advice. A hand-written robots.txt is a snapshot of best practice on the day somebody wrote it.
Two things people get backwards
`noindex` does not save crawl budget. A crawler has to fetch a page to discover the directive telling it not to index the page. noindex keeps URLs out of the index, which is about quality of results; robots.txt keeps them from being fetched, which is about crawl. If your problem is that new products take too long to appear, noindex on a million filter URLs will not fix it.
Blocking in `robots.txt` hides your canonical, and your `noindex`. The two levers work against each other, which is why they belong in different buckets. A URL you block cannot tell a crawler where the canonical version lives — so if a blocked URL has already accumulated links, blocking it strands whatever signals it was passing. Use robots.txt for parameters that were never meant to be pages and have no links pointing at them. Use canonicals and noindex for the long tail of filter URLs that shoppers generate and occasionally share.
The other thing worth saying: neither lever is a ranking intervention. Getting faceted navigation right stops you diluting and slowing things down. It does not, on its own, earn a position. The facet collections earn positions, and they do it with copy.
What we would do, in order
Two days of work, and the sequence matters — the audit tells you which facets to promote, so doing it first saves writing copy for pages nobody searches for.
- Pull the filtered URLs Google has actually indexed, and the query report for the categories that matter. Look for facet phrases with demand.
- Audit the filters you offer against how much they are used. Delete the dead ones; each removal shrinks the combination space multiplicatively.
- Pick five to ten facets with demand and build them as real collections, with copy and internal links from the parent.
- Check what your theme emits in the canonical tag on a filtered URL, and fix it if filtered URLs are canonicalising to themselves.
- Add
robots.txt.liquidand disallow sort and price parameters — using therobotsobject, not a hand-rolled file. - Come back in eight weeks and read the coverage report, not the rankings. The signal you are looking for is faster discovery of new products, not a step change in traffic.
Questions this raises
How do you handle faceted navigation for SEO?
Sort combinations into three buckets. Facets with real search demand become proper collections with their own h1 and copy. The long tail of shopper-generated combinations stays crawlable but canonicalises to the unfiltered collection and is kept out of the index. Sort orders, price sliders and app parameters are disallowed in `robots.txt` so they are never fetched. The bucketing is category-specific and cannot be automated.
What do Shopify filter URLs look like?
They follow the pattern `filter.<scope>.<attribute>=value`, where the scope is `p` for product-level filters such as product type, vendor or tags, and `v` for variant-level filters such as availability, price or a variant option. So `filter.p.product_type=shoes&filter.v.option.color=red` combines both. Values within a single filter combine with `OR`, and different filters combine with `AND`.
Does noindex reduce crawl budget?
No. A crawler must fetch a URL to read the directive that tells it not to index that URL, so `noindex` controls what appears in results, not what gets crawled. Only blocking in `robots.txt` prevents the fetch — and that also prevents the crawler seeing your canonical tag, which is why the two levers suit different buckets of URLs.
How many filters can a Shopify store have?
The filtering documentation puts the maximum at 25. Well before that ceiling, the thing to watch is that filters multiply rather than add: each additional filter multiplies the number of reachable URLs by its number of value subsets, so an unused filter is a large amount of crawl surface bought for nothing.
Should filtered collection URLs be indexed?
A handful should — the ones matching phrases people search, and those are better built as real collections with their own copy rather than left as filtered URLs of a parent. The rest should not, because they are near-duplicates of their parent with no unique content, and indexing them puts your own pages in competition with each other.
Is crawl budget a real problem for a small store?
Usually not. Below a few thousand products, a crawler gets through the site regardless and the filter URLs are untidy rather than harmful. The symptom that makes it real is new products taking a long time to be discovered, visible in Search Console. Fix the facet collections for the traffic; fix the crawl surface when the coverage report says to.
NEXT STEP
Free store audit
A senior Shopify engineer reviews your storefront, theme performance and checkout, then sends a prioritised list of fixes.
