How to fetch products dynamically by tag for app feature

Topic summary

A developer is building a dynamic tag-based product filtering feature for their Shopify app (EasyBoost) and needs to fetch products by tags without creating manual collections.

Current Challenge:

  • Initial Liquid approach (collections.all.products loop with tag filtering) is inefficient and doesn’t scale
  • Loops through all products, risks hitting memory limits

Explored Solution:

  • Testing /search/suggest.json endpoint with tag queries (e.g., ?q=tag:summer&type=product)
  • Works but returns limited data and seems less flexible

Community Recommendations:

  • Storefront API is the preferred solution (mentioned by multiple respondents)
  • Can be unauthenticated for basic product data
  • Allows querying only needed fields for better performance
  • Suggestion to cache results in LocalStorage for efficiency

Additional Suggestions:

  • One user recommended integrating “Ketch” for consent/privacy management when collecting user data
  • Another provided a detailed Liquid code snippet for template-based tag filtering

Status: Discussion remains open with the developer seeking clarification on the “Ketch” reference and evaluating whether Storefront API is the official best practice for production apps.

Summarized with AI on October 24. AI used: claude-sonnet-4-5-20250929.

Hello Shopify community,
hope you all are doing well! :waving_hand:

I’m currently working on our EasyBoost app, and I’m trying to build a dynamic tag-based product filtering feature.

Here’s the goal:

The merchant can select one or more product tags (e.g. “smartphone”, “screen protector”, “charger”, “usb cable” etc.).

On the frontend, I want to dynamically load and display products that match those selected tags.

I don’t want to manually create or manage any Shopify collections for this — the app should handle it automatically.

So far, I experimented with:

{% for product in collections.all.products %}
{% if product.tags contains ‘summer’ %}
{{ product.title }}
{% endif %}
{% endfor %}

But this approach is inefficient — it loops through all products (which can be thousands), just to find a few with the right tag. It’s not scalable and may hit Liquid memory limits.

What I want to achieve

I’m looking for the best possible way to:

Fetch products by specific tag(s)

Do it dynamically from the frontend

Avoid needing to create collections manually

Keep it performant and scalable

This works great, but I’d like to confirm if this is the recommended approach for production apps.

/search/suggest.json endpoint

Example:
fetch("/search/suggest.json?q=tag:summer&type=product")

It’s public and doesn’t need an API token — but returns limited data and seems less flexible.

What’s the recommended and reliable method to dynamically load products by tag from the frontend or app side — without relying on pre-made collections?
Should I go with the Storefront API, or is there a better pattern within Shopify’s ecosystem (for embedded apps or custom storefronts)?

Any official best practices or examples would be super helpful

Thanks in advance!
– Mahfuz, EasyBoost team

Use a Storefront API – this kind of request can be unauthenticated too.
And cache results in LocalStorage for efficiency.

You should be able to control what data you want to see (of course, some data will require authorization – say, you can’t get inventory levels unauthorized, but otherwise…)

1 Like

You should use the Shopify Storefront API to fetch products by tags dynamically. It’s scalable and avoids looping through all products in Liquid. You can query only the needed fields for performance. For easier consent and privacy handling in apps, consider integrating Ketch to manage any user data collection while fetching products.

Ketch ? which one can you please give me any reference ? so that i can check on my code ?

Hello @Mahfz_ShopiDevs

I have this solution and i think this code are solve your problem 


{%- comment %} Set products to all products in the store {%- endcomment %}
{%- assign products = collections.all.products %}

{%- comment %} Debug products and image availability {%- endcomment %}
{%- if products == nil or products.size == 0 %}
  <p>Debug: No products available in the store. Please check Products in Shopify admin.</p>
{%- else %}
  <p>Debug: Found {{ products.size }} products in the store.</p>
{%- endif %}

{%- if template.suffix -%}
  {% assign template_suffix = template.suffix | split: ',' %}
{%- endif -%}

<section class="custom-product-list">
    <div class="page-width">
        <div class="product-list-item">
            <ul class="custom-product-list-items">
            {%- if products.size == 0 -%}
                <li>No products available to display.</li>
            {%- else -%}
                {%- assign product_count = 0 -%}
                {%- for product in products -%}
                    {%- if forloop.index > 2 -%}
                        {%- assign lazy_load = true -%}
                    {%- else -%}
                        {%- assign lazy_load = false -%}
                    {%- endif -%}
                    {%- assign should_display = false -%}
                    {%- if template_suffix %}
                        {%- for tag in product.tags -%}
                            {%- if template_suffix contains tag -%}
                                {%- assign should_display = true -%}
                            {%- endif -%}
                        {%- endfor -%}
                    {%- else -%}
                        {%- assign should_display = true -%}
                    {%- endif -%}
                    {%- if should_display -%}
                        {%- assign product_count = product_count | plus: 1 -%}
                        <li
                            class="grid__item{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--slide-in{% endif %}"
                            data-tag="{{ product.tags | join: ',' }}"
                            {% if settings.animations_reveal_on_scroll %}
                                data-cascade
                                style="--animation-order: {{ forloop.index }};"
                            {% endif %}
                        >
                            {% render 'card-product',
                                card_product: product,
                                media_aspect_ratio: section.settings.image_ratio,
                                image_shape: section.settings.image_shape,
                                show_secondary_image: section.settings.show_secondary_image,
                                show_vendor: section.settings.show_vendor,
                                show_rating: section.settings.show_rating,
                                lazy_load: lazy_load,
                                quick_add: section.settings.quick_add,
                                section_id: section.id
                            %}
                            {%- comment %} Debug image availability {%- endcomment %}
                            {%- if product.featured_image == nil %}
                                <p>Debug: No featured image for product "{{ product.title }}".</p>
                            {%- else %}
                                <p>Debug: Featured image available for "{{ product.title }}": {{ product.featured_image.src | img_url: '300x' }}</p>
                            {%- endif %}
                        </li>
                    {%- endif -%}
                {%- endfor -%}
                {%- if product_count == 0 and template_suffix -%}
                    <li>No products match the tag "{{ template.suffix }}".</li>
                {%- endif -%}
            {%- endif -%}
            </ul>
        </div>
    </div>
</section>

{% schema %}
{
  "name": "Custom Product List",
  "settings": [
    {
      "type": "select",
      "id": "image_ratio",
      "options": [
        {
          "value": "adapt",
          "label": "Adapt to image"
        },
        {
          "value": "portrait",
          "label": "Portrait"
        },
        {
          "value": "square",
          "label": "Square"
        }
      ],
      "default": "adapt",
      "label": "Image ratio"
    },
    {
      "type": "select",
      "id": "image_shape",
      "options": [
        {
          "value": "default",
          "label": "Default"
        },
        {
          "value": "circle",
          "label": "Circle"
        },
        {
          "value": "arch",
          "label": "Arch"
        }
      ],
      "default": "default",
      "label": "Image shape"
    },
    {
      "type": "checkbox",
      "id": "show_secondary_image",
      "default": false,
      "label": "Show secondary image"
    },
    {
      "type": "checkbox",
      "id": "show_vendor",
      "default": false,
      "label": "Show vendor"
    },
    {
      "type": "checkbox",
      "id": "show_rating",
      "default": false,
      "label": "Show product rating"
    },
    {
      "type": "checkbox",
      "id": "quick_add",
      "default": false,
      "label": "Enable quick add"
    }
  ],
  "presets": [
    {
      "name": "Custom Product List"
    }
  ]
}
{% endschema %}

Thank you.