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

# Search in hyphenated attributes

> Learn the best practices for searching in hyphenated attributes that can have multiple formats, such as SKUs, ISBNs, phone numbers, and serial numbers.

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>;

Indexing hyphenated attributes like SKUs, ISBNs, phone numbers, and serial numbers is tricky because your users may search for them in different ways. For example, your users might search for an item with the serial number `1234-XYZ-B5` with any of these queries:

* `1234-XYZ-B5`
* `1234XYZB5`
* `1234XYZ-B5`
* `1234-XYZ`
* `XYZ-B5`

To ensure your users find what they're looking for, you need to consider the following steps:

* Formatting the numbers in your <Records />
* Configuring the relevance

The following guidelines also apply to hyphenated attributes such as SKUs, ISBNs, phone numbers, or serial numbers.

## Formatting records

### Formatting non-alphanumeric characters

Searching through hyphenated attributes is tricky,
and it's not just because users search with different formats.
It's also tricky because these attributes include non-alphanumeric characters,
such as hyphens (`-`),
pound signs (`#`), or plus signs (`+`).

By default, [Algolia doesn't index non-alphanumeric characters](/doc/guides/managing-results/optimize-search-results/override-search-engine-defaults#enable-search-on-non-alphanumeric-characters), or "separators", meaning they aren't searchable. They're, however, essential for [tokenization](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp/in-depth/tokenization).

For example, the string `1234-XYZ-B5` is tokenized as `1234`,
`-`, `XYZ`, `-`, `B5`, because the hyphen (`-`) is a separator,
and all the other characters aren't.
Then, by default, Algolia only indexes the non-separator tokens `1234`, `XYZ`, `B5`.
The same is true for the string `1234 XYZ B5`, since a space is also a separator.
That's why `1234-XYZ-B5` and `1234 XYZ B5` are functionally the same.
Both would be a match, whether your user searches for `1234-XYZ-B5` or `1234 XYZ B5`.

```json JSON icon=braces theme={"system"}
{
  "serial_number": "1234-XYZ-B5"
}
```

```json JSON icon=braces theme={"system"}
{
  "serial_number": "1234 XYZ B5"
}
```

**Don't index special characters, unless they distinguish important differences between records**.
For example, you should only do this if the results for `1234-XYZ-B5` must be different than the results or `1234 XYZ B5`.
This distinction is uncommon for ISBN, SKU, phone number, or serial number use cases, but could be true for other use cases, such as when searching for the programming languages `C` vs. `C++`.

### Include all possible formats

Difficulties can arise when users search with a format that removes spaces or special characters, for example, `1234XYZB5`. While Algolia handles some [splitting and concatenation](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp/in-depth/splitting-and-concatenation), there are [special considerations when numbers are involved](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp/in-depth/splitting-and-concatenation#concatenation-with-numbers), and Algolia may not handle several concatenations at once. For that reason, **index all possible formats your users search with, not counting using different separators.**

For example, this indexing format includes all different formats but only uses spaces and doesn't include any versions with hyphens:

```json JSON icon=braces theme={"system"}
{
  "serial_number": [
    "1234 XYZ B5",
    "1234XYZB5",
    "1234XYZ B5",
    "1234 XYZB5",
    "1234 XYZ",
    "XYZ B5"
  ]
}
```

This format will make the record a match when users search for any of these variants:

* `1234-XYZ-B5`
* `1234 XYZ B5`
* `1234XYZB5`
* `1234XYZ-B5`
* `1234XYZ B5`
* `1234-XYZB5`
* `1234 XYZB5`
* `1234-XYZ`
* `1234 XYZ`
* `XYZ-B5`
* `XYZ B5`

This formatting has some redundancy.
The following tokens are indexed for each element:

```jsonc JSON icon=braces theme={"system"}
{
  "serial_number": [
    "1234 XYZ B5", // tokens: ["1234", "XYZ", "B5"]
    "1234XYZB5",   // tokens: ["1234XYZB5"]
    "1234XYZ B5",  // tokens: ["1234XYZ", "B5"]
    "1234 XYZB5",  // tokens: ["1234", "XYZB5"]
    "1234 XYZ",    // tokens: ["1234", "XYZ"]
    "XYZ B5",      // tokens: ["XYZ", "B5"]
  ]
}
```

Since the unique tokens are `["1234", "XYZ", "B5", "1234XYZB5", "1234XYZ", "XYZB5"]`, these are the only ones that need indexing.

However, you may not want to undertake the work necessary to deduplicate redundant tokens.
Having all variants allows for a more accurate [proximity score](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria#proximity), if your users tend to search with tokens in the same order as the original version.
For example, while a user may search for `1234 XYZB5`, they probably won't search for  `XYZB5 1234`.

### Suffix search

Since Algolia doesn't support [infix or suffix matching](/doc/guides/managing-results/optimize-search-results/override-search-engine-defaults/how-to/how-can-i-make-queries-within-the-middle-of-a-word) it can't find sub-strings in the middle or the end of a string.
If you want users to be able to search on suffixes, you need to index them.

```json JSON icon=braces theme={"system"}
{
  "serial_number": "1234XYZB5",
  "serial_number_suffixes": [
    "234XYZB5",
    "34XYZB5",
    "4XYZB5",
    "XYZB5",
    "YZB5",
    "ZB5",
    "B5"
  ]
}
```

<Note>
  Algolia is optimized to search in textual attributes.
  Therefore, make sure to store numerical attributes used for search (as opposed to [filtering](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-numeric-value),
  [ranking](/doc/guides/managing-results/must-do/custom-ranking),
  or [sorting](/doc/guides/managing-results/refine-results/sorting)) as strings, not numbers.
</Note>

### One format for search and another for display

If you want to display one particular format, you need to include another attribute for the display value.

```json JSON icon=braces theme={"system"}
{
  "serial_number": [
    "1234 XYZ B5",
    "1234XYZB5",
    "1234XYZ B5",
    "1234XYZ B5",
    "1234 XYZ",
    "XYZ B5"
  ],
  "serial_number_for_display": "1234-XYZ-B5"
}
```

To make the response payload more lightweight, you can exclude the non-display version from the response using [`attributesToRetrieve`](/doc/api-reference/api-parameters/attributesToRetrieve).

## Configuring the relevance

### Configuring searchable attributes

To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).

To ensure that various formats are searchable,
add the formatted attribute to the [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes) setting.

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.SetSettingsAsync(
    "INDEX_NAME",
    new IndexSettings { SearchableAttributes = new List<string> { "serial_number" } }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(
      searchableAttributes: [
        "serial_number",
      ],
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.SetSettings(client.NewApiSetSettingsRequest(
    "INDEX_NAME",
    search.NewEmptyIndexSettings().SetSearchableAttributes(
      []string{"serial_number"})))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  UpdatedAtResponse response = client.setSettings(
    "INDEX_NAME",
    new IndexSettings().setSearchableAttributes(Arrays.asList("serial_number"))
  );
  ```

  ```js JavaScript theme={"system"}
  const response = await client.setSettings({
    indexName: 'theIndexName',
    indexSettings: { searchableAttributes: ['serial_number'] },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(searchableAttributes = listOf("serial_number")),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->setSettings(
      'INDEX_NAME',
      ['searchableAttributes' => [
          'serial_number',
      ],
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.set_settings(
      index_name="INDEX_NAME",
      index_settings={
          "searchableAttributes": [
              "serial_number",
          ],
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.set_settings(
    "INDEX_NAME",
    Algolia::Search::IndexSettings.new(searchable_attributes: ["serial_number"])
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(
        searchableAttributes = Some(Seq("serial_number"))
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.setSettings(
      indexName: "INDEX_NAME",
      indexSettings: IndexSettings(searchableAttributes: ["serial_number"])
  )
  ```
</CodeGroup>

If you added alternative forms in a separate attribute, you should also include it in [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes).

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.SetSettingsAsync(
    "INDEX_NAME",
    new IndexSettings
    {
      SearchableAttributes = new List<string> { "serial_number", "serial_number_suffixes" },
    }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(
      searchableAttributes: [
        "serial_number",
        "serial_number_suffixes",
      ],
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.SetSettings(client.NewApiSetSettingsRequest(
    "INDEX_NAME",
    search.NewEmptyIndexSettings().SetSearchableAttributes(
      []string{"serial_number", "serial_number_suffixes"})))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  UpdatedAtResponse response = client.setSettings(
    "INDEX_NAME",
    new IndexSettings().setSearchableAttributes(Arrays.asList("serial_number", "serial_number_suffixes"))
  );
  ```

  ```js JavaScript theme={"system"}
  const response = await client.setSettings({
    indexName: 'theIndexName',
    indexSettings: { searchableAttributes: ['serial_number', 'serial_number_suffixes'] },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings =
        IndexSettings(searchableAttributes = listOf("serial_number", "serial_number_suffixes")),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->setSettings(
      'INDEX_NAME',
      ['searchableAttributes' => [
          'serial_number',

          'serial_number_suffixes',
      ],
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.set_settings(
      index_name="INDEX_NAME",
      index_settings={
          "searchableAttributes": [
              "serial_number",
              "serial_number_suffixes",
          ],
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.set_settings(
    "INDEX_NAME",
    Algolia::Search::IndexSettings.new(searchable_attributes: ["serial_number", "serial_number_suffixes"])
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(
        searchableAttributes = Some(Seq("serial_number", "serial_number_suffixes"))
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.setSettings(
      indexName: "INDEX_NAME",
      indexSettings: IndexSettings(searchableAttributes: ["serial_number", "serial_number_suffixes"])
  )
  ```
</CodeGroup>

If you added a [display attribute](/doc/guides/sending-and-managing-data/prepare-your-data#attributes-for-display),
you **shouldn't** include it in [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes).

### Hyphenated attributes need to match without typos

Often, when a user searches for a particular number, they're looking for only that record and aren't interested in close matches. In that case, you can turn off [typo tolerance](/doc/guides/managing-results/optimize-search-results/typo-tolerance) on the hyphenated number attribute using [`disableTypoToleranceOnAttributes`](/doc/api-reference/api-parameters/disableTypoToleranceOnAttributes).

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.SetSettingsAsync(
    "INDEX_NAME",
    new IndexSettings { DisableTypoToleranceOnAttributes = new List<string> { "serial_number" } }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(
      disableTypoToleranceOnAttributes: [
        "serial_number",
      ],
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.SetSettings(client.NewApiSetSettingsRequest(
    "INDEX_NAME",
    search.NewEmptyIndexSettings().SetDisableTypoToleranceOnAttributes(
      []string{"serial_number"})))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  UpdatedAtResponse response = client.setSettings(
    "INDEX_NAME",
    new IndexSettings().setDisableTypoToleranceOnAttributes(Arrays.asList("serial_number"))
  );
  ```

  ```js JavaScript theme={"system"}
  const response = await client.setSettings({
    indexName: 'theIndexName',
    indexSettings: { disableTypoToleranceOnAttributes: ['serial_number'] },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(disableTypoToleranceOnAttributes = listOf("serial_number")),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->setSettings(
      'INDEX_NAME',
      ['disableTypoToleranceOnAttributes' => [
          'serial_number',
      ],
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.set_settings(
      index_name="INDEX_NAME",
      index_settings={
          "disableTypoToleranceOnAttributes": [
              "serial_number",
          ],
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.set_settings(
    "INDEX_NAME",
    Algolia::Search::IndexSettings.new(disable_typo_tolerance_on_attributes: ["serial_number"])
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(
        disableTypoToleranceOnAttributes = Some(Seq("serial_number"))
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.setSettings(
      indexName: "INDEX_NAME",
      indexSettings: IndexSettings(disableTypoToleranceOnAttributes: ["serial_number"])
  )
  ```
</CodeGroup>

### Hyphenated attributes need to match entirely (without prefix)

Similarly, if you would like to display results only on complete (and not prefixed) matches, you can turn off prefix matching on the hyphenated attribute using [`disablePrefixOnAttributes`](/doc/api-reference/api-parameters/disablePrefixOnAttributes).

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.SetSettingsAsync(
    "INDEX_NAME",
    new IndexSettings { DisablePrefixOnAttributes = new List<string> { "serial_number" } }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(
      disablePrefixOnAttributes: [
        "serial_number",
      ],
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.SetSettings(client.NewApiSetSettingsRequest(
    "INDEX_NAME",
    search.NewEmptyIndexSettings().SetDisablePrefixOnAttributes(
      []string{"serial_number"})))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  UpdatedAtResponse response = client.setSettings(
    "INDEX_NAME",
    new IndexSettings().setDisablePrefixOnAttributes(Arrays.asList("serial_number"))
  );
  ```

  ```js JavaScript theme={"system"}
  const response = await client.setSettings({
    indexName: 'theIndexName',
    indexSettings: { disablePrefixOnAttributes: ['serial_number'] },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(disablePrefixOnAttributes = listOf("serial_number")),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->setSettings(
      'INDEX_NAME',
      ['disablePrefixOnAttributes' => [
          'serial_number',
      ],
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.set_settings(
      index_name="INDEX_NAME",
      index_settings={
          "disablePrefixOnAttributes": [
              "serial_number",
          ],
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.set_settings(
    "INDEX_NAME",
    Algolia::Search::IndexSettings.new(disable_prefix_on_attributes: ["serial_number"])
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(
        disablePrefixOnAttributes = Some(Seq("serial_number"))
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.setSettings(
      indexName: "INDEX_NAME",
      indexSettings: IndexSettings(disablePrefixOnAttributes: ["serial_number"])
  )
  ```
</CodeGroup>

### Handling non-alphanumeric characters

By default, Algolia ignores non-alphanumeric characters like hyphen (`-`), plus (`+`), and parentheses (`(`,`)`).
Whether these characters are in the query or the <Index />, Algolia won't search for them.

This is by design:
searching for `+33` returns all records with an attribute starting with `+33` or `33`, because Algolia ignores the plus sign (`+`).
If you would like users to search with special characters, you must tell Algolia to index these characters with [`separatorsToIndex`](/doc/api-reference/api-parameters/separatorsToIndex).

For example, if you include `+` in  [`separatorsToIndex`](/doc/api-reference/api-parameters/separatorsToIndex), searching for `+33` will only return records containing both `+` and `33`. Since adding [`separatorsToIndex`](/doc/api-reference/api-parameters/separatorsToIndex) can make a search more restrictive and complex, it's generally not desirable to do so for these use cases.

**Don't include a character as a [`separatorsToIndex`](/doc/api-reference/api-parameters/separatorsToIndex) unless its presence distinguishes between different records.** For example, if searches for `1234-XYZ-B5` and `1234 XYZ B5` should return different results. This distinction is uncommon for SKU, ISBN, phone numbers, and serial number use cases.

### Searching with `removeWordsIfNoResults` enabled

The [`removeWordsIfNoResults`](/doc/api-reference/api-parameters/removeWordsIfNoResults) parameter helps you avoid showing empty results by removing less critical words when a query returns nothing.
It lets you trade precision for recall, depending on your use case.
Due to the [treatment](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp/in-depth/splitting-and-concatenation#concatenation-during-indexing) of separator characters,
this parameter might not work as expected when searching for hyphenated attributes.

For more information, see [Non-alphanumeric characters](/doc/guides/managing-results/optimize-search-results/empty-or-insufficient-results/in-depth/why-use-remove-words-if-no-results#non-alphanumeric-characters).
