Want to get Elastic certified? Find out when the next Elasticsearch Engineer training is running!
Elasticsearch is packed with new features to help you build the best search solutions for your use case. Dive into our sample notebooks to learn more, start a free cloud trial, or try Elastic on your local machine now.
In this article, we will create a sample app to collect user behavior data to show how the UBI extension can be integrated with search-ui. We will also customize the data that is being collected to show the flexibility of the UBI standard and how it can cover different needs.
The sample app is a simple search engine for books and the goal here is to be able to capture events from the users and index them on Elasticsearch based on their activity, like search and clicks.
Requirements
This application requires having the UBI plugin for Elasticsearch installed. You can read our blog post about it for more information here.
Load sample data
We need to have some data on Elasticsearch first. Run the following command on Kibana DevTools Console to load a list of products to display in our UI. This will create a new index named “books” to work with this example.
Create sample app

We are going to use search-ui to create a UI application that sends UBI events to Elasticsearch. search-ui is Elastic’s JavaScript library to create UIs with built-in React components.
Search UI is Elastic’s React-based framework for building search applications. It provides components for all the essential parts of a search experience—such as search bars, facets, pagination, and autosuggestions. Customizing its behavior, including adding UBI, is straightforward.
Elasticsearch connector
To get started, we need to install the Elasticsearch connector, using the steps adapted from the connector tutorial.
1. Download the search-ui starter app from GitHub:
2. Navigate to the new directory called app-search-reference-ui-react-main and install the dependencies:
3. Install the Elasticsearch connector via the npm package manager:
Backend server
To adhere to best practices and ensure that the Elasticsearch calls are made via a middle-tier service, let’s create a backend to invoke our connector:
1. We start by creating a new directory and a new JavaScript file:
2. In the new index.js file, write:
This will create a server to proxy the requests to Elasticsearch with endpoints for search and autocomplete queries.
3. On the App.js file update:
With this change, we replace the default behavior, which is calling Elasticsearch from the browser, with calling our backend. This approach is more suitable for a production environment.
At the end of the file, replace the export default function with this definition:
This will allow us to display the book’s images and have a link to click on.

To see the full tutorial, visit this documentation.
After following the steps, you will end up with a client-side index.js
and a server-side server/index.js
related file.
Configure connector
We are going to configure onSearch
and onResultClick
handlers to set the UBI query_id
. Then, we’ll send UBI events when a search is executed and a result is clicked.
Configure onSearch: intercepts search requests, assigns each one a unique requestId
using UUID v4, and then passes the request along to the next handler in the processing chain. We will use this ID as the UBI query_id
to group searches and clicks.
Go to the server/index.js
file and extend the connector to configure the onSearch method:
After that, declare the connector and customize the search request to send the generated ID to the UBI plugin via the ext.ubi
search parameter.
Don’t forget to add new imports. Also, since our front-end is running on localhost:3000 and our backend is running on localhost:3001, they are considered different origins. An ‘origin’ is defined by the combination of scheme, domain and port, so even if both are running in the same host and use the HTTP protocol the different ports make them separate origins and so we need CORS. To learn more about CORS, visit this guide.
Go to the client’s side client/App.js
file (click to open the whole finished file).
Add an onResultClick event handler in the config object declaration to send analytics data to the backend whenever a user clicks a search result, capturing information like the query ID, result details, and user interaction specifics such as clicked document attributes, document position, and page number. Here you can add any other information the user consents to share. Make sure to follow privacy laws (like GDPR in Europe for example).
The complete event hooks in SearchUI reference can be found here.
Next, change search_fields
and result_fields
to align with the dataset. We are going to search through the book's name and author and return the name, author, image_url, url, and price.
Finally, we are going to add a couple of helper functions to define the device type and user data:
You can keep the rest of the config
object as is.
We put together a repo you can find here. It includes a more complete version of the project. It can be cloned using:
If you are using the GitHub repo, you need to provide these environment variables required for the server:
Running the app
Now you can spin up the server:
You might have to install CORS specifically if you encounter an error regarding that library:
And in another terminal:
Then go to http://localhost:3000
in your browser.
The end result will look like this:

On the Elasticsearch side, we can create a (rather simple) mapping for the ubi_events index so the user location is treated as such:
On each search, a ubi_queries
event will be generated, and on clickthrough, a ubi_events
of type click will be generated.
This is what a ubi_queries event looks like:
And this is a sample ubi_events document:
From here, we can see useful information already, like actions performed linked to a particular query.
Conclusion
Integrating search-ui with the UBI extension is a process that allows us to collect valuable insights into our users’ actions and extend them with other metadata, such as the user location and device type. This information is automatically indexed in two separate indices for queries and actions that can be related by unique IDs. This information enables the developer to better understand how the user uses the app and prioritize any problems that might impact their experience.