Marking Rendered Search Results

The HTML contract that lets the Data Collector measure autocomplete and search results you render yourself.

When you call the Perselio Search API and render autocomplete and the search results page (SERP) yourself, the Data Collector cannot see your API traffic. It only sees the rendered page. A lightweight DOM contract tells it which parts of the page are Perselio search, which query produced them, and which items they show.

Without it, search performance — click-through rate, add-to-cart and revenue from search, searches without results — cannot be reported, and search cannot learn from what visitors click.

The contract covers both search surfaces:

  • Autocomplete — suggestions shown while the visitor types.
  • Search results page — the full list of results after the visitor submits a query.
📘

If your markup already carries some of this information under different attributes, tell us. The collector is configured per site and can read what is already there — the attributes below are the recommended convention, not a requirement to restructure your templates.

1. Mark the search surface

Add the attributes to the element that wraps the results — the autocomplete panel and the results list:

<div data-per-search="autocomplete" data-per-search-query="push-up bra">
	<!-- suggestions rendered by you -->
</div>

<div data-per-search="serp" data-per-search-query="push-up bra">
	<!-- results rendered by you -->
</div>
  • data-per-search — required. Marks the element as a Perselio search surface. Value: autocomplete or serp.
  • data-per-search-query — required. The query that produced the results currently shown, exactly as you sent it to the API in searchQuery.
❗️

The query must belong to the results on screen, not to the input. While the visitor types, the search input is always ahead of the rendered suggestions. Set data-per-search-query in the same render that replaces the results — never copy the live input value.

If the autocomplete panel is rendered outside the search form — for example appended to <body> — put the attributes on the panel itself.

2. Group autocomplete results

Autocomplete usually shows several kinds of results side by side. Wrap each group and say what it contains:

<div data-per-search="autocomplete" data-per-search-query="push-up bra">
	<div data-per-search-group="products">…</div>
	<div data-per-search-group="categories">…</div>
	<div data-per-search-group="brands">…</div>
	<div data-per-search-group="articles">…</div>
	<div data-per-search-group="queries">…</div>
</div>

Values: products, categories, brands, articles, queries. Use only the groups you render. Positions are counted within each group, so the first category is position 1 even when it is shown below five products.

On the search results page, groups are needed only if you show more than the item list — for example a block of matching categories above the results.

3. Identify every result

Every rendered result carries its identifier. Search returns more than items, so results other than items also say what they are:

<!-- item -->
<div class="product-card" data-per-item-id="12345">…</div>

<!-- other entities -->
<a data-per-item-type="category" data-per-item-id="Women > Lingerie > Bras" href="…">Bras</a>
<a data-per-item-type="brand" data-per-item-id="Triumph" href="…">Triumph</a>
<a data-per-item-type="article" data-per-item-id="size-guide" href="…">Size guide</a>
<a data-per-item-type="query" data-per-item-id="push-up bra black" href="…">push-up bra black</a>
Resultdata-per-item-typedata-per-item-id
Itemomit (default)the item ID, same as everywhere else on your site — see Identifying Products on Your Site
Categorycategorythe category value returned by the API (facetCounts value, or id from the category scenario)
Brandbrandthe brand value returned by the API (facetCounts value, or id from the brand scenario)
Articlearticlethe article id returned by the API — the same ID as in the Text Feed
Suggested queryquerythe suggested query text

Put the attributes on the root element of the result — the element that wraps its link, image and name. For an item this is the card itself, exactly as on a category listing.

Position on paginated results

If the results page is paginated or uses "load more", add the absolute position of each result across all pages:

<div class="product-card" data-per-item-id="12345" data-per-position="25">…</div>

The first result of page 2 with 24 results per page is 25. Without pagination, the position is read from the order of the results and the attribute is not needed.

4. Mark the no-results state

Keep the surface wrapper on the page even when the API returns no results, and mark the message you show instead:

<div data-per-search="serp" data-per-search-query="xyzzy">
	<p data-per-search-no-results>We found nothing for "xyzzy".</p>
</div>

Searches without results are among the most useful signals for tuning — synonyms and redirects in Search Curation start from them.

5. Mark the search box

Each submitted search is recorded: pressing Enter, clicking the search button, or clicking "show all results" in autocomplete.

<form data-per-search-form action="/search">
	<input type="search" name="q" />
	<button type="submit">Search</button>
</form>

<a data-per-search-show-all href="/search?q=push-up+bra">Show all results</a>
  • data-per-search-form — the form containing the search input.
  • data-per-search-show-all — the link from autocomplete to the full results page.

Recommendations inside search

Recommended items shown within a search surface — for example popular items in autocomplete before the visitor types, or recommendations on a page without results — are recommendations, not search results. Mark their block as described in Marking Rendered Recommendations.

Prerequisites

The Data Collector script must be present on the page. It observes the marked surfaces and sends the events — it is required even when every search API call is made from your backend.

Validation checklist

  • Autocomplete and the results page each carry data-per-search on the element that wraps the results.
  • data-per-search-query matches the query of the results currently shown, including while the visitor types.
  • Each autocomplete group carries data-per-search-group.
  • Every result carries data-per-item-id; results other than items also carry data-per-item-type.
  • Paginated results carry data-per-position with the absolute position.
  • The no-results state keeps the wrapper and marks the message with data-per-search-no-results.
  • The search form and the "show all results" link are marked.
  • The attributes are present before the surface becomes visible.

Related


Did this page help you?