# Types and attributes

Say what each entry of the catalog is and what its values mean, so "164 cm" filters as a width and "Samt" shows as velvet in English.

Every [entity](/docs/reference/glossary#entity) in the catalog has one [type](/docs/reference/glossary#type), such as product or category, and its properties are [attributes](/docs/reference/glossary#attribute), such as color and width. `types.json` says how each type is searched and shown, `attributes.json` what each value means. Here is the sofa `SOFA-ASKA-2` on an English page:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/product?id=SOFA-ASKA-2&locale=en' \
  | jq -c '.product | (.details[] | select(.attribute | IN("material", "width", "seats"))),
      (.variants[] | {id, color: .details[0].values})'
```

```json title="Answer: 200 · 2.4 ms · release b25a6aaa"
{"attribute":"material","label":"Material","values":["Engineered wood","Velvet"]}
{"attribute":"width","label":"Width","values":[164],"unit":"cm"}
{"attribute":"seats","label":"Seats","values":[2]}
{"id":"SOFA-ASKA-2-GRAU","color":["Grey"]}
{"id":"SOFA-ASKA-2-SALBEI","color":["Sage"]}
```

The catalog writes "Holzwerkstoff", "Samt", "164 cm", "Grau" and "Salbei". `attributes.json` makes them options with English labels, a width in centimeters and a color per variant.

## Types

Each entity names its type, such as `"type": "product"`. `types.json` lists the types a [release](/docs/reference/glossary#release) searches, and for each what it searches, returns and lists:

```json title="tests/fixtures/home/types.json (trimmed)"
{
    "types": [
        {
            "type": "product",
            "search": ["title", "brand", "keywords", "attributes.color", "description"],
            "result": ["title", "url", "image", "price", "sale_price", "badges", "signals.rating"],
            "details": ["color", "material", "upholstery", "width", "seats"]
        },
        { "type": "category", "search": ["title", "keywords", "description"], "result": ["title", "url", "path"] }
    ]
}
```

- `search` names the searched fields in priority order ([Fields and typos](/docs/fields-and-typos)).
- `result` names the fields a hit returns, badges and signals such as the rating included.
- `details` names the attributes a product page lists, in this order.

`product`, `service`, `category`, `content` and `brand` are built in. Any other code is a type of the merchant's own, with the `capabilities` it needs: `variants`, `offers` or `tree`. Only listed types are searched; an entity of another type is left out and reported.

**Reference:** [`types.json`](/docs/reference/release-files/types) · [`capabilities`](/docs/reference/release-files/types#capabilities) · [`result`](/docs/reference/release-files/types#result) · [`details`](/docs/reference/release-files/types#details)

## Attributes

An attribute says what one property's values mean: their type, their unit and, for an option, the values it knows. Here are the home store's color and width:

```json title="tests/fixtures/home/attributes.json (trimmed)"
{
    "attributes": [
        {
            "code": "color",
            "type": "option",
            "label": { "de": "Farbe", "en": "Color" },
            "level": "variant",
            "values": [
                {
                    "code": "sage",
                    "label": { "de": "Salbei", "en": "Sage" },
                    "aliases": ["salbei", "salbeigrün", "sage green"],
                    "group": "green",
                    "swatch": "#9CAF92"
                }
            ],
            "groups": [{ "code": "green", "label": { "de": "Grün", "en": "Green" }, "aliases": ["grün", "grünes"] }],
            "color_groups": true,
            "filterable": true,
            "detect_in_query": true
        },
        {
            "code": "width",
            "type": "quantity",
            "label": { "de": "Breite", "en": "Width" },
            "unit": "cm",
            "filterable": true
        }
    ]
}
```

Each value has a label per locale, aliases for other spellings, a swatch and a group: sage shows under green. `color_groups` places a value without a group by the color words in its name, and `level: variant` lets the color differ between [variants](/docs/variants).

Both attributes are `filterable`, so pages offer them as [facets](/docs/reference/glossary#facet). `detect_in_query` reads "graues Sofa" (grey sofa) as a filter by grey ([How search understands a query](/docs/query-understanding)).

**Reference:** [`type`](/docs/reference/release-files/attributes#type) · [`values`](/docs/reference/release-files/attributes#values) · [`groups`](/docs/reference/release-files/attributes#groups) · [`color_groups`](/docs/reference/release-files/attributes#color_groups) · [`filterable`](/docs/reference/release-files/attributes#filterable)

## How values are read

Each type reads what the catalog writes its own way:

| `type`       | The catalog writes             | It becomes                                  | Filters as                 |
| ------------ | ------------------------------ | ------------------------------------------- | -------------------------- |
| `option`     | "Grau", "GRAU", "salbeigrün"   | The value it names: grey, sage              | `list`, `swatch`           |
| `quantity`   | "164 cm", "6 ft", `164`        | 164, 182.88 and 164 in the attribute's unit | `range`, `buckets`, `list` |
| `number`     | `2`, "1,5"                     | 2, 1.5                                      | `range`, `list`            |
| `boolean`    | `true`, "ja", "nein" (yes, no) | true, false                                 | `toggle`                   |
| `text`       | Free text                      | Searchable text                             | Never                      |
| `identifier` | A GTIN or a model number       | Text matched exactly                        | Never                      |

`date` and `range` are read too. A value that does not fit, such as a width in kilograms, is left out, its entity stays searchable without it, and the [import report](/docs/import-report) lists it.

In the panel, an import that leaves `attributes.json` out lists what it derived under **Set up for you → Product details**, each with **Why**. The panel does not change an attribute: its **Filters and search** step only chooses whether a detail is a filter, searched or not used.

**Reference:** [`unit`](/docs/reference/release-files/attributes#unit)

## Spellings

A spelling is the value whose code, label or alias it matches, whatever its case, accents or ß: "Weiß", "weiss" and "WEISS" are all white. A spelling nobody listed becomes a value of its own, labeled as written, and the live [index run's](/docs/reference/glossary#index-run) report lists it:

```sh title="Terminal"
curl -s -H "Authorization: Bearer $key" $api/live \
  | jq -c '.report.values.derived[] | select(.attribute == "color") | {value, label, groups, placed: .placed.by}'
```

```json title="Answer: 200 · 12 ms"
{"value":"klarglas","label":"Klarglas","groups":["clear"],"placed":"name"}
{"value":"rauchglas","label":"Rauchglas","groups":null,"placed":"nothing"}
```

The pendant lamp comes in Klarglas (clear glass) and Rauchglas (smoked glass), which `attributes.json` does not list. `color_groups` placed Klarglas in the group clear by its name; Rauchglas names no color, so it stands alone in the color facet until the merchant gives it a group.

**Reference:** [`aliases`](/docs/reference/release-files/attributes#values.aliases) · [`GET /api/v1/live`](/docs/reference/management-api#live)

## Slugs

A slug is a value's word in URLs, per locale. It is made once from the label and stored:

```json title="tests/fixtures/home/attributes.json (trimmed)"
{ "code": "green", "label": { "de": "Grün", "en": "Green" }, "slug": { "de": "gruen", "en": "green" } }
```

- **CLDR's rules per locale:** Grün is `gruen` in German, Größe (size) `groesse`.
- **Kept when the label changes.** A draft that renames a label keeps its slug. One that changes the slug keeps the old one in `slug_aliases`, so old URLs still lead to the value.
- **One value per slug.** A value nobody listed gets a numbered one, such as `gruen-2`.

A release stored whole with `POST $api/releases` is taken as written: a value without a slug gets one made at every run, counted in `values.unstored`, since a new label would move its URLs.

**Reference:** [`slug`](/docs/reference/release-files/attributes#values.slug) · [`slug_aliases`](/docs/reference/release-files/attributes#values.slug_aliases)

## Next

- [Variants](/docs/variants): products that come in colors and sizes.
- [Channels](/docs/channels): the locales labels and slugs are written in.
- [The import report](/docs/import-report): every value left out or derived.
