The 1,000-Tag Ceiling: Why Your Collection Filters Silently Drop Tags
Your theme loops collection.all_tags to build the filter sidebar — and Shopify’s own Liquid reference caps that object at 1,000 tags. Tag number 1,001 never reaches your template. No error, no warning, just missing filter options and unfindable products. Here’s the repro and the migration that fixes it.
TL;DR: collection.all_tags returns a maximum of 1,000 tags (documented in Shopify’s Liquid reference), and the cap is consumed by your global tag vocabulary. If your filter UI is tag-based and your catalog is large, it is already dropping options. The fix is Shopify’s recommended filtering method: move filterable attributes to metafields and use storefront filtering (what the Search & Discovery app manages). Budgets to know: 25 storefront filters max per store, and collection.filters returns empty on collections over 5,000 products.
This is part 3 of our Shopify Functions in production series. Part 1 covered discount-code conflicts, part 2 the 10,000-byte metafield trap. This one is about a ceiling you hit with zero functions involved — just tags and a big catalog.
The scene
A merchant posts in the Shopify Community: their collection sidebar filters are “missing” options. The store uses product tags for everything — color, material, occasion, audience — and the theme renders the filter UI from the collection’s tags. Hundreds of products, thousands of distinct tags. Nothing errored. No warning in the admin, no exception in the theme. Some tags just never appear as filter options, and the products behind them become unfindable by filtering.
Three independent community experts converge on the same answer in that thread: stop filtering on tags, move the filterable attributes to metafields, and let storefront filtering do the job. We hit the same wall in production, and this is the write-up we wish we’d had.
Root cause #1: collection.all_tags is hard-capped at 1,000
The classic tag-filter pattern looks like this (per Shopify’s own tag-filtering docs, you loop the collection’s tags and build add/remove links from current_tags):
{% comment %} sections/main-collection.liquid — the classic tag filter UI {% endcomment %}
<ul class="tag-filters">
{% for tag in collection.all_tags %}
<li>
{% if current_tags contains tag %}
{{ tag | link_to_remove_tag: tag }}
{% else %}
{{ tag | link_to_add_tag: tag }}
{% endif %}
</li>
{% endfor %}
</ul>Here is the part nobody reads until it’s too late: the Liquid reference for the collection object states that all_tags returns a maximum of 1,000 tags. Not a soft limit, not a pagination hint — a cap. Tag number 1,001 onward simply never reaches your template. Your loop is fine; your data was truncated before it arrived.
Two properties make this failure nasty:
- It is silent. Nothing in the admin counts your distinct tags against a budget. The theme renders happily with whatever it got.
- It degrades asymmetrically.
all_tagsalso includes tags from products that are filtered out of the current view, so the cap is consumed by your global tag vocabulary, not just the tags of the products currently visible. A big catalog burns through 1,000 distinct tags far faster than intuition suggests — every typo’d tag (greyvsgray), every seasonal tag, every supplier tag counts.
Meanwhile, tag-based URLs have their own sharp edges: multiple tags in a collection URL are combined with AND (products must carry all of them), and if a tag in the URL is used by no product in the store, Shopify redirects to the collection URL with that tag stripped. For “customers browse by attributes” UX, AND-semantics plus redirect-stripping is the opposite of faceted filtering.
Root cause #2: tags have no data model
Tags are strings. There is no type, no validation, no admin UI for “this attribute has these allowed values.” That is fine for labels, and fatal for filter facets, because nothing stops the vocabulary from growing unbounded — which feeds root cause #1.
Metafields are the typed counterpart. A metafield is a key-value pair identified by a namespace + key (e.g. custom.material), with a value and a type. You first create a metafield definition, and definitions establish a data schema: type validation, Shopify admin integration, and query filtering. In other words, a definition is where “material is one of a fixed list of strings” becomes enforceable instead of aspirational.
Minimal reproduction
You can demonstrate the cap in a dev store in an afternoon:
- Create one collection and ~50 products. Against those products, set 1,001+ distinct tags (e.g.
tag-0001…tag-1100, spread across products). - In the theme, render
{{ collection.all_tags.size }}on the collection template. - Observe the output: 1000, regardless of how many distinct tags exist beyond that.
- Render the tag-filter loop above: tags past the cap never appear as filter options, and there is no error anywhere to tell you why.
That size never exceeding 1000 is the entire bug report.
The fix: metafield-backed storefront filtering
Shopify documents two filtering methods for themes, and explicitly marks storefront filtering as the recommended one (tag filtering being the other). The migration looks like this:
1. Design the attribute schema. For each tag-family that acts as a filter facet (color, material, occasion…), create a product metafield definition. Check the type against what storefront filtering actually supports — metafield-based filters can reference only these types: single_line_text_field, list.single_line_text_field, metaobject_reference, list.metaobject_reference, number_integer, number_decimal, boolean. Multi-value facets (a product that is both “wool” and “silk”) map to list.single_line_text_field.
2. Backfill values from tags. Walk the catalog, and for each product copy its tag values into the matching metafields. With the Admin GraphQL API, metafieldsSet sets metafield values (creating them if they don’t exist yet); one call accepts a maximum of 25 metafields with a 10MB total payload cap, and the operation is atomic — either the whole call persists or nothing does. Batch accordingly. (We scripted this in chunks of 25 and logged every non-atomic failure for retry; your backfill should be idempotent so re-runs are safe.)
3. Create the filters in admin. Storefront filters are not theme config — per the docs, filters need to be created in the Shopify admin before they can be applied (this is what the Search & Discovery app manages). One budget to know up front: a store can create a maximum of 25 filters. Consolidate ruthlessly — 25 well-typed facets beat 40 half-used ones, and it forces the “which attributes actually deserve a filter?” conversation you should have had anyway.
4. Verify the URL contract. Applied filters show up in the URL. A metafield facet at product scope uses filter.p.m.<namespace>.<key>=value — e.g. /collections/all?filter.p.m.custom.material=wool — with comma-separated values for multi-select. If your theme (or a headless storefront) builds its own filter links, this is the contract to match.
5. One more ceiling to plan around. Storefront filtering has its own documented limit, and you want to know it before a merchant on a huge catalog asks: collection.filters returns empty for collections containing over 5,000 products. For mega-catalogs, split big collections into smaller filtered collections rather than relying on the filter UI of a 10k-product “All” collection.
Checklist: are you near the ceiling?
- Render
{{ collection.all_tags.size }}on your biggest collection. At or near 1,000? Your tag filter UI is already dropping tags. - Count your global distinct tag vocabulary (exports, not one collection) —
all_tagsincludes tags of products filtered out of the current view, so the cap is global-pressure. - Any facet that should be multi-select OR? Tag URLs combine with AND — that’s a metafield filter job.
- Inventory which attributes customers actually filter by, and map each to a supported metafield type (
single_line_text_field,list.single_line_text_field, etc.). - Budget your filters: 25 max per store. Consolidate before you create.
- Backfill with
metafieldsSetin ≤25-metafield atomic batches; make the script idempotent. - Any single collection over 5,000 products?
collection.filterswill be empty there — split it. - Keep tags for operational labels (pick lists, automation triggers); keep filterable attributes in metafields. Two vocabularies, two jobs.
Where an agent fits
The migration above is a one-time project. The ongoing risk is drift: new tag families creep in, someone adds filter facets directly in tag-land again, a collection quietly grows past 5,000 products and its filters go empty. That’s a monitoring loop, and it’s the kind of thing a store operations agent can own: watch the global tag count against the 1,000 ceiling, watch collection sizes against the 5,000 ceiling, and flag the anomaly to the owner with the evidence attached — before a customer reports a missing filter.
Verified against Shopify’s official documentation (Liquid collection object, theme filtering methods, storefront filtering, metafields, metafieldsSet) as of August 2026. Demand evidence via Shopify Community thread 659430, where three independent experts converged on the metafields + Search & Discovery path.