# Kibble installation contract: section 14 of 29 (guide version 3)

> This is **one section only**, not the whole contract. Complete document: https://www.usekibble.dev/AI/guide.md

## 14. Site search {#search}

Site search is never detected automatically. Do not invent a site search the site does not have. When the site has one, call `search` once per search the visitor ran, after the results render.

```js
// results = the real count when known; leave it undefined when unknown; 0 only when the search really returned none.
kibble('search', { phrase: query, results: count, scope: 'site' });   // phrase goes via the separate pipeline
```

- `scope` is `site` by default; other values are `catalog` (a product or catalog search, counted as site search) and `availability` (a booking engine's stay-date or tee-time request, which Kibble reports as availability demand and never as site search or in the search terms table).
- Do not call it for every keystroke of a suggest box and again for the final submit. That counts one search twice.
- A booking widget is not site search. Do not use `search` for booking criteria.

### Send the result count

The no-result share only counts visits whose searches came with a result count, so pass `results` whenever the page knows how many results it shows. Never guess it: a search sent without a count is counted as a search, while a made-up 0 would be counted as a search that found nothing. Data tracking → Events shows how many visits using site search reported a count.

- Report the search where the results page renders, with the count that page has. A suggestion dropdown (predictive search) is not a search: do not call `search` for suggestions; its "view all results" link leads to the results page, which reports the search.
- Paginated results: report the search once, on the first page.
- GA4 `search` / `view_search_results` dataLayer events are not mapped by the SDK (their recommended parameters carry the term, never a count). Call `search` from the site's code instead.

Shopify theme (Liquid), in the search results section or template, after the website snippet:

```html
{%- if search.performed and current_page == 1 -%}
<script>
  window.kibble=window.kibble||function(){(window.kibble.q=window.kibble.q||[]).push(arguments)};
  // search.results_count is Shopify's count for this search (0 when nothing matched).
  kibble('search', { phrase: {{ search.terms | json }}, results: {{ search.results_count | json }}, scope: 'site' });
</script>
{%- endif -%}
```

Shopify Hydrogen: subscribe to search_viewed inside <Analytics.Provider>:

```tsx
// The search route must pass the result object: <Analytics.SearchView data={{ searchTerm: term, searchResults: result }} />
// (the regular search result's total is the count). With only searchTerm there is no count.
import { useEffect } from 'react';
import { useAnalytics } from '@shopify/hydrogen';

export function KibbleSearch() {
  const { subscribe } = useAnalytics();
  useEffect(() => {
    subscribe('search_viewed', (p: any) => {
      const phrase = String(p?.searchTerm ?? '').trim();
      if (!phrase) return;
      const total = p?.searchResults?.total;
      window.kibble('search', Number.isInteger(total) && total >= 0 ? { phrase, results: total, scope: 'site' } : { phrase, scope: 'site' });
    });
  }, [subscribe]);
  return null;
}
```

### Report a failed search

A search that errored (the search service failed, timed out or returned an error page) is not a search that found nothing. Report it with `error: true` and no `results`. Kibble shows it as its own "Search errors" figure, and it is never counted as a no-result search. A site that never sends `error` shows "Not tracked", not zero.

```js
kibble('search', { phrase: query, error: true, scope: 'site' });   // the search itself failed; send no results
```

### The phrase pipeline

- Two things are sent. A `search` event (scope and whether results were found) goes in the normal event stream with no phrase text. The phrase itself goes in a separate request, `POST /v1/phrases`, which carries **no browser, stream, visit or event identifier**.
- In the browser the phrase is lowercased and dropped if it contains an email or a run of nine or more digits. The same phrase is sent at most once per page view: a repeat on the same page (ignoring case, spacing and punctuation) is dropped before it leaves the browser, while the `search` event is still sent each time. Phrases are sent 5 to 10 seconds later.
- On the server, phrases that look like personal or booking details (names, passwords, card or confirmation numbers and similar) are discarded, and URLs, phone numbers and long digit runs are redacted.
- Phrases are stored as daily counts together with their scope. A phrase is shown only once it was searched at least the minimum number of times (10 by default) in the period shown; its no-result count is shown only when that part also reaches the minimum. This counts searches, not people: because a phrase is sent once per page view, one visitor repeating a search on one page counts once, but searching it again on other pages counts again.
- Phrases sent from a browser in a Validation (Site Check) session are test traffic: the collector does not store them.
- Phrases waiting in the browser are kept per tab and are lost if the tab closes before they are sent.

To check it, see the site-search step in [Verify the installation](https://www.usekibble.dev/AI#verify).

END OF SECTION search
