Search tutorial
Search results page (SERP)
Search results page is typicaly rendered after the user presses the Enter key and provides the full search experience including filtering, sorting and pagination.
Search results page URL
Each search results page should have a shareable URL which when opened, renders the search results preserving the phrase (query), selected filters and sorting.
This will usually be a dedicated page at a location such as /search
. Usually, the page will have a GET parameter such as q
which will capture the user's phrase. Visiting this location will trigger the search flow.
When this search page is visited, make an API request to Luigi's Box Search endpoint.
GET https://live.luigisbox.com/search
?q=digital+piano
&f[]=type:item
&facets=price_amount,category
&user_id=<transient_user_id>
q
- pass the search phrasef[]=type:item
- each search must have exactly one main type which will be used to compute available filters, to provide pagination and to sort on. Use an explicit filter ontype
attribute to provide the main type. You can request more than one type in a single request usingquicksearch_types
which will be demonstrated in a later step.facets=price_amount,category
- the response will include breakdown of attribute values for attributesprice_amount
andcategory
. This part will let you render filtering options.user_id=<transient_user_id>
- pass the value of the_lb
cookie.
Search API response overview
Search API response high-level structure
{
"results": {
"query": "digital piano",
"corrected_query": null,
"filters": [
"type:item"
],
"accepted_filters": [
],
"hits": [
],
"quicksearch_hits": [
],
"facets": [
],
"suggested_facet": {
},
"total_hits": 171,
"campaigns": [
]
},
"next_page": "https://live.luigisbox.com/search?tracker_id=179075-204259&f[]=type:item&q=digital%20piano&page=2"
}
The search API respnse consists of several top-level keys which contain data that let you build a full-featured search results page.
-
query
- echoes back the query -
corrected_query
- in case the search phrase was modified in some way, the corrected query will be present here -
filters
- echoes back the filters in a structured way -
hits
- contains data for the search results for the main type -
quicksearch_types
- contains data for additional types you requested (typically categories and brands -
facets
- contains data that lets you render filtering options -
total_hits
- number of results that match the phrase and filters -
campaigns
- contains campaign data if a search campaigns has been set up in Luigi's Box application for this specific query
Render the product tiles
The hits
part of the Search API response contains data for the main type (the type you requested with the f[]=type:
filter, typically products). You will use this data to render the product tiles. If you need additional data that is not indexed in Luigi's Box, you can load the additinal data from your database.
{
"hits": [
{
"url": "PR-15553",
"attributes": {
"image_link": "https://cdn.myshoptet.com/usr/demoshop.luigisbox.com/user/shop/detail/1774257_smart-piano-the-one-digital-piano.jpg",
"title": "Smart piano The ONE Digital Piano",
"price_amount": 629,
"web_url": "/smart-piano-the-one-digital-piano/"
},
"type": "item"
},
{
"url": "PR-76761",
"attributes": {
"image_link": "https://cdn.myshoptet.com/usr/demoshop.luigisbox.com/user/shop/detail/1784343_kurzweil-digital-piano.jpg",
"title": "Kurzweil Digital Piano",
"price_amount": 890,
"web_url": "/kurzweil-digital-piano/"
},
"type": "item"
}
]
}
(API response shortened for brevity)
Render facets
The facets
part of the Search API response contains data for the filtering options. There are several types of facets that you may see in the response:
float
facet which is generated for numerical fields. This facet type operates on ranges and is designed to bucket the attribute values into smaller equally distributed ranges. Each smaller range includeshits_count
which indicated number of results within this range (2.89|182.5 translates to a range of values between 2.89 and 182.5).normalized_hits_count
indicate a percentage of total, e.g. a value of 0.2 means that 20% of all results are within this range.text
facet which is generated for string fields. This facet type gives you back individual attribute values along with number of results that have this attribute value.boolean
facet which is generated for boolean attributes. This facet may contain only 2 values - "true" and "false". It is typically rendered as a single checkbox.
{
"facets": [
{
"name": "price_eur_amount",
"type": "float",
"values": [
{
"value": "2.89|182.5",
"hits_count": 34,
"normalized_hits_count": 0.2
},
{
"value": "182.5|365.0",
"hits_count": 8,
"normalized_hits_count": 0.05
},
{
"value": "365.0|547.5",
"hits_count": 12,
"normalized_hits_count": 0.07
}
]
},
{
"name": "category",
"type": "text",
"values": [
{
"value": "Musicians",
"hits_count": 171
},
{
"value": "Keys",
"hits_count": 162
},
{
"value": "Digital Pianos",
"hits_count": 91
}
]
},
{
"name": "on_sale",
"type": "boolean",
"values": [
{
"value": "true",
"hits_count": 80
},
{
"value": "false",
"hits_count": 190
}
]
}
]
}
(API response shortened for brevity)
Render facets universally
Luigi's Box can automatically select the most appropriate facets for the user's query. It is also possible to set up specific facets manually in Luigi's Box administration. In order to support these features, do not rely on the predefined facets in the response. Instead, render the facets based on the API response.
In general this requires you to prepare components for each of the main facet types (numerical, text, boolean) and act on the API response, reading the list of returned facets and their types.
response.facets.forEach(facet) {
if (facet.type === 'float') {
renderRangeFacet(facet);
}
if (facet.type === 'text') {
renderCheckboxesFacet(facet);
}
if (facet.type === 'boolean') {
renderSingleCheckboxFacet(facet);
}
}
Render total number of results
The total_hits
attribute in the API response indicates the total number of results. You can use this data to provide information to the user.
{
"total_hits": 171
}
(API response shortened for brevity)
Render pagination
There are several attributes in the API response that help you with pagination:
-
total_hits
indicates the total results that can be retrieved when using pagination. Divide it bysize
(which is a request parameter) to calculate total number of pages. Use thepage
request parameter to indicate the page you want to retrieve by the API call -
next_page
provides the API call to retrieve the next page of results. If you want to implement a simple "Next page" type of pagination, you can use the value of this attribute as-is to request the next page of results.
{
"total_hits": 171,
"next_page": "https://live.luigisbox.com/search?tracker_id=&f[]=type:item&q=digital%20piano&facets=price_eur_amount,category&page=2"
}
(API response shortened for brevity)
Render sorting options
Use the sort
API parameter to change the sorting. Note that when you request an explicit sorting, the results will be sorted by this attribute and no AI-based ranking will be used.
To use the AI-based ranking, provide no sort
parameter at all. If you pass the sort
parameter, the results are simply ranked by that attribute and personalization and AI-based ranking is not used.
Also note that requesting an explicit sort may cause the number of results to vary. See the Knowledge base article for more details.
GET https://live.luigisbox.com/search
?q=digital+piano
&f[]=type:item
&facets=price_amount,category
&sort=price_amount:desc
&user_id=<transient_user_id>
Tips
When sorting on string attributes, the sort will respect the lexicographical ordering. Keep this in mind when attempting to sort over attributes that were indexed as text. This may be the case with price, as per Luigi's Box conventions it is usually indexed as text including the currency. This may lead to surprising ordering (e.g. "10 EUR", "100 EUR", "20 EUR"). To get correct ordering, use a numerical attribute. In case of price, this will be the automatically derived
price_amount
attribute.-
Make sure to use no
sort
parameter if you want to get the AI-based ranking. Refer to the Standard ranking documentation to understand it in more detail. Note that the standard ranking will automatically consider availability and there's no need to sort by it explicitely.
Interacting with filters
When the user selects a filter, issue a new search API request using the f[]
parameter to indicate the selected filter. The example request on this page serves the search results page in the case the user selected "Stage Pianos" in the "Category" facet. The value of the f[]
attribute is always a colon separated pair - attribute:value.
GET https://live.luigisbox.com/search
?q=digital+piano
&f[]=type:item
&f[]=category:Stage+Pianos
&facets=price_amount,category
&user_id=<transient_user_id>
Notice the
f[]=category=Stage+Pianos
parameter which indicates the selected filter.
Interacting with numerical filters
When the user interacts with a numerical attribute, use a slightly different approach in the API request. The numeric attributes can be filtered using a range syntax.
GET https://live.luigisbox.com/search
?q=digital+piano
&f[]=type:item
&f[]=price_eur_amount:730.0|11590.0
&facets=price_amount,category
&user_id=<transient_user_id>
Notice the
f[]=price_eur_amount:730.0|11590.0
parameter which indicates the selected filter. This will cause the API to return only products withprice_amount_eur
attribute in the interval 730 ‒ 11590.
Filtering on several attributes
You can filter on several values and several attributes in a single request. Simply add as many f[]
parameters as necessary.
Note the implicit semantics: filtering on different values on a single attribute is a bool OR operation and there's a bool AND across different attributes.
GET https://live.luigisbox.com/search
?q=digital+piano
&f[]=type:item
&f[]=category:Stage+Pianos
&f[]=category:Digital+Pianos
&f[]=price_amount:730.0|11590.0
&facets=price_amount,category
&user_id=<transient_user_id>
Notice the various
f[]
parameters which indicate the selected filter. This specific combination will be interpreted as (category: Stage Pianos OR Digital Pianos) AND (price_amount within 730 - 11590).
Rendering results for several types (products, categories, brands)
It is a standard practice to include results for more than one type on the search results page. From the perspective of the user experience, there's always one main type that is used for filtering, sorting and pagination. The other types (called quicksearch types) are provided without pagination or filtering option (but you can specify sorting).
To request quicksearch types, add a quicksearch_types
parameter.
GET https://live.luigisbox.com/search
?q=digital+piano
&f[]=type:item
&quicksearch_types=category:6,brand:3
&facets=price_amount,category
&user_id=<transient_user_id>
- The
f[]=type:item
parameter specifies the main type to which sorting, filtering and pagination options apply.- The
quicksearch_types
parameter specifies results for the additional types that should be loaded.
{
"quicksearch_hits": [
{
"url": "/digital-pianos/",
"attributes": {
"hierarchy": [
"Musicians",
"Keys"
],
"title": "Digital Pianos",
"web_url": "/digital-pianos/"
},
"type": "category"
},
{
"url": "/stage-pianos/",
"attributes": {
"hierarchy": [
"Musicians",
"Keys"
],
"title": "Stage Pianos",
"web_url": "/stage-pianos/"
},
"type": "category"
},
{
"url": "/smart-piano/",
"attributes": {
"title": "Smart piano",
"web_url": "/smart-piano/"
},
"type": "brand"
},
{
"url": "/kurzweil/",
"attributes": {
"title": "Kurzweil",
"web_url": "/kurzweil/"
},
"type": "brand"
}
]
}
(API response shortened for brevity)
Fire a dataLayer event for search
After the results have been rendered, fire a Search Results dataLayer event describing what you have just rendered.
dataLayer.push({
event: "view_item_list",
ecommerce: {
item_list_name: "Search Results",
search_term: "digital piano",
items: [
{
item_id: "PR-15553",
item_name: "Smart piano The ONE Digital Piano",
index: 1,
price: 629,
type: "item"
},
{
item_id: "PR-76761",
item_name: "Kurzweil Digital Piano",
index: 2,
price: 890,
type: "item"
},
{
item_id: "/digital-pianos/",
item_name: "Digital pianos",
index: 3,
type: "category"
},
{
item_id: "/smart-piano/",
item_name: "Smart piano",
index: 4,
type: "brand"
}
]
}
});
(dataLayer event shortened for brevity)
Tips
- Make sure that the
item_id
refers to the object identity (theurl
attribute in the top-level object data).- The dataLayer event structure is the same as for Autocomplete, except the
item_list_name
. Make sure to useSearch Results
here.- Provide data for all types, not just for products.
- Make sure that the position index is unique for each item (independed of the type)
Fire a dataLayer event for search with filters
In case the user selected some filters, include the filters in the dataLayer event. Make sure to use the same attribute names as in the API request to provide feedback to the models.
dataLayer.push({
event: "view_item_list",
ecommerce: {
item_list_name: "Search Results",
search_term: "digital piano",
items: [
{
item_id: "PR-15553",
item_name: "Smart piano The ONE Digital Piano",
index: 0,
price: 629
},
{
item_id: "PR-76761",
item_name: "Kurzweil Digital Piano",
index: 1,
price: 890
}
],
filters: {
"category": ["Stage Pianos", "Digital Pianos"],
"price_amount": "730.0|11590.0"
}
}
});
(dataLayer event shortened for brevity)
Tips
- Make sure that the
item_id
refers to the object identity (theurl
attribute in the top-level object data).- The dataLayer event structure is the same as for Autocomplete, except the
item_list_name
. Make sure to useSearch Results
here.
Fire a dataLayer click event
When the user clicks on a result, immediately fire a dataLayer event.
dataLayer.push({
event: "select_item",
ecommerce: {
items: [
{
item_id: "PR-76761",
}
]
}
});
Banners in search results
To provide integration for the Banner campaigns feature obey the banner instructions in the campaigns
attribute in the API response.
{
"hits": [
],
"campaigns": [
{
"id": 45,
"target_url": "https://www.luigisbox.com",
"banners": {
"search_panel": {
"desktop_url": "https://luigisbox-tmp-public-feeds.s3.eu-central-1.amazonaws.com/240x280.png",
"mobile_url": "https://luigisbox-tmp-public-feeds.s3.eu-central-1.amazonaws.com/420x240.png"
},
"search_list": {
"desktop_url": "https://luigisbox-tmp-public-feeds.s3.eu-central-1.amazonaws.com/340x490.png",
"mobile_url": "https://luigisbox-tmp-public-feeds.s3.eu-central-1.amazonaws.com/340x490.png"
}
}
}
]
}
(API response shortened for brevity)
User sign in
dataLayer.push({
event: "luigisbox.collector.customer_id",
customer_id: "281827";
},
});
Once you know the personalized user ID, emit a customer_id dataLayer event
Render search results page for an identified user
When the user loads a search results page, you will fire an API request to the search endpoint to fetch the search results, passing both transient and persistent user IDs.
GET https://live.luigisbox.com/search
?q=digital+piano
&f[]=type:item
&facets=price_amount,category
&user_id=<persistent_user_id>
&client_id=<transient_user_id>
user_id=<persistent_user_id>
- pass the persistent user IDclient_id=<transient_user_id>
- pass the value of the_lb
cookie
Limit amount of data transferred
GET https://live.luigisbox.com/search
?q=digital+piano
&f[]=type:item
&facets=price_amount,category
&hit_fields=product_id,title,price,image_link
&remove_fields=nested
&user_id=<transient_user_id>
To limit the amount of data transferred between systems, specify the hit_fields
and/or remove_fields
attribute in the API request. It is an array of result properties which will be included in the API response. By using this, you can significantly reduce the amount of data transfer and increase performance.