Quickstart: Implementing Top Items with the API
Introduction
A powerful way to enhance product discovery is to show Top Items the moment a user shows intent by clicking into your search bar, even before they start typing. This guide will show you how to use the Top Items API to implement this feature for a custom UI.
This approach is for developers who need full control over the user experience and are responsible for rendering the results and tracking all related analytics events. See what you'll build ».
What you'll learn
- How to call the Top Items API to fetch popular items.
- How to call the Personalized Top Items API to fetch user-specific Top Items.
- The critical importance of manually tracking analytics when Top Items are shown on focus.
Who is this guide for
- Developers who have understood the "Quickstart: Getting query suggestions via the Autocomplete API" and now want to add Top Items shown on focus.
- Mobile developers (iOS, Android) who are integrating search suggestions.
- Any developer needing to fetch top or personalized item data directly.
Prerequisites
Before you start, please ensure you have the following in place:
- A working search input field in your application.
- The ability to make HTTP
GETrequests from your application and render the results. - Your Luigi's Box
trackerId. - A setup for manually sending analytics events, as detailed in the Events API guides.
Step-by-step
We will now modify the code from the previous guide. The changes involve adding a new API endpoint, creating a function to fetch top items, updating your analytics tracking, and adding a focus event listener.
Step 1: Add the Top Items API endpoint
In your JavaScript configuration section, add a new constant for the Top Items API URL.
Example
// --- CONFIGURATION ---
const TRACKER_ID = "YOUR_TRACKER_ID";
const TOP_ITEMS_API_URL = "https://live.luigisbox.com/v1/top_items"; // <-- ADD THIS
const AUTOCOMPLETE_API_URL = "https://live.luigisbox.com/autocomplete/v2";
// ... rest of your config
Step 2: Create a function to fetch Top Items
Next, add a new asynchronous function specifically for fetching top items. This function will call the TOP_ITEMS_API_URL and then use your existing renderResults function to display the data.
Endpoint
GET https://live.luigisbox.com/v1/top_items
Required parameters
-
tracker_id: Your unique site identifier. -
type: A comma-separated list of the content type you want Top Items for, along with the quantity for each (e.g,product:6,category:3).
Optional parameters (recommended)
-
hit_fields:A comma-separated list of attributes you need (e.g., title,url,price,image_link). Using this parameter is highly recommended to keep the API response fast and small by only fetching the data you will display.
Example: Add this new function to your core logic
const getTopItemsSuggestions = async () => {
try {
const response = await axios.get(TOP_ITEMS_API_URL, {
params: {
tracker_id: TRACKER_ID,
type: 'product:5,category:3',
hit_fields: 'title,url,price,image_link'
}
});
const hits = response.data.hits;
// Override the default group titles for this specific view
const customTitles = { item: 'Popular Products', category: 'Top Categories' };
renderResults(hits, customTitles);
// We will send analytics in the next step
} catch (error) {
console.error("Failed to fetch top items:", error);
}
};
This function is very similar to your existing getQuerySuggestions function but calls a different endpoint and doesn't require a q (query) parameter. It also prepares custom titles for the groups.
Step 3: Update Analytics for different suggestion types
To distinguish between regular query-based suggestions and Top Items shown on focus, we need to adjust our analytics tracking. You have two options for sending analytics: the DataLayer Collector (recommended for web integrations that already use a dataLayer) or the Events API (recommended for backend or mobile integrations). If you choose the DataLayer Collector, make sure the LBX script is included on your page.
- Query-driven suggestions (from the Autocomplete API) should be tracked as an Autocomplete event.
- Top Items shown on focus (loaded from the Top Items endpoint) should be tracked as a Recommendation event.
Autocomplete (query-driven) view event
// Send VIEW event (Autocomplete list)
function sendAutocompleteViewAnalytics(query, hits) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: "view_item_list",
ecommerce: {
item_list_name: "Autocomplete",
search_term: query || "",
items: hits.map((hit, index) => ({
item_id: hit.url || hit.attributes.title,
item_name: hit.attributes.title,
index: index + 1
}))
}
});
}
// Send VIEW event (Autocomplete list)
function sendAutocompleteViewAnalytics(query, hits, customFilters = {}) {
const analyticsPayload = {
id: uuid.v4(),
type: "event",
tracker_id: TRACKER_ID,
client_id: CLIENT_ID,
lists: {
"Autocomplete": {
query: {
string: query || "",
// Add custom filters to distinguish the source
filters: customFilters
},
items: hits.map((hit, index) => ({
title: hit.attributes.title,
url: hit.url || hit.attributes.title,
position: index + 1
}))
}
}
};
sendAnalyticsEvent(analyticsPayload);
}
Top Items on focus (Recommendation) view event
// Send VIEW event (Recommendation list for Top Items on focus)
function sendTopItemsViewAnalytics(hits) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: "view_item_list",
ecommerce: {
item_list_name: "Recommendation",
items: hits.map((hit, index) => ({
item_id: hit.url || hit.attributes.title,
item_name: hit.attributes.title,
index: index + 1,
price: hit.attributes.price || null,
type: hit.type
})),
filters: {
"RecommenderClientId": "autocomplete_popup",
"Recommender": "autocomplete_popup",
}
}
});
}
// Send VIEW event (Recommendation list)
function sendTopItemsViewAnalytics(hits) {
const analyticsPayload = {
id: uuid.v4(),
type: "event",
tracker_id: TRACKER_ID,
client_id: CLIENT_ID,
lists: {
"Recommendation": {
query: {
filters: {
// REQUIRED: stable identifier of this widget/list
RecommenderClientId: "autocomplete_popup",
// Optional but useful for debugging/segmentation
Recommender: "autocomplete_popup",
}
},
items: hits.map((hit, index) => ({
title: hit.attributes.title,
type: hit.type,
url: hit.url || hit.attributes.title,
position: index + 1,
price: hit.attributes.price || null
}))
}
}
};
sendAnalyticsEvent(analyticsPayload);
}
Example: Update the two fetcher functions
In getTopItemSuggestions, add this line after renderResults(hits):
if (hits && hits.length > 0) {
sendTopItemsViewAnalytics(hits);
}
In your existing getQuerySuggestions, modify the analytics call:
// Change the original analytics call to this:
if (hits && hits.length > 0) {
sendAutocompleteViewAnalytics(query, hits); // No custom filter needed here
}
Step 4: Add the focus event listener
Finally, add a focus event listener to your search input. This will trigger your new getTopItemsSuggestions function whenever a user clicks into an empty search box.
Example
// --- EVENT LISTENERS ---
searchInput.addEventListener('input', debounce(e => getQuerySuggestions(e.target.value), 300));
// ADD THIS NEW LISTENER
searchInput.addEventListener('focus', () => {
// Only fetch top items if the search box is empty
if (searchInput.value === '') {
getTopItemsSuggestions();
}
});
Advanced: Adding personalized Top items
Step 1: Add the Personalized API endpoint
First, add the URL for the personalized API to your configuration constants.
Example: Add the following line to your configuration
const PERSONALIZED_TOP_ITEMS_API_URL = "https://live.luigisbox.com/v1/personalized_top_items";
Step 2: Create a function for personalized suggestions
Now, create a new function to handle fetching personalized data. This function calls the personalized endpoint and requires a user_id parameter.
Example: Add the following function to your core logic
const getPersonalizedTopItemsSuggestions = async (userId) => {
if (!userId) return; // Don't run if there's no user ID
try {
const response = await axios.get(PERSONALIZED_TOP_ITEMS_API_URL, {
params: {
tracker_id: TRACKER_ID,
user_id: userId, // The specific user's ID
type: 'items:5,query:3' // e.g., personalized items and last searched queries
}
});
const hits = response.data.hits;
const customTitles = { item: 'Recommended For You', query: 'Your Recent Searches' };
renderResults(hits, customTitles);
// Add the analytics call for this new function
if (hits && hits.length > 0) {
sendAutocompleteViewAnalytics(null, hits, { source: 'personalized_top_items' });
}
} catch (error) {
console.error("Failed to fetch personalized top items:", error);
}
};
Step 3: Update the focus event listener
The final step is to update your focus event listener with logic to choose which function to call based on whether a user is logged in.
Example: Replace your existing focus listener with the following version
// Replace the previous searchInput.addEventListener('focus', ...) with this:
searchInput.addEventListener('focus', () => {
if (searchInput.value === '') {
// In a real application, you would check if the user is logged in
// and get their actual ID from your session or auth system.
const currentUserId = "user-123"; // Example: get this from your session
if (currentUserId) {
getPersonalizedTopItemsSuggestions(currentUserId);
} else {
getTopItemsSuggestions();
}
}
});
Best Practices
- You are in control: Remember that when using the direct API, you are fully responsible for the implementation, including all analytics tracking. Failure to send analytics events will prevent the system from learning.
-
Use
hit_fields: To minimize the response size and improve speed, use the optionalhit_fieldsparameter in your API call to request only the attributes you actually need to display (e.g.,&hit_fields=title,price,image_link). -
Match user IDs for personalization For the Personalized Top Items API to be effective, the
user_idyou send in the API request must match thecustomer_idyou send in your analytics events for that same user.
Next Steps
Now that Top Items shown on focus are working, you can continue to enhance your autocomplete experience.