---
title: "Rendering banner campaigns with the Autocomplete API"
description: "Learn how to read the campaigns array from the Autocomplete API response and render autocomplete banner campaigns in your own custom UI."
slug: quickstart/autocomplete/banner-campaigns
docKind: guide
hub: quickstart
---

## Introduction

This guide is for developers who already use the [Autocomplete API](/autocomplete/api/v2/autocomplete/) directly and now want to display banner campaigns inside their own custom autocomplete UI.

Banner campaigns are returned as part of the normal autocomplete response. There is no separate banners endpoint. Your application must read the `campaigns` array, choose the correct banner image, and render it in the right part of your interface.

At a high level, nothing changes about how you fetch autocomplete suggestions. You still send the same request and still render the normal `hits` array. The difference is that you now also inspect the `campaigns` array and decide whether your UI should show a banner above the suggestions, beside them, or not at all for the current query.

### What you'll learn

- How to read banner campaign data from the Autocomplete API response.
- How to identify supported autocomplete banner positions.
- How to choose between desktop and mobile banner image URLs.
- How to render banner campaigns alongside your existing autocomplete suggestions.

### Who is this guide for

- Developers who already render autocomplete results from the API directly.
- Teams building custom web or mobile autocomplete interfaces.
- Developers who want banner campaigns without using [Autocomplete.js](/autocomplete/autocomplete-js/).

### Prerequisites

Before you start, please ensure you have the following in place:

- A working custom autocomplete UI that already calls the [Autocomplete API](/autocomplete/api/v2/autocomplete/).
- The ability to make HTTP `GET` requests and render the JSON response.
- Your Luigi's Box `trackerId`.
- Banner campaigns configured in the Luigi's Box application for at least one test query.

:::caution
This guide builds on the same integration style as the [Autocomplete API quickstart](/quickstart/autocomplete/query-suggestions/). If you do not already have a custom autocomplete request and rendering flow, start there first.
:::

:::note[Runnable example HTML]
Open or download the full standalone sample: [banner-campaigns.html](/examples/autocomplete/banner-campaigns.html)
:::

## Step-by-step

### Step 1: Keep your existing autocomplete request

Banner campaigns use the same endpoint as regular autocomplete suggestions.

That is an important architectural point: banners are not an additional API call and they are not a separate feature toggle in the request. Luigi's Box evaluates the current query and, if a matching campaign exists, includes the banner definitions in the response automatically.

#### Endpoint

`GET` `https://live.luigisbox.com/autocomplete/v2`

#### Required parameters

- **`tracker_id`:** Your unique site identifier.
- **`q`:** The current user query.
- **`type`:** The suggestion types and counts you want to render, for example `product:6,category:3,query:3`.

#### Recommended parameters

- **`hit_fields`:** Request only the fields you need for rendering, such as `title,url,price,image_link`.

#### Example

The code below does two things. First, it fetches the regular autocomplete suggestions into `hits`. Second, it reads the `campaigns` array from the same response and passes both datasets to separate rendering functions. Keeping those rendering paths separate makes the code much easier to reason about.

```javascript
const TRACKER_ID = 'YOUR_TRACKER_ID';
const AUTOCOMPLETE_API_URL = 'https://live.luigisbox.com/autocomplete/v2';

async function loadAutocomplete(query) {
  const response = await axios.get(AUTOCOMPLETE_API_URL, {
    params: {
      tracker_id: TRACKER_ID,
      q: query,
      type: 'product:6,category:3,query:3',
      hit_fields: 'title,url,price,image_link',
    },
  });

  const hits = response.data.hits || [];
  const campaigns = response.data.campaigns || [];

  renderResults(hits);
  renderCampaigns(campaigns);
}
```

The important change is that you now read both `hits` and `campaigns` from the same response.

### Step 2: Understand the banner campaign response

Autocomplete banner campaigns are returned in the top-level `campaigns` array.

Think of the `campaigns` array as a list of banner definitions that matched the current query. Each campaign object tells you three things:

- which campaign matched, through its `id`
- where the user should land after clicking, through `target_url`
- which visual banner positions are available, through the `banners` object

Inside the `banners` object, the keys are fixed Luigi's Box position names such as `autocomplete_top` and `autocomplete_list`. Each of those position objects then contains the actual image URLs you can render.

#### Example response fragment

```json
{
  "hits": [
    {
      "url": "https://yourshop.com/products/acoustic-guitar",
      "attributes": {
        "title": "Acoustic Guitar",
        "price": "499 EUR",
        "image_link": "https://yourshop.com/images/guitar.jpg"
      },
      "type": "product"
    }
  ],
  "campaigns": [
    {
      "id": 9,
      "target_url": "https://yourshop.com/brands/fender",
      "banners": {
        "autocomplete_list": {
          "desktop_url": "https://yourshop.com/banners/fender-list-desktop.jpg",
          "mobile_url": "https://yourshop.com/banners/fender-list-mobile.jpg"
        }
      }
    },
    {
      "id": 10,
      "target_url": "https://yourshop.com/sale",
      "banners": {
        "autocomplete_top": {
          "desktop_url": "https://yourshop.com/banners/sale-top-desktop.jpg",
          "mobile_url": "https://yourshop.com/banners/sale-top-mobile.jpg"
        }
      }
    }
  ]
}
```

In this example, the response contains two separate campaigns:

- one campaign that provides a banner for the `autocomplete_list` position
- one campaign that provides a banner for the `autocomplete_top` position

That does not mean both positions must always be present. Many queries will return no banners at all, some will return only one position, and some may return multiple campaign objects. Your rendering code should therefore always treat `campaigns` as optional data.

The supported autocomplete banner positions are:

- `autocomplete_top`
- `autocomplete_list`

Each banner position contains:

- `desktop_url` for larger screens
- `mobile_url` for smaller screens

At the campaign level, you will also typically use:

- `target_url` as the click destination for the banner
- `id` if you want to debug which campaign was returned

The practical rendering model is: the campaign tells you where the click should go, while the nested banner position tells you which image to show there.

### Step 3: Add a helper to extract banner positions

In practice, you will usually look for the first campaign that contains a banner for a specific position.

This is useful because your UI usually renders by slot, not by campaign object. For example, your layout may have a dedicated area for a top banner and a separate area for a list banner. A small normalization helper turns the raw API structure into something your rendering code can use directly.

#### Example

The helper below loops through all campaigns, keeps only those that contain the requested position, and returns the first matching entry in a simplified shape.

```javascript
function firstBannerForPosition(campaigns, position) {
  return campaigns
    .filter((campaign) => campaign.banners && campaign.banners[position])
    .map((campaign) => ({
      targetUrl: campaign.target_url,
      banner: campaign.banners[position],
      position,
    }))[0];
}
```

This gives you a normalized object you can pass to your rendering logic.

### Step 4: Choose the correct image for the current viewport

Your UI should prefer the mobile banner image on smaller screens and the desktop banner image on larger screens.

This step matters because Luigi's Box gives you two image variants for the same banner placement. Your frontend is responsible for deciding which one to use. The simplest approach is to inspect the viewport width and fall back to whichever URL is available.

#### Example

The following helper first detects whether the current viewport should be treated as mobile and then returns the best available image URL for that context.

```javascript
function isMobileViewport() {
  return window.matchMedia('(max-width: 767px)').matches;
}

function resolveBannerImage(entry) {
  if (!entry) return null;

  return isMobileViewport()
    ? entry.banner.mobile_url || entry.banner.desktop_url
    : entry.banner.desktop_url || entry.banner.mobile_url;
}
```

This fallback logic is useful when only one of the image URLs is present.

### Step 5: Render the autocomplete banners

Once you have extracted the campaign entries, render them into the relevant parts of your autocomplete layout.

The key idea here is to keep the banner rendering logic small and explicit:

- if there is no matching banner, render an empty or placeholder state
- if a banner exists, build a clickable image that points to the campaign `target_url`
- render each supported position into its dedicated DOM container

#### Example

In the example below, `createBannerMarkup` handles a single banner entry, while `renderCampaigns` decides which entry belongs to which autocomplete slot.

```javascript
function createBannerMarkup(entry, label) {
  if (!entry) {
    return `<div class="placeholder">No ${label} banner returned for this query.</div>`;
  }

  const imageUrl = resolveBannerImage(entry);
  if (!imageUrl) {
    return `<div class="placeholder">No usable ${label} image URL returned.</div>`;
  }

  return `
    <a class="campaign-card" href="${entry.targetUrl}" target="_blank" rel="noreferrer noopener">
      <img src="${imageUrl}" alt="${label} banner campaign">
    </a>
  `;
}

function renderCampaigns(campaigns) {
  const topBanner = firstBannerForPosition(campaigns, 'autocomplete_top');
  const listBanner = firstBannerForPosition(campaigns, 'autocomplete_list');

  document.getElementById('autocomplete-top').innerHTML =
    createBannerMarkup(topBanner, 'autocomplete top');

  document.getElementById('autocomplete-list-banner').innerHTML =
    createBannerMarkup(listBanner, 'autocomplete list');
}
```

This approach keeps your campaign rendering separate from your hits rendering, which makes the code easier to maintain.

It also makes failures easier to reason about. If the query suggestions render correctly but the banner does not, you only need to inspect the campaign-related helpers instead of your entire autocomplete renderer.

### Step 6: Keep your manual analytics flow intact

If you use the API directly, your regular autocomplete analytics tracking still needs to stay in place. Banner campaigns do not change that requirement.

Use the same manual analytics approach described in [Getting query suggestions via the Autocomplete API](/quickstart/autocomplete/query-suggestions/).

:::note
Banner campaigns affect rendering, not the endpoint contract. You do not need to add extra request parameters for banners. If a query matches an active campaign, the `campaigns` array is included automatically.
:::

## Next steps

- **Start from the base API autocomplete flow:** If you need the underlying autocomplete request and manual analytics flow in more detail, go to the ["Getting query suggestions via the Autocomplete API"](/quickstart/autocomplete/query-suggestions/) guide.
- **Extend the autocomplete experience:** If you also want to show suggestions before the user starts typing, continue with the ["Implementing top items with the API"](/quickstart/autocomplete/top-items-api/) guide.
- **Review banner behavior and placements:** For a fuller explanation of supported positions, dimensions, and integration paths, see [Banner campaigns](/search/guides/banner-campaigns/).
