> This page is for Developer.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.bigcommerce.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.bigcommerce.com/_mcp/server.

# Integrate Third-Party Search with Algolia

> Replace Catalyst's built-in product search with Algolia by indexing your catalog and querying Algolia from the search server action.

This guide shows you how to replace Catalyst's built-in product search with [Algolia](https://www.algolia.com/). You'll learn how to:

* Connect your BigCommerce data with Algolia
* Replace Catalyst's default search experience
* Use the Algolia JavaScript API client to fetch search results on the server

By the end of this guide, you'll have a search experience powered by Algolia, with product and category results rendered in your Catalyst store's existing search UI.

![Algolia Demo](/_fern-files/bigcommerce.docs.buildwithfern.com/a29251b96d03326d8c3a2e295675be5fbd82c8c97fa71c0c73414ad9caeec746/assets/storefront/catalyst/third-party-search/algolia-demo.gif)

## Prerequisites

* An Algolia account.
* A Catalyst project running locally. See [Getting Started](/developer/docs/storefront/catalyst/getting-started).
* Products in your Catalyst channel's catalog. If you don't have any, choose **Use sample data** when you create your Catalyst channel.

## Create an application in Algolia

In your Algolia dashboard, create a new application. You can start with the free **Build** plan.

![Create new algolia application](/_fern-files/_img/5a73ba4b896be0b793806db816439f55a45e00c1d70417ba75f59b2126777282.webp)

1. Choose a region based on your location (for example, **US East**)
2. Click **Next: Review & Confirm**
3. Click **Create Application**

You should see a **Congratulations** page which indicates your application was created successfully.

## Connect Algolia to BigCommerce

Next, create an Algolia index for your Catalyst channel's products. An index is where Algolia stores the data it searches. From the [Algolia docs](https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/prepare-data-in-depth/#algolia-index):

> An index is the equivalent of a table in a database but optimized for search and discovery operations.

The [Algolia Single-Click App](https://www.bigcommerce.com/apps/algolia-search-discovery) for BigCommerce creates the index for you.

> **Warning**
>
> Due to the [10 KB limit on Algolia Record sizes for the Build plan](https://support.algolia.com/hc/en-us/articles/4406981897617-Is-there-a-size-limit-for-my-index-records),
> unassign the **3 Plant Bundle** product from your Catalyst channel's catalog
> if you chose **Use sample data** when creating your Catalyst channel.
> Otherwise, indexing fails.

1. Open the Algolia Single-Click App
2. Click **Get this App** and log into your BigCommerce store
3. Click **Install**
4. Review and accept the API scopes that the Algolia app requires to properly function
5. Click **Login With Algolia** and log into your Algolia account
6. Select the application you created from the dropdown and click **Next step: Channel selection**
7. Choose your BigCommerce channel
8. Name your Algolia index using **Product level indexing** as the **Record type** and click **Save**
9. Click **Yes, index now** to start indexing your products

You can find your new index in Algolia under **Data Sources > Indices**.

![View index in Algolia](/_fern-files/_img/3ea3a54052cb03f55b7f46b5f78882dba88dfd19523452383ff253f920f23723.webp)

## Set environment variables

In your Algolia dashboard, navigate to the **Overview** tab and copy your Application ID and Search API Key.

![Algolia application overview showing the Application ID and Search API Key](/_fern-files/_img/59852d3110c75bf2987a79774940091d29203cc1b89fa26ff2faf5c8f4cabbe6.webp)

Add the following environment variables to your `.env.local` file:

```bash showLineNumbers={false}
# ...existing env variables...

ALGOLIA_APPLICATION_ID="<YOUR_ALGOLIA_APPLICATION_ID>"
ALGOLIA_SEARCH_API_KEY="<YOUR_ALGOLIA_SEARCH_API_KEY>"
ALGOLIA_INDEX_NAME="<YOUR_ALGOLIA_INDEX_NAME>"
```

These are used by the Algolia search client, which runs on the server.

## Install Algolia dependencies

From the root of your Catalyst project, install the Algolia API client:

```bash showLineNumbers={false}
pnpm add algoliasearch
```

> **Info**
>
> If your project uses the older monorepo structure, run this from the `core/` directory, or use `pnpm --filter "./core" add algoliasearch` from the repository root. File paths in this guide are relative to that directory.

## Implement Algolia search

To replace Catalyst's search with Algolia:

1. Create an Algolia client.
2. Modify the search results data transformer.
3. Modify the search server action to fetch results from Algolia.

### Create the Algolia client

Create a new directory `lib/algolia` and, inside, add a new file `client.ts`. Then, create and export a new Algolia client:

**`lib/algolia/client.ts`**

```tsx title="lib/algolia/client.ts" showLineNumbers={false}
import { algoliasearch } from 'algoliasearch';

if (!process.env.ALGOLIA_APPLICATION_ID) {
  throw new Error('ALGOLIA_APPLICATION_ID is required');
}

if (!process.env.ALGOLIA_SEARCH_API_KEY) {
  throw new Error('ALGOLIA_SEARCH_API_KEY is not set');
}

const algoliaClient = algoliasearch(
  process.env.ALGOLIA_APPLICATION_ID,
  process.env.ALGOLIA_SEARCH_API_KEY
);

export default algoliaClient;
```

### Modify the search results data transformer

Transform the data from Algolia into the shape Catalyst expects. For this, you need a type definition for the Algolia data and an `algoliaResultsTransformer` function to transform the data. Open `data-transformers/search-results-transformer.ts` and replace its contents with the following.

**`data-transformers/search-results-transformer.ts`**

```tsx title="data-transformers/search-results-transformer.ts" showLineNumbers={false}
// 1. Remove the imports required by the existing searchResultsTransformer
import { getFormatter, getTranslations } from 'next-intl/server';

import { SearchResult } from '@/vibes/soul/primitives/navigation';

// 2. Define the AlgoliaHit type returned by the Algolia API via the algoliaClient
export interface AlgoliaHit {
  objectID: string;
  name: string;
  url: string;
  product_images: Array<{
    description: string;
    is_thumbnail: boolean;
    url_thumbnail: string;
  }>;
  categories_without_path: string[];
  default_price: string;
  prices: Record<string, number>;
  sales_prices: Record<string, number>;
  calculated_prices: Record<string, number>;
  retail_prices: Record<string, number>;
}

// 3. Implement the algoliaResultsTransformer function
export async function algoliaResultsTransformer(
  hits: AlgoliaHit[]
): Promise<SearchResult[]> {
  // 4. Get the formatter and translations for the current locale
  const format = await getFormatter();
  const t = await getTranslations('Components.Header.Search');

  // 5. Transform the Algolia hits into SearchResult objects
  const products = hits.map((hit) => {
    // Algolia stores prices per currency. This example formats the calculated
    // USD price as a single display price.
    const amount = hit.calculated_prices.USD ?? parseFloat(hit.default_price);
    const price = format.number(amount, { style: 'currency', currency: 'USD' });

    return {
      id: hit.objectID,
      title: hit.name,
      href: hit.url,
      price,
      image: {
        src:
          hit.product_images.find((image) => image.is_thumbnail)
            ?.url_thumbnail ?? '',
        alt: hit.product_images[0]?.description ?? '',
      },
    };
  });

  // 6. Create the product results SearchResult object
  const productResults: SearchResult = {
    type: 'products',
    title: t('products'),
    products,
  };

  // 7. For the returned product results, create a unique list of categories.
  //    For example, if you had two products, one with categories ['Electronics',
  //    'Computers'] and another with ['Electronics', 'Cameras'], this step would
  //    produce ['Electronics', 'Computers', 'Cameras'].
  const uniqueCategories = Array.from(
    new Set(hits.flatMap((product) => product.categories_without_path))
  );

  // 8. Create the category results SearchResult object
  const categoryResults: SearchResult = {
    type: 'links',
    title: t('categories'),
    links: uniqueCategories.map((category) => {
      return {
        label: category,
        href: `/${category.toLowerCase().replace(/\s+/g, '-')}`,
      };
    }),
  };

  // 9. Create an array to hold the final results
  const results = [];

  // 10. If there are any categories, add them to the results
  if (categoryResults.links.length > 0) {
    results.push(categoryResults);
  }

  // 11. If there are any products, add them to the results
  if (products.length > 0) {
    results.push(productResults);
  }

  return results;
}
```

### Modify the search server action

To use the new transformer, update the `components/header/_actions/search.ts` file, which handles search on the server. Open that file and replace its contents with the following:

**`components/header/_actions/search.ts`**

```tsx title="components/header/_actions/search.ts" showLineNumbers={false}
'use server';

import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { strict } from 'assert';
import { getTranslations } from 'next-intl/server';
import { z } from 'zod';

import { SearchResult } from '@/vibes/soul/primitives/navigation';

// 1. Import required Algolia dependencies, including the transformer and client
import {
  AlgoliaHit,
  algoliaResultsTransformer,
} from '~/data-transformers/search-results-transformer';
import algoliaClient from '~/lib/algolia/client';

export async function search(
  prevState: {
    lastResult: SubmissionResult | null;
    searchResults: SearchResult[] | null;
    emptyStateTitle?: string;
    emptyStateSubtitle?: string;
  },
  formData: FormData
): Promise<{
  lastResult: SubmissionResult | null;
  searchResults: SearchResult[] | null;
  emptyStateTitle: string;
  emptyStateSubtitle: string;
}> {
  const t = await getTranslations('Components.Header.Search');
  const submission = parseWithZod(formData, {
    schema: z.object({ term: z.string() }),
  });
  const emptyStateTitle = t('noSearchResultsTitle', {
    term: submission.status === 'success' ? submission.value.term : '',
  });
  const emptyStateSubtitle = t('noSearchResultsSubtitle');

  if (submission.status !== 'success') {
    return {
      lastResult: submission.reply(),
      searchResults: prevState.searchResults,
      emptyStateTitle,
      emptyStateSubtitle,
    };
  }

  if (submission.value.term.length < 3) {
    return {
      lastResult: submission.reply(),
      searchResults: null,
      emptyStateTitle,
      emptyStateSubtitle,
    };
  }

  try {
    // 2. Ensure the Algolia index name is set
    strict(process.env.ALGOLIA_INDEX_NAME);

    // 3. Send the search term from the form submission to Algolia instead of
    //    BigCommerce
    const algoliaResults = await algoliaClient.searchSingleIndex<AlgoliaHit>({
      indexName: process.env.ALGOLIA_INDEX_NAME,
      searchParams: {
        query: submission.value.term,
        // 4. Add any filters you want to apply to the search results
        filters: 'is_visible:true',
      },
    });

    return {
      lastResult: submission.reply(),
      // 5. Transform the Algolia hits into SearchResult objects
      searchResults: await algoliaResultsTransformer(algoliaResults.hits),
      emptyStateTitle,
      emptyStateSubtitle,
    };
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error(error);

    if (error instanceof BigCommerceGQLError) {
      return {
        lastResult: submission.reply({
          formErrors: error.errors.map(({ message }) => message),
        }),
        searchResults: prevState.searchResults,
        emptyStateTitle,
        emptyStateSubtitle,
      };
    }

    if (error instanceof Error) {
      return {
        lastResult: submission.reply({ formErrors: [error.message] }),
        searchResults: prevState.searchResults,
        emptyStateTitle,
        emptyStateSubtitle,
      };
    }

    return {
      lastResult: submission.reply({
        formErrors: [t('somethingWentWrong')],
      }),
      searchResults: prevState.searchResults,
      emptyStateTitle,
      emptyStateSubtitle,
    };
  }
}
```

### Check your work

Save your changes, then run the development server:

```bash showLineNumbers={false}
pnpm dev
```

Open `http://localhost:3000` and search for **Plant**. Your storefront shows the search results from Algolia.

![Algolia Demo](/_fern-files/bigcommerce.docs.buildwithfern.com/a29251b96d03326d8c3a2e295675be5fbd82c8c97fa71c0c73414ad9caeec746/assets/storefront/catalyst/third-party-search/algolia-demo.gif)

## Next steps

* Optimize your Algolia settings for [ranking](https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking) and [typo tolerance](https://www.algolia.com/doc/guides/managing-results/optimize-search-results/typo-tolerance/in-depth/configuring-typo-tolerance)
* Expand your [facets](https://www.algolia.com/doc/guides/managing-results/refine-results/faceting) for improved filtering