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

# Structure ecommerce product records

> Find the optimal structure for your Algolia records for products

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 Facet = () => <Tooltip tip="An attribute in your records that lets users filter or group results (for example, by color, brand, or price)." cta="Faceting" href="/doc/guides/managing-results/refine-results/faceting">
    facet
  </Tooltip>;

In ecommerce, you often deal with *base products* and their *variants*.
For example, a t-shirt (the base product) might be available in different colors and sizes (the variants).

You can store this data in Algolia in three ways:

* **Variant-level records:** one Algolia record for each product variation.
* **Product-level records:** one Algolia record for each base product. All variations are contained in this record.
* **Variation-group-level records:** one Algolia record for each variation of a specific attribute,
  generally the `color`. Each record contains variations sharing this attribute value.

## Record structure examples

The following example shows records for a t-shirt available in two color.
Each color is available in two sizes.

With the **variant-level** records model,
each variant is a separate record:

```json JSON icon=braces theme={"system"}
[
  {
    "objectID": "v-neck-t-shirt-white-m",
    "name": "V-neck t-shirt",
    "category": "Sport",
    "color": "White",
    "size": "M",
    "image": "v-neck-t-shirt-white.jpg",
    "price": 19.99
  },
  {
    "objectID": "v-neck-t-shirt-white-l",
    "name": "V-neck t-shirt",
    "category": "Sport",
    "color": "White",
    "size": "L",
    "image": "v-neck-t-shirt-white.jpg",
    "price": 19.99
  },
  {
    "objectID": "v-neck-t-shirt-blue-m",
    "name": "V-neck t-shirt",
    "category": "Sport",
    "color": "Blue",
    "size": "M",
    "image": "v-neck-t-shirt-blue.jpg",
    "price": 22.99
  },
  {
    "objectID": "v-neck-t-shirt-blue-l",
    "name": "V-neck t-shirt",
    "category": "Sport",
    "color": "Blue",
    "size": "L",
    "image": "v-neck-t-shirt-blue.jpg",
    "price": 22.99
  }
]
```

With the **product-level** records model,
all variants of a base product are in a single record.
The common attributes are at the top level.
The varying attributes (color, size, images, and prices) are nested in a `variants` array.
Each array item corresponds to a unique variant:

```json JSON icon=braces theme={"system"}
{
  "objectID": "v-neck-t-shirt",
  "name": "V-neck t-shirt",
  "category": "Sport",
  "variants": [
    {
      "variantID": "v-neck-t-shirt-white-m",
      "color": "White",
      "size": "M",
      "image": "v-neck-t-shirt-white.jpg",
      "price": 19.99
    },
    {
      "variantID": "v-neck-t-shirt-white-l",
      "color": "White",
      "size": "L",
      "image": "v-neck-t-shirt-white.jpg",
      "price": 19.99
    },
    {
      "variantID": "v-neck-t-shirt-blue-m",
      "color": "Blue",
      "size": "M",
      "image": "v-neck-t-shirt-blue.jpg",
      "price": 22.99
    },
    {
      "variantID": "v-neck-t-shirt-blue-l",
      "color": "Blue",
      "size": "L",
      "image": "v-neck-t-shirt-blue.jpg",
      "price": 22.99
    }
  ]
}
```

With the **variation-group-level** records model,
variants sharing the same value of a given attribute, typically the color, are grouped in a single record.
For our t-shirt example, we have two records, one for each color.
The common attributes are at the top level.
The varying attributes (size and prices) are nested in a `variants` array.
Each array item corresponds to a unique variant:

```json JSON icon="braces" theme={"system"}
[
  {
    "objectID": "v-neck-t-shirt-white",
    "name": "V-neck t-shirt",
    "category": "Sport",
    "color": "White",
    "image": "v-neck-t-shirt-white.jpg",
    "variants": [
      {
        "variantID": "v-neck-t-shirt-white-m",
        "size": "M",
        "price": 19.99
      },
      {
        "variantID": "v-neck-t-shirt-white-l",
        "size": "L",
        "price": 19.99
      }
    ]
  },
  {
    "objectID": "v-neck-t-shirt-blue",
    "name": "V-neck t-shirt",
    "category": "Sport",
    "color": "Blue",
    "image": "v-neck-t-shirt-blue.jpg",
    "variants": [
      {
        "variantID": "v-neck-t-shirt-blue-m",
        "size": "M",
        "price": 22.99
      },
      {
        "variantID": "v-neck-t-shirt-blue-l",
        "size": "L",
        "price": 22.99
      }
    ]
  }
]
```

## Choose your record model

When choosing between the three indexing models,
consider whether you use Algolia AI or merchandising features.

**Use the variation-group-level or product-level record model if you use Algolia AI or merchandising features.**

These models can be more effective if your products share many similarities between variants:

* [Click and conversion events](/doc/guides/sending-events) from each variant
  are grouped by `objectID`, i.e. by base product or by variation-group.
  This lets the AI re-rank or personalize results at the product or variation-group level and not only for specific variants.

* It's easier to manage rules and [merchandising](/doc/guides/managing-results/rules/merchandising-and-promoting)
  at the product-level or variation-group-level when dealing with many variants at once.
  For example, you can [promote or hide products or variations](/doc/guides/managing-results/rules/rules-overview/in-depth/implementing-rules#promote-or-hide-specific-hits)
  rather than individual variants.

* [Search analytics](/doc/guides/search-analytics/overview) results are grouped by product or variations,
  making them easier to analyze.

**Use the variant-level record model if you don't use Algolia AI or merchandising.**

This model is more granular and better suited for precise control:

* Results match the most relevant variant directly.
  For example, a search for "red shoes" returns only variants with "red" in their data.
  In contrast, the product model requires additional frontend work to display the correct variant.

* Faceting is more accurate.
  A <Filter /> like `color: green AND size: 40` returns only matching variants,
  not entire products with partial matches.

* You can update individual variant attributes (like price or stock).
  The product model requires resending the full list of variants, as partial updates aren't supported.

<Tip>
  The variation-group-level model is a good compromise between the two other models.

  It provides better AI and merchandising capabilities than the variant-level model,
  while being more granular than the product-level model.
  For example, using the color as the grouping attribute means that a search for "red shoes" returns only
  records with "red" in their data, permitting to display the associated image without additional frontend work.
</Tip>

### Capabilities by record model

{/* vale Algolia.British = NO */}

|                         | Variant-level                                                                                              | Product-level                            | Variation-group-level                                   |
| ----------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------- |
| AI features             | <Badge color="blue">PER VARIANT</Badge>                                                                    | <Badge color="green">PER PRODUCT</Badge> | <Badge color="green">PER VARIATION GROUP</Badge>        |
| Rules and merchandising | <Badge color="blue">PER VARIANT</Badge>                                                                    | <Badge color="green">PER PRODUCT</Badge> | <Badge color="green">PER VARIATION GROUP</Badge>        |
| Search analytics        | <Badge color="blue">PER VARIANT</Badge>                                                                    | <Badge color="green">PER PRODUCT</Badge> | <Badge color="green">PER VARIATION GROUP</Badge>        |
| Textual relevance       | <Badge color="green">OPTIMIZED</Badge>                                                                     | <Badge color="blue">SUPPORTED</Badge>    | <Badge color="blue">OPTIMIZED FOR THE VARIATION</Badge> |
| Faceting support        | <Badge color="green">OPTIMIZED</Badge>                                                                     | Requires frontend work (see below)       | <Badge color="blue">OPTIMIZED FOR THE VARIATION</Badge> |
| Variant-powered PLP     | <Badge color="green">OPTIMIZED</Badge>                                                                     | Requires frontend work (see below)       | <Badge color="blue">OPTIMIZED FOR THE VARIATION</Badge> |
| Granular variant update | <Badge color="green">SUPPORTED</Badge>                                                                     | Not supported                            | Not supported                                           |
| Average record size     | Depending on further choices (see [Average record size](#average-record-size-for-the-variant-level-model)) | <Badge color="green">OPTIMIZED</Badge>   | <Badge color="green">OPTIMIZED</Badge>                  |
| Number of records       | <Badge color="blue">HIGH</Badge>                                                                           | <Badge color="green">OPTIMIZED</Badge>   | Depends on the number of variants of each variation     |

{/* vale Google.Parens = YES */}

#### Average record size for the variant-level model

The average record size for the variant-level model depends on what you want to show on your results,
for example, if you want to include color swatches or images carousels.
To display those, each of your variant records needs to have information about their siblings,
which increases the average record size.

## Results display

### Group records per product

Depending on your desired end-user experience and catalog size,
showing a single result per product—rather than listing each variant separately—
can offer a cleaner and more user-friendly interface.

With the **product-level** record model, you get one result per product by default.

The **variant-level** model requires using the [distinct](/doc/guides/managing-results/refine-results/grouping) feature to group variants under a single result.
To do this, include a shared product ID in each variant record and set the distinct parameter on that attribute:

```jsonc JSON icon=braces theme={"system"}
{
  "objectID": "v-neck-t-shirt-white-m"
  "name": "V-neck t-shirt",
  "baseProductID": "v-neck-t-shirt",
  // ...
}
```

The **variation-group-level** model gives you the choice:

* By default, you get one result per variation (for example, one per color).
* Or you can use the distinct feature the same way as for the variant-level model and display one result per product.

### Facets

Users can refine results more easily with the **variant-level** record model.
In this model, Algolia returns only the variants that match a selected <Facet />:
no additional frontend logic required.

This works also well for the **variation-group-level** model based on color.
Images are often the same for all variants of a given color, so the variants of a record usually share
the same image, thus no additional frontend logic is required neither.

In contrast, the **product-level** model always returns the entire product, including all variants.
To display only the matching variants, you need to post-process the search results in your InstantSearch code:

```js JavaScript icon=code theme={"system"}
hits({
  // ...
  transformItems(items, { results }) {
    return items.map((item) => {
      const colorFacets = results._state.disjunctiveFacetsRefinements['variants.color'] || [];

      let selectedVariant;
      if (colorFacets.length > 0) {
        selectedVariant = item.variants.find(variant => {
            return colorFacets.includes(variant.color);
        });
      } else {
        selectedVariant = item.variants[0];
      }

      item.image = selectedVariant.image;
      item.price = selectedVariant.price;
      item.url = selectedVariant.url;

      return item;
    });
  },
});
```
