> ## Documentation Index
> Fetch the complete documentation index at: https://www.algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Keep track of query IDs

> Track query IDs across pages so conversion events attribute to the originating search.

export const SearchQuery = () => <Tooltip tip="The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.">
    search query
  </Tooltip>;

export const Index = () => <Tooltip tip="An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.">
    index
  </Tooltip>;

export const Application = () => <Tooltip tip="An Algolia application is a self-contained environment with its own indices, configuration, and API keys. Applications don't share data or settings with each other.">
    application
  </Tooltip>;

Often, conversions start with a <SearchQuery /> and finish outside the search results page,
such as on a shopping cart or checkout page.

To link conversions to a preceding search, Algolia generates a query ID, a unique identifier for a search query.
If you set `clickAnalytics` to `true`, the search response includes the `queryID`.

Users often reach the conversion page from several searches,
so the page doesn't know which search produced each item.
Each item may need the query ID of a different search.
To attribute conversions correctly, capture and send the right query ID with each event.

## Get a query ID for every search

If you use InstantSearch or Autocomplete,
set the `insights` option to `true`:

* [Autocomplete](/doc/ui-libraries/autocomplete/guides/sending-algolia-insights-events#enable-the-insights-option)
* [InstantSearch](/doc/guides/sending-events/instantsearch/send-events#enable-events-collection)

If you use one of the API clients,
set the [`clickAnalytics`](/doc/api-reference/api-parameters/clickAnalytics) parameter to `true`:

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.SearchSingleIndexAsync<Hit>(
    "INDEX_NAME",
    new SearchParams(new SearchParamsObject { ClickAnalytics = true })
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.searchSingleIndex(
    indexName: "INDEX_NAME",
    searchParams: SearchParamsObject(
      clickAnalytics: true,
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
    "INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
    search.NewEmptySearchParamsObject().SetClickAnalytics(true))))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  SearchResponse response = client.searchSingleIndex("INDEX_NAME", new SearchParamsObject().setClickAnalytics(true), Hit.class);
  ```

  ```js JavaScript theme={"system"}
  const response = await client.searchSingleIndex({ indexName: 'indexName', searchParams: { clickAnalytics: true } });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.searchSingleIndex(
      indexName = "INDEX_NAME",
      searchParams = SearchParamsObject(clickAnalytics = true),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->searchSingleIndex(
      'INDEX_NAME',
      ['clickAnalytics' => true,
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.search_single_index(
      index_name="INDEX_NAME",
      search_params={
          "clickAnalytics": True,
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.search_single_index(
    "INDEX_NAME",
    Algolia::Search::SearchParamsObject.new(click_analytics: true)
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.searchSingleIndex(
      indexName = "INDEX_NAME",
      searchParams = Some(
        SearchParamsObject(
          clickAnalytics = Some(true)
        )
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response: SearchResponse<Hit> = try await client.searchSingleIndex(
      indexName: "INDEX_NAME",
      searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(clickAnalytics: true))
  )
  ```
</CodeGroup>

## Automatic query ID inference for conversion events

If a conversion event and an earlier click event share the same [user token](/doc/guides/sending-events/concepts/usertoken), <Application />, <Index />, and `objectID`, Algolia automatically infers the `queryID`.
This happens when Algolia processes events.
You don't need to configure anything.

This means you might not need to track the query ID manually.
For best accuracy, set the `queryID` whenever possible with one of the following methods.

## Match the query ID requirements of your conversion events

Which query IDs you need to keep, and for how long, depends on the events you send when users convert:

* [`convertedObjectIDsAfterSearch`](/doc/libraries/search-insights/converted-object-ids-after-search) accepts one `queryID` for the whole event.
  All `objectIDs` in the event must come from the same search.
* [`addedToCartObjectIDsAfterSearch`](/doc/libraries/search-insights/added-to-cart-object-ids-after-search) and [`purchasedObjectIDsAfterSearch`](/doc/libraries/search-insights/purchased-object-ids-after-search) accept a `queryID` per item in the `objectData` array.
  Use this for events with items that originate from different searches, such as a cart checkout.
  [Revenue analytics](/doc/guides/search-analytics/concepts/query-aggregation#revenue-transactions) doesn't include items without a `queryID`.

For a conversion event with a single item, or items from one search, one query ID is enough.
For a purchase event with items from several searches, keep a query ID for each item.

## Track query IDs across pages

Choose one of the following methods to track the query ID from a search until the user converts.

### Track query IDs as URL parameters

Add the `queryID` as a URL parameter to the URL of the destination page.
For example, on your InstantSearch search results page:

<CodeGroup>
  ```js JavaScript theme={"system"}
  search.addWidgets([
    instantsearch.widgets.hits({
      templates: {
        item: (item, { html }) =>
          html`...`
      }
    })
  ]);
  ```

  ```jsx React theme={"system"}
  const Hit = ({ hit }) => (
    <article>
      <a href={`/item.html?queryID=${hit.__queryID}`}>
        {/* ... */}
      </a>
    </article>
  );
  ```

  ```vue Vue theme={"system"}
  <ais-hits>
    <template v-slot:item="{ item }">
      <a href="/item.html?queryID={{item.__queryID}}">
        ...
      </a>
    </template>
  </ais-hits>
  ```
</CodeGroup>

To read the `queryID` from the URL, use the [URL API](https://developer.mozilla.org/en-US/docs/Web/API/URL/searchParams):

```js JavaScript icon=code theme={"system"}
const url = new URL(window.location.href)
const searchParams = url.searchParams

const queryID = searchParams.get('queryID')
```

URL parameters are the most reliable way to match conversion events to the correct search.
For example, users might open several detail pages from a listing page or search results.
This pattern is sometimes called [page parking](https://www.nngroup.com/articles/multi-tab-page-parking/).
Because each URL carries one `queryID`, this method works for conversions on the destination page itself.
A shared page, such as a checkout page, can hold items from several searches.
For conversions there, store a query ID for each item with one of the following methods.

When you use URL parameters to track the query ID,
follow [SEO best practices](/doc/guides/building-search-ui/resources/seo/js).
For more advice, read the article [URL parameter handling](https://www.searchenginejournal.com/technical-seo/url-parameter-handling/) in the Search Engine Journal.

### Automatically track query IDs

If you use InstantSearch or Autocomplete, you don't need to track query IDs yourself.
Both libraries track the query ID when users click search results.
If you [pass `inferQueryID: true` for each call](/doc/libraries/search-insights/send-events#param-infer-query-id),
[`search-insights`](/doc/libraries/search-insights) automatically includes the `queryID` in the payload of conversion events.

### Track query IDs in cookies or local storage

If you can't use the automatic query ID tracking in InstantSearch or Autocomplete, build a similar pattern yourself.
Track the query ID in a cookie, `localStorage`, or `sessionStorage`.

<Warning>
  Don't overwrite a single stored query ID on every search.
  If users open several detail pages from different searches, a single stored value attaches the wrong query ID to conversion events.
  This skews attribution.
</Warning>

When a user clicks a result and a query ID is available, write an entry to a cache of query IDs.
Key each entry by index name and `objectID`:

```js JavaScript icon=code theme={"system"}
// Store the `queryID` for a specific index and `objectID`
function storeQueryID(index, objectID, queryID) {
  const cache = JSON.parse(localStorage.getItem("objectQueryCache") || "{}");
  cache[`${index}:${objectID}`] = [queryID, Date.now()];
  localStorage.setItem("objectQueryCache", JSON.stringify(prune(cache)));
}

// Retrieve the `queryID` for a specific index and `objectID`
function getQueryID(index, objectID) {
  const cache = JSON.parse(localStorage.getItem("objectQueryCache") || "{}");
  return cache[`${index}:${objectID}`]?.[0];
}

// Keep the newest 5,000 entries, using the stored timestamps
function prune(cache) {
  const entries = Object.entries(cache);
  if (entries.length <= 5000) return cache;
  entries.sort(([, [, a]], [, [, b]]) => b - a);
  return Object.fromEntries(entries.slice(0, 5000));
}
```

Store a timestamp alongside each `queryID` so you can cap the size of the cache.
When a write exceeds the cap, discard the oldest entries.

When a user converts, retrieve the cached `queryID` for that index and `objectID`.
Include it in the conversion event.
After a successful conversion event, delete that entry from the cache.

If you store the query ID in a cookie or the browser's local storage, you may need your users' consent.

## Agentic query IDs

Agentic interfaces can show results that an assistant selected, reordered, or explained.
Attribute events from these results to the assistant message that showed them,
not only to the underlying Search API request.

If you use InstantSearch Chat, InstantSearch creates and sends this agentic `queryID` automatically for events from assistant-rendered results.

If you integrate with the Agent Studio API yourself, use the assistant message ID from your conversation state.
With Agent Studio, this is the `id` of the assistant message in the completion response.
Store that assistant message, including its `id`.
When you send the assistant message back as part of the conversation history, reuse the same ID.

Build the agentic `queryID` from that assistant message ID:

```text theme={"system"}
message_<assistantMessageId>
```

For example, if the assistant message ID is `alg_msg_assistant_002`, the `queryID` is:

```text theme={"system"}
message_alg_msg_assistant_002
```

Keep this value stable for the lifetime of the assistant message:

* Use the same `queryID` for all results shown in the same assistant message.
* Use the same `queryID` for clicks and conversions that come from those results.
* Don't create a new `queryID` for each render, click, or event retry.
* Don't use an agentic `queryID` for events that aren't caused by an agent-rendered result.

The following example uses the `search-insights` library:

```js JavaScript icon=code theme={"system"}
import aa from 'search-insights';

aa('init', {
  appId: 'ALGOLIA_APPLICATION_ID',
  apiKey: 'ALGOLIA_SEARCH_API_KEY',
});

aa('setUserToken', 'user-123');

// Response body from the Agent Studio completions endpoint.
const assistantMessage = await completionResponse.json();

const queryID = `message_${assistantMessage.id}`;

aa('clickedObjectIDsAfterSearch', {
  index: 'items',
  eventName: 'Agent Result Clicked',
  objectIDs: ['item-123'],
  positions: [1],
  queryID,
});
```

Set each position to the item's visible position in the assistant message, starting at `1`.

Users can open a detail page from an assistant message and convert later.
In that case, pass the agentic `queryID` like a regular search `queryID`, such as in the URL:

```text theme={"system"}
/item/123?queryID=message_alg_msg_assistant_002
```

Then include that `queryID` in the later conversion event.

## Track conversions unrelated to a search

To track conversions unrelated to a previous search,
[send a conversion event without a `queryID`](/doc/libraries/search-insights/converted-object-ids).

For more information about where you can use conversion events without a query ID,
see [Event types](/doc/guides/sending-events/concepts/event-types#events-by-features).
