# Fields and typos

Choose which fields a search reads, in which order, and how many typos it forgives.

A shopper's words are looked for in the fields their [type](/docs/reference/glossary#type) lists, in priority order, and a long word is found even with a typo. Here is how the home store searches its products:

```sh title="Terminal"
curl -s -H "Authorization: Bearer $key" $api/search-settings \
  | jq -c '.types[] | select(.type == "product") | .fields.source, [.fields.fields[].field], .typos'
```

```json title="Answer: 200 · 24 ms · release b25a6aaa"
"set"
["title","brand","keywords","attributes.color","attributes.material","attributes.upholstery","attributes.style","attributes.model_number","attributes.gtin","description"]
{"source":"built_in","enabled":true,"one_typo":5,"two_typos":9}
```

A word found in an earlier field ranks a hit higher, so the title comes first. Each setting's `source` says where it comes from: `set` in `types.json`, `derived` from the catalog, or `built_in`, OrbSearch's default.

**Reference:** [`search`](/docs/reference/release-files/types#search) · [`GET /api/v1/search-settings`](/docs/reference/management-api#show-search-settings)

## Typos

A word of 5 letters or more finds what holds it with one typo, and from 9 letters with two. A typo is a letter added, left out or changed, or two neighbours swapped; one at the first letter counts as two. "Teppcih" swaps two letters of "Teppich" (rug), and the trace's rank step names the typo:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/search?query=Teppcih&debug=1' \
  | jq -r '.trace.steps[] | select(.step == "rank") | .sections[] | select(.type == "product")
      | .hits[0] | .entity, (.why[] | select(.because.kind == "text") | .sentence)'
```

```text title="Answer: 200 · 5.6 ms · release b25a6aaa"
product:TEPPICH-MARE
Found by every word of the query, with 1 typo (“teppcih” as “Teppich”), in title, description and words.
```

`words` holds what the index adds, such as the titles of a product's categories. A shorter word finds only as written or by its start, so "Lnud" does not find the Lund sofa.

**Reference:** [`typos`](/docs/reference/release-files/types#typos) · [`rank`](/docs/reference/trace#rank)

## Identifiers exempt from typos

A part number one character off names another product, so the words of an attribute in `no_typos` are found only as written or by their start. Where a type leaves `no_typos` out, every [index run](/docs/reference/glossary#index-run) derives it from the catalog, with a reason for each identifier the type searches:

```sh title="Terminal"
curl -s -H "Authorization: Bearer $key" $api/search-settings \
  | jq -c '.types[] | select(.type == "product") | .exempt | .source, (.open[] | {attribute, reason})'
```

```json title="Answer: 200 · 5.5 ms · release b25a6aaa"
"derived"
{"attribute":"model_number","reason":{"by":"few","values":0}}
{"attribute":"gtin","reason":{"by":"few","values":0}}
```

An identifier is exempt as `unique` when 90 % of at least 10 values belong to one product alone. Values products share, such as cross-reference numbers, stay open as `shared`, and too few to tell as `few`: the home store's catalog holds no model numbers or GTINs.

An exemption covers the attribute's own words; the same word in a title still finds with a typo.

**Reference:** [`no_typos`](/docs/reference/release-files/types#no_typos)

## A word the search does not look for

When a product holds the shopper's word in a field the search skips, `POST $api/preview/find` says so, as the playground's **Why isn't it here?** does. The Fjord bed's finish is "Geölt" (oiled), but the product type does not search its finish:

```sh title="Terminal"
curl -s -H "Authorization: Bearer $key" -H 'Content-Type: application/json' \
  -d '{"request": {"query": "geölt"}, "entity": "product:BETT-FJORD"}' $api/preview/find \
  | jq -c '.verdict.held[], .next[0]'
```

```json title="Answer: 200 · 12 ms · release b25a6aaa"
{"word":"geolt","field":"attributes.finish","holds":"Geölt","why":"not_searched"}
{"step":"open_search_settings","type":"product"}
```

`held` names the word as the engine reads it, the field that holds it and why: `not_searched`, or `exempt_from_typos` for an exempt attribute that holds it a typo away. The first next step opens the type's search settings.

**Reference:** [`held`](/docs/reference/management-api#preview-find.answer.verdict.text.held) · [`POST /api/v1/preview/find`](/docs/reference/management-api#preview-find)

## Change how a type is searched

Changes go into a [draft](/docs/reference/glossary#draft), which you publish when it is right. Here the product type's words forgive a typo from 4 letters on.

**In the panel:**

**Search → Fields & typos** shows one type at a time, picked on top:

- **Searched fields**: drag a field by its handle, or press ↑ or ↓ on it. **Add a field** adds one the type could search.
- **Typos**: **Find words with typos**, **One typo from** and **Two typos from**. Set **One typo from** to 4 letters.
- **Exempt from typos**: **Exempt an attribute** adds one; the identifiers left **Open to typos** show with why.

Each card says **Set here** or **Import's default**, and **Back to the default** undoes a setting. The publish bar above says how the draft goes live, beside **Publish**. The **Playground** links here from a hit's typo reason and from **Why isn't it here?**.

**Through the API:**

Start a draft, then change the product type's typo lengths. `typos` replaces the setting whole, a setting left out stays, and `null` sets one back to its default:

```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/search-settings | jq -r .version)
jq -n --arg version "$version" '{version: $version, typos: {enabled: true, one_typo: 4, two_typos: 9}}' \
  | curl -s -X PATCH -H "Authorization: Bearer $key" -H 'Content-Type: application/json' -d @- \
    $api/imports/$draft/search-settings/product \
  | jq -c '.settings.types[] | select(.type == "product") | .typos'
```

```json title="Answer: 200 · 67 ms"
{"source":"set","enabled":true,"one_typo":4,"two_typos":9}
```

The answer is the draft's settings after the write, the typos now `set`. `fields` and `exempt` change the same way. 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: types.json changed only settings the engine applies without reindexing.
discarded
```

**Reference:** [`PATCH .../search-settings/{type}`](/docs/reference/management-api#change-search-settings) · [`GET .../search-settings`](/docs/reference/management-api#show-draft-search-settings)

## How it goes live

The order of the fields and the typos are settings the engine takes at once. The rest changes what the index holds for every product:

| Change                       | Goes live                   |
| ---------------------------- | --------------------------- |
| Order of the searched fields | Within moments, as settings |
| Typo settings                | Within moments, as settings |
| Which fields are searched    | By a rebuild                |
| Which attributes are exempt  | By a rebuild                |

The shop answers with what is live while a rebuild runs ([Releases](/docs/releases)). A preview searches the live index, so a draft's typo settings show only once it is live.

## Next

- [Synonyms](/docs/synonyms): words that find each other, such as "couch" and "sofa".
- [How search understands a query](/docs/query-understanding): how the words left as text are matched.
- [Releases](/docs/releases): draft, preview, publish and roll back.
