# Synonyms

Tell OrbSearch which words mean the same, so a shopper who types "couch" finds the sofas.

Shoppers say "couch" where the shop says "sofa". A [synonym](/docs/reference/glossary#synonym) tells search that the words mean the same, so either one finds the sofas. Here are three entries of the home store's German dictionary, one of each kind:

```sh title="Terminal"
curl -s -H "Authorization: Bearer $key" $api/synonyms \
  | jq -r '.dictionaries[] | select(.locale == "de") | .entries[]
      | select(.id | IN("de/groups/0", "de/one_way/2", "de/never/0")) | "\(.id)  \(.summary)"'
```

```text title="Answer: 200 · 6.7 ms"
de/groups/0  sofa and couch find each other.
de/one_way/2  läufer finds teppich, not the other way round.
de/never/0  sessel and sofa are never merged.
```

- A **group** holds words that find each other.
- A **one-way** entry leads its first word to the others only: "Läufer" (runner) finds the rugs, but "Teppich" (rug) finds no runners.
- A **never** entry keeps look-alike words apart, such as "Sessel" (armchair) and "Sofa". It changes no search.

Each entry's `id` is its place in `synonyms.json`, which a change names. Synonyms are always on, with no condition and no schedule.

**Reference:** [`groups`](/docs/reference/release-files/synonyms#groups) · [`one_way`](/docs/reference/release-files/synonyms#one_way) · [`never`](/docs/reference/release-files/synonyms#never) · [`GET /api/v1/synonyms`](/docs/reference/management-api#list-synonyms)

## A synonym names what its partner names

A word with a synonym also names what its partner names. "Sofa" is the title of the category Sofas, so "couch" names it too, and so does a compound that ends in a synonym:

```sh title="Terminal"
for q in couch tischlampe; do
  curl -s "http://127.0.0.1:7800/v1/search?query=$q&debug=1" \
    | jq -r '.trace.steps[] | select(.step == "resolve") | .detections[].entry'
done
```

```text title="Answer: 200 · 6.5 ms · release b25a6aaa"
"couch", a synonym of "sofa", and "sofa" is the title "Sofas" of the category wohnen/sofas
"tischlampe" ends in "lampe", a synonym of "leuchte", and "leuchte" is the title "Leuchten" of the category leuchten
```

"Couch" leads to the sofa page `wohnen/sofas`. "Tischlampe" (table lamp) is in no entry, but its last part "lampe" is grouped with "leuchte" (light), which names the category Leuchten. The detection names its entry in `synonym`, such as `de/groups/0`, so the [trace](/docs/reference/glossary#trace) shows which entry decided.

**Reference:** [`resolve`](/docs/reference/trace#resolve) · [`Detection`](/docs/reference/trace#Detection)

## Add a synonym

A shopper searched "Kanapee", an old word for sofa, and found nothing. Add it in a [draft](/docs/reference/glossary#draft), which shoppers see once you publish it.

**In the panel:**

**Search → Synonyms** lists one language's entries, and your first change starts the draft. Type `kanapee, sofa` on the line on top, and it shows what each word finds now and what the line clashes with:

![The line on top holds “kanapee, sofa”: kanapee finds nothing, sofa finds 4 results, and sofa is in the group sofa, couch already.](/docs/shots/synonyms-light.avif)

Panel · Search → Synonyms

A word is in one group at most, so the line offers **Add “kanapee” there**, which joins the group of sofa and couch. A line without a clash goes in with Enter; **One way** makes its first word find the others only, and words a never entry keeps apart go in only with **Add anyway**.

The publish bar above the list says how the draft goes live, here within moments and without a rebuild, beside **Publish** and **Discard**. A search that finds nothing in the **Playground** or **Insights** offers **Add a synonym** with its words on this line.

**Through the API:**

Start a draft with the store's export, then add the entry. "sofa" is in a group already, so the add is refused with the entry that holds it:

```sh title="Terminal"
draft=$(curl -s -X POST -H "Authorization: Bearer $key" -H 'Content-Type: application/x-ndjson' \
  --data-binary @tests/fixtures/home/catalog.jsonl "$api/imports?name=catalog.jsonl" | jq -r .import.id)
version=$(curl -s -H "Authorization: Bearer $key" $api/imports/$draft | jq -r .version)
jq -n --arg version "$version" '{version: $version, locale: "de", entries: [{kind: "group", words: ["kanapee", "sofa"]}]}' \
  | curl -s -H "Authorization: Bearer $key" -H 'Content-Type: application/json' -d @- $api/imports/$draft/synonyms \
  | jq -c '.refused[].conflicts[]'
```

```json title="Answer: 422 · 15 ms"
{"id":"taken","word":"sofa","entry":"de/groups/0","words":["sofa","couch"],"sentence":"“sofa” is in the group sofa, couch already."}
```

Add the word to that group instead. A `PATCH` replaces the entry's words:

```sh title="Terminal"
jq -n --arg version "$version" '{version: $version, entry: {kind: "group", words: ["sofa", "couch", "kanapee"]}}' \
  | curl -s -X PATCH -H "Authorization: Bearer $key" -H 'Content-Type: application/json' -d @- \
    $api/imports/$draft/synonyms/de/groups/0 \
  | jq -r '.synonyms.dictionaries[] | select(.locale == "de") | .entries[0].summary'
```

```text title="Answer: 200 · 41 ms"
sofa, couch and kanapee find each other.
```

Open the search page for "kanapee" with the draft's files in place of the live ones:

```sh title="Terminal"
body=$(curl -s -H "Authorization: Bearer $key" $api/imports/$draft | jq '{url: "/suche?q=kanapee", files}')
curl -s -H "Authorization: Bearer $key" -H 'Content-Type: application/json' -d "$body" $api/preview/page \
  | jq -c '.location, [.results.sections[] | select(.type == "product") | .hits[].entity]'
```

```json title="Answer: 200 · 32 ms · release 380e9fcc"
"/wohnen/sofas?from=kanapee"
["product:SOFA-ASKA-2","product:SOFA-LUND-3","product:SOFA-NIKO-SCHLAF","product:SOFA-VIDAR-ECK"]
```

The search page `/suche` (search) sends "kanapee" to the sofa page with all four sofas. Ask how the draft goes live, then publish it with `POST $api/imports/$draft/publish`, or discard it:

```sh title="Terminal"
curl -s -H "Authorization: Bearer $key" $api/imports/$draft/publication | jq -r .course.sentence
curl -s -X DELETE -H "Authorization: Bearer $key" $api/imports/$draft | jq -r .state
```

```text title="Answer: 200 · 16 ms"
Goes live within moments: synonyms.json changed only settings the engine applies without reindexing.
discarded
```

A write on a draft that changed since its `version` is refused with `409`. `POST .../synonyms/check` answers what the panel's line shows, without writing.

**Reference:** [`POST .../synonyms`](/docs/reference/management-api#add-synonyms) · [`PATCH .../synonyms/{synonym}`](/docs/reference/management-api#change-synonym) · [`POST .../synonyms/check`](/docs/reference/management-api#check-synonym) · [`POST /api/v1/preview/page`](/docs/reference/management-api#preview-page)

A draft that changes only synonyms goes live as settings, within moments and without a rebuild ([Releases](/docs/releases)).

## Traps and limits

- **Words are folded.** Entries are lowercased, and "Weiß" and "weiss" (white) are one word, in an entry as in a search.
- **`never` guards edits only.** It refuses an added or changed entry that merges its words, unless the write says `despite_never`. A `synonyms.json` written whole keeps such a group, and the list names the clash in its `conflicts`.
- **Each language has its own dictionary.** A locale reads its own, else that of its shorter tag: `de-CH` reads `de`, as the list's `reads` says.
- **A compound counts by its last part.** An entry for "lampe" reaches "Tischlampe" too.
- **A preview shows what a synonym names, not what it finds in text.** The engine keeps the live synonyms until you publish, so "kanapee" previews through the sofa category, while a synonym for a word in product texts shows once live.

**Reference:** [`despite_never`](/docs/reference/management-api#add-synonyms.despite_never) · [`SynonymList`](/docs/reference/management-api#SynonymList)

## Next

- [Patterns](/docs/patterns): turn a phrase such as "bis 220 cm" into a filter.
- [How search understands a query](/docs/query-understanding): everything a query can name.
- [Releases](/docs/releases): draft, preview, publish and roll back.
