Documentation

Integration manual

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:

  • Google, Bing, Yahoo and other search engines use them to show rich snippets about your product directly on the search results page. Compare the following results and see how the first one with metadata stands out. Using schema.org is an amazing way to draw attention to your listing and stand out from the competition.
  • Apple’s Siri and Google’s Assistant can use them to answer questions about shopping. “Hey Siri, where can I buy a new phone?”
Search results

A basic example

Simple product
<div id="product-123">
    <div class="prod-name">Milk</div>
    <span class="prod-price">25</div>
</div>

can be made readable by software by adding just a few simple annotations telling what it describes — a product with a price:

Machine-readable product
<div id="product123" itemscope itemtype="http://schema.org/Product">
    <div class="prod-name" itemprop="name">Milk</div>
    <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
        <span class="prod-price" itemprop="price">25</div>
        <meta itemprop="priceCurrency" content="EUR">
    </div>
</div>
Hints
These two HTML attributes mark that this page (and nested HTML) contains a product. There are many types of entities that you can declare (e.g., an Article, Book, Recipe…), but in the context of e-shop, we'll work with just the Product entity.
This itemprop attribute declares that the Product you are annotating has a name and it is Milk. There are multiple ways that you can add value to an attribute, the easiest way is to use the textContent of the HTML node, like we did in this example.
Annotating the price is a bit more complicated since it involves declaring a price as a numeric value, and declaring the currency the price is in. Schema.org reserves a special itemtype Offer for this purpose.
This declares a price using the same mechanism as we used for product name annotation.
We were not displaying the currency in the original markup, so let's not display it in the annotated machine-readable version either. The meta HTML tag is hidden by default. Also notice that unlike in the previous examples, we are assigning the priceCurrency value not by using a textContent of the meta element, but instead by using another attribute called content.

The example above has shown all the basics. Using annotations:

  • itemscope & itemtype – You tell that the element describes an item of a type (e.g, a Product) and its descendant nodes describe its properties.
  • itemprop – You tell that this element contains a property of the parent item. This can be a “text content” of a user visible element (price in the example), or it can be defined using an invisible meta or link element (currency in the example).

Annotating search results

To successfully process your search in Luigi's Box, we need to know:

  • The query the user entered — if it's not empty
  • Filters the user used, e.g. 'search only books', 'search only items in stock', or 'sort the results by price'.
  • The results and for each result, we need to know it's title, URL address, price and position in the list of results.

The following sections will guide you through the annotation process.

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.

Find a suitable HTML element which includes the list of results and place the following markup.

Query
<body>
    <nav>…</nav>
    <section class="container-fluid">
        <input type="text" name="q" value="phone"/>
        <div itemscope itemtype="http://schema.org/SearchAction">
            <meta itemprop="query">phone</meta>
            <div itemprop="result" itemscope itemtype="http://schema.org/ItemList">
                …
            </div>
    </section>
</body>
Hints
SearchAction is a base schema.org entity encapsulating search. Everything related to search - query, results and filters must be nested under this entity.
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. You can also use the content attribute, e.g.:
<meta itemprop="query" content="phone">
Results should be nested somewhere inside the SearchAction (they don't need to have a class '.result', it's just an example here).

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.

Results

Results
<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>
Hints
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.
A name for the list of search results. For search analytics purposes, this needs to be set to "Search Results".
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.
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.
Product name - this is what you'll see in Luigi's Box dashboards.
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.
schema.org requires that the price is nested inside Offer itemtype.

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.

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.

Here's what you should know about filters:

  • 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 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.
Filters

Take a look at the picture above. There are 4 filters: Color, Price, Brand and Sort by. Notice that the Brand filter is unused so there is no need to annotate it. In this case, you should send: Color: Black, Price: 20-800, Sort: Price.

Filters
<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>
Hints
Each filter is an instrument of the SearchAction.
Each filter is a nested schema.org entity — a ChooseAction.
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.
The actionOption is a filter value. Again, the value is arbitrary, but it's best to use the exact same value that the user sees.
Here we have declared a filter called 'Sort'. The name is arbitrary, but notice that even a sort is a filter. Anything that has influence on which items you show is a filter.

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

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>
Hints
It's ok to repeat actionOptions, but it's not ok to have multiple ChooseAction-s with the same name.

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

Autocomplete

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

  • You must create a separate SearchAction that cannot be nested inside the SearchAction for regular Search Results.
  • Autocomplete is usually not paginated, so you can safely skip the position annotations.
  • In case your autocomplete results don't show prices, you can safely skip the Offer entity.
  • Sometimes autocomplete can have filters too. See the image below for a an example. In this case, you must include this filter in the markup.
Searchbox
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>
Hints
Autocomplete annotations must be nested under their own SearchAction.
On the rare occasion that your autocomplete uses filters, include them here. See the image above for an example of autocomplete with filters.
In the image above, you can see that the filter has no visible name, but we name it 'category'.
The ItemList must be named Autocomplete.
Autocomplete items are usually not paginated, so the explicit item position is not necessary.
If your autocomplete items show prices, feel free to anotate them, they are not required in this context though.

Conversions

Everything that is important to you from the business perspective can be tracked as “conversion”, regardless whether it is an action of “buying” an item, “liking” it, “favoriting” it, or anything else. For this, we use a FindAction. Any element where click should be tracked can be annotated in the following manner, either in search results or on individual product pages:

Conversions
<button type="submit" itemscope itemtype="http://schema.org/FindAction">
    <meta itemprop="name" content="buy">
    <span …>
        Add to cart
    </span>
</button>
Hints
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 of the action can be replaced with any text of choice, e.g. "favorite", "wishlist".

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

  • It is usually possible to convert both from the list of search results and the product detail itself. You should annotate both conversion actions.
  • It's ok to have several conversion actions for the same product, e.g. buy a product, or add a product to favorite list. Annotate each one with a different name.

Product listings

Any display of product list which is of interest is considered “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 can be annotated in the manner of the above example (leaving the query out ). It will be collected and analyzed.

Individual products

Although our collector framework does not need to know about individual products on their product pages apart from the annotations of the converting actions (buy, etc.), since we derive all the attributes from search results we saw, 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.

Support

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