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 build dynamic filtering, lazy loading and infinite scroll without reloading the page, giving the customer a fast and modern experience.
This article is primarily written for developers and those 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 meta-data and pagination, add include_meta=1 and include_pagination=1. You will then get an object with products, meta and pagination instead.
Available parameters
Several of the parameters take pipe-separated values, so you can filter on multiple things at once. For example, categories=12|34|56.
| Parameter | Parameter Description |
|---|---|
| categories | Category IDs, pipe-separated. Optionally combine with exclude=1 to exclude instead |
| force_categories | Like categories, but forces the categories without letting other filters 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), pipe-separated. Include exclude=1 to exclude |
| attribute_tags | Filter attribute values by their tag instead of ID, e.g. red|blue |
| attribute_tags_in_stock | Like attribute_tags, but only variants in stock |
| attribute_tag | Filter on an entire attribute (not a specific value) by its tag, e.g. only products that have the attribute "color" |
| extension[id] | Filter on extra fields. id is the extra 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 |
| atags | Filter by product tags |
| sort + sort_order | Sort the result. sort_order is asc or desc (default) |
| limit / offset | Pagination |
| 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 in the variant_stock field on each product |
| include_meta | Set to 1 to get attributes, categories and brands in the result (good for building filter UI) |
| include_pagination | Set to 1 to get offset, limit, count and total |
| pretty | Set to 1 for nicely formatted JSON (good for debugging) |
| rebuild | Set to 1 to force a new generation of the cache for the specific URL |
Note: The category_id, tag, extra_field[...], price_from and price_to parameters do not exist. Instead, use categories, atags, extension[id] and price_range. The wrong names will simply return 0 results or be ignored, not an error message.
Example: Category, price range and color
Retrieve red products between 100 and 500 kr. from two categories, sorted by price ascending:
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 with extra fields and get meta data
Extension fields are indicated with the ID of the extension field in brackets. You can find the ID under Settings → Extended fields in admin. Example where extension field 5 (e.g. "material") should 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); });
Fields on 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 (Danish formatted)
- stock, attr_stock, variant_stock, stock_string_en
- brand_name, supplier_id, supplier_name, profile_name
- category_ids, category_names
- thumbnail (200x200 fit), 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=...)
If the shop has "hide stock via ajax" enabled, stock, attr_stock and stock quantities in variant_stock will be null, so the stock numbers will not leak publicly.
Caching
The endpoint is cached on the server for 12 hours per unique URL. The response is also sent with proper Last-Modified and Expires headers, so browsers and intermediate caches can return a quick 304 Not Modified if the content is unchanged. This makes response times fast, but also means that changes to a product only take effect after the cache expires. You can force a cache rebuild for a specific URL by adding rebuild=1.
Read more in the article Cache in Shoporama that goes through the cache layers in general.
Implementing in your theme
To build a complete ajax-filtered product list, the developer typically needs to
- Build the filter UI with checkboxes or dropdowns based on meta.attributes, meta.brands and meta.categories
- Listen to changes in the filters and compile them into a query string
- Call /ajax with the selected parameters
- Dynamically update the product list and show pagination based on pagination.total
Tip: Learn more about filtering in general in Filtering on your online shop. If you're using the Delaware theme, you already have ajax filtering out of the box.
Frequently asked questions
Where do I find the ID of an extra field or attribute value?
In the admin under Settings → Extended fields for extra fields and under Settings → Profiles for attribute values. The IDs are shown in the list or in the URL when you edit a field.
Why do I get no results when I use category_id=123?
Because the parameter does not exist. Change to categories=123 (plural). This is one of the most common mistakes when building ajax filtering for the first time. Also check that you are not using price_from/price_to or extra_field[...], which do not exist either.
My changes to a product are not reflected in /ajax. What should I do?
The endpoint is cached for 12 hours. Wait, or fetch the URL with &rebuild=1 to force a new generation of that particular URL.
Do many ajax calls affect my website speed? (Mikkel, developer)
The cache makes sure that repeated calls are fast. However, be careful not to make a new call for every keystroke in a search box. Use "debounce" so you only call when the user pauses for 200-300 ms. The browser also utilizes If-Modified-Since, so unchanged answers come as 304 and almost no bandwidth is used.
Do I get the same result as on the category page?
Pretty much. /ajax respects the same rules that ProductFactory uses on category pages, so filtering, sorting and visibility (e.g. hidden products) behave similarly.
Can I do search on top of /ajax? (Sofie, new employee)
/ajax does not take a free text search parameter. For search, use Shoporama's dedicated search endpoint in the theme (typically /search) or filter on product_ids if you are doing the search yourself and just want to retrieve data on a known list of products.
How many products can I retrieve in one call? (Jonas, scale)
There's no hard limit in the code, but to keep the JSON payload small and the response fast, stay practically under a few hundred per call. Use limit and offset to page count, or only fetch the next batch when the user scrolls.
Will my customers' inventory counts be exposed publicly? (Malene, Marketing)
Basically, the answer contains the stock count. If you want to hide it (so competitors or robots can't see how much you have in stock), your developer can enable "hide stock via ajax" on the webshop, after which the stock fields are 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 on a deeper level, use the actual REST API instead, which requires an API key.
Should I be concerned that the filtering works differently for customers in different languages?
The endpoint runs in the same online store context as the front page, so prices, currency and visibility follow the online store the call frames. If you have multiple webshops or languages, remember that each webshop has its own URL, and thus its own /ajax cache.
Would you like us to help you build ajax filtering into your theme or do you have technical questions? Write to 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.
Delaware-themed configuration and customization
Complete guide to setting up the Delaware theme on your Shoporama online store. Footer, mega menu, colors, payment icons, Trustpilot, Instagram...
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.