Ajax filtering
Technical documentation for Shoporama's /ajax endpoint for filtering products. For developers and theme designers.
All Shoporama stores have a built-in /ajax endpoint that returns products in JSON format. This makes it possible to implement dynamic filtering, lazy loading, and infinite scrolling without reloading the page, providing customers with a fast and modern experience.
This article is primarily written for developers and those who are building or customizing their own theme. If you’re using the Delaware theme, you already have AJAX filtering out of the box and don’t need to call the endpoint yourself.
How to call the endpoint
A simple call that fetches products from one or more categories:
fetch('/ajax?categories=123&limit=24')
.then(response => response.json())
.then(products => {
products.forEach(p => console.log(p.name, p.price));
});
By default, the endpoint returns a JSON array of products. If you also want to include metadata and pagination, add `include_meta=1 ` and `include_pagination=1`. You’ll then receive an object containing ` products`, `meta`, and `pagination` instead.
Available Parameters
Several of the parameters accept pipe-separated values, allowing you to filter by multiple criteria at once. For example, ` categories=12|34|56`.
| Parameter | Description |
|---|---|
| categories | Category IDs, separated by pipes. If multiple IDs are specified, products in at least one of the categories are returned (OR). Add match=all to return only products in all specified categories (AND) |
| force_categories | Same as categories, but enforces the categories without allowing other filters to narrow them down further |
| price_range | Price range as min|max, e.g., 100|500 |
| attribute_values | IDs of attribute values (e.g., color, size), separated by pipes. Multiple values are matched as OR. Add match=all to require that the product has all the specified values (AND), e.g., both a color and a size |
| attribute_tags | Filter attribute values by their tag instead of their ID, e.g., red|blue |
| attribute_tags_in_stock | Same as attribute_tags, but only variants in stock |
| attribute_tag | Filter by an entire attribute (not a specific value) based on its tag, e.g., only products that have the "color" attribute |
| shared_values | IDs of shared values, e.g., everything in Medium regardless of profile. | means “or,” and , means “and,” e.g. , 2|3.5. See the section on shared values further down |
| shared_values_in_stock | Same as shared_values, but only variants in stock with the value |
| extension[id] | Filter by custom fields. id is the custom field ID, and the value can be pipe-separated |
| brands | Brand IDs, pipe-separated |
| suppliers | Supplier IDs, pipe-separated |
| landing_pages | Landing page IDs, pipe-separated |
| product_ids | Specific product IDs, pipe-separated |
| tags | Filter by product tags |
| sort + sort_order | Sort the results. sort_order is asc or desc (default) |
| limit / offset | Pagination. limit can be at most 1000; higher values are automatically truncated |
| meta | Specific meta field to include on each product. Use _all for all meta fields, or pipe-separate multiple names |
| only_in_stock_variants | Return only variants that are in stock, as indicated in the variant_stock field for each product |
| include_meta | Set to 1 to include attributes, categories, and brands in the results (useful for building a filter UI) |
| include_pagination | Set to 1 to include offset, limit, count, and total |
| include_similar | Set to 1 to include the product’s similar products (e.g., other colors) and the product’s own attributes on each product. See the section on similar products below |
| pretty | Set to 1 for nicely formatted JSON (good for debugging) |
| rebuild | Set to 1 to force a new cache generation for the specific URL |
Filtering with AND (match=all)
When you send multiple categories or attribute_values, the endpoint matches by default using OR: a product is included if it matches at least one of the values. This works well for broad overviews, but for faceted filtering, you’ll often want the opposite—namely, only those products that match all the selected values at once.
Add `match=all` to switch to AND logic. Then, the product must have every single one of the specified values to be included. This is exactly what you need when a customer selects both a color and a size in your filter:
// Only products that have BOTH value 401815 AND 401822
fetch('/ajax?attribute_values=401815|401822&match=all&limit=50')
.then(response => response.json())
.then(products => { /* ... */ });
`match=all` applies to both `categories ` and ` attribute_values`. Without this parameter, `OR` is used as before.
Note: The older `exclude=1` parameter does exactly the same thing as `match=all` and still works for backward compatibility. The name is misleading, as it does not exclude anything, so use `match=all` in new code.
Note: The parameters `category_id`, `tag`, ` extra_field[..]`, `price_from `, and `price_to ` no longer exist. Use `categories`, `tags`, `extension[id] `, and `price_range` instead. Incorrect names will simply return 0 results or be ignored; they do not trigger an error message.
Filtering by Shared Values
If the store uses shared values, you can filter across profiles. A shared value such as “Medium” can be linked to a value in both the T-shirt and pants profiles, and `shared_values` finds products with all of them without requiring you to know the individual attribute value IDs. Both variants and regular attribute values in the product’s current profile are included.
The parameter uses the same structure as atags: | means “or” within a group, and , separates groups that must all match. If Medium is ID 2, Large is ID 3, and 400 bar is ID 5, then `shared_values=2|3,5 ` returns products that are available in Medium or Large and also have 400 bar. An invalid ID returns no products—not the entire catalog.
// Everything in Medium or Large, across profiles
fetch('/ajax?shared_values=' + encodeURIComponent('2|3') + '&include_meta=1&limit=24')
.then(r => r.json())
.then(data => {
console.log(data.products);
console.log(data.meta.shared_values);
});
If you use `shared_values_in_stock` instead, only variants that are in stock will be included. For regular attribute values (not variants), the product itself must be in stock or allow negative stock. If you send both parameters, `shared_values_in_stock` is used.
With `include_meta=1`, the response also includes `meta.shared_values`: the shared values found on the products in the result, sorted in the same order as under “Shared Values” in the admin panel. Each entry has ` attribute_shared_value_id`, `value`, `num_value`, `weight`, and `attribute_value_ids ` (the profile values in the results that are linked to the shared value). At the same time, each value in the ` meta.attributes ` field has an `attribute_shared_value_id`, which is ` null ` for values that are not shared. This allows you to create one filter condition per shared value instead of one per profile value.
`num_value` is the number at the beginning of the value, e.g., 400 for “400 bar.” The webshop’s own filter doesn’t use it, but you can use it yourself in your theme, e.g., for sorting.
In your theme, you can also retrieve the list of the store’s shared values using `$webshop->getSharedValues()`, which returns the id, value, and num_value for each one.
Example: Category, price range, and color
Retrieve red products priced between 100 and 500 kr. from two categories, sorted by price in ascending order:
const params = new URLSearchParams({
categories: '12|34',
price_range: '100|500',
attribute_tags: 'red',
sort: 'price',
sort_order: 'asc',
limit: 24
});
fetch('/ajax?' + params)
.then(r => r.json())
.then(products => renderProducts(products));
Example: Filter using extension fields and retrieve metadata
Extension fields are specified by the extension field’s ID in parentheses. You can find the ID under Settings → Extended Fields in the admin panel. Example where extension field 5 (e.g., "material") must be either "cotton" or "linen":
// extension[5]=cotton|linen
fetch('/ajax?categories=12&extension[5]=' + encodeURIComponent('cotton|linen') + '&include_meta=1&include_pagination=1&limit=24')
.then(r => r.json())
.then(data => {
console.log(data.products);
console.log(data.meta.attributes);
console.log(data.pagination);
});
Similar Products and Color Variants
If each color is listed as its own product and the colors are linked under "Similar Products," you can display the other colors directly on the product card, for example as small color dots. Add `include_similar=1`, and each product will have two additional fields:
- similar_products: the similar products with product_id, name, url, thumbnail (200x200 fit), in_stock (true or false), and attributes. Only products that are online are included, with a maximum of 25 per product, in the order they appear on the product page in the admin panel. The product itself is not included.
- attributes: the product’s own attributes, so you also know the card’s own color. Each attribute has attribute_id, attribute_name, attribute_tag, purpose, attribute_value_id, value, and value_tag.
The attributes are those assigned to the product. Variants (e.g., sizes) and fields that the customer fills out themselves are not included. "purpose" is the purpose the attribute serves in the profile (e.g., "color" when "Purpose" is set to "Color"), and "attribute_tag " and "value_tag" are the tags you’ve assigned to the attribute and its value. For example, add the hex code as a tag to each color value, and the theme can draw the dot directly. A text attribute containing the hex code also works. In that case, the code is stored in ` value`.
"attributes": [
{"attribute_id": 120, "attribute_name": "Color",
"attribute_tag": "color", "purpose": "color",
"attribute_value_id": 801, "value": "White",
"value_tag": "#ffffff"}
],
"similar_products": [
{
"product_id": 1043,
"name": "Original T-shirt | Black",
"url": "https://example.com/original-t-shirt-sort",
"thumbnail": "https://example.com/cache/.../fit-200x200x90.png",
"in_stock": true,
"attributes": [
{"attribute_id": 120, "attribute_name": "Color",
"attribute_tag": "color", "purpose": "color",
"attribute_value_id": 802, "value": "Black",
"value_tag": "#000000"}
]
}
]
// Color dots on product cards: the card's own color first, then the others
const color = attributes => attributes.find(a => a.purpose === 'color');
fetch('/ajax?categories=12&include_similar=1&limit=24')
.then(r => r.json())
.then(products => {
products.forEach(product => {
const own = color(product.attributes);
const others = product.similar_products
.map(similar => ({ url: similar.url, color: color(similar.attributes) }))
.filter(similar => similar.color);
console.log(product.name, own ? own.value_tag : null, others);
});
});
Without `include_similar`, the two fields are not included, and the result is the same as before. Similar products can also be items other than colors, such as accessories. Therefore, only display those that have a color attribute.
Fields for each product in the response
Each product in the `products` array has the following fields, among others:

- product_id, own_id, name, description, list_description
- price, real_price, sale_price, price_dk (formatted for Danish)
- stock, attr_stock, variant_stock, stock_string_da
- brand_name, supplier_id, supplier_name, profile_name
- category_ids, category_names
- thumbnail (200x200), thumbnails (array of all images in 200x200), url
- avg_rating, online_since, delivery_time, delivery_time_not_in_stock, approx_shipping
- has_campaigns, campaign_info
- meta_values (only filled in if you send meta=...)
- attributes, similar_products (only included if you send include_similar=1)
If the online store has enabled "hide stock via AJAX," stock, attr_stock, and stock quantities in variant_stock will be null, so that stock numbers are not publicly disclosed.
Caching
The endpoint is cached on the server for 12 hours per unique URL. The response is also sent with the correct Last-Modified and Expires headers, so that browsers and intermediate caches can return a fast 304 Not Modified if the content has not changed. This ensures fast response times but also means that changes to a product won’t take effect until the cache expires. You can force a cache rebuild for a specific URL by adding `rebuild=1`.
Read more in the article “Caching in Shoporama,” which provides an overview of the cache layers in general.
Implementation in Your Theme
To build a complete AJAX-filtered product list, the developer typically needs to:
- Build a filter UI with checkboxes or dropdowns based on ` meta.attributes`, ` meta.brands `, and `meta.categories`
- Listen for changes to the filters and combine them into a query string
- Call /ajax with the selected parameters
- Dynamically update the product list and display pagination based on `pagination.total`
Tip: Read more about filtering in general in Filtering on Your Webshop. If you’re using the Delaware theme, you already have AJAX filtering out of the box.
Frequently Asked Questions
Where can I find the ID of a custom field or an attribute value?
In the admin panel under Settings → Extended Fields for custom fields, and under Webstore → Product Profiles for attribute values. The IDs are displayed in the list or in the URL when you edit a field.
Where can I find the ID of a shared value?
The IDs are not displayed on the Shared Values page in the admin panel. Instead, retrieve them in the theme using $webshop->getSharedValues(), from meta.shared_values in an /ajax call with include_meta=1, or from the REST endpoint GET /attribute-shared-value.
Why do I get no results when I use category_id=123?
Because the parameter doesn’t exist. Change it to categories=123 (plural). This is one of the most common mistakes when setting up AJAX filtering for the first time. Also, make sure you’re not usingprice_from/price_to or extra_field[...], as those don’t exist either.
My changes to a product aren’t taking effect on /ajax. What should I do?
The endpoint is cached for 12 hours. Wait, or retrieve the URL with &rebuild=1 to force a regeneration of that specific URL.
Do many AJAX calls affect my website’s speed? (Mikkel, developer)
The cache ensures that repeated calls are fast. However, be careful not to make a new call for every single keystroke in a search field. Use "debounce" so that you only make the call after the user pauses for 200–300 ms. The browser also uses the If-Modified-Since header, so unchanged responses return a 304 status and use almost no bandwidth.
Will I get the same result as on the category page?
Pretty much. /ajax follows the same rules that ProductFactory uses on category pages, so filtering, sorting, and visibility (e.g., hidden products) behave the same way.
Can I perform a search on /ajax? (Sofie, new hire)
/ajax does not accept a free-text search parameter. For searching, you must use Shoporama’s dedicated search endpoint in the theme (typically /search) or filter by product_ids if you’re handling the search yourself and simply want to retrieve data for a known list of products.
How many products can I retrieve in a single call? (Jonas, Scale)
You can retrieve up to 1,000 products in a single call. If you request more, the response is automatically capped at 1,000, and the shop will simultaneously receive a notification in the admin panel under Messages so you can see it. In practice, you should aim for a somewhat lower number. A few hundred per call keeps the JSON payload small and the response fast, which is especially important for mobile users. Use `limit` and `offset` to paginate, or only fetch the next batch when the user scrolls.
Will my customers’ stock levels be publicly exposed? (Malene, Marketing)
By default, the response includes stock levels. If you want to hide them (so competitors or bots can’t see how much you have in stock), your developer can enable “hide stock via AJAX” on the online store, after which the stock fields will be sent as null.
Can I use /ajax as a “real” REST API? (Mikkel, developer)
No, it’s a public, cache-friendly product endpoint for front-end use. If you need to create, update, or integrate at a deeper level, use the actual REST API instead, which requires an API key.
Can I display the other colors on the product card? (Line, theme developer)
Yes, if the colors are linked under “Similar Products.” Send `include_similar=1`, and find the color in the `attributes` of the product and each product in `similar_products`—for example, where `purpose` is `color`. The hex code may be included as a tag on the color value. This doesn’t require an extra call per product, since the similar products are fetched collectively for the entire list.
Should I be concerned that the filtering works differently for customers in different languages?
The endpoint runs in the same webshop context as the homepage, so prices, currency, and visibility follow the webshop’s settings. If you have multiple webshops or languages, remember that each webshop has its own URL and, therefore, its own /ajax-cache.
Would you like us to help you integrate AJAX filtering into your theme, or do you have technical questions? Email us at support@shoporama.dk.
Related articles
Filtering on your online store
Guide to setting up filtering on your Shoporama online store so customers can filter products.
The Delaware Theme: Configuration and Customization
A complete guide to Delaware and DelawareDK on your Shoporama online store: theme settings, footer, mega menu, colors, payment icons, Trustpilot,...
REST API
Complete guide to Shoporama's REST API: authentication, all endpoints, examples and Swagger documentation.
Import extra fields on products
Guide to importing extra fields via CSV import of products in Shoporama.
Cache in Shoporama
How caching works in Shoporama. How often caches are built, when your changes take effect, and how to force a reset.
Shared values: consistent sizes across profiles
Use shared values so that, for example, "Medium" is the same value across all your product profiles, and customers can filter by "Medium" for all...
Related features
Filtering
Filtering lets customers narrow down the product selection by price, color, size and more. Learn about filter types, best practices and SEO...
Fast search with Elastic Search
Lightning-fast search with Elastic Search. Spelling tolerance, synonyms, 0 searches and search analytics for your online store.
Speed is a feature - that's why we've made Shoporama shops lightning fast
We love fast online shops. Everyone does. That's why we've made Shoporama webshops lightning fast. It's good for you, your user and your SEO