Emergency situation

In case of emergencies or breakdowns, you can send an SMS to our emergency hotline

On-call phone (SMS only)

+45 29 70 15 95

Send an SMS with the following information:

  • Your name and webshop
  • Description of the problem
  • Your callback phone number

Notes: This service is only for critical situations where your webshop is down or has serious problems. For regular support, please use our normal support channels.

Ajax filtering

Technical documentation for Shoporama's /ajax endpoint for filtering products. For developers and theme designers.

Reading time: approx. {eight} minutes
Shopejer Developer

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.

ParameterParameter Description
categoriesCategory IDs, pipe-separated. Optionally combine with exclude=1 to exclude instead
force_categoriesLike categories, but forces the categories without letting other filters narrow them down further
price_rangePrice range as min|max, e.g. 100|500
attribute_valuesIDs of attribute values (e.g. color, size), pipe-separated. Include exclude=1 to exclude
attribute_tagsFilter attribute values by their tag instead of ID, e.g. red|blue
attribute_tags_in_stockLike attribute_tags, but only variants in stock
attribute_tagFilter 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
brandsBrand IDs, pipe-separated
suppliersSupplier IDs, pipe-separated
landing_pagesLanding page IDs, pipe-separated
product_idsSpecific product IDs, pipe-separated
atagsFilter by product tags
sort + sort_orderSort the result. sort_order is asc or desc (default)
limit / offsetPagination
metaSpecific meta field to include on each product. Use _all for all meta fields or pipe-separate multiple names
only_in_stock_variantsReturn only variants that are in stock in the variant_stock field on each product
include_metaSet to 1 to get attributes, categories and brands in the result (good for building filter UI)
include_paginationSet to 1 to get offset, limit, count and total
prettySet to 1 for nicely formatted JSON (good for debugging)
rebuildSet 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

  1. Build the filter UI with checkboxes or dropdowns based on meta.attributes, meta.brands and meta.categories
  2. Listen to changes in the filters and compile them into a query string
  3. Call /ajax with the selected parameters
  4. 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.