Skip to main content
A federated search offers search results, Query Suggestions, product categories, and other content from multiple indices in a single interface. Federated search user interface

Before you begin

This tutorial extends Build a Query Suggestions UI with Autocomplete.

Combine different data sources

With different indices for different content types, you can optimize the relevance for each type. For example, blog posts might require a different ranking strategy than products. Federated search can search multiple indices simultaneously and show the results in the same interface. You can combine data sources in four different ways:
Data sourcesExample
Two or more Algolia indicesOne index for Query Suggestions, another for products, and another for a blog. Each index has different data types, data structures, and ranking strategies.
One Algolia index with different filtersYou can apply filters to contextualize and get extra data sets from the same index. For example, if your index has both movies and books, you can query a single index twice: one query with a filter for “movies” and the second query for “books”.
One Algolia index and filter values from the same indexIn a “product” index, you can have a set of category filters. You can apply these category filters and return relevant category values.
One Algolia index and third-party dataYou can search both an Algolia index and a third-party API asynchronously. For example, you can search into an Algolia index of restaurants and get a list of nearby addresses using the Google Places API.
You can add multiple data sources to Autocomplete with getSources. In this tutorial, Autocomplete uses these data sources:
  • Query Suggestions from the index instant_search_demo_query_suggestions
  • Product catalog from the index instant_search
  • Hierarchical product categories from instant_search

Add Query Suggestions as first data source

Follow the steps in Adding Query Suggestions to set up autocomplete-plugin-query-suggestions.

Add search results for products as second data source

First, import getAlgoliaResults from the autocomplete-js package to get results from an Algolia index.
JavaScript
import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js";
Next, use getSources and getAlgoliaResults to get matching products from the Algolia index instant_search. This example uses custom rendering with Preact components.
JavaScript
// ...
autocomplete({
  // ...
  // Add the product display
  getSources({ query, state }) {
    if (!query) {
      return [];
    }
    return [
      {
        sourceId: "products",
        getItems() {
          // Get the products from an Algolia index
          return getAlgoliaResults({
            searchClient,
            queries: [
              {
                indexName: "instant_search",
                params: {
                  query,
                  hitsPerPage: 3,
                  attributesToSnippet: ["name:10"],
                  snippetEllipsisText: "…",
                },
              },
            ],
          });
        },
        // Control how products are shown in Autocomplete
        templates: {
          // Show `Products` before the search results
          header() {
            return (
              <Fragment>
                <span className="aa-SourceHeaderTitle">Products</span>
                <div className="aa-SourceHeaderLine" />
              </Fragment>
            );
          },
          // Control the rendering of the actual product
          item({ item, components }) {
            return (
              // This Preact component is defined below
              <ProductItem hit={item} components={components} />
            );
          },
          // Display this text when no products match the query
          noResults() {
            return "No products for this query.";
          },
        },
      },
    ];
  },
});

// A Preact component to render a product item with an image on the left and
// product details on the right
function ProductItem({ hit, components }) {
  return (
    <a href={hit.url} className="aa-ItemLink">
      <div className="aa-ItemContent">
        <div className="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
          <img src={hit.image} alt={hit.name} width="40" height="40" />
        </div>
        <div className="aa-ItemContentBody">
          <div className="aa-ItemContentTitle">
            <components.Snippet hit={hit} attribute="name" />
          </div>
          <div className="aa-ItemContentDescription">
            From <strong>{hit.brand}</strong> in{" "}
            <strong>{hit.categories[0]}</strong>
          </div>
          {hit.rating > 0 && (
            <div className="aa-ItemContentDescription">
              <div style={{ display: "flex", gap: 1, color: "#ffc107" }}>
                {Array.from({ length: 5 }, (_value, index) => {
                  const isFilled = hit.rating >= index + 1;

                  return (
                    <svg
                      key={index}
                      width="16"
                      height="16"
                      viewBox="0 0 24 24"
                      fill={isFilled ? "currentColor" : "none"}
                      stroke="currentColor"
                      strokeWidth="3"
                      strokeLinecap="round"
                      strokeLinejoin="round"
                    >
                      <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
                    </svg>
                  );
                })}
              </div>
            </div>
          )}
          <div className="aa-ItemContentDescription" style={{ color: "#000" }}>
            <strong>${hit.price.toLocaleString()}</strong>
          </div>
        </div>
      </div>
      <div className="aa-ItemActions">
        <button
          className="aa-ItemActionButton aa-DesktopOnly aa-ActiveOnly"
          type="button"
          title="Select"
          style={{ pointerEvents: "none" }}
        >
          <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
            <path d="M18.984 6.984h2.016v6h-15.188l3.609 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.609 3.609h13.172v-4.031z" />
          </svg>
        </button>
        <button
          className="aa-ItemActionButton"
          type="button"
          title="Add to cart"
        >
          <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
            <path d="M19 5h-14l1.5-2h11zM21.794 5.392l-2.994-3.992c-0.196-0.261-0.494-0.399-0.8-0.4h-12c-0.326 0-0.616 0.156-0.8 0.4l-2.994 3.992c-0.043 0.056-0.081 0.117-0.111 0.182-0.065 0.137-0.096 0.283-0.095 0.426v14c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h14c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-14c0-0.219-0.071-0.422-0.189-0.585-0.004-0.005-0.007-0.010-0.011-0.015zM4 7h16v13c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-14c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707zM15 10c0 0.829-0.335 1.577-0.879 2.121s-1.292 0.879-2.121 0.879-1.577-0.335-2.121-0.879-0.879-1.292-0.879-2.121c0-0.552-0.448-1-1-1s-1 0.448-1 1c0 1.38 0.561 2.632 1.464 3.536s2.156 1.464 3.536 1.464 2.632-0.561 3.536-1.464 1.464-2.156 1.464-3.536c0-0.552-0.448-1-1-1s-1 0.448-1 1z" />
          </svg>
        </button>
      </div>
    </a>
  );
}

Add product categories as third data source

To add categories to the federated search, import getAlgoliaFacets from autocomplete-js.
JavaScript
import {
  autocomplete,
  getAlgoliaResults,
  getAlgoliaFacets,
} from "@algolia/autocomplete-js";
// ...
Now add getAlgoliaFacets to getSources to add the product categories:
JavaScript
// ...
autocomplete({
  // ...
  getSources() {
    return [
      {
        // The source for the products
        // ...
      },
      {
        sourceId: "productsCategories",
        getItems({ query }) {
          // Use the product categories as facets
          return getAlgoliaFacets({
            searchClient,
            queries: [
              {
                indexName: "instant_search",
                facet: "hierarchicalCategories.lvl1",
                params: {
                  facetQuery: query,
                  maxFacetHits: 2,
                },
              },
            ],
          });
        },
        // Control the rendering of the product categories
        templates: {
          header() {
            return (
              // Show 'Product Categories' before the actual categories
              <Fragment>
                <span className="aa-SourceHeaderTitle">
                  Products Categories
                </span>
                <div className="aa-SourceHeaderLine" />
              </Fragment>
            );
          },
          item({ item }) {
            return <div>{item.label}</div>;
          },
        },
      },
    ];
  },
});

Next steps

Beyond this example, you can add more data sources. To make your data sources reusable and shareable, encapsulate them as plugins. For more information, see Build your own plugin. You can also add more features, such as:
  • Merchandising. Use Rules and merchandising to promote queries or items in the empty-search state or no-results state.
  • Personalization. Use Algolia’s personalization feature to enhance Query Suggestions or products.
  • Dynamic Re-ranking. Let Algolia’s Dynamic Re-ranking feature boost popular Query Suggestions or products.

See also

I