Custom page for dynamic products

Topic summary

A developer is building a motorcycle parts store with dynamic product filtering based on motorcycle ID. They’re fetching compatible products from a custom backend and attempting to display them using Shopify’s existing product card snippet.

Initial Problem:

  • Tried using Section Rendering API but received 404 errors
  • Attempted to pass product IDs as parameters to a custom section

Solution Provided:

  • Section Rendering API doesn’t accept custom parameters
  • Instead, create an alternate product template (product.card.liquid) with {% layout none %} tag
  • Fetch products using /products/{handle}?view=card endpoint
  • The layout tag prevents rendering headers, footers, and other sections

Resolution:

  • Issue was a filename typo: used product-card.liquid instead of product.card.liquid
  • The .card suffix (not hyphen) is required for Shopify to recognize the alternate template and respond to the ?view=card parameter
  • Solution is now working; developer plans to share the live URL when deployed
Summarized with AI on November 9. AI used: claude-sonnet-4-5-20250929.

Hello,

I am working on a motorcycle parts store.

I created a custom page page.motorcycle.liquid that takes the motorId from the URL and sends a request to my own backend in order to find the products that are compatible with that motorId. Then I use the fetch API to get the shopify products based on the handles of the products and I want to render them using the already existent card-product.liquid snippet.


I tried to display the products using the Section Rendering API, but i failed:

function displayProducts(productHandles) {
    const productPromises = productHandles.map(productHandle => {
        return fetch(`/products/${productHandle}.json`)
            .then(response => response.json())
            .then(data => data.product)
    });      
    
    Promise.all(productPromises)
    .then(products => {
        products.map(product=>{
            const sectionUrl = `/sections/custom-product-section?product_id=${product.id}`;
            const response = fetch(sectionUrl);
            if (!response.ok) {
                throw new Error('Failed to fetch section');
            }
            const html = response.text();
            document.getElementById('motor-parts').innerHTML += html; 
        })
    })
    .catch(error => {
        console.error(error);
    });
}

The custom section I created is custom-product-section.liquid:

{% assign product_id = section.settings.product_id %}

    {% assign product = all_products[product_id] %}
    {% if product %}
        {% render 'card-product', card_product: product %}
    {% endif %}

{% schema %}
    {
        "name": "Custom Product",
        "settings": [
            {
                "type": "text",
                "id": "product_id",
                "label": "Product ID",
                "default": ""
            }
        ]
    }
    {% endschema %}

The error I get is 404 when I try to fetch the section html.
I would appreciate some help regarding what I am doing wrong about that section. Or if I should approach rendering the products in another way?

You’re using section rendering API in a wrong way.

First, you can’t pass any data/parameters into section.

Second, the url should be an url of a page on your site and section id comes as parameter. Like https://store.name/?sections=main_product_page.

If you’re getting a list of handles from your API there is no need to fetch product data since liquid does not really use product id.

What you can do is to create an alternate product page template with {% layout none %} which will actually render the product card HTML:

templates/product.card.liquid

{% layout none %}
- {% render 'card-product', card_product: product %}

and then have something like this in your JS

function displayProducts(productHandles) {
    const productPromises = productHandles.map(productHandle => {
        return fetch(`/products/${productHandle}?view=card`)
            .then(response => response.text())
    });

    Promise.all(productPromises)
    .then( productItems => productsItems.join(' ') )
    .then( productGridInnerHTML => {
        document.getElementById('motor-parts').innerHTML = ` ${ productGridInnerHTML } 
`
    });
}
1 Like

Thank you so much, Tim.

Seems to be working. Can you also help me how to figure how to get the ?view=card parameter to work?
Now it just returns the whole product page with header, footer and other sections. The ?view=card does not seem to make a difference. I tried to edit the product.json file from templates, but no luck. I want the /product/handle URL to work normally, but when the parameter ?view=card is added it should render a minimal product template (card-product.liquid).

You’re getting the whole product page because your alternate template is not recognised.
In this case Shopify falls back to the default one.

I can’t really say what’s wrong in your setup, but to make ?view=card work you need to have an alternate product page template with suffix card, as I’ve suggested above. Here it’s just much simpler to have it as a .liquid template rather then create a json template, then create a section, etc…
And the layout tag should take care of headers, footers and other stuff we do not need here.

1 Like

Thank you again for the response.
I had a typo in the template file name: I wrote product-card.liquid instead of product.card.liquid. I did not realize it actually parses the file name in order to get the card suffix.

1 Like

Cool! Message with the URL when it’s live – would be interesting to have a look.

1 Like