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

# Match queries in the middle or end of words

> How to match queries with records when the query text appears mid-word or at the end.

Algolia matches queries from the beginning of words (prefix matches),
but it doesn't match queries that appear in the middle (infix) or at the end (suffix).
For example, searching for "note" matches "notepad" and "notebook",
but not "keynote".
This can cause issues when users search for a product reference without its internal prefix,
such as searching "ABCDEF" for an item stored as "XXXABCDEF" in your index.

To support infix and suffix matching,
generate every possible suffix of a string
(for example, "ABCDEF", "BCDEF", "CDEF", "DEF", "EF", "F").

## Create suffix attributes

In the following product dataset,
the `product_reference` attribute has a numeric prefix, used for internal purposes.

```json JSON icon=braces theme={"system"}
[
  {
    "name": "Apple iPhone 16",
    "product_reference": "002ABCDEF"
  },
  {
    "name": "Apple iPhone 16 Plus",
    "product_reference": "001GHIJKL"
  }
]
```

To run the code examples on this page, [install the latest API client](https://www.algolia.com/doc/libraries/sdk/install.md).
The function shown below generates an attribute that contains all possible suffixes for `product_reference`, as an array.

```cs C# theme={"system"}
namespace Algolia;

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using Algolia.Search.Clients;
using Algolia.Search.Http;
using Algolia.Search.Models.Search;

class SaveObjectsModified
{
  async Task Main(string[] args)
  {
    var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY"));

    var jsonContent = await File.ReadAllTextAsync("products.json");
    var products = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(jsonContent);

    var records = products
      ?.Select(product =>
      {
        var reference = product.GetValueOrDefault("product_reference", "") as string;
        var suffixes = new List<string>();

        for (var i = reference!.Length; i > 1; i--)
        {
          suffixes.Add(reference[i..]);
        }

        return new Dictionary<string, object>(product) { ["suffixes"] = suffixes };
      })
      .ToList();

    await client.SaveObjectsAsync("INDEX_NAME", records);
  }
}

```

```dart Dart theme={"system"}
import 'dart:convert';
import 'dart:io';

import 'package:algolia_client_search/algolia_client_search.dart';

void saveObjectsModified() async {
  final client =
      SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY');

  final path = '/tmp/records.json';
  final json = File(path).readAsStringSync();
  List<Map<String, dynamic>> products;
  try {
    products = List<Map<String, dynamic>>.from(jsonDecode(json));
  } catch (e) {
    throw Exception('Failed to read file at $path: $e');
  }

  final records = products.map((product) {
    final reference = product['product_reference'].toString();
    final suffixes = [
      for (int i = 1; i <= reference.length; i++)
        reference.substring(i - 1, reference.length)
    ];
    final updatedProduct = {
      ...product,
      'product_reference_suffixes': suffixes,
    };
    return updatedProduct;
  }).toList();

  final batchParams = BatchWriteParams(
      requests: records
          .map((record) => BatchRequest(
                action: Action.addObject,
                body: record,
              ))
          .toList());
  await client.batch(
    indexName: "INDEX_NAME",
    batchWriteParams: batchParams,
  );
}

```

```go Go theme={"system"}
package main

import (
	"encoding/json"
	"os"

	"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
)

func saveObjectsModified() {
	client, err := search.NewClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
	if err != nil {
		// The client can fail to initialize if you pass an invalid parameter.
		panic(err)
	}

	content, err := os.ReadFile("products.json")
	if err != nil {
		panic(err)
	}

	var products []map[string]any

	err = json.Unmarshal(content, &products)
	if err != nil {
		panic(err)
	}

	records := make([]map[string]any, 0, len(products))

	for _, product := range products {
		reference := product["product_reference"].(string)
		suffixes := make([]string, 0, len(reference)-1)

		for i := len(reference); i > 1; i-- {
			suffixes = append(suffixes, reference[i:])
		}

		record := product
		record["product_reference_suffixes"] = suffixes

		records = append(records, record)
	}

	_, err = client.SaveObjects(
		"INDEX_NAME", records)
	if err != nil {
		panic(err)
	}
}

```

```java Java theme={"system"}
package com.algolia;

import com.algolia.api.SearchClient;
import com.algolia.config.*;
import com.algolia.model.search.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class saveObjectsModified {

  public static void main(String[] args) throws Exception {
    try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) {
      JsonNode content = new ObjectMapper().readTree(new File("products.json"));
      List<Map<String, Object>> products = new ObjectMapper().readerForListOf(Map.class).readValue(content);

      List<Map<String, Object>> records = products
        .stream()
        .map(product -> {
          String reference = (String) product.get("product_reference");
          List<String> suffixes = new ArrayList<>();

          for (int i = reference.length(); i > 1; i--) {
            suffixes.add(reference.substring(i));
          }

          Map<String, Object> record = new HashMap<>(Map.copyOf(product));
          record.put("product_reference_suffixes", suffixes);
          return record;
        })
        .toList();

      client.saveObjects("INDEX_NAME", records);
    } catch (Exception e) {
      System.out.println("An error occurred: " + e.getMessage());
    }
  }
}

```

```js JavaScript theme={"system"}
import { algoliasearch } from 'algoliasearch';

const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');

// @ts-ignore
const { default: products } = await import('./products.json');

const records = products.map((product) => {
  const reference = product['product_reference'];
  const suffixes: string[] = [];

  for (let i = reference.length; i > 1; i--) {
    suffixes.unshift(reference.slice(i));
  }
  return { ...product, product_reference_suffixes: suffixes };
});

await client.saveObjects({ indexName: 'indexName', objects: records });

```

```kotlin Kotlin theme={"system"}
import com.algolia.client.api.SearchClient
import com.algolia.client.configuration.*
import com.algolia.client.extensions.*
import com.algolia.client.model.search.*
import com.algolia.client.transport.*
import java.io.File
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.*

suspend fun saveObjectsModified() {
  val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")

  val path = "/tmp/records.json"
  val json =
    try {
      File(path).readText()
    } catch (e: Exception) {
      throw RuntimeException("Failed to read file at $path", e)
    }
  val products: List<JsonObject> =
    Json.decodeFromString(ListSerializer(JsonObject.serializer()), json)

  val records =
    products.map { product ->
      val reference = product["product_reference"].toString()
      val suffixes =
        reference.windowed(reference.length, 1, partialWindows = true).drop(1).map {
          JsonPrimitive(it)
        }
      JsonObject(product + ("product_reference_suffixes" to JsonArray(suffixes)))
    }

  client.saveObjects(indexName = "INDEX_NAME", objects = records)
}

```

```php PHP theme={"system"}
<?php

require __DIR__.'/../vendor/autoload.php';
use Algolia\AlgoliaSearch\Api\SearchClient;

$client = SearchClient::create('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');

$path = dirname(__FILE__).DIRECTORY_SEPARATOR.'products.json';

if (!file_exists($path)) {
    throw new Exception("File not found: {$path}");
}

$data = file_get_contents($path);

if (false === $data) {
    throw new Exception("Failed to read file: {$path}");
}

$products = json_decode($data, true);

if (JSON_ERROR_NONE !== json_last_error()) {
    throw new Exception('JSON decode error: '.json_last_error_msg());
}

$records = array_map(function ($product) {
    $reference = $product['product_reference'];
    $suffixes = [];

    while (strlen($reference) > 1) {
        $reference = substr($reference, 1);
        $suffixes[] = $reference;
    }

    $product['product_reference_suffixes'] = $suffixes;

    return $product;
}, $products);

$client->saveObjects(
    'INDEX_NAME',
    $records,
);

```

```python Python theme={"system"}
import json

from algoliasearch.search.client import SearchClientSync


with open("products.json", "r", encoding="utf-8") as f:
    products = json.load(f)

    _client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")

    records = []

    for product in products:
        reference = product["product_reference"]

        suffixes = []
        while len(reference) > 1:
            reference = reference[1:]
            suffixes.append(reference)

        product["product_reference_suffixes"] = suffixes
        records.append(product)

    _client.save_objects(
        index_name="INDEX_NAME",
        objects=records,
    )

```

```ruby Ruby theme={"system"}
require "json"
require "algolia"

client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")

products = JSON.parse(File.read("products.json"))

records = []

products.each do |product|
  reference = product["product_reference"]
  suffixes = []

  while reference.length > 1
    reference = reference[1..-1]
    suffixes << reference
  end

  product["product_reference_suffixes"] = suffixes
  records << product
end

client.save_objects("INDEX_NAME", records)

```

```scala Scala theme={"system"}
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.io.Source
import scala.util.Using

import org.json4s.native.JsonMethods
import org.json4s.jvalue2extractable

import algoliasearch.api.SearchClient
import algoliasearch.config.*
import algoliasearch.extension.SearchClientExtensions
import algoliasearch.search.JsonSupport

def saveObjectsModified(): Future[Unit] = {
  implicit val formats: org.json4s.Formats = JsonSupport.format

  val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")

  val path = "/tmp/records.json"
  val result = Using(Source.fromFile(path))(_.mkString).getOrElse {
    throw new RuntimeException(s"Failed to read file at $path")
  }
  val products = JsonMethods.parse(result).extract[Seq[Map[String, Any]]]

  val records = products.map { product =>
    val reference = product("product_reference").toString
    val suffixes = reference.tails.toList.drop(1).filter(_.nonEmpty)
    product + ("product_reference_suffixes" -> suffixes)
  }

  Await.result(
    client
      .saveObjects(
        indexName = "INDEX_NAME",
        objects = records
      )
      .map(_ => Future.unit),
    Duration(5, "seconds")
  )
}

```

```swift Swift theme={"system"}
import Foundation
#if os(Linux) // For linux interop
    import FoundationNetworking
#endif

import AlgoliaCore
import AlgoliaSearch

func saveObjectsModified() async throws {
    let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY")

    let path = URL(string: #file)!.deletingLastPathComponent()
        .appendingPathComponent("products.json")
    let data = try Data(contentsOf: URL(fileURLWithPath: path.absoluteString))
    let products = try JSONDecoder().decode([[String: AnyCodable]].self, from: data)

    let records = products.map { product -> [String: AnyCodable] in
        var reference = product["product_reference"]?.value as! String
        var suffixes: [String] = []

        while reference.count > 1 {
            reference = String(reference.dropFirst())
            suffixes.append(reference)
        }
        var record: [String: AnyCodable] = product
        record["product_reference_suffixes"] = AnyCodable(suffixes)
        return record
    }

    try await client.saveObjects(indexName: "INDEX_NAME", objects: records)
}

```

The processed records include a new attribute,
`product_reference_suffixes`:

```json JSON icon=braces theme={"system"}
[
  {
    "name": "Apple iPhone 16",
    "product_reference": "002ABCDEF",
    "product_reference_suffixes": ["02ABCDEF", "2ABCDEF", "ABCDEF", "BCDEF", "CDEF", "DEF", "EF", "F"]
  },
  {
    "name": "Apple iPhone 16 Plus",
    "product_reference": "001GHIJKL",
    "product_reference_suffixes": ["01GHIJKL", "1GHIJKL", "GHIJKL", "HIJKL", "IJKL", "JKL", "KL", "L"]
  }
]
```

### Limitations

Generating a list of suffixes for each record can increase index size and slow performance,
It may also reduce relevance for short suffixes such as "EF" or "F",
because these can match unrelated records with similar character sequences.
Use this approach only if you genuinely need to query in the middle or the end of words.

To reduce the impact of suffix generation, consider the following strategies:

* Generate suffixes only for strings longer than a defined length, such as 4 or more characters.
* Limit suffix generation to a specific number per record, such as 5.
* Apply suffix generation only to structured fields, such as product codes or identifiers.

## Update your searchable attributes

Add `product_reference_suffixes` to your [searchable attributes](https://www.algolia.com/doc/guides/managing-results/must-do/searchable-attributes) to make the suffixes searchable.

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

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

```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
  "INDEX_NAME",
  search.NewEmptyIndexSettings().SetSearchableAttributes(
    []string{"name", "product_reference", "product_reference_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("name", "product_reference", "product_reference_suffixes"))
);
```

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

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

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

        'product_reference',

        'product_reference_suffixes',
    ],
    ],
);
```

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

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

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

```swift Swift theme={"system"}
let response = try await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(searchableAttributes: [
        "name",
        "product_reference",
        "product_reference_suffixes",
    ])
)
```

In your list of searchable attributes,
place `product_reference_suffixes` below `product_reference` to exact matches rank higher than partial matches.

## See also

* [Searching in hyphenated attributes](https://www.algolia.com/doc/guides/managing-results/optimize-search-results/typo-tolerance/how-to/how-to-search-in-hyphenated-attributes.md)
* [Transform your data with code](https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code.md)
