> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Collections

> Create a collection, a curated set of records, in your Algolia index.

export const Records = () => <Tooltip tip="A record is a searchable object in an Algolia index. Each record consists of named attributes." cta="Algolia records" href="/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records">
    records
  </Tooltip>;

export const Index = () => <Tooltip tip="An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.">
    index
  </Tooltip>;

export const Filter = () => <Tooltip tip="A filter is a condition that limits which records Algolia returns. Filters often use one or more facet-value pairs, such as brand:Apple AND color:red. You can also filter by numeric values, dates, tags, booleans, or geographic constraints." cta="Filtering" href="/doc/guides/managing-results/refine-results/faceting">
    filter
  </Tooltip>;

export const Application = () => <Tooltip tip="An Algolia application is a self-contained environment with its own indices, configuration, and API keys. Applications don't share data or settings with each other.">
    application
  </Tooltip>;

export const AlgoliaSearch = () => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="20" height="20" className="inline" fill="none" role="presentation" ariaLabel="Algolia Search">
    <circle cx="40" cy="32" r="28" fill="#5468FF"></circle>
    <rect x="30" y="22" width="20" height="20" rx="10" fill="#fff"></rect>
    <path d="M43 63.5 54.5 60l6 17h-12L43 63.5Z" fill="#36395A"></path>
  </svg>;

<Callout icon="credit-card" color="#c084fc">
  This feature isn't available on every plan.
  Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included.
</Callout>

**Collections are curated product listings.**
They're similar to category pages but you don't need to create them in your <Index /> ahead of time.
Instead, you can create a collection by manually selecting <Records /> or defining them using conditions.
After initial setup, creating and editing a collection is fast and code-free.

The Collections feature is ideal for seasonal promotions,
featured collections,
or any manually curated selection.

Here's how to set up collections:

1. Create and update collections with the Algolia dashboard or Merchandising Studio.
2. Build the collection pages with InstantSearch on the frontend.

## Create a collection

To get started with Collections, use the Algolia dashboard or Merchandising Studio.

An index supports up to **[500 (Premium) / 1,000 (Elevate) collections per index](/doc/guides/scaling/algolia-service-limits#collections-limits).**

<Note>
  Collections don't support quotes and [escape characters](https://en.wikipedia.org/wiki/Escape_character) in object IDs.
  Ensure you "clean" the `objectID` attributes in your index before creating a collection.
</Note>

### In the dashboard

1. Go to the [Algolia dashboard](https://dashboard.algolia.com/explorer/browse) and select your Algolia <Application />.
2. On the left sidebar, select <AlgoliaSearch /> **Search**.
3. Select your Algolia index and go to **Collections**.
4. Select **New collection** to start your build.

### In the Merchandising Studio

Using the [Merchandising Studio](/doc/guides/managing-results/rules/merchandising-and-promoting#merchandising-studio)
you can add collections to your existing merchandising strategies.

1. Go to the **Merchandising Studio > Visual Merchandiser**.
2. Open the experience selector and choose **Collection**.
3. Select **Create new collection** to start your build.
4. Start adding items to your collection.

### Add items to a collection

Once you've created a collection,
you can populate it manually or dynamically:

* **Manually:** you choose individual items for your collection.
  The collection only changes when you add or remove items.
  Use cases include a fixed collection of best sellers or a themed editorial collection.
* **Dynamically:** you define conditions to match the items to include in your collection
  (such as brand, category, or price). The collection updates when you update your index with items that match the defined criteria. For example, an "Under \$50" collection that updates based on price changes.

You can also combine both options in a single collection.
By combining manual and conditional items,
you can curate part of the collection while also allowing dynamic updates based on real-time record data.

#### Handpick items from an index (manual)

Use this option to manually select specific items from your index to include in the collection.
This is useful for curated collections requiring exact control over the selection.

You can either query your index or use filters to look for specific items.

<Note>
  You can have up to **[10,000 manually selected items per collection](/doc/guides/scaling/algolia-service-limits#collections-limits)**.
</Note>

#### Upload a file (manual)

You can bulk upload a predefined list of object IDs from a file (`.csv`, `.txt`, or `.xlsx`).
Include one `objectID` per row, in a single-column file with no header.

<Note>
  You can have up to **[10,000 manually selected items per collection](/doc/guides/scaling/algolia-service-limits#collections-limits)**.
</Note>

#### Set conditions (dynamic)

This option dynamically adds items to the collection based on conditions you define.
It automatically includes new matching records as they're added to your index.

<Note>
  Define the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) to use in conditions.
  You can have up to [50 conditions per collection](/doc/guides/scaling/algolia-service-limits#collections-limits).
</Note>

## Delete a collection

If a collection is no longer relevant or you've reached the limit on the number of collections per index, you can remove it:

1. Go to the collections list page in the Algolia dashboard or Merchandising Studio and find the collection you want to delete.
2. Open the <Icon icon="ellipsis-vertical" /> **Actions** menu and select **Delete collection**.
3. To confirm, click **Delete**.

Removing the collection also updates the index.

To ensure a clean user experience, also make sure a deleted collection has no references left:

* **In your frontend:** if a collection is linked in your navigation or marketing materials, update those links.
* **In your rules or Merchandising Studio:** update any merchandising rules or display logic that rely on the deleted collection to prevent errors or empty pages.

<Warning>
  Deleting a collection is permanent and can't be undone.
</Warning>

## Ensure collections remain up-to-date

If you're indexing records with one of the Algolia API clients,
update your code to ensure your collections work as intended.

### `WithTransformation` methods

The Ingestion API client has `withTransformation` methods which ensure that your collections are updated correctly.
You can replace your calls to the Search API with these methods:

* [`partialUpdateObjectWithTransformation`](/doc/libraries/sdk/methods/search/partial-update-objects-with-transformation)
* [`replaceAllObjectsWithTransformation`](/doc/libraries/sdk/methods/search/replace-all-objects-with-transformation)
* [`saveObjectsWithTransformation`](/doc/libraries/sdk/methods/search/save-objects-with-transformation)

### With the `pushTask` method

If your version of the client doesn't have `withTransformation` methods, instead of using the `saveObject`, `addOrUpdateObject`, `batch`, or `multipleBatch` methods,
use the Ingestion API [`pushTask`](/doc/rest-api/ingestion/push-task) method so that your updates go through the Ingestion pipeline and preserve your collection data.

1. In the Algolia dashboard, open the **Collections** page and select **Indexing Guidelines.**
2. On the **After** tab, copy the task ID for updating your collections (**Your unique Push Task ID**).
3. Use the Ingestion API [`pushTask`](/doc/rest-api/ingestion/push-task) method to send your records with the task ID you copied in the previous step.

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.PushTaskAsync(
    "6c02aeb1-775e-418e-870b-1faccd4b2c0f",
    new PushTaskPayload
    {
      Action = Enum.Parse<Action>("AddObject"),
      Records = new List<PushTaskRecords>
      {
        new PushTaskRecords
        {
          ObjectID = "o",
          AdditionalProperties = new Dictionary<string, object>
          {
            { "key", "bar" },
            { "foo", "1" },
          },
        },
        new PushTaskRecords
        {
          ObjectID = "k",
          AdditionalProperties = new Dictionary<string, object>
          {
            { "key", "baz" },
            { "foo", "2" },
          },
        },
      },
    }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.pushTask(
    taskID: "6c02aeb1-775e-418e-870b-1faccd4b2c0f",
    pushTaskPayload: PushTaskPayload(
      action: Action.fromJson("addObject"),
      records: [
        PushTaskRecords(
          objectID: "o",
          additionalProperties: {
            'key': "bar",
            'foo': "1",
          },
        ),
        PushTaskRecords(
          objectID: "k",
          additionalProperties: {
            'key': "baz",
            'foo': "2",
          },
        ),
      ],
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.PushTask(client.NewApiPushTaskRequest(
    "6c02aeb1-775e-418e-870b-1faccd4b2c0f",
    ingestion.NewEmptyPushTaskPayload().SetAction(ingestion.Action("addObject")).SetRecords(
      []ingestion.PushTaskRecords{
        *ingestion.NewEmptyPushTaskRecords().SetAdditionalProperty("key", "bar").SetAdditionalProperty("foo", "1").SetObjectID("o"),
        *ingestion.NewEmptyPushTaskRecords().SetAdditionalProperty("key", "baz").SetAdditionalProperty("foo", "2").SetObjectID("k"),
      }),
  ))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  WatchResponse response = client.pushTask(
    "6c02aeb1-775e-418e-870b-1faccd4b2c0f",
    new PushTaskPayload()
      .setAction(Action.ADD_OBJECT)
      .setRecords(
        Arrays.asList(
          new PushTaskRecords().setAdditionalProperty("key", "bar").setAdditionalProperty("foo", "1").setObjectID("o"),
          new PushTaskRecords().setAdditionalProperty("key", "baz").setAdditionalProperty("foo", "2").setObjectID("k")
        )
      )
  );
  ```

  ```js JavaScript theme={"system"}
  const response = await client.pushTask({
    taskID: '6c02aeb1-775e-418e-870b-1faccd4b2c0f',
    pushTaskPayload: {
      action: 'addObject',
      records: [
        { key: 'bar', foo: '1', objectID: 'o' },
        { key: 'baz', foo: '2', objectID: 'k' },
      ],
    },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.pushTask(
      taskID = "6c02aeb1-775e-418e-870b-1faccd4b2c0f",
      pushTaskPayload =
        PushTaskPayload(
          action = Action.entries.first { it.value == "addObject" },
          records =
            listOf(
              PushTaskRecords(
                objectID = "o",
                additionalProperties =
                  mapOf("key" to JsonPrimitive("bar"), "foo" to JsonPrimitive("1")),
              ),
              PushTaskRecords(
                objectID = "k",
                additionalProperties =
                  mapOf("key" to JsonPrimitive("baz"), "foo" to JsonPrimitive("2")),
              ),
            ),
        ),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->pushTask(
      '6c02aeb1-775e-418e-870b-1faccd4b2c0f',
      ['action' => 'addObject',
          'records' => [
              ['key' => 'bar',
                  'foo' => '1',
                  'objectID' => 'o',
              ],

              ['key' => 'baz',
                  'foo' => '2',
                  'objectID' => 'k',
              ],
          ],
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.push_task(
      task_id="6c02aeb1-775e-418e-870b-1faccd4b2c0f",
      push_task_payload={
          "action": "addObject",
          "records": [
              {
                  "key": "bar",
                  "foo": "1",
                  "objectID": "o",
              },
              {
                  "key": "baz",
                  "foo": "2",
                  "objectID": "k",
              },
          ],
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.push_task(
    "6c02aeb1-775e-418e-870b-1faccd4b2c0f",
    Algolia::Ingestion::PushTaskPayload.new(
      action: "addObject",
      records: [
        Algolia::Ingestion::PushTaskRecords.new(key: "bar", foo: "1", algolia_object_id: "o"),
        Algolia::Ingestion::PushTaskRecords.new(key: "baz", foo: "2", algolia_object_id: "k")
      ]
    )
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.pushTask(
      taskID = "6c02aeb1-775e-418e-870b-1faccd4b2c0f",
      pushTaskPayload = PushTaskPayload(
        action = Action.withName("addObject"),
        records = Seq(
          PushTaskRecords(
            objectID = "o",
            additionalProperties = Some(List(JField("key", JString("bar")), JField("foo", JString("1"))))
          ),
          PushTaskRecords(
            objectID = "k",
            additionalProperties = Some(List(JField("key", JString("baz")), JField("foo", JString("2"))))
          )
        )
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.pushTask(
      taskID: "6c02aeb1-775e-418e-870b-1faccd4b2c0f",
      pushTaskPayload: PushTaskPayload(
          action: IngestionAction.addObject,
          records: [
              PushTaskRecords(from: [
                  "objectID": AnyCodable("o"),
                  "key": AnyCodable("bar"),
                  "foo": AnyCodable("1"),
              ]),
              PushTaskRecords(from: [
                  "objectID": AnyCodable("k"),
                  "key": AnyCodable("baz"),
                  "foo": AnyCodable("2"),
              ]),
          ]
      )
  )
  ```
</CodeGroup>

Periodic reindex operations are also performed to ensure that collections remain up-to-date.
They help maintain the accuracy of your collections by synchronizing them with the latest changes in your index, ensuring that any new or modified records are reflected in your collections.

<Note>
  This **adds read and write operations** which might lead to costs, depending on your plan.
</Note>

## Update a collection

To update a collection:

1. Go to the collections list page in the Algolia dashboard or Merchandising Studio and find the collection you want to update.
2. Click the menu for that collection and select **Edit**.
3. Make your changes. For example, update the collection name or description, add or remove items, or modify the conditions of the collection.
4. Save your changes.

## Build a collections page with InstantSearch

InstantSearch lets you create collection pages using a `menu` widget and routing capabilities.

**You don't need to create a new page for each collection.**
Instead, define a dynamic template to display any collection.

<Note>
  The `_collections` attribute is [reserved](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#reserved-attribute-names) only if you're using InstantSearch to implement a collections page.
  If you're not using InstantSearch, `_collections` behaves like any other attribute.
</Note>

### Installation

Before building your page, install the InstantSearch library:

* [InstantSearch.js](/doc/guides/building-search-ui/installation/js)
* [React InstantSearch](/doc/guides/building-search-ui/installation/react)
* [Vue InstantSearch](/doc/guides/building-search-ui/installation/vue)

### Set up the collection page

First, you must create a page to display collections (for example, to render to the `example.org/collections/` URL).
On this page, add the following code to render collections:

<CodeGroup>
  ```js InstantSearch.js theme={"system"}
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import instantsearch from "instantsearch.js";
  import { hits, menu } from "instantsearch.js/es/widgets";

  const searchClient = algoliasearch("undefined", "undefined");

  const search = instantsearch({
    searchClient,
    indexName: "INDEX_NAME",
    insights: true,
  });

  search.addWidgets([
    menu({
      container: "#menu",
      attribute: "_collections",
    }),
    hits({
      container: "#hits",
    }),
  ]);

  search.start();
  ```

  ```jsx React InstantSearch theme={"system"}
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import { Hits, InstantSearch, Menu } from "react-instantsearch";

  const searchClient = algoliasearch("undefined", "undefined");

  function App() {
    return (
      <InstantSearch
        searchClient={searchClient}
        indexName="INDEX_NAME"
        insights={true}
      >
        <Menu attribute="_collections" />
        <Hits />
      </InstantSearch>
    );
  }
  ```

  ```vue Vue InstantSearch theme={"system"}
  <template>
    <div>
      <ais-instant-search
        :search-client="searchClient"
        index-name="instant_search"
        :insights="true"
      >
        <ais-menu attribute="_collections" />
        <ais-hits />
      </ais-instant-search>
    </div>
  </template>

  <script>
  import { liteClient as algoliasearch } from 'algoliasearch/lite';

  export default {
    data() {
      return {
        searchClient: algoliasearch(
          'undefined',
          'undefined',
        ),
      };
    },
  };
  </script>
  ```
</CodeGroup>

When users go to this page,
they can select any collection and see a list of unfiltered hits.

For now, users can click the menu to <Filter /> on any collection but you can't display a given collection by default.
To do this, enable routing.

### Enable routing

Once your template is ready, you need to be able to link to specific collections and have them already filtered.
To do this, enable routing so the URL syncs with InstantSearch and displays the right collection:

<CodeGroup>
  ```js InstantSearch.js theme={"system"}
  const search = instantsearch({
    searchClient,
    indexName: "INDEX_NAME",
    insights: true,
    routing: true,
  });
  ```

  ```jsx React InstantSearch theme={"system"}
  function App() {
    return (
      <InstantSearch
        searchClient={searchClient}
        indexName="INDEX_NAME"
        insights={true}
        routing={true}
      >
        {/* … */}
      </InstantSearch>
    );
  }
  ```

  ```vue Vue InstantSearch theme={"system"}
  <template>
    <div>
      <ais-instant-search
        :search-client="searchClient"
        index-name="instant_search"
        :insights="true"
        :routing="true"
      >
        <!-- …  -->
      </ais-instant-search>
    </div>
  </template>
  ```
</CodeGroup>

This option enables basic URL routing, with query parameters.
When you go to your collection page with a collection in the URL
(for example, "Christmas Deals"), it only displays the records belonging to this collection.

```txt theme={"system"}
example.org/collections/?INDEX_NAME[menu][_collections]=Christmas Deals
```

For more SEO-friendly URLs, create a custom router:

* [SEO-friendly URLs (InstantSearch.js)](/doc/guides/building-search-ui/going-further/routing-urls/js#seo-friendly-urls)
* [SEO-friendly URLs (React InstantSearch)](/doc/guides/building-search-ui/going-further/routing-urls/react#seo-friendly-urls)
* [SEO-friendly URLs (Vue InstantSearch)](/doc/guides/building-search-ui/going-further/routing-urls/vue#seo-friendly-urls)

### Hide the menu

You may want to avoid displaying unrelated and irrelevant collections in your menus.
While you still need a menu for the collection state to exist in InstantSearch,
you can make it render nothing by using the connector instead and building a virtual widget.

<CodeGroup>
  ```js InstantSearch.js theme={"system"}
  import { connectMenu } from "instantsearch.js/es/connectors";

  const virtualMenu = connectMenu(() => null);

  search.addWidgets([
    virtualMenu({
      attribute: "_collections",
    }),
  ]);
  ```

  ```jsx React InstantSearch theme={"system"}
  import { useMenu } from "react-instantsearch";

  function VirtualMenu(props) {
    useMenu(props);

    return null;
  }

  function App() {
    return (
      <InstantSearch
        routing={true}
        searchClient={searchClient}
        indexName="INDEX_NAME"
      >
        <VirtualMenu />
        <Hits />
      </InstantSearch>
    );
  }
  ```

  ```vue Vue InstantSearch theme={"system"}
  <template>
    <div>
      <ais-instant-search
        :search-client="searchClient"
        index-name="instant_search"
        :insights="true"
        :routing="true"
      >
        <ais-menu attribute="_collections">
          <template />
        </ais-menu>
        <ais-hits />
      </ais-instant-search>
    </div>
  </template>
  ```
</CodeGroup>

The virtual widget retains the menu features without rendering anything,
allowing you to make each collection page fully independent.

## Troubleshoot problems

### Debug the collections pipeline

If you encounter issues with your collections,
contact the [Algolia support](https://support.algolia.com/hc/en-us/requests/new) team.

### Adding an attribute takes too long

When you update a record that should be added to a collection,
it takes a few seconds for your index to update because the [indexing pipeline is asynchronous](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/index-operations-are-asynchronous).

If updates take an unusually long time (hours),
check that you're sending them to the [push endpoint](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push) with the [`pushtask` method](#ensure-collections-remain-up-to-date).
This ensures immediate pipeline triggering.

### Determine if updates use the pipeline

If you suspect you might be sending updates to the Search API instead of the Ingestion API,
search your code for `saveObject`, `addOrUpdateObject`, `batch`, or `multipleBatch`.
If you find these methods,
you are sending updates to the Search API,
which could override the collections data in your index.

You can also check the logs in the Algolia dashboard to see if the Ingestion API is triggered.

To ensure your collections remain up-to-date, [update your code to use the Ingestion API](#ensure-collections-remain-up-to-date).

### A collection isn't showing up

A collection might not show up because you're not sending the entire record to the [push endpoint](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push). This is especially true for conditional collections, because even though the endpoint uses partial updates, conditions are re-evaluated whenever the records update.

When updating your records, ensure you include all attributes referenced in the conditions.

### Fix the " `_collections` isn't a facet" error

When creating a collection, the `_collections` attribute is automatically added to the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) setting so it can be filtered.
The "`_collections` isn't a facet" error indicates that the `_collections` attribute has been removed from the list of facets.
You need to [re-enable it in your index settings](/doc/guides/managing-results/refine-results/faceting).
You can do this in the Algolia dashboard or with the API.

### The collection page is empty

If you're manually displaying a collection on an HTML page by referencing the `_collections` attribute (for example, with a `configure` widget), **delete the page before you remove the collection in the Algolia dashboard.** Otherwise, the page will display an empty collection.

To avoid this issue, [use a `menu` widget](#build-a-collections-page-with-instantsearch) to generate your page.
If you're already doing so, make sure to remove any static link on your site pointing to the deleted collection.

### Collection status

The list of collections in the Algolia dashboard shows the status of each collection:

* **Synchronized**. The collection is up-to-date with the index.
* **Synchronizing**. The collection is being updated. This happens when you add or remove items, use the push task, or when the periodic reindex operation runs. This can take a few seconds to a few minutes, depending on the number of items in the collection and the size of your index.
* **Pending**. The collection and the index aren't synchronized. This can happen if you add new items without using the push task or if the periodic reindex operation fails. However, if it goes straight from "Synchronizing" to "Pending", contact the [Algolia support team](https://support.algolia.com/hc/en-us/requests/new).
* **Error**. This indicates a problem retrieving the collection's status, not that the collection itself is experiencing an error. If you see this status, refresh the page or check your network connection. If the issue persists, contact the Algolia support team.

## Next steps

After configuring collections and creating your collections page, you can:

* Add [rules](/doc/guides/managing-results/rules/rules-overview) to curate them.
* Use [Dynamic Re-Ranking](/doc/guides/algolia-ai/re-ranking) to adjust the order of categories.
* [Personalize](/doc/guides/personalization/classic-personalization/what-is-personalization) them.

While the following guides refer to category pages, they work the same for collections:

* [Merchandise category pages](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/merchandising-category-pages)
* [Add Dynamic Re-Ranking to category pages](/doc/guides/algolia-ai/re-ranking/tutorials/re-rank-category-pages)
* [Personalizing results](/doc/guides/personalization/classic-personalization/personalizing-results)
