NAV

Javascript collector

We provide starter integration that allows you to start using Luigi's Box immediately and without any major programming effort on your side. This integration relies on the specific HTML structure that was used on your site at the time you signed up for Luigi's Box. As you keep improving your site, it is very likely that the HTML structure will change and your Luigi's Box integration will break and we will receive incorrect data, which will negatively impact your data quality and negatively impact the process of learning the optimal rankings (if you are using Luigi's Box Autocomplete or Luigi's Box Search).

To maintain high data quality and remove the risk of breaking the data collection process when changing HTML structure, you should explicitely annotate search data in your HTML. We provide a tracker script which can read structured data about search and search results from standard schema.org annotations (we support both inline annotations or embedded json+ld). These annotations are not tied to your design and HTML structure and therefore robust against redesign and other front-end changes.

Note that the starter script does not support schema.org/json+ld annotations and even if you annotate your searches, the starter script will keep collecting data using hardcoded HTML structure. If you are using our starter integration and want to switch to robust schema.org/json+ld annotations, contact us and we will prepare you a standard tracking script.

Script inclusion

When you sign up for Luigi's Box you will receive a tracking script and a Tracker ID. You must include this script in the HTML code of your website to start tracking search data.

The Tracker ID is a unique identifier of your domain and is tied to the tracking script. You should not use the same script for different domains; if you do, your data will get mixed into a single view inside Luigi's Box, making any relevant analysis very difficult. If you want to track different domains, contact us for separate Tracker IDs and tracking scripts.

Inside <head> element

We recommend that you insert the script tag inside the <head> element in your HTML code. Note, that the script is marked as async and thus will not impact your page load speed in any way.

Just for reference, the script tag that you should have received upon signing up will look like the following example.

<script async src="https://scripts.luigisbox.com/LBX-123.js"></script>

Google Tag Manager integration

If you are unable to insert the script tag directly into your HTML, you can use Google Tag Manager. Tracking script you insert into your HTML is the same you would use with Google Tag Manager, however using Google Tag Manager will impact the data collection process and some advanced features (e.g., fixit rules) will appear to be working slowly. We recommend that you always insert the script to <head> element for full experience.

Content Security Policy

If your website is using Content Security Policy, you need to add following rules to enable Luigi's Box search analytics script.

CSP directive Value
connect-src https://api.luigisbox.com
script-src https://scripts.luigisbox.com
script-src https://cdn.luigisbox.com

Annotations Introduction

Luigi’s Box recognizes search and product microdata annotations in schema.org format - a standard and accepted way to give structure to data and make it understandable to machines.

You want to use schema.org annotations, because:

There are 2 ways to add schema.org annotations to your web, and both are supported by Luigi's Box:

  1. Using an inline markup and adding additional HTML attributes where appropriate.

  2. Embedding a separate JSON object which includes all semantic information in one place.

Both options are equivalent, as they convey the same semantic information. They only differ in the used notation. When you choose inline markup, you have to intersperse the annotations throughout the HTML, but on the other hand, there is no duplicated information. When you use a JSON object, the semantic data is contained in a single place, but duplicates the information that is already present in HTML.

Based on our experience, inline annotations are easier to maintain, but the embedded JSON document is somewhat easier to implement and understand, especially when you are adding schema.org annotations for the first time.

Concepts

Regardless of which implementation you choose (inline annotations or JSON+LD), there are several concepts that you need to know about and which, together, form a complete information about the search performed.

Concept  
Query what the user typed into your searchbox
Filters additional restrictions used, e.g. search only in "Accessories" department
Search results specifically title, URL address, position in the list of results and price (if applicable).
Conversion intent did the user add an item to the shopping cart?

Query

Query is what the user typed into the search box to get search results.

It is important that you don't encode the query in any way (e.g., you should do no percent-encoding, or whitespace trimming). Use excactly the same query string that the user typed, even if you do some internal preprocessing before using the query for search.

Note, that a valid search may not have a query at all. E.g., imagine a scenario when using an advanced search and the user types no query, but chooses to search for all products from a certain brand. In this case you would use empty query with filters.

See below for filters explanation.

Filters

Anything that influences which products are displayed should be annotated as filters. Sorting, or facets, or sometimes even the category have effect on what is displayed.

Luigi's Box only cares about active filters. When you are showing a facet where a user can limit phones by the manufacturer, but the user did not select anything yet, it's not a filter and you should not annotate it.

Filters

Take a look at the picture above. There are 4 filters:

Note that some filters are implicit and not visible or modifiable by the user.

Imagine a scenario where your users are assigned specific access levels and you are limiting the search results to only show results the user can access. In this case, you should send the access level as a filter.

Search results

The relevant information about search result is:

URL  
https://www.example.com/products/black-shirt?ref=search notice the ref parameter which is not necessary for the link to work and is only used for analytical purposes.
https://www.example.com/products/black-shirt?q=shirt&page=2 notice the q and page parameters which are not necessary for the link to work and are only used to construct a link back to search.
https://www.example.com/products/black-shirt?gclid=283h1bxz81jzgj notice the gclid parameter which is not necessary for the link to work and is only used for tracking purposes.
https://www.example.com/products/black-shirt This is the correct canonical URL that should be used in all cases

Luigi's Box only cares about search results that are visible. Imagine a scenario where your search found thousands of search results, but you only present a list of first 20 search results, along with a pagination component, which lets the user scroll through the search results and view additional results.

In this case, you should only annotate the 20 visible search results, and ignore the rest. The first result will have position 1, the last result position 20. When the user clicks through to page 2, annotate the visible results, and the first result will have position 21, the last one position 40.

Conversions

Everything that is important to you from the business perspective can be tracked as “conversion”, regardless of whether it is an action of “buying” an item, “liking” it, “favoriting” it, or anything else.

There are usually many different types of conversions found at different places.

Autocomplete

The process of annotating autocomplete results is the same as with the regular Search Results. Here are the notable differences:

Autocomplete

Product listings

Any display of product list which is of interest can be considered a “search”. Therefore, if the user clicks a menu element and is taken to the list of products which he/she can manipulate through filters and/or sort, this is considered a “search” too (albeit without a query, only with filters, if any) and you should send it to Luigi's Box to be analyzed. It makes sense to send the category name as a filter.

Embedded JSON+LD

Search example

Sample JSON+LD document

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "SearchAction",
  "query": "phone",
  "result": {
    "@type": "ItemList",
    "name": "Search Results",
    "itemListElement": [
      {
        "@type": ["ListItem", "Product"],
        "position": "1",
        "name": "Android Phone PX100",
        "url": "https://myshop.com/products/236",
        "offers": {
          "@type": "Offer",
          "priceCurrency": "EUR",
          "price": "120"
        }
      },
      {
        "@type": ["ListItem", "Product"],
        "position": "2",
        "name": "iPhone X",
        "url": "https://myshop.com/products/293",
        "offers": {
          "@type": "Offer",
          "priceCurrency": "EUR",
          "price": "999"
        }
      },
      {
        "@type": ["ListItem", "CollectionPage"],
        "position": "3",
        "name": "Apple iPhone",
        "url": "https://myshop.com/category/iphone"
      }
    ]
  },
  "instrument": [{
    "@type": "ChooseAction",
    "name": "Sort by",
    "actionOption": "Relevance"
  },
  {
    "@type": "ChooseAction",
    "name": "Color",
    "actionOption": ["Black", "Silver"]
  }]
}
</script>

The sample document shows all concepts in a JSON+LD format. You should include a document similar to this, wrapped in a script tag somewhere in your page HTML code.

Top-level attributes

Element Hint
<script type="application/ld+json"> The JSON+LD document must be embedded inside a this script tag.
@context This attribute marks this as schema.org-compatible.
@type This attribute marks this as search-related information in the shema.org vocabulary. You must use SearchAction here (even for recommendation).
query The query that the user entered (required only for search results and autocomplete).
result This element contains the list of search results. The name is a bit confusing, but in the schema.org nomenclature, results of the SearchAction is an ItemList of search results.
result.name Valid values here are "Search Results" for regular search results, "Autocomplete" for autocomplete results and "Recommendation" for recommendation results.

itemListElement

itemListElement represents a single result either in autocomplete widget, or on regular search results page. Below is a list of attributes that we rely on for analytics purposes. While you can add more attributes from the schema.org spec, it will have no effect and they will be ignored by the parser.

Element Hint
@type An array of types. One of the elements has to be "ListItem", which will mark the element as a search resut in the schema.org vocabulary. You should use additional type which will help us differentiate between results that are products, categories, brands, or articles, or anything else that you might show on the search results page. See below for supported types.
position Position of the result in the results list.
name The user-visible title of the result
url Canonical URL of the result
offersOPTIONAL An Offer object indicating price

Supported itemListElement @types

We currently support these standard schema.org types:

instruments (filters)

Element Hint
instrumentREQUIRED This field contains an array of filters.
instrument.@typeREQUIRED Marks this as filter in schema.org vocabulary. Must be ChooseAction.
instrument.nameREQUIRED The filter name. You can use whatever name fits best.
instrument.actionOptionREQUIRED When a single filter has multiple values, you can include all values at once in an array.

Recommendation example

Sample JSON+LD document for Recommendation

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "SearchAction",
  "result": {
    "@type": "ItemList",
    "name": "Recommendation",
    "itemListElement": [
      {
        "@type": ["ListItem", "Product"],
        "position": "1",
        "name": "Android Phone PX100",
        "url": "https://myshop.com/products/236",
        "offers": {
          "@type": "Offer",
          "priceCurrency": "EUR",
          "price": "120"
        }
      },
      {
        "@type": ["ListItem", "Product"],
        "position": "2",
        "name": "iPhone X",
        "url": "https://myshop.com/products/293",
        "offers": {
          "@type": "Offer",
          "priceCurrency": "EUR",
          "price": "999"
        }
      }
    ]
  },
  "instrument": [{
    "@type": "ChooseAction",
    "name": "RecommenderClientId",
    "actionOption": "basket"
  },
  {
    "@type": "ChooseAction",
    "name": "ItemIds",
    "actionOption": ["/products/123", "/products/129"]
  }]
}
</script>

For the recommendation, you can use the following filters (and you must always include at least RecommenderClientId):

name actionOption
RecommenderClientIdREQUIRED Unique identifier of the recommender (recommender_client_identifier from recommendation result). Its value should define type of recommender user along with its position on the site (e.g., product_detail_bottom_alternatives).
ItemIdsoptional List of input items of a recommendation request (item_ids from recommendation request).
Recommenderoptional Name of the recommender (recommender from recommendation result).
Typeoptional Type of the recommender (recommendation_type from recommendation result).
_Variantoptional Determines variant in A/B testing (e.g., Original, Luigis).

Explicit trigger

An example to show you the logical flow of results rendering. In the example below, the script requests results asynchronously, then renders the results with annotations and only after the results are rendered, calls Luigis.Scan. You only need to use the Luigis.Scan call with appropriate arguments, the rest of the code is just for demonstration.

  <div id="search-results">
  <div>

  <script id="search-results-annotations" type="application/ld+json">
  </script>

  <script>
    $.get('/search/results?q=iphone', function(data) {
      renderResults(data);
      generateJsonLd(data);
      Luigis.Scan('#search-results-annotations', '#search-results');
    });
  </script>

After filling in the JSON+LD, you must call a notification function which will trigger data collection.

The notification function is called Luigis.Scan and accepts 2 arguments:

  1. Selector for annotations which must point to a <script> element with JSON+LD annotations

  2. Selector for an element which contains the actual user-visible search results. We need to find the actual search result elements so we can detect user interactions (clicks, conversions). This selector is optional, and by default set to body, meaning we are searching all body for search result elements, but we strongly suggest that you provide this selector as narrowly scoped as possible.

Since you are (and should be) loading the analytics script asynchronously, there is a possibility that when you call Luigis.Scan, the analytics script is not yet loaded and the function does not exist.

To prevent this situation, you must add the following code to the <head> element of your website.

<script>
  window._lbcq = [];
  window.Luigis = window.Luigis || {};
  window.Luigis.User = '...'; // Optionally, set your user identifier here; see below
  window.Luigis.Scan = window.Luigis.Scan || function(a, r) {
    window._lbcq.push([a, r]);
  }
</script>

The code will define a simple implementation of the Luigis.Scan function, which will just add "scan" commands to a queue. When the integration script is loaded it redefines the function with real implementation and processes the queued commands.

Defining the simple implementation early will allow you to load the integration script asynchronously, without impacting your page load speed.

This is also the place to customize user ID used for search analytics via window.Luigis.User property. Even though it is not necessary in most use cases, see User identifiers section for examples when it might be better for you.

Conversions

Conversion actions cannot be embedded directly in the JSON+LD document, so you'll need to add HTML data-action attributes to conversion elements. Make sure that you are adding the annotation to all places where users can convert. This usually includes:

Conversion place   Example
Search results list Add the data-action attribute to all buttons on the search results page. Most sites include an "Add to cart" button next to each product directly in the list of search results.
Product detail Add the data-action annotation to appropriate buttons on the product detail page. This will usually be an "Add to cart" button.

It is acceptable to use several different conversion types. Most common conversion types in e-commerce are:

Choose whatever names are convenient for you, you will be able to filter and segment on conversion type in Luigi's Box Search Analytics.

In some cases, you may want to send a negative conversion (i.e. user clicks a thumbs-down button). We use negative conversions as additional signal when learning optimal search ranking. Negative conversions must be annotated with a data-action-attitude="negative" annotation.




Conversion

<div data-action="buy">
    Add to cart
</div>
<div data-action="wishlist">
    Add to wishlist
</div>
Element Hint
data-action="buy" Clicks anywhere within the element will be considered as conversion action with type "buy".
data-action="wishlist" Clicks anywhere within the element will be considere as conversion action with type "wishlist".

Negative Conversion

<div data-action="thumbs-down" data-action-attitude="negative">
    Add to cart
</div>
Element Hint
data-action-attitude="negative" Clicks anywhere within the element will be considere as a negative conversion actions. Note that you must still include data-action attribute for the click to be considered (negative) conversion.

Autocomplete

Sample JSON+LD document for Autocomplete

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "SearchAction",
  "query": "phone",
  "result": {
    "@type": "ItemList",
    "name": "Autocomplete",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": "1",
        "name": "Android Phone PX100",
        "url": "https://myshop.com/products/236",
      },
      {
        "@type": "ListItem",
        "position": "2",
        "name": "iPhone X",
        "url": "https://myshop.com/products/293",
      }
    ]
  }
}
</script>

You should annotate Autocomplete results in the same way as regular search results. We recommend that you create a separate <script type="application/ld+json"> tag where you will describe your autocomplete results. When you update Autocomplete results, you should also update the JSON+LD document for the Autocomplete search. A good strategy is to assign the script tag containing the Autocomplete JSON+LD a special id attribute and then replace its contents with new JSON+LD when autocomplete results change.



Element Hint
name It is important that the name attribute is set to "Autocomplete" to mark this as an autocomplete list.

No search results

JSON+LD for no search results

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "SearchAction",
  "query": "skirt",
  "result": {
    "@type": "ItemList",
    "name": "Search Results",
    "itemListElement": []
  },
  "instrument": [{
    "@type": "ChooseAction",
    "name": "Color",
    "actionOption": "Black"
  }]
}
</script>

When your search returns no results, you need to add a json+ld markup anyway. You must annotate the query, the used filters and the search name (Search Results/Autocomplete). Since your search returned no results, set itemListElement to empty array.




Element Hint
itemListElement Notice that this attribute is present, but set to empty array since there are no results.

Infinite scrolling

When your site is using infinite scrolling, you should update the JSON+LD document for regular search results. It is not necessary to build JSON+LD document for all visible search results — only build the JSON+LD for the search results that were freshly loaded.

Using custom identifiers to pair analytics data with catalog

Changing the identifiers requires several changes.

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "SearchAction",
  "query": "phone",
  "result": {
    "@type": "ItemList",
    "name": "Search Results",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": "1",
        "name": "Android Phone PX100",
        "url": "https://myshop.com/products/236",
        "identifier": "SKU_9293"
      },
      {
        "@type": "ListItem",
        "position": "2",
        "name": "iPhone X",
        "url": "https://myshop.com/products/293",
        "identifier": "SKU_1929"
      }
    ]
  }
}
</script>

 1. Update the JSON+LD annotations to also include the item identifier. Use the identifier key for each item inside itemListElement. The value of the identifier will be used for pairing with catalog data and must be present in the catalog data.

<div data-lb-id="SKU_9293" class="product-result">
  <h1>Android Phone PX100</h1>
  <img src="thumbnail.png"/>
  <p class="description">A nice and reliable phone</p>
</div>
</script>

 2. Mark the HTML for each particular search result with the data-lb-id annotation. Mark the element that is wrapping the item "tile" in search results.

<html>
  <head>
    <meta property="lb:id" content="SKU_9293">
    ...
</script>

 3. On the item detail page (e.g. the product detail page), insert a <meta> tag in the <head> section to associate the item with the identifier.

All of the above changes are necessary to correctly link conversion attributions to searches using your provided item identifiers instead of URLs.

Inline schema.org annotations

A basic example

Simple product HTML code

<div id="product-6">
    <a href="/products/6?ref=search" class="prod-name">Milk</a>
    <span class="prod-price">25</div>
</div>

Inline schema.org annotations allow you to turn an unstructured HTML code, like the one in this example.

Machine-readable search results

<div itemscope itemtype="http://schema.org/SearchAction">
    <meta itemprop="query" content="phone">
    <div itemprop="result" itemscope itemtype="http://schema.org/ItemList">
      <meta itemprop="name" content="Search Results">
      <div id="product-6" itemprop="itemListElement" itemscope itemtype="http://schema.org/Product http://schema.org/ListItem">
        <a href="/products/6?ref=search" class="prod-name" itemprop="name">Milk</a>
        <meta itemprop="url" content="http://example.com/products/226">
        <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
            <span class="prod-price" itemprop="price">25</div>
            <meta itemprop="priceCurrency" content="EUR">
        </div>
      </div>
    </div>
    <div style="display:none !important;" itemprop="instrument" itemscope itemtype="http://schema.org/ChooseAction">
        <meta itemprop="name" content="Color">
        <meta itemprop="actionOption" content="Black">
    </div>
  </div>

Into a machine understandable code by adding just a few annotations (itemscope and itemprop) telling what it describes — a list of search results showing single product with a price.

This example is quite complex because it shows all concepts. The HTML annotations encode information about query, filters and search results. Please see the subsequent sections for more details.

Using annotations

The itemscope, itemtype and itemprop HTML attributes, you can add deeper meaning and structure to the data. When using the schema.org annotations, keep in mind these rules:

Nesting structure

  > SearchAction
      - itemprop="query"
      > itemprop="result", ItemList
          - itemprop="name"
          > itemprop="itemListElement", ListItem
              - itemprop="name"
              - itemprop="url"
              - itemprop="position"
              > itemprop="offers", Offer
                  - itemprop="price"
          > itemprop="itemListElement", ListItem
            ...
      > itemprop="instrument", ChooseAction
          - itemprop="name"
          - itemprop="actionOption"
      > itemprop="instrument", ChooseAction
          ...

Explicit trigger

An example to show you the logical flow of results rendering. In the example below, the script requests results asynchronously, then renders the results with annotations and only after the results are rendered, calls Luigis.Scan. You only need to use the Luigis.Scan call with appropriate arguments, the rest of the code is just for demonstration.

  <div id="search-results">
  <div>

  <script>
    $.get('/search/results?q=iphone', function(data) {
      renderResultsWithAnnotations(data);
      Luigis.Scan('#search-results', '#search-results');
    });
  </script>

After rendering the inline schema.org annotation, you must call a notification function which will trigger data collection.

The notification function is called Luigis.Scan and accepts 2 arguments:

  1. Selector which must point to an HTML element which contains the root SearchAction annotation.

  2. Selector for an element which contains the actual user-visible search results. We need to find the actual search result elements so we can detect user interactions (clicks, conversions). This selector is optional, and by default set to body, meaning we are searching all body for search result elements, but we strongly suggest that you provide this selector as narrowly scoped as possible.

Since you are (and should be) loading the analytics script asynchronously, there is a possibility that when you call Luigis.Scan, the analytics script is not yet loaded and the function does not exist.

To prevent this situation, you must add the following code to the <head> element of your website.

<script>
  window._lbcq = [];
  window.Luigis = window.Luigis || {};
  window.Luigis.User = '...'; // Optionally, set your user identifier here; see below
  window.Luigis.Scan = window.Luigis.Scan || function(a, r) {
    window._lbcq.push([a, r]);
  }
</script>

The code will define a simple implementation of the Luigis.Scan function, which will just add "scan" commands to a queue. When the integration script is loaded it redefines the function with real implementation and processes the queued commands.

Defining the simple implementation early will allow you to load the integration script asynchronously, without impacting your page load speed.

This is also the place to customize user ID used for search analytics via window.Luigis.User property. Even though it is not necessary in most use cases, see User identifiers section for examples when it might be better for you.

Query

Schema.org annotations require strict nesting. The markup that defines search must be placed somewhere on top of the page so the results can be nested inside.

To annotate query, find a suitable HTML element which includes the list of results and place the query markup.

If your site has autocomplete, make sure that the autocomplete results/suggestions are not nested under the same SearchAction as the regular search. It's usually not a problem, since most of the autocomplete libraries place the HTML markup for autocomplete results at the bottom of the HTML, just before the closing body tag.




Query markup

<body>
    <nav></nav>
    <section class="container-fluid">
        <input type="text" name="q" value="phone"/>
        <div itemscope itemtype="http://schema.org/SearchAction">
            <meta itemprop="query" content="phone">
            <div itemprop="result" itemscope itemtype="http://schema.org/ItemList"></div>
    </section>
</body>
Element Hint
SearchAction This is a base schema.org entity encapsulating search. Everything related to search - query, results and filters must be nested under this entity.
itemprop="query" We are showing the query back to the user in the text input field. It is repeated here just for the purpose of annotation. There's no need to show the query twice — we used an invisible meta tag.
ItemList Results should be nested somewhere inside this SearchAction.

Results

Make sure that in the HTML structure, the ItemList is nested under SearchAction.

Sometimes the user can switch view type from list to grid. Make sure that items in all views are annotated.

Results markup

<div  itemprop="result" itemscope itemtype="http://schema.org/ItemList">
  <meta itemprop="name" content="Search Results">
  <a href="/products/236"  itemprop="itemListElement" itemscope itemtype="http://schema.org/Product http://schema.org/ListItem">
    <meta itemprop="position" content="1">
    <div itemprop="name">Android Phone PX100</div>
    <meta itemprop="url" content="https://myshop.com/products/236">
    <div style="display:none !important;" itemprop="offers" itemscope itemtype="http://schema.org/Offer">
      <meta itemprop="priceCurrency" content="EUR">
      <meta itemprop="price" content="120.00">
    </div>
  </a>
  <a href="/products/237"  itemprop="itemListElement" itemscope itemtype="http://schema.org/Product http://schema.org/ListItem">
    <meta itemprop="position" content="2"></a>
  <a href="/products/123"  itemprop="itemListElement" itemscope itemtype="http://schema.org/Product http://schema.org/ListItem">
    <meta itemprop="position" content="3"></a>
</div>
Element Hint
ItemList This indicates a list of results. Notice that there are actually two things happening at once. We are annotating a result itemprop of the parent SearchAction and at the same time declare that the results are an ItemList entity.
meta itemprop="name" A name for the list of search results. For search analytics purposes, this needs to be set to "Search Results" or "Autocomplete".
ListItem This indicates an item in the list of search results — a search result. Each result has two schema.org types. It is a Product but also a ListItem at the same time. We only require that the item has ListItem type. You can also add additonal types, such as Product. Consult schema.org reference for list of supported types.
position Invisible element with the result position. When you use pagination, this should be the position in the paginated list, e.g., assuming pages of 10 items, the first item on a 2nd page should be 11.
name Product name - this is what you'll see in Luigi's Box dashboards.
url Use canonical product URL here. Do not include tracking parameters, query, or other parameters that have no effect on which product is displayed. It's ok to use those parameters on the user clickable URL though, just don't use them here.
offers schema.org requires that the price is nested inside Offer itemtype.
price Make sure that the price parses as a floating point number. That means using dot (.) instead of comma (,) to separate the fractional part of the price. E.g., "24,3" is wrong and "24.3" is correct. Also using currency symbols here is invalid, the currency is already specified.

Filters

Filters can get complicated very fast. When you use facets, price range selectors, sorting etc., there can be many filters each with a different HTML structure. You could annotate the filters in-place, but there is a much easier way. Put the markup for all active filters in a single place, inside invisible elements, somewhere inside the SearchAction.

When you have a multi-value filter, e.g., a facet with checkboxes, just repeat the actionOption meta tag.

Make sure that in the HTML structure, the ChooseActions are nested under SearchAction.

Filters markup

<div style="display:none !important;" itemprop="instrument"
     itemscope itemtype="http://schema.org/ChooseAction">
    <meta itemprop="name" content="Color">
    <meta itemprop="actionOption" content="Black">
</div>
<div style="display:none !important;" itemprop="instrument"
     itemscope itemtype="http://schema.org/ChooseAction">
    <meta itemprop="name" content="Price">
    <meta itemprop="actionOption" content="20-800">
</div>
<div style="display:none !important;" itemprop="instrument"
     itemscope itemtype="http://schema.org/ChooseAction">
    <meta itemprop="name" content="Sort">
    <meta itemprop="actionOption" content="Price">
</div>


Element Hint
instrument Each filter is an instrument of the SearchAction.
ChooseAction Each filter is a nested schema.org entity — a ChooseAction.
name The name itemprop relates to the ChooseAction scope and is a name of the filter. The value can be arbitrary, whatever you use, we'll show you in Luigi's Box analytics verbatim, so it's best to use a name that will make sense to you later.
ActionOption The filter value. Again, the value is arbitrary, but it's best to use the exact same value that the user is seeing.



Multi-value filters

<div style="display:none !important;" itemprop="instrument"
     itemscope itemtype="http://schema.org/ChooseAction">
    <meta itemprop="name" content="Brand">
    <meta itemprop="actionOption" content="Samsung">
    <meta itemprop="actionOption" content="Lenovo">
    <meta itemprop="actionOption" content="Apple">
</div>
Element Hint
actionOption/ChooseAction It's ok to repeat actionOptions, but it's not ok to have multiple ChooseAction-s with the same name.

Autocomplete

The process of annotating autocomplete results is the same as with the regular Search Results.

You must create a separate SearchAction that cannot be nested inside the SearchAction for regular Search Results.


Autocomplete

<div itemscope itemtype="http://schema.org/SearchAction">
  <div itemprop="query">phone</div>
  <div itemprop="instrument" itemscope itemtype="http://schema.org/ChooseAction">
    <meta itemprop="name" content="category">
    <meta itemprop="actionOption" content="Phones">
  </div>
  <div itemprop="result" itemscope itemtype="http://schema.org/ItemList">
    <meta itemprop="name" content="Autocomplete">

    <a href="/products/236"  itemprop="itemListElement" itemscope itemtype="http://schema.org/Product http://schema.org/ListItem">
      <meta itemprop="position" content="1">
      <div itemprop="name">Android Phone PX100</div>
      <meta itemprop="url" content="https://myshop.com/products/236">
      <div style="display:none !important;" itemprop="offers" itemscope itemtype="http://schema.org/Offer">
        <meta itemprop="priceCurrency" content="EUR">
        <meta itemprop="price" content="120.00">
      </div>
    </a>

    <a href="/products/237"  itemprop="itemListElement" itemscope
   itemtype="http://schema.org/Product http://schema.org/ListItem"></a>
    <a href="/products/123"  itemprop="itemListElement" itemscope
   itemtype="http://schema.org/Product http://schema.org/ListItem"></a>
  </div>
</div>


Element Hint
SearchAction Autocomplete annotations must be nested under their own SearchAction.
ChooseAction On the rare occasion that your autocomplete uses filters, include them here. See the image above for an example of autocomplete with filters.
name We name the filter 'category'.
ItemList The ItemList must be named Autocomplete.
position Autocomplete items are usually not paginated, so the explicit item position is not necessary.
offersOPTIONAL If your autocomplete items show prices, feel free to anotate them, they are not required in this context though.

No results

No results markup

<div itemscope itemtype="http://schema.org/SearchAction">
  <div itemprop="query">phone</div>
  <div itemprop="instrument" itemscope itemtype="http://schema.org/ChooseAction">
    <meta itemprop="name" content="category">
    <meta itemprop="actionOption" content="Phones">
  </div>
  <div itemprop="result" itemscope itemtype="http://schema.org/ItemList">
    <meta itemprop="name" content="Search Results">
    <span>No results found!</span>
  </div>
</div>

When your search returns no results, you need to add schema.org annotations anyway. You must annotate the query, the used filters and the search name (Search Results/Autocomplete). Since your search returned no results, do not include any ListItems.

Make sure that you include the ItemList scope though and set the correct name itemprop (either Search Results or Autocomplete).

Conversions

Use a FindAction from schema.org to annotate conversion elements. Any element where click should be tracked can be annotated in the following manner, either in search results or on individual product pages:

There are usually many different types of conversions found at different places.

Conversion place   Example
Search results list Add the FindAction annotations to all buttons on the search results page. Most sites include an "Add to cart" button next to each product directly in the list of search results.
Product detail Add the FindAction annotation to appropriate buttons on the product detail page. This will usually be an "Add to cart" button.

It is acceptable to use several different conversion types. Most common conversion types in e-commerce are "buy" and "wishlist". Choose whatever names are convenient for you, you will be able to filter and segment on conversion type in Luigi's Box Search Analytics.

Conversions markup

<button type="submit" itemscope itemtype="http://schema.org/FindAction">
  <meta itemprop="name" content="buy">
  <span>
      Add to cart
  </span>
</button>
Element Hint
FindAction Each conversion must be nested under a FindAction. Think about it as an action you can do when you have found what you have been looking for.
name Name of the action can be replaced with any text of choice, e.g. "favorite", "wishlist".



In some cases, you may want to send a negative conversion (i.e. user clicks a thumbs-down button). We use negative conversions as additional signal when learning optimal search ranking.



Negative conversions markup

<button type="submit" itemscope itemtype="http://schema.org/FindAction">
  <meta itemprop="name" content="thumbs-down">
  <meta itemprop="additionalType" content="http://schema.org/DislikeAction">
  <span >
      Add to cart
  </span>
</button>
Element Hint
name You can choose any conversion name that you like.
additionalType To mark the conversion as negative, set additionalType to DislikeAction.

Individual products

Although we do not need to know about individual products on their product pages apart from the annotations of the converting actions (buy, etc.), it may be beneficial to also annotate product pages, since external search engines and other tools do use them. Refer to example in the intro. Further information can be found here:

https://developers.google.com/search/docs/data-types/products

User identifiers

By default Luigi's Box Search Analytics script assigns each user a unique identifier and saves it in a _lb cookie. We use this cookie to count various usage metrics in Luigi's Box application and it serves also as a basis for personalization of search and recommendation services. However, there are some use cases when it might be better to use your own unique user identifiers:

  1. If you would like to integrate our Search as a Service with personalization enabled or Recommender on backend without using our JavaScript libraries, using you own unique user identifiers enables you to use the services up to their full potential by sending user identifier also for the first visit of a user, when you do not have our _lb cookie identifier available on your backend.
  2. If you know that most or all of your users are logged in while browsing your site, you may leverage your user identifiers to get insight into their behavior cross-device.

If you would like to set your own unique user identifiers add the following code to the element of your website.

<script>
  window._lbcq = [];
  window.Luigis = window.Luigis || {};
  window.Luigis.User = '...'; // Your user identifier goes here
  window.Luigis.Scan = window.Luigis.Scan || function(a, r) {
    window._lbcq.push([a, r]);
  }
</script>

If the window.Luigis.User property is not set or empty the default behavior will be triggered and Luigi's Box Search Analytics script will assign a unique identifier as describe.

The window._lcbq and window.Luigis.Scan are part of asynchronous loading of the script and help to make sure everything works even in case Luigi's Box Search Analytics script is not yet loaded. See more details when implementing via embedded JSON+LD or via inline Schema.org annotations.

Frequent problems

Here's a list of frequent problems that we have noticed with implementation.

Troubleshooting

While you are developing the integration, we recommend that you turn on data linter to see debugging information. Make sure that Luigi's Box integration script is included in the page and then, in your web browser, open the developer console (usually by pressing the F12 key), find the "Console" tab and type in the following command: Luigis.lint = true

After that, reload the page, but do not close the developer console. Each time, the integration collects search-related data, you will see what was parsed from your site and you'll get a report about data quality in the console tab of the developer tools.

If you've done everything correctly, you should see a blue Luigi's Box logo. If there were some problems with the data, you will see a red logo and a list of errors.

If you are not seeing the linter messages and logos, the most probable cause is that you are already running an early version of integration that does not support linting. Let us know and we will upgrade your integration.

Left side: no errors found.  Right side: linter found some errors.

Support

Troubles? Different nesting? Cannot get it to work? Contact us at support@luigisbox.com, we are glad to help!