> ## Documentation Index
> Fetch the complete documentation index at: https://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](/doc/libraries/sdk/install).
The function shown below generates an attribute that contains all possible suffixes for `product_reference`, as an array.

<CodeGroup>
  ```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)
  }

  ```
</CodeGroup>

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](/doc/guides/managing-results/must-do/searchable-attributes) to make the suffixes searchable.

<CodeGroup>
  ```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",
      ])
  )
  ```
</CodeGroup>

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](/doc/guides/managing-results/optimize-search-results/typo-tolerance/how-to/how-to-search-in-hyphenated-attributes)
* [Transform your data with code](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code)
