> ## 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.

# Prevent typosquatting

> How to prevent typosquatting using Algolia's sort-by feature.

Algolia and other search engines prefer exact matches.
Sometimes, this is used to **take advantage of people's typing mistakes and get ranked high on a popular search query**.

For example, consider Twitter users.
An example of typosquatting is the account @BarakObama,
which has 15,800 followers, but isn't @BarackObama (Barack Obama's official account).
Because Algolia prioritizes exact matches,
typing "BarakObama" would return the "BarakObama" record first,
regardless of custom ranking.

**Not all use cases need to prevent typosquatting**.

However, if this is your case,
which often happens when you have to deal with user-generated content,
you may need to put a strategy in place.

## Dataset example

Back to the Twitter example: assume you have an index called `twitter_accounts` that looks like this:

```json JSON icon=braces theme={"system"}
[
  {
    "twitter_handle": "BarackObama",
    "nb_followers": 103500000
  },
  {
    "twitter_handle": "BarakObama",
    "nb_followers": 15800
  }
]
```

Even if you set descending custom ranking on `nb_followers`,
because Algolia prioritizes exact results,
the @BarakObama account would benefit from the traffic coming from users making a typo when searching for the official Barack Obama account.

You can short-circuit this issue with Algolia's [sort-by-attribute feature](https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute.md).

## Update the dataset

The recommended solution is to add a boolean attributes that separates popular records from the rest.
For example, you could add something like `is_verified_account = true`,
or `is_popular = true`, and sort on that attribute.

> [!TIP]
>
> For this approach to work well, **the number of records with `is_popular` or `is_verified_account`
> set to `true` should be a small subset of the dataset** (up to 1% of the dataset).

You have a popularity metric (`nb_followers`),
so you can use it to define a rule that determines if a record is popular or not.
In this example, you could say that a user is popular if they have more than a million followers.

To run the code examples on this page, [install the latest API client](https://www.algolia.com/doc/libraries/sdk/install.md).
You can use the [`browse`](https://www.algolia.com/doc/libraries/sdk/v1/methods/browse) method to update the index:

```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 SavePopularRecords
{
  class Record
  {
    public int NbFollowers { get; set; }
    public required string TwitterHandle { get; set; }
    public bool IsPopular { get; set; }
  }

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

    var hits = await client.BrowseObjectsAsync<Record>(
      "INDEX_NAME",
      new BrowseParamsObject()
    );

    var records = hits.Select(hit => new Record
      {
        TwitterHandle = hit.TwitterHandle,
        NbFollowers = hit.NbFollowers,
        IsPopular = hit.NbFollowers >= 1_000_000,
      })
      .ToList();

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

```

```dart Dart theme={"system"}
import 'package:algolia_client_search/algolia_client_search.dart';

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

  final List<Map<String, dynamic>> records = [];

  BrowseResponse? browseResponse;
  do {
    final browseParams =
        BrowseParamsObject(hitsPerPage: 1000, cursor: browseResponse?.cursor);
    browseResponse =
        await client.browse(indexName: "indexName", browseParams: browseParams);
    for (final hit in browseResponse.hits) {
      final props = hit.toJson();
      final isPopular = props['nbFollowers'] > 1000;
      final updatedProduct = {
        ...props,
        'isPopular': isPopular,
      };
      records.add(updatedProduct);
    }
  } while (browseResponse.cursor != null);

  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 (
	"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
)

func savePopularRecords() {
	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)
	}

	records := []map[string]any{{ /* Your records */ }}

	err = client.BrowseObjects("INDEX_NAME", search.BrowseParamsObject{}, search.WithAggregator(func(res any, err error) {
		if err != nil {
			panic(err)
		}

		browseRes, ok := res.(search.BrowseResponse)
		if !ok {
			panic("Invalid response")
		}

		for _, hit := range browseRes.Hits {
			record := hit.AdditionalProperties
			record["isPopular"] = record["nbFollowers"].(int) >= 1_000_000

			records = append(records, record)
		}
	}))
	if err != nil {
		panic(err)
	}

	_, 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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class savePopularRecords {

  public static void main(String[] args) throws Exception {
    try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) {
      List<Map<String, Object>> records = new ArrayList<>();

      client.browseObjects("INDEX_NAME", Hit.class).forEach(hit -> {
        Map<String, Object> props = hit.getAdditionalProperties();
        int nbFollowers = (int) props.get("nbFollowers");

        Map<String, Object> record = new HashMap<>();
        record.put("twitterHandle", props.get("twitterHandle"));
        record.put("nbFollowers", nbFollowers);
        record.put("isPopular", nbFollowers >= 1_000_000);

        records.add(record);
      });

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

const records: Array<Record<string, any>> = [];

await client.browseObjects<Record<string, any>>({
  indexName: 'INDEX_NAME',
  browseParams: undefined,
  aggregator: (res) =>
    records.push(
      res.hits.map((record) => ({
        ...record,
        isPopular: record.nbFollowers >= 1_000_000,
      })),
    ),
});

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 kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject

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

  var records: List<JsonObject> = listOf()

  client.browseObjects(
    "INDEX_NAME",
    BrowseParamsObject(),
    aggregator = { response ->
      records =
        records +
          response.hits.map { hit ->
            val props = hit.additionalProperties ?: mapOf()

            val nbFollowers = props["nbFollowers"].toString().toInt()

            buildJsonObject {
              put("twitterHandle", JsonPrimitive(props["twitterHandle"].toString()))
              put("nbFollowers", JsonPrimitive(nbFollowers))
              put("isPopular", JsonPrimitive(nbFollowers > 1000000))
            }
          }
    },
  )
  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');

$results = $client->browseObjects('INDEX_NAME');

$records = [];
foreach ($results as $hit) {
    $records[] = [
        'twitterHandle' => $hit['twitterHandle'],
        'nbFollowers' => $hit['nbFollowers'],
        'isPopular' => $hit['nbFollowers'] > 1000000,
    ];
}

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

```

```python Python theme={"system"}
from algoliasearch.search.client import SearchClientSync

from algoliasearch.search.models.browse_response import BrowseResponse


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


records = []


def _aggregator(resp: BrowseResponse):
    for hit in resp.hits:
        hit_dict = hit.to_dict()
        records.append(
            {
                "twitterHandle": hit_dict["twitterHandle"],
                "nbFollowers": hit_dict["nbFollowers"],
                "isPopular": hit_dict["nbFollowers"] > 1000000,
            }
        )


_client.browse_objects(
    index_name="INDEX_NAME",
    aggregator=_aggregator,
)

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

```

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

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

records = []

client.browse_objects(
  index_name = "INDEX_NAME"
) do |resp|
  resp.hits.each { |hit|
    record = hit
    record["isPopular"] = hit["nbFollowers"] > 1000000
    records.append(record)
  }
end

client.save_objects("INDEX_NAME", records)

```

```scala Scala theme={"system"}
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import algoliasearch.api.SearchClient
import algoliasearch.config.*
import algoliasearch.extension.SearchClientExtensions
import algoliasearch.search.{BrowseParamsObject, BrowseResponse}

case class Record(
    twitterHandle: String,
    nbFollowers: Int,
    isPopular: Boolean
)

def savePopularRecords(): Future[Unit] = {
  val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")

  var records: Seq[Record] = Seq.empty

  client
    .browseObjects(
      "INDEX_NAME",
      BrowseParamsObject(),
      aggregator = (response: BrowseResponse) => {
        records = records ++ response.hits.map { hit =>
          val props = hit.additionalProperties.getOrElse(List()).toMap

          var nbFollowers = props("nbFollowers").toString.toInt

          Record(
            twitterHandle = props("twitterHandle").toString,
            nbFollowers = nbFollowers,
            isPopular = nbFollowers > 1000000
          )
        }
      }
    )
    .flatMap { _ =>
      client.saveObjects(
        indexName = "INDEX_NAME",
        objects = records
      )

      Future.unit
    }
    .recover { case ex: Exception =>
      println(s"An error occurred: ${ex.getMessage}")
    }
}

```

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

import AlgoliaCore
import AlgoliaSearch

struct Record: Codable {
    let twitterHandle: String
    let nbFollowers: Int
    var isPopular = false
}

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

    var records: [Record] = []

    try await client.browseObjects(
        indexName: "INDEX_NAME",
        browseParams: BrowseParamsObject(),
        aggregator: { (response: BrowseResponse<Record>) in
            records.append(contentsOf: response.hits.map {
                $0.nbFollowers < 1_000_000 ? $0 : Record(
                    twitterHandle: $0.twitterHandle,
                    nbFollowers: $0.nbFollowers,
                    isPopular: true
                )
            })
        }
    )

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

```

After the update, your dataset would look like this:

```json JSON icon=braces theme={"system"}
[
  {
    "twitter_handle": "BarackObama",
    "nb_followers": 103500000,
    "is_popular": true
  },
  {
    "twitter_handle": "BarakObama",
    "nb_followers": 15800,
    "is_popular": false
  }
]
```

By default, Typo is the first criterion in [Algolia's ranking formula](https://www.algolia.com/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria.md) which, for most use cases, is sensible.

To prevent typosquatting,
you need to add another ranking signal that's higher than the `typo` rule—the sort-by attribute.

When your ranking has been applied,
searching for "BarakObama" returns the "BarackObama" record first.

## Add a sort-by attribute in the dashboard

1. Go to the [Algolia dashboard](https://dashboard.algolia.com/explorer/browse) and select your Algolia application (An Algolia application is a self-contained environment with its own indices, configuration, and API keys. Applications don't share data or settings with each other.).
2. On the left sidebar, select  **Search**.
3. Select your Algolia index.
4. On the **Configuration** tab, select **Ranking and Sorting**.
5. Click **Add sort-by attribute** and type or select your attributes.
6. Save your changes.

## Add a sort-by attribute with the API

To set a sort-by attribute,
you need to use the [`ranking`](https://www.algolia.com/doc/api-reference/api-parameters/ranking) with the [`setSettings`](https://www.algolia.com/doc/libraries/sdk/v1/methods/set-settings) method.

```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
  "INDEX_NAME",
  new IndexSettings
  {
    Ranking = new List<string>
    {
      "desc(is_popular)",
      "typo",
      "geo",
      "words",
      "filters",
      "proximity",
      "attribute",
      "exact",
      "custom",
    },
  }
);
```

```dart Dart theme={"system"}
final response = await client.setSettings(
  indexName: "INDEX_NAME",
  indexSettings: IndexSettings(
    ranking: [
      "desc(is_popular)",
      "typo",
      "geo",
      "words",
      "filters",
      "proximity",
      "attribute",
      "exact",
      "custom",
    ],
  ),
);
```

```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
  "INDEX_NAME",
  search.NewEmptyIndexSettings().SetRanking(
    []string{"desc(is_popular)", "typo", "geo", "words", "filters", "proximity", "attribute", "exact", "custom"})))
if err != nil {
  // handle the eventual error
  panic(err)
}
```

```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
  "INDEX_NAME",
  new IndexSettings().setRanking(
    Arrays.asList("desc(is_popular)", "typo", "geo", "words", "filters", "proximity", "attribute", "exact", "custom")
  )
);
```

```js JavaScript theme={"system"}
const response = await client.setSettings({
  indexName: 'theIndexName',
  indexSettings: {
    ranking: ['desc(is_popular)', 'typo', 'geo', 'words', 'filters', 'proximity', 'attribute', 'exact', 'custom'],
  },
});
```

```kotlin Kotlin theme={"system"}
var response =
  client.setSettings(
    indexName = "INDEX_NAME",
    indexSettings =
      IndexSettings(
        ranking =
          listOf(
            "desc(is_popular)",
            "typo",
            "geo",
            "words",
            "filters",
            "proximity",
            "attribute",
            "exact",
            "custom",
          )
      ),
  )
```

```php PHP theme={"system"}
$response = $client->setSettings(
    'INDEX_NAME',
    ['ranking' => [
        'desc(is_popular)',

        'typo',

        'geo',

        'words',

        'filters',

        'proximity',

        'attribute',

        'exact',

        'custom',
    ],
    ],
);
```

```python Python theme={"system"}
response = client.set_settings(
    index_name="INDEX_NAME",
    index_settings={
        "ranking": [
            "desc(is_popular)",
            "typo",
            "geo",
            "words",
            "filters",
            "proximity",
            "attribute",
            "exact",
            "custom",
        ],
    },
)
```

```ruby Ruby theme={"system"}
response = client.set_settings(
  "INDEX_NAME",
  Algolia::Search::IndexSettings.new(
    ranking: ["desc(is_popular)", "typo", "geo", "words", "filters", "proximity", "attribute", "exact", "custom"]
  )
)
```

```scala Scala theme={"system"}
val response = Await.result(
  client.setSettings(
    indexName = "INDEX_NAME",
    indexSettings = IndexSettings(
      ranking = Some(
        Seq("desc(is_popular)", "typo", "geo", "words", "filters", "proximity", "attribute", "exact", "custom")
      )
    )
  ),
  Duration(100, "sec")
)
```

```swift Swift theme={"system"}
let response = try await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(ranking: [
        "desc(is_popular)",
        "typo",
        "geo",
        "words",
        "filters",
        "proximity",
        "attribute",
        "exact",
        "custom",
    ])
)
```
