# Algolia documentation | Search API guides, SDK and developer docs
Source: https://www.algolia.com/doc
Explore Algolia documentation, API references, SDKs, implementation guides, and developer resources to build fast, relevant search and AI-powered experiences.
# Algolia docs
Build search, discovery, and AI-powered retrieval experiences with Algolia.
[Get started](/doc/guides/get-started)
> [card] [Libraries and tools](/doc/libraries)
> [card] [API reference](/doc/rest-api)
> [card] [Search API parameters](/doc/api-reference/api-parameters)
### Send and manage data
* [Prepare your data](/doc/guides/sending-and-managing-data/prepare-your-data)
* [Send and update data](/doc/guides/sending-and-managing-data/send-and-update-your-data)
* [Manage indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices)
### Build search and AI experiences
* [Build a search UI](/doc/guides/building-search-ui/getting-started/js)
* [Agent Studio](/doc/guides/algolia-ai/agent-studio)
* [Recommend](/doc/guides/algolia-recommend/overview)
* [MCP Server](/doc/guides/model-context-protocol)
* [Algolia Wizard](/doc/tools/algolia-wizard)
### Tune and personalize ranking
* [Relevance overview](/doc/guides/managing-results/relevance-overview)
* [Dynamic Re-Ranking](/doc/guides/algolia-ai/re-ranking)
* [NeuralSearch](/doc/guides/ai-relevance/neuralsearch/get-started)
* [Personalization](/doc/guides/personalization/advanced-personalization/what-is-advanced-personalization)
* [Query categorization](/doc/guides/algolia-ai/query-categorization)
### Measure and optimize
* [Click and conversion events](/doc/guides/sending-events/getting-started)
* [Search analytics](/doc/guides/search-analytics/overview)
* [A/B testing](/doc/guides/ab-testing/what-is-ab-testing)
## More resources
> [card] [Algolia Academy](https://academy.algolia.com)
>
> Take self-paced courses and earn certifications.
> [card] [Support center](https://support.algolia.com)
>
> Find answers to specific issues and browse FAQs.
> [card] [Community](https://alg.li/discord)
>
> Connect with other Algolia users on Discord.
> [card] [Changelog](https://changelog.algolia.com)
>
> Stay up to date with the latest Algolia product releases and changes.
Looking for an older version of the docs? Browse the [archived site](https://www.algolia.com/old-docs).
# Algolia glossary
Source: https://www.algolia.com/doc/glossary
Terms used at Algolia and their definitions.
## API Client
A program that provides an abstraction over the raw Algolia [API](/doc/glossary#application-programming-interface-api),
hiding implementation details to make development for [Subscribers](#subscriber) easier in the language of their choice.
[Learn more](/doc/libraries/sdk)
## API key
A string with alphanumeric characters used to control access to Algolia's [API](/doc/glossary#application-programming-interface-api),
determining what is allowed to do, such as searching an [Index](#index), or adding new [Records](#record).
[Learn more](/doc/guides/security/api-keys)
## Affinity
A [Facet-Value pair](#facet-value-pair) that reflects [End-User](#user)'s preferences.
## Algolia AI
Certain Algolia Services with an ability to train or adapt via use,
such as [NeuralSearch](/doc/guides/ai-relevance/neuralsearch/get-started),
[Dynamic Re-Ranking](/doc/guides/algolia-ai/re-ranking),
[AI Synonyms](/doc/guides/algolia-ai/dynamic-synonym-suggestions),
[Query Categorization](/doc/guides/algolia-ai/query-categorization),
[Advanced Personalization](/doc/guides/personalization/advanced-personalization/what-is-advanced-personalization) and [Recommend](/doc/guides/algolia-recommend/overview) features:
Frequently Bought Together, Alternative Items, Looking Similar, Content Based Alternative Items, and otherwise identified by Algolia.
## Application
A siloed set of [indices](#index). Data cannot be shared between applications.
## Application ID
A string with alphanumeric characters as a unique identifier for Algolia [applications](#application).
## Application Programming Interface (API)
A software intermediary that allows pieces of software to interact with each other.
The Algolia API describes how the [Subscribers'](#subscriber) systems can interact with Algolia Services.
[Learn more](/doc/rest-api)
## Click-through rate (CTR)
The percentage of tracked searches with at least one click [Event](#event).
A tracked search is a search with the [`clickAnalytics`](/doc/api-reference/api-parameters/clickAnalytics) parameter set to true.
[Learn more](/doc/guides/search-analytics/concepts/metrics#click-through-rate)
## Conversion rate
The percentage of tracked searches with at least one conversion [Event](#event).
A tracked search is a search with the [`clickAnalytics`](/doc/api-reference/api-parameters/clickAnalytics) parameter set to true.
[Learn more](/doc/guides/search-analytics/concepts/metrics#conversion-rate)
## Dashboard
The [Subscriber's](#subscriber) administrator interface to Algolia Services.
The Dashboard is hosted by Algolia.
## Event
An action performed by a [User](#user) on [Subscriber's](#subscriber) app or website.
[Learn more](/doc/guides/sending-events)
## Facet
A specific characteristic for describing [index](#index) items, such as color or brand.
[Learn more](/doc/guides/managing-results/refine-results/faceting)
## Facet-Value pair
A pair made of a [Facet](#facet) name and a value.
For example, if "priority" and "quality" are two Facets, "priority:high" and "quality:high" are two Facet-Value pairs.
## Filter
A [Facet-Value pair](#facet-value-pair)
used to select results with a specific facet value for a given [facet](#facet) name.
[Learn more](/doc/guides/managing-results/refine-results/filtering)
## Generative Experiences
Certain Algolia Services that leverage generative artificial intelligence, such as Ask AI, Agent Studio, certain features of AI Assist, and Generative Experience Guides.
## Index
A container for a set of [Records](#record) that can be searched together.
[Learn more](/doc/guides/sending-and-managing-data/send-and-update-your-data)
## Insights API
Algolia's API designed to ingest [Events](#event) from [Subscriber's](#subscriber) apps and websites.
[Learn more](/doc/rest-api/insights)
## InstantSearch
A collection of open source software libraries that let [Subscribers](#subscriber)
build Algolia search interfaces for their frontend application.
The InstantSearch libraries are built on top of [API Clients](#api-client).
[Learn more](/doc/guides/building-search-ui/what-is-instantsearch/js)
## Library
A collection of reusable software, designed to help developers solve common problems.
A library is often targeted to be used with a specific software platform or operating system.
## Primary Serving Region
Region selected by [Subscriber](#subscriber) and used by Algolia as the primary region for Search query processing.
## Profile
Designates a set of information about a single [End-User](#user),
including their [Affinities](#affinity) based on enriched [Events](#event) and audience segment to which the [End-User](#user) belongs.
## Record
[Subscriber](#subscriber) items stored in an Algolia [Index](#index) for retrieval.
[Learn more](/doc/guides/sending-and-managing-data/prepare-your-data)
## Search Request
A group of one or more [search queries](#search-query) sent in a single HTTP request.
## Search query
A sequence of characters typed into a search box, or specific filters or facets applied to a search.
## Subscriber
A customer of Algolia Services, such as Search or Recommend.
## User
Customers or end-users of Algolia [Subscribers](#subscriber) who interact with Algolia Services, such as Search and Recommend.
## User token
A unique string of alphanumeric characters that uniquely identifies a [User](#user) throughout [applications](#application).
A user token must contain only alphanumeric, `=`, `-`, `/`, or `_` characters and be between 1 and 128 characters in length.
[Learn more](/doc/guides/sending-events/concepts/usertoken)
# Get started with Algolia
Source: https://www.algolia.com/doc/guides/get-started
Learn what Algolia is, how it works, and which path to follow to build your first search or AI experience.
## What is Algolia?
Algolia is a hosted AI search and retrieval platform.
Built API-first, it combines keyword, vector, and hybrid retrieval to power search, recommendations, retrieval-augmented generation, and AI agents.
Algolia stores your data as JSON records that you query through the API, client libraries, and UI libraries.
## How Algolia works
You send records from your data sources to an Algolia index.
Index settings determine how Algolia matches, ranks, and filters those records.
Your website, app, or AI agent queries the index and presents the response.
Click and conversion events provide behavioral data for analytics and other features.
Use the Algolia dashboard to tune relevance, set up recommendations, create AI agents with Agent Studio, merchandise results, review analytics, and run A/B tests.
## Choose a starting point
> [card] [Build your first search experience](/doc/guides/get-started/quickstart)
>
> Build a working React search experience with sample data.
> [card] [Get started with the Algolia Wizard](/doc/tools/algolia-wizard)
>
> Let the Algolia Wizard index your app's data and add a search UI.
> [card] [Build with AI agents](/doc/guides/get-started/build-with-ai)
>
> Use AI assistants and agents to build and maintain your Algolia integration.
> [card] [Understand the basics](/doc/guides/get-started/concepts)
>
> Learn how indices, records, attributes, and events work in Algolia.
> [card] [Pick a library or integration](/doc/libraries)
>
> Find the API client, UI library, or platform integration for your stack.
## Build your implementation
A basic search implementation has three parts:
1. [Index your data](/doc/guides/sending-and-managing-data/prepare-your-data) as records.
2. [Build a search UI](/doc/guides/building-search-ui/getting-started/js) that queries your index.
3. [Tune relevance](/doc/guides/managing-results/relevance-overview) to improve matching, ranking, and filtering.
Once your data is in an index, you can work on the search UI and relevance in parallel.
## Extend your implementation
Add other features when you need them:
* [Send click and conversion events](/doc/guides/sending-events/getting-started) to collect analytics data and power features such as Personalization, Dynamic Re-Ranking, and most Recommend models.
* [Add recommendations](/doc/guides/algolia-recommend/how-to/set-up) to suggest relevant items.
* [Build a conversational experience with Agent Studio](/doc/guides/algolia-ai/agent-studio) to ground AI responses in your index data.
## Measure and optimize
Once your implementation is working, measure performance and test changes.
* [Search analytics](/doc/guides/search-analytics/overview) to see how users search and where they don't find results.
* [Recommend analytics](/doc/guides/algolia-recommend/analytics/recommend-analytics) to see how users interact with recommendations.
* [Agent Studio analytics](/doc/guides/algolia-ai/agent-studio/how-to/analytics) to see how users interact with your agents.
* [A/B testing](/doc/guides/ab-testing/what-is-ab-testing) to compare index configurations before you roll them out.
## Prepare to go live
Before you launch to production, secure your account and work through the checklist.
* [Security best practices](/doc/guides/security/security-best-practices) to restrict API keys and protect sensitive data.
* [Implementation checklist](/doc/guides/going-to-production/implementation-checklist) to catch gaps before launch.
# Build with AI agents
Source: https://www.algolia.com/doc/guides/get-started/build-with-ai
Use AI assistants and agents to build, integrate, and maintain Algolia in your applications.
AI assistants can write integration code, manage your indices, and power search inside your own agents.
This page shows how to connect AI assistants to Algolia and which tools to use.
**Agent setup**
These are official, Algolia-validated instructions to set up an Algolia development environment.
This section is the complete setup: run only the commands below.
The MCP, CLI, and skills sections further down this page are reference for people.
Don't run them.
Setting up the MCP server is optional and isn't needed here.
**1. Install the Algolia CLI and skills**
For Claude Code:
```sh theme={"system"}
claude plugin marketplace add algolia/skills
claude plugin install algolia-cli@algolia-skills
claude plugin install algolia-quickstart@algolia-skills
```
Then tell the user to run `/reload-plugins` inside Claude Code to activate the plugins.
Don't use `npx skills` for Claude Code.
The plugin commands handle everything.
For any other agent:
```sh theme={"system"}
npx -y skills add algolia/skills --skill algolia-cli algolia-quickstart --yes --global
```
**2. Confirm and hand off**
Tell the user their Algolia agent environment is ready, then suggest the next step:
```text theme={"system"}
Ask me to "create an Algolia account and application" to get started.
```
## Install the Algolia Productivity MCP
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) is an open standard for connecting AI assistants to external systems.
Productivity MCP lets you query your Algolia data from your AI assistant.
Enable it in your [Algolia dashboard](https://dashboard.algolia.com), then connect it to your AI assistant.
> [tab] Claude Code
>
> ```sh icon=square-terminal theme={"system"}
> claude mcp add --transport http algolia https://mcp.algolia.com/mcp
> ```
>
> Then run `/mcp` in a Claude Code session and sign in when prompted.
> [tab] Cursor
>
> [**Add Algolia MCP to Cursor**](cursor://anysphere.cursor-deeplink/mcp/install?name=algolia\&config=eyJ1cmwiOiJodHRwczovL21jcC5hbGdvbGlhLmNvbS9tY3AifQ%3D%3D) (requires Cursor 1.0+).
>
> Or add this to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for a global setup:
>
> ```json .cursor/mcp.json icon=braces theme={"system"}
> {
> "mcpServers": {
> "algolia": {
> "url": "https://mcp.algolia.com/mcp"
> }
> }
> }
> ```
> [tab] VS Code
>
> Add this to `~/.vscode/mcp.json` (macOS, Linux) or `%USERPROFILE%\.vscode\mcp.json` (Windows):
>
> ```json ~/.vscode/mcp.json icon=braces theme={"system"}
> {
> "servers": {
> "algolia": {
> "type": "http",
> "url": "https://mcp.algolia.com/mcp"
> }
> }
> }
> ```
> [tab] Gemini CLI
>
> ```sh icon=square-terminal theme={"system"}
> gemini mcp add algolia https://mcp.algolia.com/mcp -s user -t http
> ```
> [tab] ChatGPT
>
> In [Connectors settings](https://chatgpt.com/#settings/Connectors), create a connector with:
>
> * **Server URL:** `https://mcp.algolia.com/mcp`
> * **Authentication:** OAuth
>
> Sign in when prompted.
> Leave OAuth client ID and secret empty.
> [tab] Claude AI & Desktop
>
> In [Connectors settings](https://claude.ai/new#settings/customize-connectors), add a custom connector with:
>
> * **Remote MCP server URL:** `https://mcp.algolia.com/mcp`
>
> Sign in when prompted.
> Leave OAuth client ID and secret empty.
For more details, see [Productivity MCP](/doc/guides/model-context-protocol/productivity-mcp).
> [!NOTE]
>
> Your favorite AI tool isn't listed?
> [Reach out to Algolia support](https://support.algolia.com/hc/en-us/requests/new) and request it.
***
### Productivity MCP example prompts
Once connected, you can ask your assistant questions like:
* *"What are the top searches with no results this week?"*
* *"List all indices in this application and show their record counts."*
* *"Which filters are most used on the `products` index?"*
* *"Find products matching 'noise cancelling headphones' and rank them by sales."*
* *"Show the click-through rate for `black friday sale` over the last 30 days."*
### Use Algolia Public MCP for unauthenticated access
For agent-facing search and recommendations on a curated set of indices, use [Algolia Public MCP](/doc/guides/model-context-protocol/public-mcp) instead.
It serves search and recommendations to your own agents without requiring sign-in.
## Install the Algolia DevEx MCP
DevEx MCP gives your AI coding agent version-accurate API client snippets, generated from Algolia OpenAPI specifications.
Use it when you add Algolia to an app, so the agent uses client methods and parameters that match your installed version.
DevEx MCP doesn't require authentication and doesn't access data in your Algolia applications.
To query data in your Algolia applications, use Productivity MCP.
To search public documentation, use DocSearch MCP.
DevEx MCP covers C#, Dart, Go, Java, JavaScript, Kotlin, PHP, Python, Ruby, Scala, and Swift.
> [tab] Claude Code
>
> ```sh icon=square-terminal theme={"system"}
> claude mcp add --transport http algolia-devex https://mcp.algolia.com/1/snippets/mcp
> ```
> [tab] Cursor
>
> [**Add DevEx MCP to Cursor**](cursor://anysphere.cursor-deeplink/mcp/install?name=algolia-devex\&config=eyJ1cmwiOiJodHRwczovL21jcC5hbGdvbGlhLmNvbS8xL3NuaXBwZXRzL21jcCJ9) (requires Cursor 1.0+).
>
> Or add this to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for a global setup:
>
> ```json .cursor/mcp.json icon=braces theme={"system"}
> {
> "mcpServers": {
> "algolia-devex": {
> "url": "https://mcp.algolia.com/1/snippets/mcp"
> }
> }
> }
> ```
> [tab] VS Code
>
> Add this to `~/.vscode/mcp.json` (macOS, Linux) or `%USERPROFILE%\.vscode\mcp.json` (Windows):
>
> ```json ~/.vscode/mcp.json icon=braces theme={"system"}
> {
> "servers": {
> "algolia-devex": {
> "type": "http",
> "url": "https://mcp.algolia.com/1/snippets/mcp"
> }
> }
> }
> ```
> [tab] Gemini CLI
>
> ```sh icon=square-terminal theme={"system"}
> gemini mcp add algolia-devex https://mcp.algolia.com/1/snippets/mcp -s user -t http
> ```
> [tab] ChatGPT
>
> In [Connectors settings](https://chatgpt.com/#settings/Connectors), create a connector with:
>
> * **Server URL:** `https://mcp.algolia.com/1/snippets/mcp`
> * **Authentication:** none
> [tab] Claude AI & Desktop
>
> In [Connectors settings](https://claude.ai/new#settings/customize-connectors), add a custom connector with:
>
> * **Remote MCP server URL:** `https://mcp.algolia.com/1/snippets/mcp`
> * **Authentication:** none
### Example prompts for API client snippets
Once connected, you can ask your assistant questions like:
* *"Show how to save records with the JavaScript API client."*
* *"Get a Python snippet for searching an index."*
* *"Create an API key with the C# API client."*
* *"Return a Go snippet for setting index settings."*
For a full walkthrough with credentials, settings, records, and search, see [Add Algolia to your app with DevEx MCP](/doc/guides/model-context-protocol/devex-mcp).
## Install the Algolia CLI
Command-line tools are part of most developer workflows.
With coding agents running in the terminal, they're even more useful.
The [Algolia CLI](/doc/tools/cli/get-started) lets agents script index setup, update settings, and run admin tasks—without extra integration code.
> [tab] npm
>
> If you have [Node.js](https://nodejs.org) installed,
> you can run the CLI with `npx`, which doesn't require installation:
>
> ```sh icon=square-terminal theme={"system"}
> npx @algolia/cli --help
> ```
>
> Or install it globally with npm:
>
> ```sh icon=square-terminal theme={"system"}
> npm install -g @algolia/cli
> ```
> [tab] macOS
>
> ```sh icon=square-terminal theme={"system"}
> brew install algolia/algolia-cli/algolia
> ```
> [tab] Linux
>
> Download a `.deb` or `.rpm` from the [Releases page](https://github.com/algolia/cli/releases) and install it:
>
> ```sh icon=square-terminal theme={"system"}
> sudo dpkg -i algolia_*.deb # Debian, Ubuntu
> sudo rpm -i algolia_*.rpm # Fedora, RHEL
> ```
> [tab] Windows
>
> ```powershell icon=square-terminal theme={"system"}
> choco install algolia
> ```
After installing, run `algolia auth login` to sign in, or `algolia auth signup` to create an account.
This lets your agent run commands without exporting keys each session.
For other install options and authentication, see [Get started with the Algolia CLI](/doc/tools/cli/get-started).
### What agents can do with the CLI
Pair the CLI with your coding assistant to automate Algolia tasks as it writes integration code:
* **Bootstrap a new index**: create the index, upload sample records, and apply searchable attributes and ranking in one script.
* **Sync data from a file**: import records from JSON or CSV with `algolia objects import`.
* **Tune relevance**: update `searchableAttributes`, `customRanking`, and other settings with `algolia settings set`.
* **Manage rules and synonyms**: create, browse, and export rules and synonyms across environments.
* **Promote between environments**: export an index configuration from staging and import it into production.
* **Run searches and inspect results**: query an index from the terminal to validate changes before shipping.
For more examples, see [Usage examples](/doc/tools/cli/examples).
## Get started with the Algolia Wizard
The [Algolia Wizard](/doc/tools/algolia-wizard) is an AI-assisted command-line tool that sets up Algolia in your project.
It finds the data to index, writes an ingestion script in your project's language, sends the records to an index, and adds an InstantSearch UI to your frontend.
A run typically takes less than 10 minutes, and the wizard asks before any command that changes files, installs software, or sends data.
```sh icon=square-terminal theme={"system"}
npx @algolia/wizard@latest
```
## Install Algolia skills
[Algolia skills](https://github.com/algolia/skills) are agent skills for Claude Code, Cursor, OpenAI Codex, and OpenCode.
Add them to your assistant to teach it how to work with Algolia.
While MCP servers and the Algolia CLI give your assistant the building blocks, skills package the patterns.
They tell your assistant which tool to reach for, what conventions to follow (such as which InstantSearch widgets to use for a given UI), and how to chain steps for common tasks—so you don't have to re-explain them in every prompt.
The repository includes these skills:
* `algolia-quickstart`: provision an Algolia account and application from the [Algolia CLI](/doc/tools/cli/get-started).
* `algolia-mcp`: search, analytics, and recommendations through the Algolia MCP server.
* `algolia-cli`: provision accounts, and manage applications, indices, settings, rules, and synonyms through the [Algolia CLI](/doc/tools/cli/get-started).
* `algolia-migration`: upgrade existing API client code to a newer major version.
* `algolia-crawler`: crawl and index web pages into Algolia for RAG.
* `algobot-cli`: build AI agents, chat experiences, and RAG on Algolia.
* `instantsearch`: build search interfaces with autocomplete, results, and facets.
> [tab] Claude Code
>
> Install the skills as plugins:
>
> ```sh icon=square-terminal theme={"system"}
> claude plugin marketplace add algolia/skills
> claude plugin install algolia-quickstart@algolia-skills
> claude plugin install algolia-cli@algolia-skills
> claude plugin install algolia-migration@algolia-skills
> ```
>
> Then run `/reload-plugins` inside Claude Code to activate them.
> [tab] Other agents
>
> Run the following command and select the skills you want to add:
>
> ```sh icon=square-terminal theme={"system"}
> npx skills add algolia/skills
> ```
### Upgrade an API client
The `algolia-migration` skill upgrades existing API client code to a newer major version.
It covers backend clients and search-only frontend builds.
For InstantSearch UI, use the `instantsearch` skill instead.
After you add the skill, open your project and ask the agent to upgrade the Algolia API client.
The skill reads the language and version from your package manifest, then updates breaking call sites such as `initIndex` and chained `.wait()`.
Paste this prompt into your AI agent to upgrade the API client
Upgrade the Algolia API client in this project to the latest semantic version available in the language package manager, or GitHub release otherwise
If the skill doesn't start on its own, run `/algolia-migration` in the agent.
The skill supports these upgrades:
| Language | From | To |
| - | - | - |
| JavaScript | Version 4 | Version 5 |
| Python | Version 3 | Version 4 |
| Go | Version 3 | Version 4 |
| PHP | Version 3 | Version 4 |
| Java | Version 3 | Version 4 |
| C# | Version 6 | Version 7 |
| Ruby | Version 2 | Version 3 |
| Kotlin | Version 2 | Version 3 |
| Scala | Version 1 | Version 2 |
| Swift | Version 8 | Version 9 |
You can also name the language and versions:
* *"Migrate the JavaScript API client from version 4 to version 5."*
* *"Replace `initIndex` with client methods in this Python app."*
* *"Upgrade the C# API client from version 6 to version 7."*
## Search developer documentation with DocSearch MCP
Connect DocSearch MCP so that your AI coding agent can search public developer documentation indexed by DocSearch.
DocSearch MCP doesn't require authentication or provide access to data in your Algolia applications.
To query data in your Algolia applications, use Productivity MCP.
> [card] [Set up DocSearch MCP](https://docsearch.algolia.com/mcp/)
>
> Connect DocSearch MCP to your agent.
## Build no-code data pipelines
Not all Algolia workflows require coding.
[Connectors](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors) let merchandisers and operators build data pipelines from sources (JSON, CSV, BigQuery, Supabase, and more) to an Algolia index, with [no-code transformations](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-without-code) that change attributes, filter records, or compute new values from existing ones.
For complex logic, use [AI-assisted transformations](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/ai-assisted-transformations) to describe what you want in plain English and let AI generate the configuration.
Examples of prompts you can give the AI assistant:
* *"If price is greater than 100, add the label 'expensive'."*
* *"Add a discount percentage attribute computed from `full_price` and `sale_price`."*
* *"Remove records where `status` is `draft` or `archived`."*
* *"Combine popularity and recency into a single ranking signal."*
# Understand the basics
Source: https://www.algolia.com/doc/guides/get-started/concepts
Core concepts for building with Algolia: indices, records, attributes, and events.
This page introduces the core concepts used throughout Algolia.
Understanding these terms helps you structure your data, configure relevance, and build search experiences.
## Index
An index is a collection of records optimized for fast retrieval.
It's the searchable representation of your data.
An index contains:
* **Records.** The data you want to make searchable and return as results
* **Settings.** Configuration that controls matching, ranking, and filtering
* **Rules.** Conditional logic for overriding default behavior in specific situations
* **Synonyms.** Equivalences between terms, so a search for "laptop" also matches "notebook"
The same index can power multiple experiences,
such as search results, recommendations, or AI-assisted retrieval,
without duplicating data.
A typical implementation uses several indices, for example,
one per content type or ranking strategy.
For more information, see [Send and manage your data](/doc/guides/sending-and-managing-data/prepare-your-data).
## Record
A record is an object stored in an index and returned as a result.
Records are typically JSON objects with fields called **attributes**.
```json JSON icon=braces theme={"system"}
{
"objectID": "prod-42",
"title": "Trail Running Shoes",
"brand": "Salomon",
"category": "Footwear",
"price": 129.99,
"in_stock": true
}
```
Records are schemaless.
Algolia doesn't require a predefined structure,
but consistent shapes make indexing and UI development easier.
Every record must include an `objectID`,
which uniquely identifies it within an index.
## Attribute
An attribute is a named field in a record,
such as `title`, `category`, or `price`.
Attributes determine:
* **What users can search,** through searchable attributes in your index settings
* **How results are ranked,** for example by popularity or rating
* **What users can filter on,** when configured as facets
* **What you display** in your UI
Choosing the right attributes and configuring them correctly has one of the biggest effects on relevance.
## Query
A query is the text a user enters to search.
Your app sends it to Algolia, which matches it against the searchable attributes in your index and returns ranked results.
## Facet
A facet is an attribute used to let users filter and refine results, such as category, brand, price range, or rating.
Facets are configured in your index settings and exposed in your UI as interactive filters.
When a user selects a facet value, Algolia returns only the records that match.
To learn more, see [Facets](/doc/guides/managing-results/refine-results/faceting).
## Relevance and ranking
Relevance determines which records match a query and in what order they appear.
Algolia combines:
* **Textual relevance.** Matching based on searchable attributes, synonyms, and typo tolerance
* **Ranking criteria.** A sequence of criteria in your ranking formula, such as Typo, Words, Proximity, Attribute, and Exact.
You can also add **custom ranking**, such as popularity or sales, to promote more relevant results.
For more information, see [Relevance overview](/doc/guides/managing-results/relevance-overview).
## Event
An event captures a user interaction with your search experience, such as a click on a result or a completed conversion.
You send events from your UI to Algolia.
They power:
* Analytics
* Personalization
* Recommendations
* AI-based ranking improvements
Events help you improve search quality based on user behavior.
The earlier you start sending events, the more interaction data is available for analytics, personalization, and recommendations.
For details, see [Click and conversion events](/doc/guides/sending-events/getting-started).
## API key
Every request to Algolia must be authenticated with an API key.
The main types are:
* **Search API key.** Read-only key for search requests. Safe to use in frontend code.
* **Write API key.** Key with permissions for indexing operations. Keep it private and use it only in your backend.
* **Admin API key.** Key with full access to your Algolia application.
Keep it private and use it only to create more restricted API keys.
For more information, see [API keys](/doc/guides/security/api-keys).
## Ready to build?
Build your first search experience in the [Quickstart](/doc/guides/get-started/quickstart).
For more terms and their definitions, see the [Glossary](/doc/glossary).
# Build your first search experience
Source: https://www.algolia.com/doc/guides/get-started/quickstart
Set up a React app, index sample data in Algolia, and build a search interface with React InstantSearch.
This quickstart builds a React app that searches a sample product catalog,
filters by product type, and paginates results.
[Build your first search experience](https://www.youtube.com/embed/gvkdmY6UWxw)
> [card] [View the demo](https://product-search-react-typescript.netlify.app/)
>
> Explore the product search app you'll build in this quickstart.
> [card] [Explore the source code](https://github.com/algolia/quickstarts/tree/main/product-search-react-typescript)
>
> Download the complete quickstart app from GitHub.
## Before you begin
Make sure you have:
* **An Algolia account**. [Create one for free](https://www.algolia.com/users/sign_up) if you don't already have one.
* **[Node.js](https://nodejs.org/en/download)** 20.19 or later.
* Are you building with AI agents?
To prepare your agent to work with Algolia,
see [Build with AI](/doc/guides/get-started/build-with-ai).
## Set up your app and index sample data
Set up your project and index a sample product dataset.
In Algolia, the way you structure and configure your index affects both the search experience and how results are ranked.
**1. Create a Vite app**
Create a new Vite app with React and TypeScript.
```sh icon=square-terminal theme={"system"}
npm create vite@latest algolia-quickstart-products -- --template react-ts --no-interactive
cd algolia-quickstart-products
```
The Vite template includes a global style sheet that centers content and overrides heading colors.
These styles conflict with the search UI, so remove its import from `src/main.tsx`:
```tsx src/main.tsx icon="react" theme={"system"}
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
createRoot(document.getElementById("root")!).render(
,
);
```
**2. Install dependencies**
Install the packages for indexing and search:
* `algoliasearch` sends data to Algolia.
* `react-instantsearch` builds the search UI.
* `instantsearch.css` provides base styles.
* `tsx` runs the indexing script.
* `ora` shows progress in the terminal while the script runs.
```sh icon=square-terminal theme={"system"}
npm install algoliasearch instantsearch.css react-instantsearch ora tsx
```
**3. Add your Algolia credentials**
In the Algolia dashboard, open the [API Keys](https://dashboard.algolia.com/account/api-keys) page and copy these values:
* **Application ID**. Identifies your Algolia application.
* **Write API key**. Used by the indexing script to update records and index settings.
* **Search API key**. Used by the React app to query the index from the browser.
Create a `.env.local` file and add the values to it:
```dotenv .env.local icon=lock-keyhole theme={"system"}
VITE_ALGOLIA_APPLICATION_ID=
ALGOLIA_WRITE_API_KEY=
VITE_ALGOLIA_SEARCH_API_KEY=
```
> [!NOTE]
>
> Keep your Write API key secret.
> Don't commit `.env.local` to version control or expose the write key in your app.
**4. Create the indexing script**
Create a `scripts` directory and a `scripts/indexing.ts` file.
```sh icon=square-terminal theme={"system"}
mkdir scripts && touch scripts/indexing.ts
```
This script loads your credentials, then runs two independent steps:
* `indexProducts` fetches a sample product dataset and uploads those products as records with `saveObjects`.
* `configureIndex` uses `setSettings` to add `product_type` as a facet so your UI can filter results,
and to snippet `description` so your UI can show a shortened excerpt of it.
Keeping the two separate lets you reapply the index settings without reimporting the records,
which is what you need when your records come from a connector or another pipeline.
Run this script once to create and configure the index before connecting it to your UI.
Since indexing operations are asynchronous,
`waitForTasks` and `waitForTask` make sure the records and settings are applied before the script exits.
```ts scripts/indexing.ts icon=code expandable theme={"system"}
import { algoliasearch } from "algoliasearch";
import ora from "ora";
import { loadEnv } from "vite";
const env = loadEnv(process.env.MODE ?? "dev", process.cwd(), "");
const appId = env.VITE_ALGOLIA_APPLICATION_ID;
const writeApiKey = env.ALGOLIA_WRITE_API_KEY;
const indexName = "quickstart-products";
if (!appId) {
throw new Error("Missing VITE_ALGOLIA_APPLICATION_ID environment variable.");
}
if (!writeApiKey) {
throw new Error("Missing ALGOLIA_WRITE_API_KEY environment variable.");
}
const client = algoliasearch(appId, writeApiKey);
const spinner = ora();
async function indexProducts() {
spinner.text = "Fetching the products dataset...";
const response = await fetch(
"https://dashboard.algolia.com/api/1/sample_datasets?type=apparel",
);
if (!response.ok) {
throw new Error(
`Error fetching products dataset: ${response.status} ${response.statusText}`,
);
}
const products = await response.json();
spinner.text = `Indexing ${products.length.toLocaleString()} products into ${indexName}...`;
await client.saveObjects({
indexName,
objects: products,
waitForTasks: true,
});
}
async function configureIndex() {
spinner.text = `Configuring ${indexName}...`;
const { taskID } = await client.setSettings({
indexName,
indexSettings: {
attributesForFaceting: ["product_type"],
attributesToSnippet: ["description:30"],
},
});
await client.waitForTask({ indexName, taskID });
}
try {
spinner.start("Beginning index setup...");
await indexProducts();
await configureIndex();
spinner.succeed("Successfully indexed and configured products.");
} catch (error) {
spinner.fail("Indexing failed.");
console.error(error);
process.exitCode = 1;
}
```
**5. Index the sample products**
Add the indexing script to `package.json`:
```sh icon=square-terminal theme={"system"}
npm pkg set scripts.index:products="tsx scripts/indexing.ts"
```
Run the script to create the `quickstart-products` index, add the sample records, and apply the index settings.
```sh icon=square-terminal theme={"system"}
npm run index:products
```
> [!TIP]
>
> Look for the message `Successfully indexed and configured products.` in your terminal.
> At this point, your data is ready for search and you can explore the `quickstart-products` index
> in the [Algolia dashboard](https://dashboard.algolia.com/explorer/browse/quickstart-products).
> In the next section, you'll connect a React UI to this index.
## Build the search UI
Connect a React UI to your `quickstart-products` index.
React InstantSearch automatically keeps the search box, filters, and pagination in sync with the current results.
**1. Connect your app to Algolia**
Replace `src/App.tsx` with the following code to connect your app to Algolia and render a basic search interface.
```tsx src/App.tsx icon=code expandable theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import type { Hit } from "instantsearch.js";
import {
Configure,
Highlight,
Hits,
InstantSearch,
Pagination,
PoweredBy,
RefinementList,
SearchBox,
Snippet,
} from "react-instantsearch";
import "instantsearch.css/themes/reset-min.css";
import "./App.css";
const appId = import.meta.env.VITE_ALGOLIA_APPLICATION_ID;
const apiKey = import.meta.env.VITE_ALGOLIA_SEARCH_API_KEY;
if (!appId) {
console.error("Missing environment variable: VITE_ALGOLIA_APPLICATION_ID");
}
if (!apiKey) {
console.error("Missing environment variable: VITE_ALGOLIA_SEARCH_API_KEY");
}
const searchClient = algoliasearch(appId, apiKey);
type ProductRecord = {
title: string;
description: string;
product_type: string;
price: number;
showcase_image: string;
};
type ProductHit = Hit;
function ProductCard({ hit }: { hit: ProductHit }) {
return (
{hit.product_type}
${hit.price}
);
}
export default function App() {
return (
);
}
```
This app connects to your `quickstart-products` index and renders a search interface.
> [!NOTE]
>
> * [`SearchBox`](/doc/api-reference/widgets/search-box/react) lets users enter search terms.
> * [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) adds filtering by product type.
> * [`Hits`](/doc/api-reference/widgets/hits/react) displays matching records from your index.
> * [`Pagination`](/doc/api-reference/widgets/pagination/react) lets users move through results.
> * [`Highlight`](/doc/api-reference/widgets/highlight/react) and [`Snippet`](/doc/api-reference/widgets/snippet/react) emphasize matching parts of `title` and `description`.
**2. Style the search interface**
Replace `src/App.css` with the following code.
These styles lay out the search interface and make the results and filters easier to scan.
They change the presentation, not the search behavior.
```css src/App.css icon=paintbrush expandable theme={"system"}
*,
*::before,
*::after {
box-sizing: border-box;
}
:root {
--color-text: #111827;
--color-background: #ffffff;
--color-border: #e5e7eb;
--color-surface: #ffffff;
--color-surface-muted: #f3f4f6;
--color-muted: #6b7280;
--color-accent: #5468ff;
--color-accent-strong: #5468ff;
--color-accent-ring: rgb(84 104 255 / 0.15);
}
body {
margin: 0;
font-family: system-ui, sans-serif;
color: var(--color-text);
background-color: var(--color-background);
}
#root {
inline-size: min(100%, 70rem);
margin-inline: auto;
padding-block: 2rem;
padding-inline: 1.25rem;
color: inherit;
}
.search-header {
display: grid;
gap: 0.5rem;
margin-block-end: 1.5rem;
}
.search-body {
display: grid;
grid-template-columns: 12.5rem minmax(0, 1fr);
gap: 2rem;
}
.ais-SearchBox-input,
.ais-Pagination-link {
border: 1px solid var(--color-border);
border-radius: 0.5rem;
background-color: var(--color-surface);
}
.ais-SearchBox-input {
inline-size: 100%;
padding-block: 0.75rem;
padding-inline: 1rem;
font: inherit;
font-size: 1rem;
color: inherit;
outline: none;
}
.ais-SearchBox-input:focus {
border-color: var(--color-accent);
box-shadow: 0 0 0 0.1875rem var(--color-accent-ring);
}
.ais-SearchBox-submit,
.ais-SearchBox-reset {
display: none;
}
.ais-RefinementList-list,
.ais-Hits-list {
margin: 0;
padding: 0;
list-style: none;
}
.ais-Hits-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12.5rem, 1fr));
gap: 1rem;
}
.ais-Hits-item {
display: flex;
}
.ais-Pagination-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: center;
padding-block: 0.75rem;
}
.ais-RefinementList-label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.ais-PoweredBy,
.ais-RefinementList-count,
.product-card-type {
font-size: 0.75rem;
color: var(--color-muted);
}
.ais-PoweredBy {
display: flex;
align-items: center;
min-block-size: 1.5rem;
line-height: 1;
justify-self: end;
}
.ais-PoweredBy-link {
display: flex;
align-items: center;
}
.ais-PoweredBy-text {
line-height: 1;
}
.ais-RefinementList-list {
display: grid;
gap: 0.5rem;
}
.ais-RefinementList-count {
margin-inline-start: auto;
}
.ais-RefinementList-checkbox {
inline-size: 0.875rem;
block-size: 0.875rem;
accent-color: var(--color-accent);
}
.ais-RefinementList-item--selected .ais-RefinementList-label {
font-weight: 500;
}
.ais-Pagination-link {
display: block;
padding-block: 0.5rem;
padding-inline: 0.75rem;
font-size: 0.875rem;
color: inherit;
text-decoration: none;
}
.ais-Pagination-item--selected .ais-Pagination-link {
color: #fff;
background: var(--color-accent-strong);
border-color: var(--color-accent-strong);
}
.ais-Pagination-item--disabled .ais-Pagination-link {
opacity: 0.4;
pointer-events: none;
}
.product-card {
display: flex;
flex: 1;
flex-direction: column;
inline-size: 100%;
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: 0.75rem;
background-color: var(--color-surface);
}
.product-card-image {
aspect-ratio: 1;
overflow: hidden;
background: var(--color-surface-muted);
}
.product-card-image img {
inline-size: 100%;
block-size: 100%;
object-fit: cover;
}
.product-card-body {
display: flex;
flex: 1;
flex-direction: column;
gap: 0.25rem;
padding: 0.75rem;
}
.product-card-type {
margin: 0;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.product-card-title,
.product-card-price,
.filter-panel-section-title {
margin: 0;
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text);
}
.product-card-description {
flex: 1;
margin: 0;
overflow: hidden;
font-size: 0.875rem;
line-height: 1.5;
color: var(--color-muted);
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
}
.filter-panel-section {
padding-block-end: 0.75rem;
}
.filter-panel-section-title {
margin-block-end: 0.5rem;
}
@media (max-width: 800px) {
#root {
padding-block: 1.5rem;
padding-inline: 1rem;
}
.search-body {
grid-template-columns: 1fr;
gap: 1.5rem;
}
}
```
**3. Start the app**
Start the development server.
```sh icon=square-terminal theme={"system"}
npm run dev
```
Open [`http://localhost:5173`](http://localhost:5173) and start searching.
> [!TIP]
>
> You see a product grid with images, a product type filter,
> and a search box.
> As you type, the product grid updates.
> Matching text is highlighted in the results.
You now have a working end-to-end search:
* Records come from the `quickstart-products` index you created earlier.
* Queries from `SearchBox` update the results and pagination together.
* The `product_type` filter works because that attribute is configured for faceting.
* `Highlight` and `Snippet` show which parts of each record match the query.
## Improve relevance and add features
> [accordion] Improve relevance
>
> Your app already returns matching products.
> You can improve relevance by adjusting which matches appear first.
>
> Update the existing `indexSettings` object in `scripts/indexing.ts`,
> then rerun `npm run index:products`.
>
> ```ts scripts/indexing.ts icon=code expandable theme={"system"}
> const { taskID } = await client.setSettings({
> indexName,
> indexSettings: {
> attributesForFaceting: ["product_type"],
> attributesToSnippet: ["description:30"],
> searchableAttributes: [ // [!code ++:6]
> "unordered(title)",
> "unordered(product_type)",
> "unordered(description)",
> ],
> customRanking: ["desc(units_sold)", "desc(price)"],
> },
> });
> ```
>
> This makes search results more useful by [prioritizing matches](/doc/api-reference/api-parameters/searchableAttributes) in product titles and types before descriptions,
> then [ranking similar matches](/doc/api-reference/api-parameters/customRanking) by best-selling and higher-priced products.
> [accordion] Add more search features
>
> Add features that help users understand and refine results.
> For example, you can add [search stats](/doc/api-reference/widgets/stats/react),
> [active filters](/doc/api-reference/widgets/current-refinements/react),
> or more ways to filter results, such as [filter lists](/doc/api-reference/widgets/refinement-list/react),
> [menus](/doc/api-reference/widgets/menu/react),
> and [numerical filters](/doc/api-reference/widgets/numeric-menu/react).
>
> > [!NOTE]
> >
> > If you add more filtering features,
> > update `attributesForFaceting` in `scripts/indexing.ts` to match.
> [accordion] Collect events from your search interface
>
> Collect events from your UI to measure engagement and
> support [Algolia features that use behavioral data](/doc/guides/sending-events/concepts/event-types).
>
> Update the `InstantSearch` component in `src/App.tsx`:
>
> ```tsx src/App.tsx icon=code theme={"system"}
> indexName="quickstart-products"
> searchClient={searchClient}
> insights // [!code ++]
> >
> ```
>
> This turns on the default React InstantSearch events,
> including `view` events for hits and filter click events for `RefinementList`.
>
> To send click events for hits, see [Send events with React InstantSearch](/doc/guides/building-search-ui/events/react).
## What's next
* [Get started with React InstantSearch](/doc/guides/building-search-ui/getting-started/react) to continue building your search interface.
* [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) to replace the sample catalog with your own product data.
* [Manage results](/doc/guides/managing-results/relevance-overview) to learn how to adjust relevance and ranking to your needs.
# Custom ranking
Source: https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking
Learn how to use custom ranking attributes to order records by popularity, rating, release date, or other metrics.
Search has two crucial steps:
returning accurate results and ranking them.
Custom ranking lets you influence the order of search results.
After Algolia applies its default relevance criteria, custom ranking breaks ties using attributes you define, such as popularity, rating, or release date.
This helps surface your most important content.
## How custom ranking fits into Algolia's ranking criteria
Algolia ranks results with a tie-breaking algorithm.
It first applies the default ranking criteria, such as Typo, Geo, Words, Filters, Proximity, Attribute, and Exact.
Custom ranking is the final default criterion and is only used when records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) are still tied after the earlier criteria.
For a full explanation of the ranking order, see [The eight ranking criteria](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria).
## Use custom ranking attributes
Algolia's default ranking criteria determine how well records match a query.
When multiple records are equally relevant, custom ranking breaks ties using attributes you define, instead of falling back to arbitrary ordering such as `objectID`.
This lets you use metrics like popularity, rating, or sales to order records that are otherwise equally relevant.
Typical custom ranking attributes include number of sales, views, likes, ratings, and release date.
**Any boolean or numeric attribute works with custom ranking.**
You can also use custom ranking attributes to boost or penalize specific records.
For example, you might add a boolean attribute such as `promoted` or `discontinued` and use it to rank promoted records higher or discontinued records lower.
Custom ranking attributes help align relevance with the metrics that matter for your search experience.
The following dataset illustrates a common use case with custom ranking on the `popularity` attribute in descending order:
```json JSON icon=braces theme={"system"}
[
{
"name": "Graphic t-shirt",
"popularity": 80
},
{
"name": "Limited edition t-shirt",
"popularity": 20
},
{
"name": "Basic t-shirt",
"popularity": 200
}
]
```
If you use `popularity` as a custom ranking attribute in descending order, a search for `t-shirt` ranks the most popular matching records first:
1. Basic t-shirt
2. Graphic t-shirt
3. Limited edition t-shirt
You can decide whether you want the order to be descending (greater values first) or ascending (smaller values first).
### Custom ranking precision
If a custom ranking attribute is too precise, it can prevent other attributes from influencing the ranking.
When records aren't tied, later attributes never come into play.
Consider a movie index that uses rating and views (in that order) as custom ranking attributes.
With precise ratings such as 4.321321, records are unlikely to share the same value.
As a result, views rarely affects the ranking.
This can produce less useful results.
For example, a movie with slightly lower rating but significantly more views may rank lower than expected.
To address this, reduce the precision of the first attribute.
For example, create a `truncated_rating` rounded to one decimal place (4.321321 → 4.3).
With more shared values, records can tie on `truncated_rating`, allowing views to act as a secondary ranking factor.
That's why [reducing precision is critical to effective tie-breaking](/doc/guides/managing-results/must-do/custom-ranking/how-to/controlling-custom-ranking-metrics-precision).
## See also
* [Create custom ranking attributes](/doc/guides/managing-results/must-do/custom-ranking/how-to/configure-custom-ranking)
* [Precision of custom ranking metrics](/doc/guides/managing-results/must-do/custom-ranking/how-to/controlling-custom-ranking-metrics-precision)
* [Boost or penalize records](/doc/guides/managing-results/must-do/custom-ranking/how-to/boost-or-penalize-some-records)
* [12 ways to improve your search index (blog)](https://www.algolia.com/blog/engineering/12-ways-to-improve-your-search-index/)
* [Building a query suggestion UX like Google's (blog)](https://www.algolia.com/blog/algolia/query-suggestion-ux-like-googles/)
# Bayesian averages in custom ranking
Source: https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking/how-to/bayesian-average
Learn how to use the Bayesian average for a more reliable and accurate rank star rating average.
Product reviews and ratings play an important role in consumer decision making.
Online shoppers look for products with the highest ratings.
They often read reviews that give details behind the ratings.
In the search and discovery context, businesses consider product reviews to be as relevant as product descriptions.
Both are relevant for matching users' queries.
Algolia's [custom ranking](/doc/guides/managing-results/must-do/custom-ranking) feature enables you to use business signals such as the number of sales and profit margins.
You can also use ratings to influence the ranking.
For example, when a user types a broad query such as "headphones", a ratings-based custom ranking ensures that the highest-rated earbuds show up first.
> [!NOTE]
>
> Though this tutorial focuses on star ratings,
> this solution can work with any product scoring system.
> For example, you could compute the Bayesian average for the number of up and down votes,
> or scores based on the number of sells or views.
## Concepts
The challenges with star ratings and how the Bayesian average has become the preferred method for ranking by star ratings.
### The difficulties with calculating a reliable rating
Creating a meaningful ranking strategy based on ratings can be challenging.
Is only one rating enough?
Or do you need a certain quantity of ratings for the ratings to be reliable?
Is a product with ratings ranging from 1 to 5 (in a 5-star rating system) better than a product with a smaller number of positive ratings?
Obviously, a product that receives only 5 stars is a good choice, but is it better than a popular item with hundreds of 4 stars?
A good example is the query "lion" on a movie streaming site.
Should the movie "The Lion King" rank above a higher-rated but lesser known film like "The Lion in Winter"?
Suppose that "The Lion King" has an average of 4.5 stars but "The Lion in Winter" has a higher average of 4.8.
If the 4.5 average rating comes from 10,000 ratings and the 4.8 average rating from 100 ratings, which movie should show up first?
The challenge with any rating system—whether for handbags, electronics, or movies—is that the quantity of ratings is as important as the rating itself.
Intuitively, the more ratings received, the more confidence you can have in the rating.
But again, how large should the sample size be before you can trust that the rating is meaningful?
#### Different ratings comparison
Consider two ways to rank star ratings:
* Use an arithmetic average that adds together all ratings and divides by the total quantity of ratings. If there are 100 1-star ratings and 10 5-star ratings, the calculation is ((100x1) + (10x5))/ (100+10) = 1.36.
* Use a [Bayesian average](https://en.wikipedia.org/wiki/Bayesian_average) that adjusts a product's average rating by how much it varies from the catalog average. This favors products with a higher quantity of ratings.
As already suggested, ignoring the quantity of ratings doesn't help distinguish between items with 10 ratings and 1000 ratings.
You need to at least calculate an average that includes the quantity of ratings.
The following image shows three items ranked by different averages.
The left side uses the arithmetic average for ranking.
The right side uses the Bayesian average.

Both sides display the arithmetic average in parenthesis just right of the stars.
They also display the average used for ranking as `avg_star_rating` and `bayes_avg` respectively, under each item.
The left side's ranking puts Item A at the top.
That's misleading and unsatisfying.
The ranking on the right, based on the Bayesian average, reflects a better balance of rating and quantity of ratings.
This example shows how the Bayesian average lowered item A's average to 4.3 because it measured A's 10 ratings against B and C's much larger numbers of ratings.
The Bayesian average left Items B and C unchanged, as described later.
That's because it affects items with low rating counts much more than items with more ratings.
In sum, by relativizing ratings in this way,
the Bayesian average creates a more reliable comparison between products.
It ensures that products with lower numbers of ratings have less weight in the ranking.
What follows is a description of the Bayesian average and how to code it.
### Bayesian averages
The Bayesian average adjusts the average rating of products whose rating counts fall below a threshold.
Suppose the threshold amount is calculated to be 100.
That means average ratings with less than 100 ratings get adjusted,
while average ratings with more than 100 ratings change only slightly.
This threshold amount of 100 is called a confidence number,
because it gives you confidence that averages with 100 or more ratings are more reliable than averages with fewer than 100 ratings.
This confidence number derives from the catalog's distribution of rating counts and the average rating of all products.
By factoring in ratings counts and averages from the whole catalog,
the Bayesian average has the following effect on an item's individual average rating:
* For an item with fewer than average ratings, the Bayesian average lowers its artificially high rating. It weighs the rating down slightly, toward the lower catalog average.
* For an item with a lot of ratings (that is, more than the threshold), the Bayesian average doesn't change its rating average by a significant amount.
#### How to calculate the Bayesian average
The Bayesian average uses two constants to offset the arithmetic average of an individual product:
* The arithmetic average rating of all products (`m`)
* A confidence number (`C`).
The calculation for `m` is an [arithmetic average](https://en.wikipedia.org/wiki/Arithmetic_mean) for all products:
the sum of all ratings divided by the number of ratings.
Calculating `C` requires a bit more math.
This tutorial calculates `C` based on the distribution of the rating counts for each product, where `C` is equal to the 25% percentile (= the lower [quartile](https://en.wikipedia.org/wiki/Quartile)).
For example, suppose a store has 100 products.
To compute `C`, you take all the products and sort them by the quantity of ratings each has.
Some have 10 ratings and others have 100 or 1000 ratings.
Once sorted, you find the product at the 25% position on the sorted list and note the number of ratings it has.
This is the lower quartile for `C`.
For simplicity, this guide sets `C = 100`.
Thus, if you calculate the overall average rating (`m`) of the store's catalog to be 3.5,
the Bayesian average uses both of these values ( `m = 3.5` and `C = 100`) to adjust the arithmetic average.
It does this using the following formula:
$$
\mathrm{bayesAvg} = \frac{\mathrm{productRatingsAvg} \times \mathrm{productRatingsCount} + C \times m}{\mathrm{productRatingsCount} + C}
$$
Here's the same formula with the example numbers plugged in:
$$
\mathrm{bayesAvg} = \frac{\mathrm{productRatingsAvg} \times \mathrm{productRatingsCount} + 100 \times 3.5}{\mathrm{productRatingsCount} + 100}
$$
## Compute the Bayesian average
The following shows how to code the Bayesian constants (`m` and `C`) and the Bayesian average itself.
It also discusses when to calculate these values.
### Necessary attributes in your index
You need a dataset of products where each product has at least these three attributes:
* The product's arithmetic average rating (`avg_stars_rating`)
* The product's Bayesian average (`bayes_average`), this can be empty or `0` to start
* The quantity of ratings (`ratings_count`)
Here's a sample dataset.
```json JSON icon=braces theme={"system"}
[
{
"name": "Item A",
"avg_stars_rating": 5,
"bayes_average": 0,
"ratings_count": 10
},
{
"name": "Item B",
"avg_stars_rating": 4.8,
"bayes_average": 0,
"ratings_count": 100
},
{
"name": "Item C",
"avg_stars_rating": 4.6,
"bayes_average": 0,
"ratings_count": 1000
}
]
```
> [!NOTE]
>
> This JSON already includes a `bayes_average` attribute.
> The purpose of the following code is to calculate the value for `bayes_average`.
>
> Additionally, the sample dataset doesn't show other attributes, such as the description of the product, price, item specifications, etc.
### Bayesian constants
The variables `m` and `C` represent the two Bayesian constants.
In this code, they're assigned the values from the preceding section (`m = 3.5` and `C = 100`):
```js JavaScript icon=code theme={"system"}
// Set the value of the catalog average rating
const m = 3.5; // average rating of all products
// Set the value of the confidence number
const C = 100; // confidence number based on quartile
```
You can calculate the `C` constant in different ways.
As suggested in the preceding section, you can use a lower quartile % that corresponds to the 25% percentile.
You can calculate this value using the following SQL function:
```sql SQL theme={"system"}
/* The value C is equal to: */
PERCENTILE_CONT(0.25) WITHIN GROUP ( ORDER BY ratings_count asc) OVER ()
```
> [!NOTE]
>
> The syntax of this SQL function can differ from one SQL database to another, so you might need to adapt it.
You may also want to calculate separate constants for each product category.
This is helpful for use cases with multiple categories of items, like Amazon and other marketplaces.
### Calculate the Bayesian average for each product
The variables `avg_stars_rating` and `ratings_count` match the attributes in the index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) that represent, respectively, the rating average and quantity of ratings for each product:
```js JavaScript icon=code theme={"system"}
// Name of the attribute having the arithmetic average rating per record
const product_ratings_average = "avg_stars_rating";
// Name of the attribute having the quantity of ratings per record
const product_ratings_count = "ratings_count";
```
**1. Create function for calculating Bayesian averages**
Using all four variables, this function generates the Bayesian average for a single product:
```js JavaScript icon=code theme={"system"}
const bayesianAverage = (
product_ratings_count,
product_ratings_average,
m,
C,
) => {
return (
(product_ratings_count * product_ratings_average + m * C) /
(product_ratings_count + C)
);
};
```
**2. Add Bayesian average to each record**
Perform the calculations for every product. You can do this by:
* Retrieving every product in the index, using the [`browse`](/doc/libraries/sdk/v1/methods/browse) method.
* Running them through the preceding Bayesian average function to calculate their Bayesian average
* Updating the index with their new values
```js JavaScript icon=code theme={"system"}
// retrieve every product in the index
var browser = index.browseAll();
// go through the records and calculate the Bayesian average
browser.on("result", function onResult(content) {
hits = hits.concat(content.hits);
const decorated_hits = _.map(hits, (hit) => ({
...hit,
bayes_average: bayesianAverage(
hit[product_ratings_count],
hit[product_ratings_average],
m,
C,
),
}));
// update every product with its newly calculated Bayesian average
index.saveObjects(decorated_hits, function (err, content) {
if (err) throw err;
console.log("Updated batch of 1000 records");
});
});
```
### Full example
```js JavaScript icon=code theme={"system"}
var algoliasearch = require("algoliasearch");
var hits = [];
var _ = require("lodash");
// Set your unique credentials and index name here
var client = algoliasearch("", "");
var index = client.initIndex("");
var browser = index.browseAll();
// Name of the attribute having the arithmetic average rating per record
const product_ratings_average = "avg_stars_rating";
// Name of the attribute having the quantity of ratings per record
const product_ratings_count = "ratings_count";
// Set the value of the prior to be used
const m = 3;
// Set the value of the confidence to be used
const C = 25;
// END of configuration
const bayesianAverage = (
product_ratings_count,
product_ratings_average,
m,
C,
) => {
return (
(product_ratings_count * product_ratings_average + m * C) /
(product_ratings_count + C)
);
};
// Use the browse method to retrieve all hits and update them with their Bayesian average
browser.on("result", function onResult(content) {
hits = hits.concat(content.hits);
const decorated_hits = _.map(hits, (hit) => ({
...hit,
bayesian_average: bayesianAverage(
hit[product_ratings_count],
hit[product_ratings_average],
m,
C,
),
}));
index.saveObjects(decorated_hits, function (err, content) {
if (err) throw err;
console.log("Updated batch of 1000 records");
});
});
browser.on("end", function onEnd() {
console.log("We got %d hits", hits.length);
});
browser.on("error", function onError(err) {
throw err;
});
```
### Frequency and methods of updating the Bayesian average
As users rate products, their Bayesian average changes—but not necessarily with every new rating.
Here's some guidance on how often to update the Bayesian average.
For [calculating the Bayesian constants (`m` and `C`)](#bayesian-constants),
you can create a batch job that runs once per week or once per month.
These constants don't need to change that often.
> [!TIP]
>
> It's important to store these constants so that you can calculate all products based on the same constants.
> Thus, whenever you change these constants, recalculate every product's Bayesian average.
For [calculating the Bayesian average for one or more products](#calculate-the-bayesian-average-for-each-product),
you can recalculate the Bayesian average periodically during the day, every night, or once per week.
It depends on the number of ratings you get each day.
For example, Uber might want to update their drivers' averages several times a day,
whereas Amazon might be fine with an overnight process, or once per week.
Update the Bayesian average at a frequency that matches how often people rate your products.
Because the Bayesian calculation is an average,
the values don't change with every new rating.
Thus, recalculating after every new rating isn't necessary.
You can recalculate Bayesian averages whenever you update your product index.
Note that because the Bayesian average affects one attribute per record,
you can do a [partial update](/doc/libraries/sdk/v1/methods/partial-update-objects) to change only the Bayesian average attribute.
## Use the Bayesian average as a custom ranking
As you've seen, you add the Bayesian average attribute to every product.
It's used as a [custom ranking](/doc/guides/managing-results/must-do/custom-ranking) attribute to rank records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) by ratings.
If you're using the Bayesian average as a custom ranking, consider:
* You can use it as a single custom ranking or combine it with other custom ranking attributes.
* Sometimes it's better to group together items with similar Bayesian averages.
* You can add more factors to the Bayesian calculation to improve its accuracy.
### As single custom ranking attribute
Up to this point, the assumption is that the Bayesian average operates alone in ranking your results.
In that case, you can use the new `bayes_average` attribute you've added in each of your records as a custom ranking value,
as seen in the following image:

### Mixed with other custom ranking signals
More often, you want to add a second ranking criteria as a tie-breaker,
to handle cases where two or more results have the same Bayesian average.
In these cases, you can use typical business metrics, such as most sales, views, margins, or anything else.
For example, if you have ten records with a Bayesian average of 4, you need a second custom ranking to break the tie between these 4's.
Here's an example where a product's `profit_margin` acts as a secondary tie-breaker for the primary Bayesian average ranking:

### Round and group scores
You may want to round your scores to one of the 5 ratings (1, 2, 3, 4, or 5), or more granularly (0.5, 1, 1.5, …, 5),
by using [a rounding function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round).
This avoids impacting the ranking with insignificant differences between products.
For example, you may decide that products with the Bayesian averages of 3.15 and 3.16 should have the same ranking.
You can also group ratings.
For example, you can put products with a Bayesian average between 3.8 and 4.2 into one group with a rating of 4,
products with averages between 4.3 and 4.7 into another group with rating 4.5, and so forth.
Not every group needs to have the same range.
For example, one group can contain ratings between 1 and 2,
whereas another group can contain ratings between 3 and 3.5, etc.
This is helpful when your product ratings are unevenly distributed,
for example, more 3's than 1's.
Grouping is another name for [bucketing](/doc/guides/going-to-production/case-study#creating-buckets-to-combine-sorts-with-custom-ranking), which not only removes insignificant differences between products, but tailors the grouping to your precise business needs or dataset.
### Add multiple indicators to the Bayesian average calculation
While using Bayesian averages is an excellent way to rank results,
it might be too simplistic depending on the size or nature of your business.
Some businesses change the way they compute an "average star rating."
For example, they may incorporate other signals into their average,
such as weighing reviews from validated reviewers differently,
or excluding reviews that have less than five likes,
or removing high and low ratings that deviate too much from the average (assuming your ratings follow a bell curve, which most do.)
In sum, whether it be with rounding, grouping,
or using multiple indicators,
you're improving upon the already more reliable Bayesian average,
thereby creating an even more reliable indicator of favorite products.
## See also
* [How to calculate the Bayesian average](https://fulmicoton.com/posts/bayesian_rating/)
* [Background on the Bayesian average](https://medium.com/district-data-labs/computing-a-bayesian-estimate-of-star-rating-means-651496a890ab)
* [How Amazon ranking works](https://www.feedbackwhiz.com/blog/how-does-amazon-calculate-product-ratings/)
# Boost or penalize records
Source: https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking/how-to/boost-or-penalize-some-records
How to boost or penalize a subset of records in the search results.
To boost or penalize records,
you need to add a boost attribute that tells Algolia how to rank them.
Algolia doesn't create this attribute for you:
it only uses the value you [send](/doc/guides/sending-and-managing-data/send-and-update-your-data).
Boosted attributes can be boolean or numeric:
* Boolean values create simple boosts.
* Numeric values allow finer control over boosting.
The following examples use the `featured` attribute for boosting.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
> [tab] Boolean
>
> ```json JSON icon=braces theme={"system"}
> [
> {
> "name": "Apricot",
> "featured": false
> },
> {
> "name": "Apple",
> "featured": true
> },
> {
> "name": "Almonds",
> "featured": false
> }
> ]
> ```
> [tab] Numeric
>
> ```json JSON icon=braces theme={"system"}
> [
> {
> "name": "Apricot",
> "featured": 4
> },
> {
> "name": "Apple",
> "featured": 0
> },
> {
> "name": "Almonds",
> "featured": 1
> }
> ]
> ```
>
> > [!TIP]
> >
> > Avoid using quoted numbers, such as `"32"`, which are treated as strings.
For finer control, use numeric values.
In the example dataset:
* With the boolean `featured` value, a query for `a` returns "Apple" first.
* With the numeric `featured` value, a query for `a` returns "Apricot" first because it has the highest `featured` value.
Defining the attribute isn't enough on its own:
you must tell Algolia to use it in the ranking formula (in custom ranking).
## Add the boost attribute to custom ranking
[Custom ranking](/doc/guides/managing-results/must-do/custom-ranking) is the last criterion in Algolia's ranking formula.
Add boost attributes here to affect the order of records with the same textual relevance.
It doesn't override Algolia's default ranking criteria.
To boost records, add the attribute you're boosting to the custom ranking from the dashboard or the API.
> [!TIP]
>
> If your boosted attribute is a boolean,
> ensure you set the order of the custom ranking attribute to descending.
### With the dashboard
On the [ranking and sorting tab](https://dashboard.algolia.com/explorer/configuration/ranking-and-sorting/),
add a new custom ranking attribute.

### From the API
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings { CustomRanking = new List { "desc(boosted)" } }
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
customRanking: [
"desc(boosted)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetCustomRanking(
[]string{"desc(boosted)"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setCustomRanking(Arrays.asList("desc(boosted)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { customRanking: ['desc(boosted)'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(customRanking = listOf("desc(boosted)")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['customRanking' => [
'desc(boosted)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"customRanking": [
"desc(boosted)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(custom_ranking: ["desc(boosted)"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
customRanking = Some(Seq("desc(boosted)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(customRanking: ["desc(boosted)"])
)
```
## Use sorting to group items before ranking them
You can sort records by an attribute before applying the ranking formula.
For example, you might want results to appear in price-based groups (such as high-, medium-, and low-priced items).
To do this,
assign numeric values (for example, 1, 2, and 3) to a defined grouping attribute.
Algolia sorts group 1 first, then group 2, and so on.
> [!NOTE]
>
> Use a separate attribute for grouping.
> Don't reuse your boost attribute.
> This method uses [sorting](/doc/guides/managing-results/refine-results/sorting),
> which applies *before* custom ranking and determines the overall group order.
# Create custom ranking attributes
Source: https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking/how-to/configure-custom-ranking
How to create custom ranking attributes according to your business needs, based on your own metrics, like sales or popularity.
Algolia offers good relevance out-of-the-box.
Yet, what makes search great is fine-tuning it according to business relevance.
Consider you're developing an app based on Twitter feeds.
When searching, you want to rank results by relevance and the custom attributes `likes` and `retweets`.
You can do this by using Algolia's [custom ranking feature](/doc/guides/managing-results/must-do/custom-ranking)
to specify what attributes to rank on.
You can configure these attributes with the [`customRanking`](/doc/api-reference/api-parameters/customRanking) parameter or through Algolia's dashboard.
## Example dataset
In this example, you're developing an app based on Twitter feeds and want to rank tweets by the number of likes and retweets.
Here's what the dataset would look like:
```json JSON icon=braces theme={"system"}
[
{
"tweet": "🎈 We're introducing Create InstantSearch App today: a CLI to bootstrap InstantSearch apps from the terminal. Read more in this blog post by @FrancoisChlfr!",
"retweets": 13,
"likes": 27
},
{
"tweet": "Designers from all horizons, don't forget to register for next #ParisDesignMeetup, taking place on Sept 25th!",
"retweets": 9,
"likes": 13
},
{
"tweet": "Are you ready for GSA to be a thing of the past? Register now for our webinar, where we'll help you with migration options, tips, and tricks to make your move as painless as possible. https://go.algolia.com/gsa-migration",
"retweets": 4,
"likes": 6
}
]
```
## Configure custom ranking 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, go to **Ranking and Sorting**.
5. Click **Add custom ranking attribute** and select the attributes `retweets` and then `likes`.

6. Save your changes.
## Configure custom ranking with the API
To rank on retweets and likes,
set [`customRanking`](/doc/api-reference/api-parameters/customRanking).
criteria to be in ascending or descending order.
Custom ranking criteria are applied in order (in this example, first retweets and then likes).
To run the code examples, [install the latest API client](/doc/libraries/sdk/install).
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
CustomRanking = new List { "desc(retweets)", "desc(likes)" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
customRanking: [
"desc(retweets)",
"desc(likes)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetCustomRanking(
[]string{"desc(retweets)", "desc(likes)"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setCustomRanking(Arrays.asList("desc(retweets)", "desc(likes)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { customRanking: ['desc(retweets)', 'desc(likes)'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(customRanking = listOf("desc(retweets)", "desc(likes)")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['customRanking' => [
'desc(retweets)',
'desc(likes)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"customRanking": [
"desc(retweets)",
"desc(likes)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(custom_ranking: ["desc(retweets)", "desc(likes)"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
customRanking = Some(Seq("desc(retweets)", "desc(likes)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(customRanking: ["desc(retweets)", "desc(likes)"])
)
```
## Custom ranking on different attributes
**Custom ranking applies at the index level**.
This means records that don't have an attribute that's in the [`customRanking`](/doc/api-reference/api-parameters/customRanking) list are pushed to the bottom.
For example, say you want to add Facebook posts to your dataset, and they have attributes `likes` and `comments`.
Because `retweets` are a custom ranking attribute, and Facebook posts don't have a `retweets` attribute, they're unlikely to win the tie-break against a tweet.
You could solve this by creating a computed attribute.
For example, you could compute a single popularity attribute instead of having `likes` and `retweets` on one side and `likes` and `comments` on the other.
## Computed attributes
You might want to create a custom ranking attribute based on the calculated value of other attributes.
In other words, a computed attribute.
For example:
* [A Bayesian average of product ratings](/doc/guides/managing-results/must-do/custom-ranking/how-to/bayesian-average)
* A simple, combined score of other attributes (such as `popularity` being the sum of `likes` and `retweets`).
* A boolean value set to true or false based on whether another attribute is `null`.
To implement a computed attribute, you must create it as an attribute in your records. To populate the record with values for the computed attribute:
1. Retrieve every record in the index, using the [`browse`](/doc/libraries/sdk/v1/methods/browse) method.
2. For each record, calculate the computed attribute value.
3. Update each record's computed attribute in the index with the calculated value, using the [`partialUpdateObjects`](/doc/libraries/sdk/v1/methods/partial-update-objects) method.
The regularity of these updates depends on your use case: it might be once a month,
every week, daily, or even more frequently.
## Metric types
The custom ranking field accepts any numerical or boolean value that represents the relative relevance of your records.
The attribute type can be a raw value like the number of sales, views, or likes.
The field can also be a computed value such as a popularity score that you calculated before adding the record to Algolia.
What you set as your [`customRanking`](/doc/api-reference/api-parameters/customRanking) depends on your use case and what data you have available.
Some retail metrics commonly used in [`customRanking`](/doc/api-reference/api-parameters/customRanking) include sales rank, stock levels, free shipping (boolean), on sale (boolean), and rating.
Publish date (as a [timestamp](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#dates)), page views, and likes are often used in media applications.
> [!TIP]
>
> Check that the numeric attributes used in [`customRanking`](/doc/api-reference/api-parameters/customRanking)
> aren't formatted as strings.
> This would cause the records to be ranked alphabetically.
### Boolean values
When using a boolean value attribute within the custom ranking,
the ascending and descending setting uses the alphabetical value of the true or false text.
* Descending: true before false
* Ascending: false before true
### String and null or absent values
If a custom ranking attribute is missing from a record or has a null value,
that record is always ordered last, regardless of its ascending or descending setting.
Records with null or missing values are considered equal.
Strings are sorted after numbers and before null or absent values.
Strings are also compared by lexicographical order.
Algolia compares only the first 50 bytes of each string (50 characters for ASCII text).
If records have the same first 50 bytes for a custom ranking attribute,
Algolia compares the next custom ranking attribute.
# Precision of custom ranking metrics
Source: https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking/how-to/controlling-custom-ranking-metrics-precision
Improve your custom ranking by reducing granularity, so that similar records are ranked equally.
When you rank records with business metrics, you want to make sure the difference between those values is significant.
Sometimes, the difference is too small to be taken into account.
Yet, when the engine performs tie-breaking between two records,
it doesn't make a difference whether you have 1,000 or 1,003 more views on a blog article.
If one has more than the other, it goes first.
Therefore, **you want to make sure that the values you use for custom ranking are properly weighted to avoid false positives**.
To fix that issue, what you can do is **reduce precision**.
An example is when you have blog articles,
ranked per different attributes like unique page views and number of comments.
You may have two posts with a close number of unique page views (1,000 and 1,003).
Therefore, you don't want the engine to break the tie on this attribute.
Instead, you want to move on to the next criteria to get better tie-breaking and more relevant results.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
## Modifying the data: an example
### Before
In this example, you're adding search to a blog.
Besides Algolia's default ranking formula,
you've set two custom ranking attributes: `pageviews` first, followed by `comments`.
Here's what the dataset would look like:
```json JSON icon=braces theme={"system"}
[
{
"title": "Algolia + SeaUrchin.IO",
"author": "Nicolas Dessaigne",
"pageviews": 1023,
"comments": 23
},
{
"title": "Search Analytics: Gain Insights from User Search Data",
"author": "Nicolas Baissas",
"pageviews": 508,
"comments": 42
},
{
"title": "Algolia Vault – Bringing Physical & Digital Data Security to Search",
"author": "Liam Boogar",
"pageviews": 1022,
"comments": 54
}
]
```
And here's the setting:
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
CustomRanking = new List { "desc(pageviews)", "desc(comments)" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
customRanking: [
"desc(pageviews)",
"desc(comments)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetCustomRanking(
[]string{"desc(pageviews)", "desc(comments)"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setCustomRanking(Arrays.asList("desc(pageviews)", "desc(comments)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { customRanking: ['desc(pageviews)', 'desc(comments)'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(customRanking = listOf("desc(pageviews)", "desc(comments)")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['customRanking' => [
'desc(pageviews)',
'desc(comments)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"customRanking": [
"desc(pageviews)",
"desc(comments)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(custom_ranking: ["desc(pageviews)", "desc(comments)"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
customRanking = Some(Seq("desc(pageviews)", "desc(comments)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(customRanking: ["desc(pageviews)", "desc(comments)"])
)
```
Because [custom ranking](/doc/guides/managing-results/must-do/custom-ranking) applies its criteria sequentially,
`pageviews` has more weight than `comments`.
When you search for "Algolia", the "Algolia + SeaUrchin IO" article comes before "Algolia Vault" only because it has one more unique page view.
However, this additional view isn't meaningful.
It doesn't make the article more relevant.
Nonetheless, the engine ignores the next custom ranking attribute because of it.
For these two records, it would be better if the article with the most comments appeared first:
namely, that "Algolia Vault" article should come first because it has significantly more comments.
But changing the order of the attributes in the [`customRanking`](/doc/api-reference/api-parameters/customRanking) parameter isn't a good solution:
* It doesn't necessarily make sense business-wise (in this example, unique page views matter more than the number of comments),
* You may face the same issue with the number of comments, so this doesn't solve anything.
That's why we recommend **reducing precision to effectively use tie-breaking.**
### After
You can solve the issue by adding a `rounded_pageviews` attribute where you reduce the precision of `pageviews`,
and use it for custom ranking instead.
For example, round page views to the nearest hundred.
```json JSON icon=braces theme={"system"}
[
{
"title": "Algolia + SeaUrchin.IO",
"author": "Nicolas Dessaigne",
"pageviews": 1023,
"rounded_pageviews": 1000,
"comments": 23
},
{
"title": "Search Analytics: Gain Insights from User Search Data",
"author": "Nicolas Baissas",
"pageviews": 508,
"rounded_pageviews": 500,
"comments": 42
},
{
"title": "Algolia Vault – Bringing Physical & Digital Data Security to Search",
"author": "Liam Boogar",
"pageviews": 1022,
"rounded_pageviews": 1000,
"comments": 54
}
]
```
Then, you would use the `rounded_pageviews` attribute for custom ranking instead of `pageviews`.
Now, searching for "Algolia" would return the "Algolia Vault" article first, based on the `comments` attribute.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
CustomRanking = new List { "desc(rounded_pageviews)", "desc(comments)" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
customRanking: [
"desc(rounded_pageviews)",
"desc(comments)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetCustomRanking(
[]string{"desc(rounded_pageviews)", "desc(comments)"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setCustomRanking(Arrays.asList("desc(rounded_pageviews)", "desc(comments)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { customRanking: ['desc(rounded_pageviews)', 'desc(comments)'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings =
IndexSettings(customRanking = listOf("desc(rounded_pageviews)", "desc(comments)")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['customRanking' => [
'desc(rounded_pageviews)',
'desc(comments)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"customRanking": [
"desc(rounded_pageviews)",
"desc(comments)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(custom_ranking: ["desc(rounded_pageviews)", "desc(comments)"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
customRanking = Some(Seq("desc(rounded_pageviews)", "desc(comments)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(customRanking: ["desc(rounded_pageviews)", "desc(comments)"])
)
```
Note that this doesn't remove `pageviews` from the dataset.
You may want to keep it for display purposes.
To know more on how to rank per custom attributes, see the [guide](/doc/guides/managing-results/must-do/custom-ranking/how-to/configure-custom-ranking).
# Custom ranking of results per category
Source: https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking/how-to/custom-ranking-per-category
Adjust custom rankings per category.
When you set [custom ranking](/doc/guides/managing-results/must-do/custom-ranking) attributes in Algolia,
they affect the whole index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) (independent of categories).
But what if you want to use different custom rankings per category?
This guide shows how you can use duplicate records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in the Algolia index and use a filter (A filter is a condition that limits which records Algolia returns. Filters often use one or more facet-value pairs, such as brand:Apple AND color:red. You can also filter by numeric values, dates, tags, booleans, or geographic constraints.) to apply different custom rankings per category.
## Implementation steps
To use different custom rankings per category, you need to perform these tasks:
* **Data**: use duplicate records for each product, with extra attributes for "ranked category," and "rank within that category"
* **Index**: select the "rank within category" attribute for custom ranking and select the "ranked category" attribute for faceting. To handle duplicate search results, use a distinct identifier for each product
* **Frontend**: dynamically filter search results with or without category filters
## Prepare your data
**Duplicate records** let you use filters to rank products differently in different categories.
Each record has attributes for the category in which the product ranks and the rank within that category.
For example, assume you have an online clothing store.
One of your products is Sports shoes, and you want to rank these shoes higher as users search in narrower categories.
For each record, you add new attributes:
* `category`: a list with all category names for this product—ranked and not ranked. This is used for [faceting](/doc/guides/managing-results/refine-results/faceting) the search results. See [Creating a category data attribute](/doc/guides/solutions/ecommerce/browse/tutorials/category-pages) for more information, and how to implement hierarchical categories.
* `ranked_category`: the name of the category for which the product ranks. To show a product also for searches without category filters, **one record per product must have `ranked_category: none`**.
* `category_rank`: the rank of the product in the category. Each duplicate of the product's record has one corresponding pair of `ranked_category` and `category_rank` attributes, which enables the per-category custom ranking.
* `parent_objectID`: a unique identifier for events. When [tracking click and conversion events](/doc/guides/sending-events) with the Insights API, you use this identifier to link all events from the duplicate records to the same product.
Conceptually, users search for sports shoes in three different ways:
* Search "Sports shoes" without any category filters
* Search "Sports shoes" in the category Shoes
* Search "Sports shoes" in the category Shoes > Sports
If you want to use different custom rankings per category, you need at least three records per product.
* **Main record for searches without categories and all other categories**.
This record matches when users search without category filters or in other categories,
where this product doesn't rank.
The textual and custom relevance settings for the index decide the position in the search results.
When tracking click and conversion events for this record, use the `objectID` attribute as the unique identifier.
```json JSON icon=braces theme={"system"}
{
"objectID": "492533",
"sku": "4597310",
"name": "Sports shoes",
"category": ["Shoes", "Shoes > Sports"],
"ranked_category": "none",
"sales_count": 852
}
```
* **Record for searches in the *Shoes* category**.
When users search in the *Shoes* category,
this product shows at the custom rank 20.
To mark this record as a duplicate of the first record,
the attribute `parent_objectID` has the same value as the `objectID` of the first record.
For tracking events, use the `parent_objectID` attribute as the unique identifier.
```json JSON icon=braces theme={"system"}
{
"objectID": "473828",
"parent_objectID": "492533",
"sku": "4597310",
"name": "Sports shoes",
"category": ["Shoes", "Shoes > Sports"],
"ranked_category": "Shoes",
"category_rank": 20,
"sales_count": 852
}
```
* **Record for searches in the *Shoes > Sports* category**.
When users search in the *Shoes* > *Sports* category,
this product shows at the top of the custom ranking.
To track events for this record as duplicates of the first record,
use the attribute `parent_objectID`.
It has the same value as the `objectID` attribute of the first record.
```json JSON icon=braces theme={"system"}
{
"objectID": "511621",
"parent_objectID": "492533",
"sku": "4597310",
"name": "Sports shoes",
"category": ["Shoes", "Shoes > Sports"],
"ranked_category": "Shoes > Sports",
"category_rank": 1,
"sales_count": 852
}
```
## Configure the index
To configure the Algolia index for category-based custom ranking, follow these steps:
1. Add the `category_rank` attribute to the top of the custom ranking criteria and sort the results by ascending values so that low values rank high.
In the [Algolia dashboard](https://dashboard.algolia.com/explorer/configuration/),
choose your index and select **Ranking and Sorting**.
2. Add `ranked_category` to the attributes for faceting.
Since you want to use this attribute just for filtering the search results,
use the [`filterOnly`](/doc/api-reference/api-parameters/attributesForFaceting#modifiers) modifier to discard the facet values and counts. This reduces the size of the index and speeds up the search. In the Dashboard, go to **Facets** and add the `ranked_category` attribute.
3. When searching with category filters, the search might return multiple records for the same product.
That's why you need to deduplicate the results.
In the dashboard, go to **Deduplication and Grouping** and set **Distinct** to **true**.
Select the `sku` attribute as **Attribute for Distinct**.
> [!NOTE]
>
> Using `distinct` is computationally intensive and can slow down the search.
You can configure [sorting attributes](/doc/guides/managing-results/refine-results/sorting) independently from the custom ranking attributes per category.
Both settings don't influence each other.
For more information, see:
* [Create custom ranking attributes](/doc/guides/managing-results/must-do/custom-ranking/how-to/configure-custom-ranking)
* [Declare attributes for faceting with the API](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting)
* [`attributeForDistinct`](/doc/api-reference/api-parameters/attributeForDistinct)
* [`distinct`](/doc/api-reference/api-parameters/distinct)
## Show custom ranking per category in your frontend
To take advantage of the per-category custom ranking in the frontend,
you need to filter the search results on the `ranked_category` attribute.
Depending on whether users search with or without categories, you need to filter the results dynamically:
* When users search **without categories**:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
Query = "User search query",
FacetingAfterDistinct = true,
Filters = "ranked_category:none",
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
query: "User search query",
facetingAfterDistinct: true,
filters: "ranked_category:none",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetQuery("User search query").SetFacetingAfterDistinct(true).SetFilters("ranked_category:none"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setQuery("User search query").setFacetingAfterDistinct(true).setFilters("ranked_category:none"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { query: 'User search query', facetingAfterDistinct: true, filters: 'ranked_category:none' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
query = "User search query",
facetingAfterDistinct = true,
filters = "ranked_category:none",
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['query' => 'User search query',
'facetingAfterDistinct' => true,
'filters' => 'ranked_category:none',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"query": "User search query",
"facetingAfterDistinct": True,
"filters": "ranked_category:none",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
query: "User search query",
faceting_after_distinct: true,
filters: "ranked_category:none"
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
query = Some("User search query"),
facetingAfterDistinct = Some(true),
filters = Some("ranked_category:none")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(
query: "User search query",
filters: "ranked_category:none",
facetingAfterDistinct: true
))
)
```
For searches without categories, the results show records with the `ranked_category: none` attribute.
* When users search **with categories**, there are two possibilities:
the product ranks for the current category, or it doesn't (but it might rank for a different category):
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
Query = "User search query",
FacetingAfterDistinct = true,
Filters =
"category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)",
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
query: "User search query",
facetingAfterDistinct: true,
filters:
"category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().
SetQuery("User search query").
SetFacetingAfterDistinct(true).
SetFilters("category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)"),
)))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject()
.setQuery("User search query")
.setFacetingAfterDistinct(true)
.setFilters("category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR" + " ranked_category:none)"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: {
query: 'User search query',
facetingAfterDistinct: true,
filters: 'category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)',
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
query = "User search query",
facetingAfterDistinct = true,
filters =
"category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)",
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['query' => 'User search query',
'facetingAfterDistinct' => true,
'filters' => 'category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"query": "User search query",
"facetingAfterDistinct": True,
"filters": "category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
query: "User search query",
faceting_after_distinct: true,
filters: "category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)"
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
query = Some("User search query"),
facetingAfterDistinct = Some(true),
filters =
Some("category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(
query: "User search query",
filters: "category:{{currentCategory}} AND (ranked_category:{{currentCategory}} OR ranked_category:none)",
facetingAfterDistinct: true
))
)
```
If a product ranks for the current category, the search would return both records.
For example, if users search in the category Shoes, these records match the filter:
* `category: Shoes AND ranked_category: Shoes`
* `category: Shoes AND ranked_category: none`
Since you use `distinct` for this index, results only show the record with `ranked_category: Shoes`.
If users search in a different category, for example,
the category *Accessories*, the results show the record matching `category: Accessories and ranked_category: none`.
These rules decide the ranking between records with and without ranked categories:
1. Both records are the same except for the `ranked_category` and `category_rank` attributes.
Since they have the same textual relevance,
Algolia ranks them by their custom ranking attributes.
2. The first custom ranking attribute is `category_rank`.
As the record with `ranked_category:none` doesn't have a `category_rank` attribute,
it ranks lower than a record with a value for this attribute.
3. With `distinct(true)`, the search returns the first record from a list of identical records.
Because you set `attributeForDistinct` to `sku`, all products with the same `sku` attribute are identical.
By default, faceting is applied before the deduplication.
To ensure correct facet counts,
deduplicate and then set [`facetingAfterDistinct`](/doc/api-reference/api-parameters/facetingAfterDistinct) to `true`.
You have to apply this setting at query time.
# Searchable attributes
Source: https://www.algolia.com/doc/guides/managing-results/must-do/searchable-attributes
How to decide which searchable attributes you want to use
When setting up and configuring your index, decide which [attributes you want to use for searching](/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records).
When you create an index, **all attributes in all your records are searchable by default**.
This means you can perform searches straight away without configuring anything.
However, to enhance search relevance and exclude unnecessary results,
specify which attributes should be searchable.
* An example of a useful searchable attribute is the name of a product.
* An example of an attribute that isn't useful for searching is a URL.
Since users seldom search for URLs, exclude them from the searchable attributes list.
However, you can still use image URLs to display images in the results.
For example, on a recipes website it makes sense to make `title`, `ingredients`, and `comments` searchable, and exclude `images`.
You can still use non-searchable attributes for display or filtering.
## Attributes to include
The following attributes are commonly included (searchable):
* Descriptive attributes: name, description, brand, list of actors in a film, list of features in a computer.
* Filters: color, brand, size.
* Keywords and tags.
* Valuable data like telephone numbers, SKU, ISBN, and other identifying attributes.
## Attributes to exclude
These are often excluded (not searchable):
* For display only: image and other URLs, price, brand logos, SKU.
* Short descriptions are meant to be displayed, not searched since they usually summarize what's in a longer description.
* For information only: not yet available, just released, on promotion.
* For ranking only: most sales, most viewed, highest rated.
## Set searchable attributes
Specify searchable attributes from the dashboard or API.
> [!NOTE]
>
> You can only apply search restrictions to the first 65 searchable attributes and searchable facets in your index, combined.
> Algolia ignores attributes beyond this limit:
> queries can't match them and [`restrictSearchableAttributes`](/doc/api-reference/api-parameters/restrictSearchableAttributes) doesn't affect them.
### Set searchable attributes in the Algolia 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 **Searchable attributes**.
5. Click **Add a Searchable Attribute** and type or select an attribute from the list.
6. Change the [order of attributes](/doc/guides/managing-results/must-do/searchable-attributes/how-to/configuring-searchable-attributes-the-right-way#attribute-order) by dragging them up or down in the **Searchable attributes** list.
To make two attributes equally important, when you add a new attribute,
type the attributes directly as a comma-separated list—for example, `title, comments`.
7. Save your changes.
### Set searchable attributes with the API
To make certain attributes searchable,
include them in the [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes) setting when configuring your index with the [`setSettings`](/doc/libraries/sdk/v1/methods/set-settings) method.
To configure [the order of searchable attributes](/doc/guides/managing-results/must-do/searchable-attributes/how-to/configuring-searchable-attributes-the-right-way#attribute-order):
* The higher in the list you place an attribute, the higher matches within that attribute will rank in search results.
* If you want two attributes to have the same priority, include them on the same level, separated by a comma.
In this example, matches in the title and comments attributes rank the same but also rank before matches in the ingredients attribute.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
SearchableAttributes = new List { "title,comments", "ingredients" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: [
"title,comments",
"ingredients",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetSearchableAttributes(
[]string{"title,comments", "ingredients"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setSearchableAttributes(Arrays.asList("title,comments", "ingredients"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { searchableAttributes: ['title,comments', 'ingredients'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings =
IndexSettings(searchableAttributes = listOf("title,comments", "ingredients")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['searchableAttributes' => [
'title,comments',
'ingredients',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"searchableAttributes": [
"title,comments",
"ingredients",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(searchable_attributes: ["title,comments", "ingredients"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
searchableAttributes = Some(Seq("title,comments", "ingredients"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(searchableAttributes: ["title,comments", "ingredients"])
)
```
## See also
* [Configuring searchable attributes the right way](/doc/guides/managing-results/must-do/searchable-attributes/how-to/configuring-searchable-attributes-the-right-way)
* [Records, attributes, and searchable attributes](/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records)
* [User-restricted access to data](/doc/guides/security/api-keys/how-to/user-restricted-access-to-data)
* [Fuzzy search and fuzzy matching (blog)](https://www.algolia.com/blog/engineering/discover-what-fuzzy-search-is-with-fuzzy-matching/)
* [How search queries are processed (blog)](https://www.algolia.com/blog/product/what-is-a-search-query-and-how-is-it-processed-by-a-search-engine/)
# Configure searchable attributes the right way
Source: https://www.algolia.com/doc/guides/managing-results/must-do/searchable-attributes/how-to/configuring-searchable-attributes-the-right-way
Learn how to select, prioritize, and set up searchable attributes for your Algolia index.
Setting searchable attributes is **one of the most important aspects of configuring your search** and one of the first steps you need to take.
It goes much further than telling Algolia which attributes to search for, and it also defines which ones to look at first and what strategy to adopt.
A small change in setting searchable attributes could lead to entirely different results.
For that reason, it's crucial to understand how it works.
Besides deciding [which attributes Algolia should search](/doc/guides/managing-results/must-do/searchable-attributes), there are two aspects you can control with searchable attributes:
* The [relative importance](#attribute-order) of each attribute
* Whether the [position of the match](#word-position) in the attribute matters
## Attribute order
While setting searchable attributes, you can order them, making some more relevant than others.
Knowing how to order your searchable attributes can be challenging when you have several.
One method is to **compare them in pairs**, each attribute with the previous one, and move them around accordingly.
This is similar to [insertion sorting](https://wikipedia.org/wiki/Insertion_sort).
For example, suppose you have the following attributes in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) of a "movies" index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.):
* `director`
* `cast`
* `title`
* `genres`
* `plot_summary`
1. Compare the first two: `director` and `cast`.
If you think `cast` should come before `director` (for example, you want to see movies with Clint Eastwood before movies directed by Clint Eastwood), move `cast` to the first place.
2. Compare `director` and `title` and decide that the movie's name is more important than the director's.
That means you'd move `title` to the second place and compare it with the previous one (`cast`).
3. Prioritize a match on an actor's name or a movie title.
If all records in your index represent movies,
it may make sense to prioritize `title`,
so you'd move it first.
Then, because you don't have any more attributes to compare `title` to, you'd move on to `genres`.
The benefit is that **you're making a thorough, granular comparison of each attribute with the others** and considering your use case for every pair.
This method is much more reliable than doing a global, intuitive sort by what seems more important.
### Searchable attributes with different priorities
**The order of searchable attributes directly affects search relevance.**
Attributes higher in the list are more relevant than attributes further down.
Therefore, set attributes higher in the searchable attributes list when their content is more relevant to your users.
Consider a movie database website where users can search movies by title, actors, and director. You may be tempted to prioritize actors before directors Yet, consider the following dataset:
```json JSON icon=braces theme={"system"}
[
{
"title": "Mystic River",
"director": "Clint Eastwood",
"actors": ["Sean Penn", "Tim Robbins", "Kevin Bacon", "Laurence Fishburne"]
},
{
"title": "In the Line of Fire",
"director": "Wolfgang Petersen",
"actors": ["Clint Eastwood", "John Malkovich", "Renee Russo"]
},
{
"title": "Invictus",
"director": "Clint Eastwood",
"actors": ["Morgan Freeman", "Matt Damon"]
},
{
"title": "Million Dollar Baby",
"director": "Clint Eastwood",
"actors": ["Clint Eastwood", "Hilary Swank", "Morgan Freeman"]
}
]
```
For a movie search, setting the `title` as the first searchable attribute makes sense.
But what if someone searched for "Clint Eastwood"?
Would they primarily be looking for movies with Clint Eastwood, by Clint Eastwood, or both?
In other words, how should you rank the `director` and `actors` attributes?
By putting `director` first, movies that Clint Eastwood directed would come before those where he appeared as an actor.
This may or may not be desirable.
For example, if a user types "jane", do you want to first display movies with director Jane Campion or actress Jane Fonda?
There's no one-size-fits-all approach, and the strategy depends on your use case.
**Defining a priority order in your searchable attributes isn't trivial**.
It can vary depending on your data, how it's structured, what your users search for, and what they expect as results.
### Searchable attributes with the same priority
Sometimes, setting an attribute before or after another doesn't make sense because you want them to be considered the same.
Here's another dataset, this time with records representing movies, actors, and directors:
```json JSON icon=braces theme={"system"}
[
{
"movie_title": "John Wick",
"cast": ["Keanu Reeves", "Michael Nyqvist", "Alfie Allen"]
},
{
"actor_name": "John Cleese",
"filmography": [
"Monty Python and the Holy Grail",
"Life of Brian",
"Harry Potter and the Philosopher's Stone"
]
},
{
"director_name": "John Carpenter",
"filmography": ["Halloween", "The Thing", "Escape from New York"]
}
]
```
If someone typed `John`, it wouldn't necessarily make more sense to show movies with *John* in the title than actors or directors named John.
Instead, you may want to rely on other criteria, like popularity, to decide what comes first.
In this case, it makes sense to set `movie_title`, `actor_name`, and `director_name` at the same level.
This way, if someone types `John`, the engine considers all three records equal in the [attribute ranking criterion](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria#attribute), and the engine would go to the next [ranking criterion](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria) to try and break the tie.
> [!NOTE]
>
> Attributes with the same priority are always [unordered](#word-position).
## Word position
Searchable attributes can be ordered or unordered:
* **ordered** means that the engine considers matches at the beginning of an attribute more important than matches in the middle or the end. When an attribute is ordered, the earlier a match occurs, the higher the engine ranks it.
* **unordered** means that the position of the match within the attribute doesn't affect ranking.
> [!TIP]
>
> Usually, set the attribute to be unordered since the position of the matching word in the attribute often doesn't matter.
Consider the following dataset:
```json JSON icon=braces theme={"system"}
[
{
"title": "Avengers: Infinity War",
"cast": [
"Robert Downey Jr.",
"Chris Hemsworth",
"Mark Ruffalo",
"Chris Evans",
"Scarlett Johansson"
]
},
{
"title": "World War Z",
"cast": [
"Brad Pitt",
"Mireille Enos",
"Daniella Kertesz",
"James Badge Dale",
"Ludi Boeken"
]
},
{
"title": "War of the Worlds",
"cast": [
"Tom Cruise",
"Dakota Fanning",
"Justin Chatwin",
"Miranda Otto",
"Tim Robbins"
]
},
{
"title": "Lost in Translation",
"cast": ["Scarlett Johansson", "Bill Murray", "Akiko Takeshita"]
},
{
"title": "North",
"cast": [
"Elijah Wood",
"Jason Alexander",
"Julia Louis-Dreyfus",
"Marc Shaiman",
"Scarlett Johansson"
]
}
]
```
If users type "war," it wouldn't make sense to return "World War Z" before "Avengers: Infinity War" because the string happens to appear earlier in the title.
The ranking should rely on other criteria like popularity or reviews, so it makes sense to set `title` as `unordered`.
Things are different with the `cast` attribute.
If users type "Scarlett Johansson," chances are they would be more interested in movies where Scarlett Johansson has a leading role than movies where she played small parts.
Assuming that the order of actors and actresses in the `cast` attribute reflects role size, keeping `cast` ordered makes more sense.
### Set unordered or ordered with the API
You can set attributes as unordered or ordered with [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes) when indexing.
If unordered isn't set for an attribute, it will default to ordered when set by the API.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
SearchableAttributes = new List { "unordered(title)", "cast" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: [
"unordered(title)",
"cast",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetSearchableAttributes(
[]string{"unordered(title)", "cast"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setSearchableAttributes(Arrays.asList("unordered(title)", "cast"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { searchableAttributes: ['unordered(title)', 'cast'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(searchableAttributes = listOf("unordered(title)", "cast")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['searchableAttributes' => [
'unordered(title)',
'cast',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"searchableAttributes": [
"unordered(title)",
"cast",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(searchable_attributes: ["unordered(title)", "cast"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
searchableAttributes = Some(Seq("unordered(title)", "cast"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(searchableAttributes: ["unordered(title)", "cast"])
)
```
### Set unordered or ordered from the dashboard
1. Select the **Search** product icon on your dashboard.
2. On the [**Index** page](https://dashboard.algolia.com/explorer/browse), select your index.
3. Click the **Configuration** tab.
4. Go to the **Searchable Attributes** section.
For each searchable attribute, consider whether it should be [ordered or unordered](#word-position).
5. Save your changes.
> [!NOTE]
>
> When using the dashboard, the UI defaults to setting your attribute as unordered.
## Successful strategies
Setting searchable attributes is more an art than a science and highly depends on your use case.
There's no magic formula that works for everyone.
However, the following tips will get you started.
### Keep searchable attributes to a minimum
As a rule of thumb, **the more searchable attributes, the noisier the search**.
It may be tempting to add searchable attributes to maximize matches.
Yet, this reduces search relevance by creating *noise* (littering good results with not-so-good ones).
Instead, be conservative with your searchable attributes and **focus on what your users tend to search for**.
Some attributes, like image URLs or review scores, shouldn't be searchable because they don't have textual value.
Some other attributes, like the plot summary of a movie, might look interesting but could be unnecessary.
How often do you find a movie by searching for the synopsis?
Probably far less often than with the title or lead actors.
The same advice goes for the length of the attribute values.
Suppose you have a "movies" index where the records have an `actors` attribute.
If this attribute has the entire cast, it's less efficient than if it only had the leading roles.
Most users would probably look at leading roles when searching for an actor.
### Choose the appropriate attribute order
Knowing how to order your searchable attributes can be challenging when you have several.
One method is to **compare them in pairs**, each attribute with the previous one, and move them around accordingly.
The process is like [insertion sorting](https://wikipedia.org/wiki/Insertion_sort).
For example, suppose you have the following attributes in the records of a "movies" index:
* `director`
* `cast`
* `title`
* `genres`
* `plot_summary`
You would:
1. Compare the first two: `director` and `cast`. If you think `cast` should come before `director` (for example, you want to see movies with Clint Eastwood before movies directed by Clint Eastwood), move `cast` to the first place.
2. Compare `director` and `title` and decide that the movie's name is more important than the director's. That means you'd move `title` to the second place and compare it with the previous one (`cast`).
3. Prioritize a match on an actor's name or a movie title. If all records in your index represent movies, it may make sense to prioritize `title`, so you'd move it first. Then, because you don't have any more attributes to compare `title` to, you'd move on to `genres`.
The benefit is that **you're making a thorough, granular comparison of each attribute with the others** and considering your use case for every pair.
This method is much more reliable than doing a global, intuitive sort by what seems more important.
### Where to put filters?
Sometimes it's better to prioritize filters over all other attributes.
This might not seem intuitive at first, as you may think users tend to search by attributes like `name` or `title`.
Suppose you have a "movies" index where the records have a `genre` attribute with values like "crime" and "action".
Films fall into these categories, but there are also films whose titles and descriptions use these exact words,
for example, "Crime Doesn't Pay", "Last Action Hero".
In that case, you can decide that when a user searches for words that are more like genres,
it's best to search filter (A filter is a condition that limits which records Algolia returns. Filters often use one or more facet-value pairs, such as brand:Apple AND color:red. You can also filter by numeric values, dates, tags, booleans, or geographic constraints.) attributes before the title.
This is guaranteed to return all crime movies whenever someone types in `crime`, regardless of the title.
Making this decision when setting up searchable attributes can affect your results.
### Change your searchable attributes strategy
Be careful before changing your searchable attributes strategy because this affects your entire index.
Don't make decisions based on a single search query (The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.).
Instead, experiment with several queries close to what your users search for (use [Algolia analytics](/doc/guides/search-analytics/overview) to explore their search habits).
## See also
* [Fuzzy search and fuzzy matching (blog)](https://www.algolia.com/blog/engineering/discover-what-fuzzy-search-is-with-fuzzy-matching/)
# Faceting
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/faceting
Algolia uses facets for categorizing and grouping your data. Users can use facets to refine their search.
Facets let you create **categories on a select group of attributes so that users can refine their search**.
For example, on an index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) of books, helpful facets might be author and genre.
Algolia also calculates results for each facet.
It lets you display facets and facet counts so that users can filter results.
## What's a facet?
Think of facets as a set of filterable categories that allow users to refine results by multiple categories simultaneously.
Algolia derives these facets from attributes.
* For a book index, facets might be `author` or `genre`.
* For a luxury shop, facets might be `brand` or `designer`.
Algolia's faceting features let you:
* List all possible values for the selected attributes and return contextual values and counts with each search result.
* Compute a facet count or the number of records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) matching each attribute's value
* Enable search within an attribute's values ([search for facet values](#search-for-facet-values)).
## Difference between filters and facets
Filters and facets often get mixed up because there's much overlap, but [understanding the difference is essential](https://www.algolia.com/blog/ux/filters-vs-facets-in-site-search/).
Both help to restrict a search to a subset of results.
* **Filters provide basic options to help narrow down search results.** Sometimes users don't see the filter restrictions, for example, when automatically filtering the records they're allowed to see based on their user ID.
* **Facets help users refine their search using several categories simultaneously.** Faceting is still filtering but displays facets to allow users to choose from a set of useful values, features, and counts.
## How to configure faceting
You can configure faceting from the dashboard or the API, but the overall process is the same:
1. [Declare attributes for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting)
2. Retrieve facets from the API (see the following section).
## Retrieve facets
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
To retrieve facets and their respective counts as part of the JSON response,
specify a list of facets in the [`facets`](/doc/api-reference/api-parameters/facets) parameter at query time.
For example, retrieve your books' facets by specifying the following list:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
Facets = new List { "author", "genre" },
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
facets: [
"author",
"genre",
],
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFacets(
[]string{"author", "genre"}))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFacets(Arrays.asList("author", "genre")),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { facets: ['author', 'genre'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(facets = listOf("author", "genre")),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['facets' => [
'author',
'genre',
],
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"facets": [
"author",
"genre",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(facets: ["author", "genre"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
facets = Some(Seq("author", "genre"))
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(facets: [
"author",
"genre",
]))
)
```
To extract all facet information, use the wildcard character (`*`).
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { Facets = new List { "*" } })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
facets: [
"*",
],
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFacets(
[]string{"*"}))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFacets(Arrays.asList("*")),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({ indexName: 'indexName', searchParams: { facets: ['*'] } });
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(facets = listOf("*")),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['facets' => [
'*',
],
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"facets": [
"*",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index("INDEX_NAME", Algolia::Search::SearchParamsObject.new(facets: ["*"]))
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
facets = Some(Seq("*"))
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(facets: ["*"]))
)
```
## Faceting types and features
### Conjunctive and disjunctive faceting
Filtering based on facet values is helpful for users since it allows them to find specific,
targeted results in a way that isn't possible with one-size-fits-all filters.
You can combine facet filters with `AND` (conjunctive) and `OR` (disjunctive) operators.
When using these operators, the engine handles the facet counts differently to keep a consistent user experience.
This doesn't happen at the API level but in the [InstantSearch libraries](/doc/guides/building-search-ui/what-is-instantsearch/js).
### Hierarchical facets
You can build a hierarchy in your facet values to enable multi-level navigation and filtering.
This pattern makes deeply nested categories more discoverable,
as your users can browse up and down in the levels to refine their search.
For example, consider a book available in **Books > Science Fiction > Time Travel**:
```json JSON icon=braces theme={"system"}
{
"categories": {
"lvl0": "Books",
"lvl1": "Books > Science Fiction",
"lvl2": "Books > Science Fiction > Time Travel"
}
}
```
Or a book available in both **Books > Science Fiction > Time Travel** and **Books > Literature and Fiction > Modernism**:
```json JSON icon=braces theme={"system"}
{
"categories": {
"lvl0": "Books",
"lvl1": ["Books > Science Fiction", "Books > Literature and Fiction"],
"lvl2": [
"Books > Science Fiction > Time Travel",
"Books > Literature and Fiction > Modernism "
]
}
}
```
The [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js) widget from the [InstantSearch libraries](/doc/guides/building-search-ui/what-is-instantsearch/js) helps you to build hierarchical faceting.
> [!NOTE]
>
> Disjunctive facets with hierarchical facets aren't possible.
> Hierarchical facets with many nested attributes have many different facet names.
> This adds a lot of metadata that needs to be processed.
> This can lead to poor search performance, even if you don't reference these facets directly.
### Contextual facet values and counts
Enabling faceting on attributes computes facet counts for each facet value, and the engine updates and returns the list of values and counts with each search result.
It's helpful to offer users a relevant and contextual set of filters and information.
### How the engine approximates facet counts
When dealing with massive indices, the engine might need to approximate facet counts to guarantee optimal performance.
To see if the facet counts are exact, check the `exhaustiveFacetsCount` property in the JSON response.
### Increase the default limit
By default, the engine lets you retrieve 100 values per facet.
If you need to increase this limit, use the [`maxValuesPerFacet`](/doc/api-reference/api-parameters/maxValuesPerFacet) parameter.
The maximum is 1,000 facet values at a time.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings { MaxValuesPerFacet = 1000 }
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
maxValuesPerFacet: 1000,
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetMaxValuesPerFacet(1000)))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings("INDEX_NAME", new IndexSettings().setMaxValuesPerFacet(1000));
```
```js JavaScript theme={"system"}
const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { maxValuesPerFacet: 1000 } });
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(maxValuesPerFacet = 1000),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['maxValuesPerFacet' => 1000,
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"maxValuesPerFacet": 1000,
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings("INDEX_NAME", Algolia::Search::IndexSettings.new(max_values_per_facet: 1000))
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
maxValuesPerFacet = Some(1000)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(maxValuesPerFacet: 1000)
)
```
### Faceting on object ID
You can't facet on the `objectID` attribute, and the engine ignores it if you declare it in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting).
Faceting on a unique identifier makes little sense, though, because every facet count would be 1.
> [!NOTE]
>
> The engine treats the `objectID` attribute as a filter-only facet.
> You can filter on that attribute (even without declaring it in `attributesForFaceting` but not get any facet counts.
### Attributes are case-sensitive
The attribute names you declare in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) are case-sensitive: **they must exactly match the attribute names in your records**.
The [`facetFilters`](/doc/api-reference/api-parameters/facetFilters) and [`filters`](/doc/api-reference/api-parameters/filters) parameters are only case-insensitive for values.
For example, if your records include an attribute named `color`, with values such as "blue", "red", and "green", you must declare `color` (not `Color`) within your [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting).
However, you can facet and filter using `color:BLUE`, `color:Blue`, or `color:blue`.
As an exception, **[`facetFilters`](/doc/api-reference/api-parameters/facetFilters) and [`filters`](/doc/api-reference/api-parameters/filters) are case-sensitive when filtering on `objectID`**.
For example, `objectID:abc` only returns the record with that specific `objectID`, not one with `objectID:ABC`.
## Search for facet values
Sometimes you may have thousands of different values for a given facet attribute,
and it would be impossible to display them all in your user interface (see the [facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/js) guide for more information).
Instead, use [`search_for_facet_values`](/doc/libraries/sdk/v1/methods/search-for-facet-values) to let users search within a specific faceted attribute (for example, `brands`, `authors`, or `categories`) without needing to create a separate index.
It means you can still display the first, most common values for each facet but also let users search for more.
By default, returned facet values are sorted by count.
> [!NOTE]
>
> Facet queries only match [prefixes](/doc/guides/managing-results/optimize-search-results/override-search-engine-defaults/in-depth/prefix-searching), [typos](/doc/guides/managing-results/optimize-search-results/typo-tolerance), and [exact](/doc/guides/managing-results/optimize-search-results/override-search-engine-defaults/in-depth/adjust-exact-settings).
### Search for facet values
In this example, Algolia returns all the authors matching the query "stephen".
```cs C# theme={"system"}
var response = await client.SearchForFacetValuesAsync(
"INDEX_NAME",
"author",
new SearchForFacetValuesRequest { FacetQuery = "stephen" }
);
```
```dart Dart theme={"system"}
final response = await client.searchForFacetValues(
indexName: "INDEX_NAME",
facetName: "author",
searchForFacetValuesRequest: SearchForFacetValuesRequest(
facetQuery: "stephen",
),
);
```
```go Go theme={"system"}
response, err := client.SearchForFacetValues(client.NewApiSearchForFacetValuesRequest(
"INDEX_NAME", "author").WithSearchForFacetValuesRequest(
search.NewEmptySearchForFacetValuesRequest().SetFacetQuery("stephen")))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchForFacetValuesResponse response = client.searchForFacetValues(
"INDEX_NAME",
"author",
new SearchForFacetValuesRequest().setFacetQuery("stephen")
);
```
```js JavaScript theme={"system"}
const response = await client.searchForFacetValues({
indexName: 'indexName',
facetName: 'author',
searchForFacetValuesRequest: { facetQuery: 'stephen' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchForFacetValues(
indexName = "INDEX_NAME",
facetName = "author",
searchForFacetValuesRequest = SearchForFacetValuesRequest(facetQuery = "stephen"),
)
```
```php PHP theme={"system"}
$response = $client->searchForFacetValues(
'INDEX_NAME',
'author',
['facetQuery' => 'stephen',
],
);
```
```python Python theme={"system"}
response = client.search_for_facet_values(
index_name="INDEX_NAME",
facet_name="author",
search_for_facet_values_request={
"facetQuery": "stephen",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_for_facet_values(
"INDEX_NAME",
"author",
Algolia::Search::SearchForFacetValuesRequest.new(facet_query: "stephen")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchForFacetValues(
indexName = "INDEX_NAME",
facetName = "author",
searchForFacetValuesRequest = Some(
SearchForFacetValuesRequest(
facetQuery = Some("stephen")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.searchForFacetValues(
indexName: "INDEX_NAME",
facetName: "author",
searchForFacetValuesRequest: SearchForFacetValuesRequest(facetQuery: "stephen")
)
```
### Increase the default limit
By default, you retrieve ten facet values at a time.
You can raise this limit up to 100 by updating the [`maxFacetHits`](/doc/api-reference/api-parameters/maxFacetHits) parameter.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings { MaxFacetHits = 100 }
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
maxFacetHits: 100,
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetMaxFacetHits(100)))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings("INDEX_NAME", new IndexSettings().setMaxFacetHits(100));
```
```js JavaScript theme={"system"}
const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { maxFacetHits: 100 } });
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(maxFacetHits = 100),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['maxFacetHits' => 100,
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"maxFacetHits": 100,
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings("INDEX_NAME", Algolia::Search::IndexSettings.new(max_facet_hits: 100))
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
maxFacetHits = Some(100)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(maxFacetHits: 100)
)
```
# How to declare attributes for faceting with the API
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting
How to declare attributes for faceting with the API.
Algolia lets you create categories based on specific attributes so users can filter search results by those categories.
For example, if you have an index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) of books, you could categorize them by author and genre.
This allows users to filter search results by their favorite author or discover new genres.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
To enable this categorization,
declare the attributes `genre` and `author` as [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting):
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
AttributesForFaceting = new List { "genre", "author" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
attributesForFaceting: [
"genre",
"author",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetAttributesForFaceting(
[]string{"genre", "author"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setAttributesForFaceting(Arrays.asList("genre", "author"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'INDEX_NAME',
indexSettings: { attributesForFaceting: ['genre', 'author'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(attributesForFaceting = listOf("genre", "author")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['attributesForFaceting' => [
'genre',
'author',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"attributesForFaceting": [
"genre",
"author",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(attributes_for_faceting: ["genre", "author"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
attributesForFaceting = Some(Seq("genre", "author"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(attributesForFaceting: ["genre", "author"])
)
```
Sometimes, you may have many facet values.
For instance, if you have a book index, you may also have a lot of different authors.
[The engine can't return more than 1,000 values per facet](/doc/api-reference/api-parameters/maxValuesPerFacet),
so if you have more than that, you may want to let your users search for them.
Do this by using the `searchable` modifier.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
AttributesForFaceting = new List { "genre", "searchable(author)" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
attributesForFaceting: [
"genre",
"searchable(author)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetAttributesForFaceting(
[]string{"genre", "searchable(author)"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setAttributesForFaceting(Arrays.asList("genre", "searchable(author)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'INDEX_NAME',
indexSettings: { attributesForFaceting: ['genre', 'searchable(author)'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(attributesForFaceting = listOf("genre", "searchable(author)")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['attributesForFaceting' => [
'genre',
'searchable(author)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"attributesForFaceting": [
"genre",
"searchable(author)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(attributes_for_faceting: ["genre", "searchable(author)"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
attributesForFaceting = Some(Seq("genre", "searchable(author)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(attributesForFaceting: ["genre", "searchable(author)"])
)
```
If you only need the filtering feature, use `filterOnly` to reduce the index size and improve search speed.
For example, you could automatically filter what genre the users can search based on the section of your website they're on (without displaying genre as a clickable filter).
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
AttributesForFaceting = new List { "filterOnly(genre)", "author" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
attributesForFaceting: [
"filterOnly(genre)",
"author",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetAttributesForFaceting(
[]string{"filterOnly(genre)", "author"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setAttributesForFaceting(Arrays.asList("filterOnly(genre)", "author"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'INDEX_NAME',
indexSettings: { attributesForFaceting: ['filterOnly(genre)', 'author'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(attributesForFaceting = listOf("filterOnly(genre)", "author")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['attributesForFaceting' => [
'filterOnly(genre)',
'author',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"attributesForFaceting": [
"filterOnly(genre)",
"author",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(attributes_for_faceting: ["filterOnly(genre)", "author"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
attributesForFaceting = Some(Seq("filterOnly(genre)", "author"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(attributesForFaceting: ["filterOnly(genre)", "author"])
)
```
**Don't include colons (`:`) in attribute names that you want to use for faceting because the [`filters`](/doc/api-reference/api-parameters/filters) syntax relies on that character as a delimiter.**
## See also
* [How to declare attributes for faceting in the dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard)
* [Auto-selected facets](/doc/guides/solutions/ecommerce/filtering-and-navigation/tutorials/auto-selected-facets)
* [Guided search](/doc/guides/solutions/ecommerce/filtering-and-navigation/tutorials/guided-search)
* [Visual facets](/doc/guides/solutions/ecommerce/filtering-and-navigation/tutorials/visual-facets)
# How to declare attributes for faceting in the dashboard
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard
How to declare attributes for faceting in the Algolia dashboard.
Algolia lets you create categories based on specific attributes so users can filter search results by those categories
For example, if you have an index of books, you could categorize them by author and genre.
This allows users to filter search results by their favorite author or discover new genres.
To enable this categorization, declare your attributes for faceting.
To do this:
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, click **Facets** (in the Filtering and faceting section).
5. In **Attributes for faceting**, click **Add an Attribute**
6. Set the attribute as:
* **searchable** if you want to use the attribute for both searching and filtering. For example, if a product has a `color` attribute, users can search for products of a specific color and also filter search results by color.
* **filter-only** if you want to use the attribute only for filtering.
* **not searchable** if you want the attribute to be used only as a facet.
For more information, see [Difference between facets and filters](/doc/guides/managing-results/refine-results/faceting#difference-between-filters-and-facets)

7. Click **Review and Save Settings**.
## See also
* [How to declare attributes for faceting with the API](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting)
* [Faceting](/doc/guides/managing-results/refine-results/faceting)
* [Auto-selected facets](/doc/guides/solutions/ecommerce/filtering-and-navigation/tutorials/auto-selected-facets)
* [Guided search](/doc/guides/solutions/ecommerce/filtering-and-navigation/tutorials/guided-search)
* [Visual facets](/doc/guides/solutions/ecommerce/filtering-and-navigation/tutorials/visual-facets)
# Filtering
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/filtering
How does filtering work with Algolia? Learn how Algolia enables filtered searches.
Filtering allows your users to drill down and create a smaller, more manageable set of results based on meaningful categories.
It gives them direct control over the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) they query.
Categories can be brand, color, or genre of film.
They can be dress or shoe sizes or price ranges like "expensive" or "cheap".
The most common use for filtering is to provide a UI that enables users to refine their search using one or more clickable items.
## How to filter your data
1. [Define filterable attributes (at indexing time)](#define-filterable-attributes)
2. [Filter by attributes (at query time)](#filter-by-attributes)
Most [records have one or more attributes](/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records), such as color, brand, genre, or category, that you can choose as a filter.
A filter attribute can be a list of single words, more complex phrases, numbers, boolean values, dates, or arrays.
Arrays of objects need more care, because filters don't preserve which values belong to the same element: see [Filters and arrays of objects](/doc/guides/managing-results/refine-results/filtering/in-depth/arrays-of-objects).
Users may use any filter combination to group records.
Filtering creates more targeted and therefore relevant subsets of results, and increases the chance that only the best results appear at the top.
### Define filterable attributes
First, define filter attributes as attributes for faceting.
You can do this on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or
with an [API client](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting).
If you only want to use an attribute for filtering, use the [`filterOnly`](/doc/api-reference/api-parameters/attributesForFaceting#param-filter-only) modifier.
This reduces the index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) size and improves search speed.
For example, if you plan to filter on an attribute, such as `color`, but don't need to calculate or display the number of items with `color:green`, `color:blue`, or similar, use `filterOnly`.
> [!NOTE]
>
> If you use [`filters`](/doc/api-reference/api-parameters/filters) or [`numericFilters`](/doc/api-reference/api-parameters/numericFilters) to filter on **numeric** attributes, you don't need to set these attributes as `attributesForFaceting`.
### Filter by attributes
* **[Filter by string](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-string)**
* **[Filter by numeric value](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-numeric-value)**
* **[Filter by date](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-date)**
* **[Filter by array](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-array)**
* **[Filter by tags](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-tags)**
* **[Filter by boolean](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-boolean)**
* **[Filter by null or missing attributes](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-null-or-missing-attributes)**
* **[Filter by `objectID`](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-objectid)**
## How to apply filtering to the search UI
After defining [filterable attributes](#define-filterable-attributes) and ensuring users can filter every record [with the appropriate attributes](#filter-by-attributes), ensure your frontend represents all these possibilities by using InstantSearch's [refinements](/doc/api-reference/widgets/refinement-list/js).
You'll typically want to display all filtering categories.
Thus, if you have created two filters, `brand` and `price_range`, you'll always want to display a list of brands and a price range slider for every query in your UI.
For example, a search for "phones" returns brands like Apple and Samsung, while a search for "speakers" returns Panasonic and Bose.
While the values may change, the `brand` filter is always present.
To learn more, see:
* [Facet drop down](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-dropdown/js)
* [Guided search](/doc/guides/solutions/ecommerce/filtering-and-navigation/tutorials/guided-search)
* [UI design: filter and sort panel](/doc/guides/building-search-ui/resources/ui-kit/js#filter-and-sort-panel)
## Other uses for filtering
Although the most common use for filtering is to enable users to refine their search and limit the records they search within,
there are other ways to use filters. For example:
* [Security-filtering](/doc/guides/security/api-keys/how-to/user-restricted-access-to-data): using filters as a security measure, where each user has access to only their private data.
* [Geo-filtering](/doc/guides/managing-results/refine-results/geolocation#geographical-filtering-and-sorting): filtering on user's geolocation (if your index has geographical data).
* [Filter scoring](/doc/guides/managing-results/refine-results/filtering/in-depth/filter-scoring): using filters to help promote some records over others.
* [Query-based filtering](/doc/guides/managing-results/rules/detecting-intent): setting rules that parse your users' queries and use specific query terms as filters instead of search terms.
For example, if a user types in "red", "red" can be used as a filter instead of a search term, thereby returning all red records.
# Filter by attributes
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes
How to use Algolia to filter content
Filters can work with strings, numbers, boolean values, dates, tags, or lists.
> [!NOTE]
>
> [Add filters to your UI](/doc/guides/managing-results/refine-results/filtering#how-to-apply-filtering-to-the-search-ui) with InstantSearch widgets or your own custom code.
## Filter by string
[Example dataset](#example-dataset)
Algolia lets you filter search results by string attributes.
For example, users of an online bookstore might want to search for books from different publishers.
With [filters](/doc/guides/managing-results/refine-results/filtering), they can refine results by:
* Showing books published by Penguin
* Showing books published by Bloomsbury or Scribe
* Showing all books except those published by Penguin
### Apply a string filter
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
Set `author` as an [attribute for faceting](/doc/guides/managing-results/refine-results/filtering#define-filterable-attributes), then apply your string filters with the [`filters`](/doc/api-reference/api-parameters/filters) parameter in your [`search`](/doc/libraries/sdk/v1/methods/search) code:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
FacetFilters = new FacetFilters(
new List
{
new FacetFilters("publisher:Penguin"),
new FacetFilters(
new List
{
new FacetFilters("author:Stephen King"),
new FacetFilters("genre:Horror"),
}
),
}
),
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
facetFilters: [
"publisher:Penguin",
[
"author:Stephen King",
"genre:Horror",
],
],
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFacetFilters(search.ArrayOfFacetFiltersAsFacetFilters(
[]search.FacetFilters{*search.StringAsFacetFilters("publisher:Penguin"), *search.ArrayOfFacetFiltersAsFacetFilters(
[]search.FacetFilters{*search.StringAsFacetFilters("author:Stephen King"), *search.StringAsFacetFilters("genre:Horror")})})))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFacetFilters(
FacetFilters.of(
Arrays.asList(
FacetFilters.of("publisher:Penguin"),
FacetFilters.of(Arrays.asList(FacetFilters.of("author:Stephen King"), FacetFilters.of("genre:Horror")))
)
)
),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { facetFilters: ['publisher:Penguin', ['author:Stephen King', 'genre:Horror']] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
facetFilters =
FacetFilters.of(
listOf(
FacetFilters.of("publisher:Penguin"),
FacetFilters.of(
listOf(FacetFilters.of("author:Stephen King"), FacetFilters.of("genre:Horror"))
),
)
)
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['facetFilters' => [
'publisher:Penguin',
[
'author:Stephen King',
'genre:Horror',
],
],
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"facetFilters": [
"publisher:Penguin",
[
"author:Stephen King",
"genre:Horror",
],
],
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
facet_filters: ["publisher:Penguin", ["author:Stephen King", "genre:Horror"]]
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
facetFilters = Some(
FacetFilters(
Seq(
FacetFilters("publisher:Penguin"),
FacetFilters(Seq(FacetFilters("author:Stephen King"), FacetFilters("genre:Horror")))
)
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(facetFilters: SearchFacetFilters
.arrayOfSearchFacetFilters([
SearchFacetFilters.string("publisher:Penguin"),
SearchFacetFilters.arrayOfSearchFacetFilters([
SearchFacetFilters.string("author:Stephen King"),
SearchFacetFilters.string("genre:Horror"),
]),
])))
)
```
## Filter by numeric value
Algolia lets you numerically filter results with a comparison or a range.
This only works with numeric values.
For example, you could allow users to only show books that cost less than a certain price or those in a specific price range.
**The `price` attribute is a numeric attribute, not a string attribute**.
### Apply a numeric filter
[Example dataset](#example-dataset)
Using the example dataset, you've decided to let users define a price limit. For example, they may only want books that cost less than \$10:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { Filters = "price < 10" })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
filters: "price < 10",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFilters("price < 10"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex("INDEX_NAME", new SearchParamsObject().setFilters("price < 10"), Hit.class);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({ indexName: 'indexName', searchParams: { filters: 'price < 10' } });
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(filters = "price < 10"),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['filters' => 'price < 10',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"filters": "price < 10",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(filters: "price < 10")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
filters = Some("price < 10")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(filters: "price < 10"))
)
```
Algolia filters use an SQL-like syntax, which lets you use comparison operator attributes: `<`, `<=`, `=`, `!=`, `>=`, and `>`.
To allow users to retrieve books between a range, say $10 and $20, set a range filter:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { Query = "books", Filters = "price:10 TO 20" })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
query: "books",
filters: "price:10 TO 20",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetQuery("books").SetFilters("price:10 TO 20"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setQuery("books").setFilters("price:10 TO 20"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { query: 'books', filters: 'price:10 TO 20' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(query = "books", filters = "price:10 TO 20"),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['query' => 'books',
'filters' => 'price:10 TO 20',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"query": "books",
"filters": "price:10 TO 20",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(query: "books", filters: "price:10 TO 20")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
query = Some("books"),
filters = Some("price:10 TO 20")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(
query: "books",
filters: "price:10 TO 20"
))
)
```
## Filter by date
[Example dataset](#example-dataset)
As well as [sorting](/doc/guides/managing-results/refine-results/sorting/how-to/sort-an-index-by-date), you can use dates to **filter search results within a specific period**.
For example, you can allow users to filter on recently published books or only see books published within a certain period.
### Format your dates suitably
Algolia can filter on date attributes,
but they must be formatted as [Unix timestamps](https://wikipedia.org/wiki/Unix_time).
For more information, see [Formatting dates](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#dates)
Instead of changing the date attribute, you could add an additional timestamp attribute.
In the example dataset,
the `publication_date` attribute has the formatted date for display, while the `date_timestamp` attribute can be used for filtering.
### Apply a date filter
With dates represented as Unix timestamp, use the [`filters`](/doc/api-reference/api-parameters/filters) parameter to only retrieve some results. For example:
* [Recently published books](#recently-published-books)
* [Books for a particular year](#books-for-a-particular-year)
* [Closest dates](#closest-dates)
#### Recently published books
If you want users to filter for recent books, decide what recent means to you.
For example, a recent book might be a book published less than a year ago.
This means you must set a filter that excludes records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) with `date_timestamp` greater than now minus one year.
```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 SearchRecentlyPublishedBooks
{
async Task Main(string[] args)
{
var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY"));
var date = DateTime.UtcNow.AddYears(-1);
var sTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var unixTime = (long)(date - sTime).TotalSeconds;
var searchParams = new SearchParams(
new SearchParamsObject
{
Query = "SEARCH_QUERY",
Filters = $"date_timestamp > {unixTime}",
}
);
await client.SearchSingleIndexAsync("INDEX_NAME", searchParams);
}
}
```
```dart Dart theme={"system"}
import 'package:algolia_client_search/algolia_client_search.dart';
void searchRecentlyPublishedBooks() async {
final client =
SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY');
final dateTimestamp =
DateTime.now().subtract(Duration(days: 365)).millisecondsSinceEpoch;
final searchParams = SearchParamsObject(
query: "SEARCH_QUERY", filters: "date_timestamp > $dateTimestamp");
await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: searchParams,
);
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"time"
"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
)
func searchRecentlyPublishedBooks() {
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)
}
dateTimestamp := time.Now().AddDate(-1, 0, 0).Unix()
searchParams := search.SearchParamsObjectAsSearchParams(
search.NewSearchParamsObject().
SetQuery("SEARCH_QUERY").
SetFilters(fmt.Sprintf("date_timestamp > %d", dateTimestamp)),
)
_, err = client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(searchParams))
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.time.ZoneOffset;
import java.time.ZonedDateTime;
public class searchRecentlyPublishedBooks {
public static void main(String[] args) throws Exception {
try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) {
long dateTimestamp = ZonedDateTime.now(ZoneOffset.UTC).plusYears(-1).toEpochSecond();
SearchParams searchParams = new SearchParamsObject().setQuery("SEARCH_QUERY").setFilters("date_timestamp > " + dateTimestamp);
client.searchSingleIndex("INDEX_NAME", searchParams, Hit.class);
} catch (Exception e) {
System.out.println("An error occurred: " + e.getMessage());
}
}
}
```
```js JavaScript theme={"system"}
import { algoliasearch } from 'algoliasearch';
import type { SearchParams } from 'algoliasearch';
const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');
const d = new Date();
const searchParams: SearchParams = {
query: 'SEARCH_QUERY',
filters: `date_timestamp > ${Math.floor(d.setDate(d.getDate() - 365) / 1000)}`,
};
await client.searchSingleIndex({ indexName: 'indexName', searchParams: searchParams });
```
```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.time.Instant
import java.time.temporal.ChronoUnit
suspend fun searchRecentlyPublishedBooks() {
val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")
val dateTimestamp = Instant.now().minus(365, ChronoUnit.DAYS).epochSecond
val searchParams =
SearchParamsObject(query = "SEARCH_QUERY", filters = "date_timestamp > $dateTimestamp")
client.searchSingleIndex(indexName = "INDEX_NAME", searchParams = searchParams)
}
```
```php PHP theme={"system"}
setQuery('SEARCH_QUERY')
->setFilters('date_timestamp > '.$dateTimestamp)
;
$client->searchSingleIndex(
'INDEX_NAME',
$searchParams,
);
```
```python Python theme={"system"}
import datetime
import time
from algoliasearch.search.client import SearchClientSync
_client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
date = datetime.datetime.now() - datetime.timedelta(days=365)
date_timestamp = int(time.mktime(date.timetuple()))
search_params = {
"query": "SEARCH_QUERY",
"filters": f"date_timestamp > {date_timestamp}",
}
_client.search_single_index(
index_name="INDEX_NAME",
search_params=search_params,
)
```
```ruby Ruby theme={"system"}
import(time)
require "algolia"
client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
date_timestamp = Time.now.to_i - 60 * 60 * 24 * 365
search_params = Algolia::Search::SearchParamsObject.new(
query: "SEARCH_QUERY",
filters: "date_timestamp > #{date_timestamp}"
)
client.search_single_index("INDEX_NAME", search_params)
```
```scala Scala theme={"system"}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import java.time.{Instant, LocalDateTime, ZoneOffset}
import algoliasearch.api.SearchClient
import algoliasearch.config.*
import algoliasearch.extension.SearchClientExtensions
import algoliasearch.search.SearchParamsObject
def searchRecentlyPublishedBooks(): Future[Unit] = {
val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")
val dateTimestamp = LocalDateTime.now().minusYears(1).toInstant(ZoneOffset.UTC).getEpochSecond
val searchParams = SearchParamsObject(
query = Some("SEARCH_QUERY"),
filters = Some(s"date_timestamp > $dateTimestamp")
)
client
.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(searchParams)
)
.map { response =>
println(response)
}
.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
func searchRecentlyPublishedBooks() async throws {
let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY")
let dateTimestamp = Int(Date().timeIntervalSince1970) - 365 * 24 * 60 * 60
let searchParams = SearchSearchParams.searchSearchParamsObject(
SearchSearchParamsObject(query: "SEARCH_QUERY", filters: "date_timestamp > \(dateTimestamp)")
)
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: searchParams
)
print(response)
}
```
Algolia filters use an SQL-like syntax, which lets you use comparison operators: `<`, `<=`, `=`, `!=`, `>=`, and `>`.
#### Books for a particular year
If you want to only retrieve books from 2018, set a range filter and provide it with Unix timestamps for the first and last day of the year. If you want to exclude a particular year or search for all books published outside a specific time you can combine time periods with the `NOT` boolean operator:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject { Filters = "NOT date_timestamp:1514764800 TO 1546300799" }
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
filters: "NOT date_timestamp:1514764800 TO 1546300799",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFilters("NOT date_timestamp:1514764800 TO 1546300799"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFilters("NOT date_timestamp:1514764800 TO 1546300799"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { filters: 'NOT date_timestamp:1514764800 TO 1546300799' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(filters = "NOT date_timestamp:1514764800 TO 1546300799"),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['filters' => 'NOT date_timestamp:1514764800 TO 1546300799',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"filters": "NOT date_timestamp:1514764800 TO 1546300799",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(filters: "NOT date_timestamp:1514764800 TO 1546300799")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
filters = Some("NOT date_timestamp:1514764800 TO 1546300799")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(
SearchSearchParamsObject(filters: "NOT date_timestamp:1514764800 TO 1546300799")
)
)
```
#### Closest dates
If your index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) contains books that will be published in the future, it can be helpful to [sort results from sooner to later](/doc/guides/managing-results/refine-results/sorting/how-to/sort-an-index-by-date), so that users will see the next published books first.
**1. Sort records**
Sort records by ascending date so that books due to be published later sit lower in the search results:
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
Ranking = new List
{
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
},
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
ranking: [
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetRanking(
[]string{"asc(date_timestamp)", "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("asc(date_timestamp)", "typo", "geo", "words", "filters", "proximity", "attribute", "exact", "custom")
)
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: {
ranking: ['asc(date_timestamp)', 'typo', 'geo', 'words', 'filters', 'proximity', 'attribute', 'exact', 'custom'],
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings =
IndexSettings(
ranking =
listOf(
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
)
),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['ranking' => [
'asc(date_timestamp)',
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
'custom',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"ranking": [
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(
ranking: ["asc(date_timestamp)", "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("asc(date_timestamp)", "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: [
"asc(date_timestamp)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
])
)
```
**2. Remove older titles**
Filter out every record with past dates:
```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 SearchFuturePublishedBooks
{
async Task Main(string[] args)
{
var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY"));
var dateTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var searchParams = new SearchParams(
new SearchParamsObject
{
Query = "SEARCH_QUERY",
Filters = $"date_timestamp > {dateTimestamp}",
}
);
await client.SearchSingleIndexAsync("INDEX_NAME", searchParams);
}
}
```
```dart Dart theme={"system"}
import 'package:algolia_client_search/algolia_client_search.dart';
void searchFuturePublishedBooks() async {
final client =
SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY');
final dateTimestamp = DateTime.now().millisecondsSinceEpoch;
final searchParams = SearchParamsObject(
query: "SEARCH_QUERY", filters: "date_timestamp > $dateTimestamp");
await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: searchParams,
);
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"time"
"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
)
func searchFuturePublishedBooks() {
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)
}
dateTimestamp := time.Now().UnixMilli()
searchParams := search.SearchParamsObjectAsSearchParams(
search.NewSearchParamsObject().
SetQuery("SEARCH_QUERY").
SetFilters(fmt.Sprintf("date_timestamp > %d", dateTimestamp)),
)
_, err = client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(searchParams))
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.*;
public class searchFuturePublishedBooks {
public static void main(String[] args) throws Exception {
try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) {
long dateTimestamp = System.currentTimeMillis();
SearchParams searchParams = new SearchParamsObject().setQuery("SEARCH_QUERY").setFilters("date_timestamp > " + dateTimestamp);
client.searchSingleIndex("INDEX_NAME", searchParams, Hit.class);
} catch (Exception e) {
System.out.println("An error occurred: " + e.getMessage());
}
}
}
```
```js JavaScript theme={"system"}
import { algoliasearch } from 'algoliasearch';
import type { SearchParams } from 'algoliasearch';
const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY');
const dateTimestamp = Date.now();
const searchParams: SearchParams = {
query: 'SEARCH_QUERY',
filters: `date_timestamp > ${dateTimestamp}`,
};
await client.searchSingleIndex({ indexName: 'indexName', searchParams: searchParams });
```
```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.time.Instant
suspend fun searchFuturePublishedBooks() {
val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")
val dateTimestamp = Instant.now().epochSecond
val searchParams =
SearchParamsObject(query = "SEARCH_QUERY", filters = "date_timestamp > $dateTimestamp")
client.searchSingleIndex(indexName = "INDEX_NAME", searchParams = searchParams)
}
```
```php PHP theme={"system"}
setQuery('SEARCH_QUERY')
->setFilters('date_timestamp > '.$dateTimestamp)
;
$client->searchSingleIndex(
'INDEX_NAME',
$searchParams,
);
```
```python Python theme={"system"}
import time
from algoliasearch.search.client import SearchClientSync
_client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
date_timestamp = time.time() # Get current timestamp
search_params = {
"query": "SEARCH_QUERY",
"filters": f"date_timestamp > {date_timestamp}",
}
_client.search_single_index(
index_name="INDEX_NAME",
search_params=search_params,
)
```
```ruby Ruby theme={"system"}
import(time)
require "algolia"
client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
# Get current timestamp
date_timestamp = Time.now.to_i
search_params = Algolia::Search::SearchParamsObject.new(
query: "SEARCH_QUERY",
filters: "date_timestamp > #{date_timestamp}"
)
client.search_single_index("INDEX_NAME", search_params)
```
```scala Scala theme={"system"}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import java.time.Instant
import algoliasearch.api.SearchClient
import algoliasearch.config.*
import algoliasearch.extension.SearchClientExtensions
import algoliasearch.search.SearchParamsObject
def searchFuturePublishedBooks(): Future[Unit] = {
val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")
val dateTimestamp = Instant.now.getEpochSecond
val searchParams = SearchParamsObject(
query = Some("SEARCH_QUERY"),
filters = Some(s"date_timestamp > $dateTimestamp")
)
client
.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(searchParams)
)
.map { response =>
println(response)
}
.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
func searchFuturePublishedBooks() async throws {
let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY")
let dateTimestamp = Int(Date().timeIntervalSince1970)
let searchParams = SearchSearchParams.searchSearchParamsObject(
SearchSearchParamsObject(query: "SEARCH_QUERY", filters: "date_timestamp > \(dateTimestamp)")
)
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: searchParams
)
print(response)
}
```
## Filter by array
[Example dataset](#example-dataset)
Some attributes, such as the genre of a book, are lists of values.
One book can belong to multiple genres.
Hence, the genre attribute should be an array.
If you have a chain of bookstores with an accompanying ecommerce website, you may want to let users filter by book genre and store.
To do this, the example dataset has two array attributes: `store` and `categories`.
Using this dataset, users could, for example, retrieve all books about politics in stock in The Corner Bookshop.
### Apply an array filter
Once you've set `categories` and `store` as [attributes for faceting](/doc/guides/managing-results/refine-results/filtering#define-filterable-attributes), apply string filters with the [`filters`](/doc/api-reference/api-parameters/filters) parameter in your [`search`](/doc/libraries/sdk/v1/methods/search) code:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
Query = "ben",
Filters = "categories:politics AND store:Gibert Joseph Saint-Michel",
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
query: "ben",
filters: "categories:politics AND store:Gibert Joseph Saint-Michel",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetQuery("ben").SetFilters("categories:politics AND store:Gibert Joseph Saint-Michel"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setQuery("ben").setFilters("categories:politics AND store:Gibert Joseph Saint-Michel"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { query: 'ben', filters: 'categories:politics AND store:Gibert Joseph Saint-Michel' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
query = "ben",
filters = "categories:politics AND store:Gibert Joseph Saint-Michel",
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['query' => 'ben',
'filters' => 'categories:politics AND store:Gibert Joseph Saint-Michel',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"query": "ben",
"filters": "categories:politics AND store:Gibert Joseph Saint-Michel",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
query: "ben",
filters: "categories:politics AND store:Gibert Joseph Saint-Michel"
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
query = Some("ben"),
filters = Some("categories:politics AND store:Gibert Joseph Saint-Michel")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(
query: "ben",
filters: "categories:politics AND store:Gibert Joseph Saint-Michel"
))
)
```
> [!NOTE]
>
> This works because categories and store are separate arrays, so each condition matches independently.
> If both values were properties of objects inside a single array, a condition on `categories` and a condition on `store` could each be satisfied by a different object in that array, not necessarily the same one.
> See [Filters and arrays of objects](/doc/guides/managing-results/refine-results/filtering/in-depth/arrays-of-objects).
## Filter by boolean
[Example dataset](#example-dataset)
Algolia lets you filter results by boolean attributes.
For example, you can use the `is_available` boolean attribute in the example dataset to exclude all records where `is_available` is `false`.
To do this, first [declare `is_available` as an attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting).
### Apply a boolean filter
If you want users to see only the available books, do the following:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { Filters = "is_available:true" })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
filters: "is_available:true",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFilters("is_available:true"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFilters("is_available:true"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { filters: 'is_available:true' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(filters = "is_available:true"),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['filters' => 'is_available:true',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"filters": "is_available:true",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(filters: "is_available:true")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
filters = Some("is_available:true")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(filters: "is_available:true"))
)
```
Algolia considers boolean values as integers: `false` as `0` and `true` as `1`.
This means you can apply [numeric filters](#filter-by-numeric-value) for boolean attributes.
With the example dataset, `is_available=0` is the same as `is_available:false`.
> [!NOTE]
>
> If creating numeric or boolean filters, you don't need to declare the attribute as `attributesForFaceting`.
## Filter by tags
[Example dataset](#example-dataset)
Sometimes, you may want to filter search results based on specific metadata, like a type or a category.
Using the example dataset, the accompanying online bookstore has fiction and non-fiction sections.
Depending on which section users are searching from, **you can provide a context-sensitive search experience**.
For example, a user goes to the website's non-fiction section, then starts searching for the biography of President Harry S. Truman.
If your search relevance is primarily based on popularity, by typing "harry", they might retrieve Harry Potter books first, which wouldn't make sense to them.
Instead, what you can do is **filter results based on what they most likely want to find**.
Algolia lets you add tags to your records with the reserved `_tags` attribute.
Using the example dataset, if users type "harry" with the search restricted to "non-fiction", it would retrieve "Where the Buck Stops" because it has "non-fiction" in the `_tags` attribute and contains the word "harry" in the record.
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { Query = "harry", Filters = "_tags:non-fiction" })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
query: "harry",
filters: "_tags:non-fiction",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetQuery("harry").SetFilters("_tags:non-fiction"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setQuery("harry").setFilters("_tags:non-fiction"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { query: 'harry', filters: '_tags:non-fiction' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(query = "harry", filters = "_tags:non-fiction"),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['query' => 'harry',
'filters' => '_tags:non-fiction',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"query": "harry",
"filters": "_tags:non-fiction",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(query: "harry", filters: "_tags:non-fiction")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
query = Some("harry"),
filters = Some("_tags:non-fiction")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(
query: "harry",
filters: "_tags:non-fiction"
))
)
```
If you don't specify an attribute name, the filter is assumed to apply to the `_tags` attribute.
For example, `fiction` translates into `_tags:fiction`.
You can also use [`tagFilters`](/doc/api-reference/api-parameters/tagFilters) to do the same thing.
### The difference between \_tags and custom attributes
It's important to consider the following when deciding whether to use the Algolia `_tags` attribute or a custom attribute for faceting:
* `_tags` is a [reserved attribute](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#reserved-attribute-names) which means it automatically works as a string filter without you having to set it as an attribute for faceting or use the `filterOnly` modifier.
* `_tags` isn't searchable by default.
* You can't use `_tags` for non-filtering purposes, such as displaying in search results or computing counts of records that match the filters.
* `_tags` can only contain strings.
In short, if you have several types of string filters or any non-string filters, creating a custom attribute is usually the way to go.
## Filter by null or missing attributes
Since Algolia **doesn't support filtering on `null` values or missing attributes**, what happens when your index contains an attribute that isn't present in all records?
For example, consider an online bookstore where people can buy and rate books from 0 to 5. Any record without the `rating` attribute is assumed to be unrated.
If you want users to search for both rated and unrated books in the same filter, you must modify your data.
To do this, you can take one of two approaches:
* [Use the `_tags` attribute](#create-a-tag)
* [Use a boolean attribute](#create-a-boolean-attribute).
### Create a tag
[Example dataset](#example-dataset)
At indexing time, you can add a tag value to indicate if a record is or isn't rated.
In the example dataset, some records have a `rating` attribute and value, others have a null `rating`, and some don't have a `rating` attribute.
> [!NOTE]
>
> A `null` or nonexistent attribute is different from zero, which represents a book with the lowest rating.
```jsonc JSON icon=braces theme={"system"}
[
{
// ...
"rating": 5,
"_tags": ["is_rated"]
},
{
// ...
"rating": null,
"_tags": ["is_not_rated"]
},
{
// ...
"_tags": ["is_not_rated"]
}
]
```
To search for records without the attribute or have a null value, you can now use [tags filtering](#filter-by-tags).
### Create a boolean attribute
At indexing time, you can add a boolean value to indicate if a record is or isn't rated:
```jsonc JSON icon=braces theme={"system"}
[
{
// ...
"rating": 5,
"is_rated": true
},
{
// ...
"rating": null,
"is_rated": false
},
{
// ...
"is_rated": false
}
]
```
To search for records without the attribute or have a null value, you can now use [boolean filtering](#filter-by-boolean).
## Filter by objectID
By default, Algolia sets a record's `objectID` attribute as a [filter-only facet](/doc/api-reference/api-parameters/attributesForFaceting#modifiers).
This means **you can use the [`filters`](/doc/api-reference/api-parameters/filters) parameter to filter on `objectID`**.
This is helpful when you want to include or exclude specific records in the current search.
For example, to exclude the record with `objectID` "1234", use the filter `NOT objectID:1234` in the [`filters`](/doc/api-reference/api-parameters/filters) parameter of your search.
## Example dataset
All the examples on this page use a bookstore index: `books`. The index has records that look like this:
```json JSON icon=braces theme={"system"}
[
{
"title": "Harry Potter and the Philosopher's Stone",
"author": "J. K. Rowling",
"publisher": "Bloomsbury",
"price": 7.94,
"publication_date": "1996-06-17",
"date_timestamp": 866505600,
"is_available": true,
"rating": 5,
"_tags": [
"fiction",
"is_rated"
],
"popularity": 1000,
"store": [
"The Corner Bookshop",
"Gibert Joseph Barbès",
"Gibert Joseph Paris 13 - Grande Bibliothèque"
],
"categories": [
"fantasy",
"science fiction",
"children's literature"
]
},
{
"title": "A Perfect Crime",
"author": "Peter Abrahams",
"publisher": "Penguin",
"price": 7.94,
"publication_date": "1998-09-16",
"date_timestamp": 905904000,
"is_available": true,
"rating": null,
"_tags": [
"fiction",
"is_not_rated"
],
"popularity": 800,
"store": [
"The Corner Bookshop",
"Gibert Joseph Barbès"
],
"categories": [
"thriller",
"crime fiction"
]
},
{
"title": "The World as It Is",
"author": "Ben Rhodes",
"publisher": "Penguin",
"price": 9.54,
"publication_date": "2018-06-05",
"date_timestamp": 1528156800,
"is_available": false,
"_tags": [
"non-fiction",
"is_not_rated"
],
"popularity": 900,
"store": [
"The Corner Bookshop"
],
"categories": [
"history",
"politics"
]
},
{
"title": "Yellowface",
"author": "R.F. Kuang",
"publisher": "The Borough Press",
"price": 9.19,
"publication_date": "2023-05-16",
"date_timestamp": 1684108800,
"is_available": true,
"_tags": [
"fiction",
"is_not_rated"
],
"popularity": 900,
"store": [
"The Corner Bookshop"
],
"categories": [
"dark humor",
"satire"
]
},
{
"title": "A Fighting Chance",
"author": "Elizabeth Warren",
"publisher": "Scribe",
"price": 12.97,
"publication_date": "2014-04-22",
"date_timestamp": 1398124800,
"is_available": false,
"rating": 5,
"_tags": [
"non-fiction",
"is_rated"
],
"popularity": 800,
"store": [
"The Corner Bookshop"
],
"categories": [
"history",
"politics"
]
},
{
"title": "Where the Buck Stops",
"author": "Harry S Truman",
"publisher": "Warner Books",
"price": 15.014,
"publication_date": "1990-10-01",
"date_timestamp": 654739200,
"is_available": true,
"_tags": [
"non-fiction",
"is_not_rated"
],
"popularity": 800,
"store": [
"The Corner Bookshop"
],
"categories": [
"history",
"politics",
"biography"
]
},
]
```
# Filters and arrays of objects
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/filtering/in-depth/arrays-of-objects
Why a filter on an array of objects can match values from different elements, and how to structure records to avoid it.
When records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) store an array of objects, Algolia indexes the values for each attribute path independently.
It doesn't preserve which values came from the same array element.
A filter combining two conditions can then match a record where a different element satisfied each one.
Consider a product sold at two warehouses at different prices:
```json JSON icon=braces theme={"system"}
{
"objectID": "thermostat-1",
"name": "Thermostat",
"prices": [
{ "warehouse": "north", "amount": 93100 },
{ "warehouse": "south", "amount": 100000 }
]
}
```
With `prices.warehouse` declared in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting), the filter `prices.warehouse:"north" AND prices.amount=100000` shouldn't match, because the north warehouse charges 93100.
It matches anyway.
The first element satisfies one condition, the second satisfies the other, and the record satisfies both.
## Why the cross-match happens
Indexing collects every value at an attribute path into a single set.
For the preceding record, `prices.warehouse` contains `["north", "south"]` and `prices.amount` contains `[93100, 100000]`, but indexing doesn't preserve that `north` pairs with `93100`.
Filters on array attributes match if **any** element matches.
Combining filters doesn't require them to match the same element, and there's no filter syntax to enforce that.
The same applies to parallel arrays.
Given `prices: [50, 200]` and `stockCount: [1, 5]`, the filter `prices < 100 AND stockCount > 2` matches even though the cheap item has low stock.
Filtering ignores array position.
Geo queries are the exception.
`_geoloc` stores coordinates as latitude/longitude pairs, so [`insideBoundingBox`](/doc/api-reference/api-parameters/insideBoundingBox) and related filters evaluate each pair together.
Other filters still cross-match independently.
The filter returns more records than expected without raising an error.
The extra records satisfy all conditions, but not for the same element.
## Choose a record structure
Three record shapes avoid the cross-match, at different costs.
Choose by what you need to filter, sort, or facet:
| Goal | Record shape |
| - | - |
| Sort, facet, or rank on each element independently | [One record per element](#one-record-per-element) |
| Filter on a property you know at query time, with a bounded number per record | [One attribute per key](#one-attribute-per-key) |
| Filter one fixed combination of values together | [A composite attribute](#composite-attribute) |
### One record per element
Index one record per array element, then group the results at query time:
```json JSON icon=braces theme={"system"}
[
{
"objectID": "thermostat-1-north",
"productID": "thermostat-1",
"name": "Thermostat",
"warehouse": "north",
"amount": 93100
},
{
"objectID": "thermostat-1-south",
"productID": "thermostat-1",
"name": "Thermostat",
"warehouse": "south",
"amount": 100000
}
]
```
Set [`attributeForDistinct`](/doc/api-reference/api-parameters/attributeForDistinct) to `productID` and [`distinct`](/doc/api-reference/api-parameters/distinct) to `true`.
Each search returns one row per product, and every filter applies to a single warehouse.
Each attribute now holds one value per record, so the usual declarations behave as written: add it to [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) to facet on it, or to a replica's ranking to sort by it.
Use this shape when per-element sorting, faceting, or ranking matters, and when a filter combines two conditions without naming the element: `price < 100 AND stockCount > 2` stays two conditions here, where keyed attributes need one clause per key.
The cost is record count.
Shared product data repeats in every row, facet counts reflect either elements or products depending on [`facetingAfterDistinct`](/doc/api-reference/api-parameters/facetingAfterDistinct), and Analytics and Recommend operate on the split records rather than the product.
See [Deduplicate results with distinct](/doc/guides/managing-results/refine-results/grouping) for the full pattern.
### One attribute per key
When you know at query time which property you'll filter on, make that property the attribute name.
Replace the array with an object keyed by it:
```json JSON icon=braces theme={"system"}
{
"objectID": "thermostat-1",
"name": "Thermostat",
"prices": {
"north": 93100,
"south": 100000
}
}
```
Each key is now its own path holding a single value, not a set: `prices.north` is `93100` and nothing else.
A filter like `prices.north=100000` has one value to test, so it can't cross-match.
Numeric attributes are filterable without appearing in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting), unless [`numericAttributesForFiltering`](/doc/api-reference/api-parameters/numericAttributesForFiltering) restricts them to a list.
Use this shape when the number of keys per record stays bounded.
#### Faceting keyed attributes
For facet counts or facet statistics, declare the parent object.
Declaring `prices` covers every nested attribute below it: the setting stays one entry, and every nested attribute becomes a facet of its own.
Request the child paths, such as `prices.north`, because requesting `prices` itself doesn't return any facet.
Facet statistics follow the same paths: one `facets_stats` entry per numeric attribute below `prices`, and none that covers them together.
To get a single range across every key, compute it when you build the record and store it beside the prices:
```json JSON icon=braces theme={"system"}
{
"objectID": "thermostat-1",
"name": "Thermostat",
"prices": {
"north": 93100,
"south": 100000
},
"priceMin": 93100,
"priceMax": 100000
}
```
Declare `priceMin` and `priceMax` in `attributesForFaceting` instead of `prices`, then read the range as `priceMin`'s `min` and `priceMax`'s `max`.
The remaining statistics on those two attributes describe the per-record extremes, not the prices.
Each one holds a single value per record, so faceting the pair costs about what one key costs, however many keys accumulate.
Recompute both whenever a price changes: a stale aggregate narrows the range, and the index doesn't return an error.
Collecting every price into one array gives a range too, but only `min` and `max` are reliable.
Facet counts count records rather than array elements, so a price that repeats at one path in one record counts once.
`sum` then falls short of the true total, and `avg` can land either side of it.
Keyed attributes support filtering but not browsing.
You can't ask "Which warehouses exist, and how many products does each one stock?" because each key becomes a separate attribute.
The facet values are the prices, not the keys.
To browse by warehouse, store the warehouse names alongside the prices:
```json JSON icon=braces theme={"system"}
{
"objectID": "thermostat-1",
"name": "Thermostat",
"warehouses": ["north", "south"],
"prices": {
"north": 93100,
"south": 100000
}
}
```
Declaring `warehouses` gives one facet with a count for each warehouse, and `searchable(warehouses)` lets users search the list.
* Keep it if users browse or search by warehouse.
* Drop it if you only filter.
The array duplicates every warehouse name and can make records larger than the array of objects it replaced.
If you keep both attributes, build them from the same source at indexing time.
Otherwise, a key in `prices` but missing from `warehouses` remains filterable while disappearing from the facet.
The index doesn't return an error.
To detect drift, search with `facets: ["*"]` and compare the `warehouses` facet values with the `prices.*` child names.
Increase [`maxValuesPerFacet`](/doc/api-reference/api-parameters/maxValuesPerFacet) to include every warehouse.
Otherwise, the default of 100 truncates the `warehouses` values but not the `prices.*` names, so the comparison reports false positives.
#### Limitations
* **Cost.** Each property increases record size. Your plan [limits both the size of individual records and the average record size across the index](/doc/guides/scaling/algolia-service-limits#application-record-and-index-limits). Declaring properties for faceting increases index size, once per nested property a record carries. `filterOnly` costs less, and numeric filters don't need any declaration at all.
* **Sorting.** Sorting by one property requires [one replica per property](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas), and each index supports only a limited number of replicas. Sort on a shared base attribute instead, such as the precomputed `priceMin` above.
* **Expressiveness.** Filters that don't specify a key, such as "cheap and well stocked anywhere", must enumerate every key. The rewritten filter grows with the product of the per-key term counts. With more than about seven keys, it exceeds the documented limit of [1,000 filters per query](/doc/guides/scaling/algolia-service-limits#filters-facets-and-rules-limits). Use one record per element instead. See [Boolean rewrites for keyed attributes](#boolean-rewrites-for-keyed-attributes) for the construction and examples of rewrites that look correct but aren't.
### Composite attribute
When the same combination is always filtered together, join the values at indexing time:
```json JSON icon=braces theme={"system"}
{
"objectID": "shirt-1",
"variants": ["red|XL", "blue|S"]
}
```
Declare `variants` in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting), then filter with `variants:"red|XL"`.
The record keeps its shape, and one condition replaces two.
Undeclared, the filter doesn't match anything and doesn't raise an error (see [Missing and negated attributes](#missing-and-negated-attributes)).
This is the smallest change, but it only covers the combination you encoded.
Filtering on color alone takes a separate attribute, and composite values match as a whole value rather than supporting numeric ranges.
## When nesting is safe
Pairing breaks when an attribute path has more than one value.
The number of elements doesn't matter: a one-element array whose fields are arrays breaks pairing, and so does an object whose fields are arrays.
Nesting is safe in two cases:
* **A single value at every path.** Given `price: { "net": 10, "gross": 12 }`, the filter `price.net > 5 AND price.gross < 20` behaves as written. Array-valued fields under `price` would break pairing.
* **Independent values.** Use nested objects when cross-matching is the intended behavior. For example, filtering a `categories` array together with a `store` array should match records that have both, in any combination.
## Reference
Attribute naming and filter edge cases referenced throughout this guide.
### Attribute names with delimiters
Attribute names can contain dashes and underscores, so `north-west` doesn't need rewriting.
Dots are different: a dot becomes an extra path level, so a literal `"north.west"` key and a nested `{"north": {"west": ... }}` share one filter path and one facet entry.
No filter syntax can tell the two apart, so replace or remove dots in property names before indexing.
> [!NOTE]
>
> The delimiters `:`, `=`, `<`, `>`, `!`, `(`, and spaces only break the unquoted spelling.
> Quote the key to keep it: `filters: '"prices.north:west" = 100'`.
> Unquoted, `facetFilters` splits at the first colon and can match the wrong record.
> `numericFilters` doesn't have a quoted form and rejects the key outright.
### Test if keys are present
Records don't all have the same keys: a product stocked in the north has `prices.north` but might not have `prices.south`.
To test whether a key exists, use a comparison that covers the full range of valid values.
For example, `prices.north >= 0` works only if prices can't be negative.
Don't use `!=` for this: `prices.north != 0` also matches records that don't have `prices.north`.
### Value types
A boolean `true` matches numerically as `1`, and a numeric string such as `"7"` doesn't match anything.
### Missing and negated attributes
Filtering on a string attribute that's not in `attributesForFaceting` doesn't match anything.
It doesn't raise an error.
This applies to both `filters` and [`facetFilters`](/doc/api-reference/api-parameters/facetFilters), even if no record has the attribute.
Negation does the opposite: `NOT color:red` and `facetFilters: ["color:-red"]` match every record, even those with `color: red`.
### Boolean rewrites for keyed attributes
A query such as "cheap and well stocked anywhere" doesn't name a key.
To preserve pairing, the filter must consider every key.
This requires a Boolean rewrite.
The rewrite is only possible when both conditions use the same kind of term.
An `OR` clause can't mix numeric comparisons and facet values, so queries such as "open somewhere and cheap in the same warehouse" can't be rewritten.
The natural form is invalid because the filter grammar doesn't allow `AND` inside `OR`:
```text Invalid icon=circle-x theme={"system"}
(prices.north < 100 AND stock.north > 2) OR
(prices.south < 100 AND stock.south > 2)
```
Rewrite it as a conjunction of disjunctions, with one term per key in each clause:
```text Filter icon=circle-check theme={"system"}
(prices.north < 100 OR prices.south < 100) AND
(prices.north < 100 OR stock.south > 2) AND
(stock.north > 2 OR prices.south < 100) AND
(stock.north > 2 OR stock.south > 2)
```
This form is equivalent to the original paired query while using only syntax the filter grammar accepts.
The number of clauses equals the product of the per-key term counts.
Two keys produce four clauses.
Ten keys produce 1,024 clauses and more than 10,000 individual filters, well past the limit of [1,000 filters per query](/doc/guides/scaling/algolia-service-limits#filters-facets-and-rules-limits).
The filter also grows exponentially in size, reaching about 188 KB at ten keys.
**In practice, treat about seven keys as the limit.**
Beyond that, use one record per element.
Two shorter rewrites look correct but aren't:
* **Cross-match.** `(prices.north < 100 OR prices.south < 100) AND (stock.north > 2 OR stock.south > 2)` reintroduces cross-matching. It matches products that are cheap in one warehouse and well stocked in another.
* **Operator precedence.** Omitting the parentheses silently changes the meaning because `OR` binds tighter than `AND`. The filter parses as `prices.north < 100 AND (stock.north > 2 OR prices.south < 100) AND stock.south > 2`, requiring one warehouse to be cheap and another to be well stocked.
## See also
* [Filters and boolean operators](/doc/guides/managing-results/refine-results/filtering/in-depth/combining-boolean-operators)
* [Create nested attributes](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/creating-and-using-nested-attributes)
* [Handle data relationships](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/handling-data-relationships)
* [Structure ecommerce product records](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/ecommerce-records)
# Filters and boolean operators
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/filtering/in-depth/combining-boolean-operators
How to combine filters and `facetFilters` using boolean operators.
Algolia supports the following operators, which must be in capital letters:
* **`OR`**: must match any of the combined conditions
* **`AND`**: must match all the combined conditions
* **`NOT`**: negates a filter
## Use of parentheses
Use parentheses `(` and `)` to combine several OR conditions.
These rules apply:
* **Don't group ANDs within ORs.**
For example, `A OR (B AND C) OR (D AND E)` isn't supported.
* **Don't use parentheses with top-level AND combinations.**
For example, `(A AND (B OR C))` isn't supported.
(But `A AND (B OR C)` is supported)
* **Don't use parentheses to negate a group of expressions.**
For example, `NOT (filter1 OR filter2)` isn't supported.
### Examples of valid combinations
The following examples are valid combinations of filter expressions:
* `A AND (B OR C)`
* `(A OR B) AND C`
* `A AND (B OR C) AND (D OR E)`
* `A AND B AND (C OR D) AND (E OR F)`
* `(A OR B) AND (C OR D) AND E`
## Example scenarios for filter combinations
The `filters` parameter gives precise control over search results with a SQL-like syntax.
Use the following examples to learn how to apply different filters and combine them with boolean logic.
### Basic filters
| Filter type | Example | Filter |
| - | - | - |
| Numeric comparison | Show products that cost \$13 or more | `price > 12.99` |
| Numeric range | Show products with a price between $5.99 and $100 | `price:5.99 TO 100` |
| Facets | Show records categorized as "Book" | `category:Book` |
| Tag filters | Show records marked with the "published" tag | `_tags:published` |
| Boolean filters | Show records where the `isEnabled` attribute is `true` | `isEnabled:true` |
| Filter by date | Show records published between two dates [Unix timestamps](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#dates) | `date: 1441745506 TO 1441755506` |
| Negation | Exclude discontinued products | `NOT is_discontinued:true` |
### Combine filters
* Find books not written by a specific author: `(category:Book OR category:Ebook) AND NOT author:"JK Rowling"`
* Filter products by price range and category: `price:10 TO 50 AND (category:Electronics OR category:Accessories)`
* Complex filter with several conditions: `inStock:true AND (category:Phones OR category:Tablets) AND (brand:Apple OR brand:Samsung) AND price>500`
## Don't mix filter types in OR conditions
You can't compare different filter types (string, numeric, tags) with an `OR`.
For example, `num=3 OR tag1 OR facet:value` isn't allowed.
## Apps and user actions change filter expressions
If a user or app selects a filter, Algolia omits the filters that don't apply from the expression.
For example, if the original filter expression is `((category:Electronics OR category:Home) AND NOT brand:LG) AND NOT price:100 TO 500` and the user selects the Electronics category, the expression simplifies to `(category:Electronics AND NOT brand:LG) AND NOT price:100 TO 500`.
## Filter syntax validator
Enter your filter to validate it:
# Filter scoring
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/filtering/in-depth/filter-scoring
How Algolia calculates filter scores, and how to control the weight of individual filter values in your ranking.
As well as refining results, you can use **filter scoring** to rank records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) according to how well they match a set of filters.
Scoring applies numerical settings to filter values, making some filter values more important than others.
The [Filters ranking criterion](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria#filters) ranks records by their filter scores.
When records tie on all earlier criteria, those with higher filter scores rank first.
If filter scores also tie, Algolia uses the remaining criteria to order the records.
Scoring works with both [`filters`](/doc/api-reference/api-parameters/filters) and [`optionalFilters`](/doc/api-reference/api-parameters/optionalFilters).
## Stock portfolio example
Consider stock portfolios where you score companies based on their monetary significance.
You could use filter scoring to rank Google higher than Facebook.
For example:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
Filters =
"(company:Google OR company:Amazon OR company:Facebook)",
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
filters:
"(company:Google OR company:Amazon OR company:Facebook)",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFilters("(company:Google OR company:Amazon OR company:Facebook)"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFilters("(company:Google OR company:Amazon OR" + " company:Facebook)"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { filters: '(company:Google OR company:Amazon OR company:Facebook)' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
filters =
"(company:Google OR company:Amazon OR company:Facebook)"
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['filters' => '(company:Google OR company:Amazon OR company:Facebook)',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"filters": "(company:Google OR company:Amazon OR company:Facebook)",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
filters: "(company:Google OR company:Amazon OR company:Facebook)"
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
filters = Some("(company:Google OR company:Amazon OR company:Facebook)")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(
SearchSearchParamsObject(
filters: "(company:Google OR company:Amazon OR company:Facebook)"
)
)
)
```
The result is that records with Google stocks sit at the top of the list, higher than Amazon and Facebook.
## Score calculation
Filter scores are integer values from 0 to 65,535.
> [!NOTE]
>
> For performance reasons,
> don't use filter scoring on searches that may return more than 100,000 results.
### Score aggregation
By default, Algolia adds the scores of `AND` filters and uses the highest matching score within each `OR` group.
It doesn't add multiple matching scores within an `OR` group.
The [stock portfolio](#stock-portfolio-example) filter is a single `OR` group.
A portfolio holding Google, Amazon, and Facebook scores 3: the highest matching score, not the sum.
When required filters use only `AND`, every returned record matches every filter and receives the same filter score.
This restriction doesn't apply to optional filters, because records don't need to match every optional filter to appear in the results.
### Accumulate scores with `sumOrFiltersScores`
You accumulate scores by setting the [`sumOrFiltersScores`](/doc/api-reference/api-parameters/sumOrFiltersScores) parameter to `true`.
The stock portfolio filter then scores 6 for a record holding all three companies (3+2+1).
If `sumOrFiltersScores` is `false`, the default, Algolia takes the highest score instead.
A search query (The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.) filtered on Google and Amazon with `sumOrFiltersScores` = `false` returns a score of 3 (google(3) > amazon(2) > facebook(1)).
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
Filters =
"(company:Google OR company:Amazon OR company:Facebook)",
SumOrFiltersScores = false,
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
filters:
"(company:Google OR company:Amazon OR company:Facebook)",
sumOrFiltersScores: false,
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().
SetFilters("(company:Google OR company:Amazon OR company:Facebook)").
SetSumOrFiltersScores(false),
)))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject()
.setFilters("(company:Google OR company:Amazon OR" + " company:Facebook)")
.setSumOrFiltersScores(false),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: {
filters: '(company:Google OR company:Amazon OR company:Facebook)',
sumOrFiltersScores: false,
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
filters =
"(company:Google OR company:Amazon OR company:Facebook)",
sumOrFiltersScores = false,
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['filters' => '(company:Google OR company:Amazon OR company:Facebook)',
'sumOrFiltersScores' => false,
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"filters": "(company:Google OR company:Amazon OR company:Facebook)",
"sumOrFiltersScores": False,
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
filters: "(company:Google OR company:Amazon OR company:Facebook)",
sum_or_filters_scores: false
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
filters = Some("(company:Google OR company:Amazon OR company:Facebook)"),
sumOrFiltersScores = Some(false)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(
filters: "(company:Google OR company:Amazon OR company:Facebook)",
sumOrFiltersScores: false
))
)
```
The same query with `sumOrFiltersScores` = `true` returns a score of 6 (google (3) + amazon(2) + facebook(1)).
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
Filters =
"(company:Google OR company:Amazon OR company:Facebook)",
SumOrFiltersScores = true,
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
filters:
"(company:Google OR company:Amazon OR company:Facebook)",
sumOrFiltersScores: true,
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().
SetFilters("(company:Google OR company:Amazon OR company:Facebook)").
SetSumOrFiltersScores(true),
)))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject()
.setFilters("(company:Google OR company:Amazon OR" + " company:Facebook)")
.setSumOrFiltersScores(true),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: {
filters: '(company:Google OR company:Amazon OR company:Facebook)',
sumOrFiltersScores: true,
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
filters =
"(company:Google OR company:Amazon OR company:Facebook)",
sumOrFiltersScores = true,
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['filters' => '(company:Google OR company:Amazon OR company:Facebook)',
'sumOrFiltersScores' => true,
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"filters": "(company:Google OR company:Amazon OR company:Facebook)",
"sumOrFiltersScores": True,
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
filters: "(company:Google OR company:Amazon OR company:Facebook)",
sum_or_filters_scores: true
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
filters = Some("(company:Google OR company:Amazon OR company:Facebook)"),
sumOrFiltersScores = Some(true)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(
filters: "(company:Google OR company:Amazon OR company:Facebook)",
sumOrFiltersScores: true
))
)
```
`sumOrFiltersScores` only changes the scoring of `OR` groups.
## Optional filter scores
[Optional filters](/doc/guides/managing-results/rules/merchandising-and-promoting/in-depth/optional-filters) don't remove records from your results, so their scores only affect ranking.
When you don't specify a score for an optional filter, it defaults to 1.
In `optionalFilters` syntax,
that means Algolia adds the scores of `AND `filters `(optionalFilters: ["a", "b"])`,
and each `OR `group `(optionalFilters: [["a", "b"]])` contributes only its highest matching score.
### Simple optional filters
Here's an example of how Algolia calculates an [`optionalFilters`](/doc/api-reference/api-parameters/optionalFilters) score:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
OptionalFilters = new OptionalFilters(
new List
{
new OptionalFilters("brand:Apple"),
new OptionalFilters("type:tablet"),
}
),
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
optionalFilters: [
"brand:Apple",
"type:tablet",
],
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetOptionalFilters(search.ArrayOfOptionalFiltersAsOptionalFilters(
[]search.OptionalFilters{*search.StringAsOptionalFilters("brand:Apple"), *search.StringAsOptionalFilters("type:tablet")})))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setOptionalFilters(
OptionalFilters.of(Arrays.asList(OptionalFilters.of("brand:Apple"), OptionalFilters.of("type:tablet")))
),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { optionalFilters: ['brand:Apple', 'type:tablet'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
optionalFilters =
OptionalFilters.of(
listOf(
OptionalFilters.of("brand:Apple"),
OptionalFilters.of("type:tablet"),
)
)
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['optionalFilters' => [
'brand:Apple',
'type:tablet',
],
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"optionalFilters": [
"brand:Apple",
"type:tablet",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(optional_filters: ["brand:Apple", "type:tablet"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
optionalFilters =
Some(OptionalFilters(Seq(OptionalFilters("brand:Apple"), OptionalFilters("type:tablet"))))
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(optionalFilters: SearchOptionalFilters
.arrayOfSearchOptionalFilters([
SearchOptionalFilters.string("brand:Apple"),
SearchOptionalFilters.string("type:tablet"),
])))
)
```
Algolia uses the highest matching score for each optional filter.
In the preceding example:
* The `brand:Apple` filter contributes 2 when it matches a record and 0 otherwise.
* The `type:tablet` filter contributes 1 when it matches and 0 otherwise.
In the following example, the iPad Air matches both optional filters and scores 3:
2 from `brand:Apple` and 1 from `type:tablet`.
The iPhone matches only `brand:Apple` and scores 2.
The iPad Air ranks above the iPhone.
```json JSON icon=braces theme={"system"}
[
{
"name": "iPad Air",
"brand": "Apple",
"type": "tablet"
},
{
"name": "iPhone 17",
"brand": "Apple",
"type": "phone"
}
]
```
With these optional filters, Algolia ranks results as follows:
1. Apple tablets (score: 3)
2. Other Apple products (score: 2)
3. Tablets by other brands (score: 1)
4. Other products by other brands (score: 0)
### Complex optional filters
Algolia calculates the highest score for each element of your optional filters,
so you can combine filters to perform more complex score calculations.
For example, you want to promote red and blue products but avoid over-promoting products that are both red and blue.
To do so, use the following optional filters:
```json JSON icon=braces theme={"system"}
[["color:red", "color:blue"], ["type:-jeans"]]
```
And the following record:
```json JSON icon=braces theme={"system"}
{
"name": "Long-sleeved shirt",
"type": "shirt",
"color": ["red", "blue"]
}
```
1. Algolia calculates the highest score for the first optional filter. In this case, since it's an array (`["color:red", "color:blue"]`), Algolia checks all nested optional filters and uses the highest matching score.
2. Since the record contains red and blue colors, it scores 2 (match on red) and 1 (match on blue). Algolia uses the highest matching score instead of a sum, meaning **this record gets a score of 2 on this filter (not 3)**.
3. Algolia matches the second optional filter (`type:-jeans`). The record also matches (it's not "jeans"), so it scores 1. Adding this to the previous optional filter's score, the total score for this record is 3.
### Sum optional filter scores
In the preceding example, the red-and-blue shirt scores 3 by default: 2 from the color group and 1 from `type:-jeans`.
With `sumOrFiltersScores` set to `true`, the same shirt scores 4: 2 for red, 1 for blue, and 1 for `type:-jeans`.
The following table compares scores for each combination:
| Product type | Matching color filters | Default score | Score with `sumOrFiltersScores: true` |
| - | - | - | - |
| Other than jeans | Red and blue | 3 | 4 |
| Other than jeans | Red only | 3 | 3 |
| Other than jeans | Blue only | 2 | 2 |
| Other than jeans | Neither | 1 | 1 |
| Jeans | Red and blue | 2 | 3 |
| Jeans | Red only | 2 | 2 |
| Jeans | Blue only | 1 | 1 |
| Jeans | Neither | 0 | 0 |
These scores decide the order only for the Filters ranking criterion.
Earlier criteria take precedence.
## Numeric filter scores
You can't apply scores with numeric filters (such as `>=`, `!=`, `>`).
You can apply scores only to facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) values using the `attribute:value` syntax.
## See also
* [Using filter scoring to control the order of records (blog)](https://www.algolia.com/blog/engineering/feature-spotlight-optional-filters/)
* [Optional filters and filter scoring (blog)](https://www.algolia.com/blog/engineering/discover-what-fuzzy-search-is-with-fuzzy-matching/)
# Filters and facet filters
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/filtering/in-depth/filters-and-facetfilters
Learn about the relationship between filters and facet filters.
For most uses, the `filters` parameter is the best choice for filtering.
However, `numericFilters` and `facetFilters` also offer comparable capabilities and can be used to suit different filtering needs.
## Differences between filtering parameters
Whatever filtering parameter you use, the aim is to filter records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) based on their attributes.
To exclude results, use the NOT [operator](/doc/guides/managing-results/refine-results/filtering/in-depth/combining-boolean-operators).
However, there are some differences:
### `filters`
| | Notes |
| - | - |
| Parameter | [`filters`](/doc/api-reference/api-parameters/filters) |
| Syntax | String |
| Best for | Filtering with a mix of string and numeric filters |
| Key points | A familiar SQL syntax. You can use boolean logical operators: AND, OR, NOT |
#### Examples
Find all books written by Stephen King:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { Filters = "author:\"Stephen King\"" })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
filters: "author:\"Stephen King\"",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFilters("author:\"Stephen King\""))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFilters("author:\"Stephen King\""),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { filters: 'author:"Stephen King"' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(filters = "author:\"Stephen King\""),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['filters' => 'author:"Stephen King"',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"filters": 'author:"Stephen King"',
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(filters: "author:\"Stephen King\"")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
filters = Some("author:\"Stephen King\"")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(filters: "author:\"Stephen King\""))
)
```
Exclude all non-fiction books (based on their [tag](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-tags)):
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { Filters = "NOT _tags:non-fiction" })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
filters: "NOT _tags:non-fiction",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFilters("NOT _tags:non-fiction"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFilters("NOT _tags:non-fiction"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { filters: 'NOT _tags:non-fiction' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(filters = "NOT _tags:non-fiction"),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['filters' => 'NOT _tags:non-fiction',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"filters": "NOT _tags:non-fiction",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(filters: "NOT _tags:non-fiction")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
filters = Some("NOT _tags:non-fiction")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(filters: "NOT _tags:non-fiction"))
)
```
### `numericFilters`
| | Notes |
| - | - |
| Parameter | [`numericFilters`](/doc/api-reference/api-parameters/numericFilters) |
| Syntax | String |
| Best for | Filtering dates and numeric values |
| Key points | Like `filters` but limited to numeric data |
#### Example
Find all books that cost more than \$20:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { NumericFilters = new NumericFilters("price>20") })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
numericFilters: "price>20",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetNumericFilters(search.StringAsNumericFilters("price>20")))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setNumericFilters(NumericFilters.of("price>20")),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { numericFilters: 'price>20' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(numericFilters = NumericFilters.of("price>20")),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['numericFilters' => 'price>20',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"numericFilters": "price>20",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(numeric_filters: "price>20")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
numericFilters = Some(NumericFilters("price>20"))
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(numericFilters: SearchNumericFilters
.string("price>20")))
)
```
### `facetFilters`
| | Notes |
| - | - |
| Parameter | [`facetFilters`](/doc/api-reference/api-parameters/facetFilters) |
| Syntax | String or string array |
| Best for | Filtering with a mix of strings and complex boolean logic |
| Key points | Can't be used for numeric filtering |
#### Examples
Find books written by Stephen King or belonging to the horror genre, and also published by Penguin.
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
FacetFilters = new FacetFilters(
new List
{
new FacetFilters("publisher:Penguin"),
new FacetFilters(
new List
{
new FacetFilters("author:Stephen King"),
new FacetFilters("genre:Horror"),
}
),
}
),
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
facetFilters: [
"publisher:Penguin",
[
"author:Stephen King",
"genre:Horror",
],
],
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFacetFilters(search.ArrayOfFacetFiltersAsFacetFilters(
[]search.FacetFilters{*search.StringAsFacetFilters("publisher:Penguin"), *search.ArrayOfFacetFiltersAsFacetFilters(
[]search.FacetFilters{*search.StringAsFacetFilters("author:Stephen King"), *search.StringAsFacetFilters("genre:Horror")})})))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFacetFilters(
FacetFilters.of(
Arrays.asList(
FacetFilters.of("publisher:Penguin"),
FacetFilters.of(Arrays.asList(FacetFilters.of("author:Stephen King"), FacetFilters.of("genre:Horror")))
)
)
),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { facetFilters: ['publisher:Penguin', ['author:Stephen King', 'genre:Horror']] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
facetFilters =
FacetFilters.of(
listOf(
FacetFilters.of("publisher:Penguin"),
FacetFilters.of(
listOf(FacetFilters.of("author:Stephen King"), FacetFilters.of("genre:Horror"))
),
)
)
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['facetFilters' => [
'publisher:Penguin',
[
'author:Stephen King',
'genre:Horror',
],
],
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"facetFilters": [
"publisher:Penguin",
[
"author:Stephen King",
"genre:Horror",
],
],
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
facet_filters: ["publisher:Penguin", ["author:Stephen King", "genre:Horror"]]
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
facetFilters = Some(
FacetFilters(
Seq(
FacetFilters("publisher:Penguin"),
FacetFilters(Seq(FacetFilters("author:Stephen King"), FacetFilters("genre:Horror")))
)
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(facetFilters: SearchFacetFilters
.arrayOfSearchFacetFilters([
SearchFacetFilters.string("publisher:Penguin"),
SearchFacetFilters.arrayOfSearchFacetFilters([
SearchFacetFilters.string("author:Stephen King"),
SearchFacetFilters.string("genre:Horror"),
]),
])))
)
```
Exclude eBooks from the results:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject { FacetFilters = new FacetFilters("category:-Ebook") }
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
facetFilters: "category:-Ebook",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetFacetFilters(search.StringAsFacetFilters("category:-Ebook")))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setFacetFilters(FacetFilters.of("category:-Ebook")),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { facetFilters: 'category:-Ebook' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(facetFilters = FacetFilters.of("category:-Ebook")),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['facetFilters' => 'category:-Ebook',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"facetFilters": "category:-Ebook",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(facet_filters: "category:-Ebook")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
facetFilters = Some(FacetFilters("category:-Ebook"))
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(facetFilters: SearchFacetFilters
.string("category:-Ebook")))
)
```
## Combining filters and facet filters
You can combine filter and facet filters in the same search query. For example:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
Filters = "(author:\"Stephen King\" OR genre:\"Horror\")",
FacetFilters = new FacetFilters(
new List { new FacetFilters("publisher:Penguin") }
),
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
filters: "(author:\"Stephen King\" OR genre:\"Horror\")",
facetFilters: [
"publisher:Penguin",
],
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().
SetFilters("(author:\"Stephen King\" OR genre:\"Horror\")").
SetFacetFilters(search.ArrayOfFacetFiltersAsFacetFilters(
[]search.FacetFilters{*search.StringAsFacetFilters("publisher:Penguin")})),
)))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject()
.setFilters("(author:\"Stephen King\" OR genre:\"Horror\")")
.setFacetFilters(FacetFilters.of(Arrays.asList(FacetFilters.of("publisher:Penguin")))),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { filters: '(author:"Stephen King" OR genre:"Horror")', facetFilters: ['publisher:Penguin'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
filters = "(author:\"Stephen King\" OR genre:\"Horror\")",
facetFilters = FacetFilters.of(listOf(FacetFilters.of("publisher:Penguin"))),
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['filters' => '(author:"Stephen King" OR genre:"Horror")',
'facetFilters' => [
'publisher:Penguin',
],
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"filters": '(author:"Stephen King" OR genre:"Horror")',
"facetFilters": [
"publisher:Penguin",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
filters: "(author:\"Stephen King\" OR genre:\"Horror\")",
facet_filters: ["publisher:Penguin"]
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
filters = Some("(author:\"Stephen King\" OR genre:\"Horror\")"),
facetFilters = Some(FacetFilters(Seq(FacetFilters("publisher:Penguin"))))
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(
filters: "(author:\"Stephen King\" OR genre:\"Horror\")",
facetFilters: SearchFacetFilters
.arrayOfSearchFacetFilters([SearchFacetFilters.string("publisher:Penguin")])
))
)
```
In this case, the results of this are the same as the preceding [example for `facetFilters`](#examples) but more generally may prove to be more flexible.
For example, you can use `filters` programmatically to create a reduced set of records based on a user's profile, and then apply `facetFilters` to reflect the facets that users have chosen.
## Limits and performance
Since each filter adds complexity to queries, there's a limit on the total number of combined filters you can use of 1,000.
> [!NOTE]
>
> Faceting on unique or uncommon attributes [may degrade search performance and relevance](/doc/api-reference/api-parameters/facets#usage).
# Geo location
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/geolocation
Geo searching is a way to search around a specific location or area and rank results based on their distance.
Geo-searching is a way to filter and sort results by distance or around specific geographical locations.
You can limit your results to a street, a city, or parts of the world.
You can sort your results according to how near or far they're from a defined location.
> [card] [Explore example code](https://github.com/algolia/doc-code-samples/tree/master/react-instantsearch/geo-search)
>
> Browse the airport search example built with React InstantSearch.
## Geo-search
Geo-searching lets you:
* Filter and sort around a set of latitude and longitude coordinates
* Filter and sort around user location based on their IP address (IPv4 only)
* Filter by one or more box-shaped geographical areas (bounded boxes)
* Filter by one or more freely drawn geographical areas (polygons)
## Enabling geo-search by adding geolocation data to records
To enable geo-searching, you must have geolocation data in your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) as a `_geoloc` attribute that contains latitude and longitude (`lat` and `lng` properties).
The coordinates must be numbers, not strings.
Here's an example of a record with one geolocation:
```json JSON icon=braces theme={"system"}
{
"_geoloc": {
"lat": 40.639751,
"lng": -73.778925
}
}
```
Here's an example of a record with multiple locations:
```json JSON icon=braces theme={"system"}
{
"_geoloc": [
{ "lat": 47.27943, "lng": 5.10645 },
{ "lat": 47.293228, "lng": 5.00457 },
{ "lat": 47.316669, "lng": 5.01667 }
]
}
```
> [!NOTE]
>
> Algolia searches geolocations with a default of 10 m of precision (4 decimal places).
> You can index coordinates with more precision, and the original values are returned with each hit.
> However, search uses the floored value.
> For details, see [Geographical ranking precision](/doc/guides/managing-results/refine-results/geolocation/in-depth/geo-ranking-precision).
Once your records contain geographical information, you can do two things:
* [Geographical filtering](#geographical-filtering-and-sorting)
* [Geographical ranking](#geographical-ranking)
## Geographical filtering and sorting
Geo-searching filters results based on a set of geographic coordinates.
You can define one or more geographic areas: the engine will only return records within that area.
You can refine this behavior by changing boundaries, distance, or precision.
### Filter around a central point
You can define a central geographical point and show only records within a certain radius of that point.
This central point is defined in one of two ways:
* Sent with the query
* Derived from user IP address (supports only IPv4)
Determine the size of this radius depending on the density of hits around the central point.
The radius should be small if many hits are close to the center—the fewer hits near the center, the larger the radius.
Radius-based filtering creates a circle within which all results fall.
It also **creates a ranking based on the distance from the central point**.
Records closest to the center rank higher than records further away.
For more information, see:
* [Filter results around a location](/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-around-a-location)
* [Understand the engine's geographical ranking](/doc/guides/managing-results/refine-results/geolocation/how-to/geo-ranking-info)
* [`aroundLatLng`](/doc/api-reference/api-parameters/aroundLatLng)
* [`aroundLatLngViaIP`](/doc/api-reference/api-parameters/aroundLatLngViaIP)
#### Increase the search radius
The default behavior is to stop finding records after approximately 1,000 matching records.
Therefore, your search radius could be quite small in dense areas.
Consider the number of cafés in Paris: 1,000 matching records wouldn't be enough to list them all.
You can override this behavior by forcing a larger radius,
either by using [`aroundRadius`](/doc/api-reference/api-parameters/aroundRadius)
or [`minimumAroundRadius`](/doc/api-reference/api-parameters/minimumAroundRadius).
This override only applies to radius-based searches, not area-based ones like rectangles and polygons.
Even with a larger radius, the results still match any applied query or filter.
The number of results can be low if the query or filters are restrictive, even if you set a huge radius.
### Sorting by distance
If you want to sort but not filter results based on radial distance,
set the [`aroundRadius`](/doc/api-reference/api-parameters/aroundRadius) parameter to `all`.
This means your records are **ordered but not restricted** based on distance from a provided point.
### Filtering inside rectangular or polygonal areas
You can restrict results to a given area by setting geometric boundaries in either the shape of a rectangle or other polygon.
To restrict results within a given area, include the `lat` and `lng` of a bounding shape in the search parameters.
The engine excludes all results outside of the shape.
**Filtering within or around an area doesn't affect the ranking of results.**
Additionally, area-based geo-searching doesn't work in combination with radius-based geo-searching.
When filtering on rectangular or polygonal areas, the engine considers all results within the area as being geographically equal.
To learn more, see:
* [Filter results inside a rectangle](/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-inside-a-rectangle-area)
* [Filter results inside a polygon](/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-inside-a-polygonal-area)
* [How to solve the 180th meridian issue](/doc/guides/managing-results/refine-results/geolocation/how-to/how-to-resolve-180-meridian-issue)
### Don't mix bounding boxes and polygons
> [!NOTE]
>
> You can't use [`insideBoundingBox`](/doc/api-reference/api-parameters/insideBoundingBox) and [`insidePolygon`](/doc/api-reference/api-parameters/insidePolygon) together.
> If you provide both, only `insideBoundingBox` actions will run.
For example, you have created an app for a chain of restaurants in the South West US,
and you want to show the locations of your restaurants on a map:
* You use a rectangular bounding box to display the South West region of the US.
Blue pins could indicate cities where you have restaurants.
* As users zoom in, you want polygons to mark the boundaries of these cities.
Red pins could mark the locations of individual restaurants within the polygons.
However, since `insideBoundingBox` takes precedence,
you'll only see blue pins on the map.
**For this reason, using a mix of bounding boxes and polygons isn't recommended.**
## Geographical ranking
Geographical ranking is based on the distance between the geolocation in the search query and the best-matching geolocation in the record.
For example, when searching around a location, the results can be sorted by distance - the closer the record is to the `lat`/`lng` you provided, the higher it ranks in the results.
### Geo criterion in the ranking formula
Algolia's ranking criteria contain a [Geo criterion](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria#geo-if-applicable), which by default is the second-ranking criterion.
This criterion is only meaningful when records contain the `_geoloc` attribute with the corresponding `lat` and `lng` properties.
### Change precision
By default, results are ranked by distance in 10 m increments.
Records within 10 m to the center rank higher than records that are 20 m away.
In some cases, this might be too precise.
Consider searching for the best restaurants within walking distance.
Here, larger increments, such as 200 m might be more appropriate.
Change the [Geo criterion's precision](/doc/guides/managing-results/refine-results/geolocation/in-depth/geo-ranking-precision) to alter the default.
## Display results on a map
Use your results' latitude and longitude values to plot each record on a map.
For more information, see [Display results with Google Maps and InstantSearch](/doc/guides/building-search-ui/ui-and-ux-patterns/geo-search/js).
## See also
* [How geo-search improves search and discovery (blog)](https://www.algolia.com/blog/product/how-geo-search-improves-search-and-discovery/)
# Filter results around a location
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-around-a-location
Use Algolia's geographical search capabilities to filter results around a location.
This guide shows you how to filter results around a location.
This location can be set manually or taken from the a user's current position.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
## Dataset
The guide uses a dataset of the 3,000+ biggest airports in the world.
```json JSON icon="braces" theme={"system"}
[
{
"objectID": "3797",
"name": "John F Kennedy Intl",
"city": "New York",
"country": "United States",
"iata_code": "JFK",
"_geoloc": {
"lat": 40.639751,
"lng": -73.778925
},
"links_count": 911
}
]
```
To tell Algolia where each record is located,
the latitude and longitude must be stored in the `_geoloc` attribute.
## Initialize the client
1. [Download the data set](https://github.com/algolia/datasets/blob/master/airports/airports.json)
2. [Set up an API client and send your data to Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api)
## Configure index settings
Even if you just want to sort by distance to a location, your textual relevance should also be good so that users can refine the search with a query.
To do that, you must configure the index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.).
The searchable attributes are: `name`, `city`, `country`, and `iata_code`.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
SearchableAttributes = new List { "name", "country", "city", "iata_code" },
CustomRanking = new List { "desc(links_count)" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: [
"name",
"country",
"city",
"iata_code",
],
customRanking: [
"desc(links_count)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetSearchableAttributes(
[]string{"name", "country", "city", "iata_code"}).SetCustomRanking(
[]string{"desc(links_count)"})))
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", "country", "city", "iata_code"))
.setCustomRanking(Arrays.asList("desc(links_count)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: {
searchableAttributes: ['name', 'country', 'city', 'iata_code'],
customRanking: ['desc(links_count)'],
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings =
IndexSettings(
searchableAttributes = listOf("name", "country", "city", "iata_code"),
customRanking = listOf("desc(links_count)"),
),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['searchableAttributes' => [
'name',
'country',
'city',
'iata_code',
],
'customRanking' => [
'desc(links_count)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"searchableAttributes": [
"name",
"country",
"city",
"iata_code",
],
"customRanking": [
"desc(links_count)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(
searchable_attributes: ["name", "country", "city", "iata_code"],
custom_ranking: ["desc(links_count)"]
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
searchableAttributes = Some(Seq("name", "country", "city", "iata_code")),
customRanking = Some(Seq("desc(links_count)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: ["name", "country", "city", "iata_code"],
customRanking: ["desc(links_count)"]
)
)
```
### Ranking
When filtering around a location, Algolia can also sort the results by distance from this location.
This sorting by distance happens in the ranking formula's `geo` criterion.
If `geo` isn't active, you can't sort by distance.
## Filtering around a given location
To filter airports around New York City (latitude 40.71 and longitude -74.01),
use the [`aroundLatLng`](/doc/api-reference/api-parameters/aroundLatLng) parameter.
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { AroundLatLng = "40.71, -74.01" })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
aroundLatLng: "40.71, -74.01",
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetAroundLatLng("40.71, -74.01"))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setAroundLatLng("40.71, -74.01"),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { aroundLatLng: '40.71, -74.01' },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(aroundLatLng = "40.71, -74.01"),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['aroundLatLng' => '40.71, -74.01',
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"aroundLatLng": "40.71, -74.01",
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(around_lat_lng: "40.71, -74.01")
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
aroundLatLng = Some("40.71, -74.01")
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(aroundLatLng: "40.71, -74.01"))
)
```
An empty query (`''`) tells Algolia to retrieve all airports
## Filtering around the user's current location
As you don't know your user's coordinates in advance, you can retrieve their IP address's associated location with the [`aroundLatLngViaIP`](/doc/api-reference/api-parameters/aroundLatLngViaIP) parameter.
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { AroundLatLngViaIP = true })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
aroundLatLngViaIP: true,
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetAroundLatLngViaIP(true))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex("INDEX_NAME", new SearchParamsObject().setAroundLatLngViaIP(true), Hit.class);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { aroundLatLngViaIP: true },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(aroundLatLngViaIP = true),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['aroundLatLngViaIP' => true,
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"aroundLatLngViaIP": True,
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(around_lat_lng_via_ip: true)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
aroundLatLngViaIP = Some(true)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(aroundLatLngViaIP: true))
)
```
## Filtering by radius
By default, the engine automatically defines a radius to filter on depending on the population density of the user's location.
To define the radius yourself, use the [`aroundRadius`](/doc/api-reference/api-parameters/aroundRadius) parameter.
The bigger the radius, the less filtering you have.
This example sorts by distance to New York City, with a radius of 1,000 km.
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
AroundLatLng = "40.71, -74.01",
AroundRadius = new AroundRadius(1000000),
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
aroundLatLng: "40.71, -74.01",
aroundRadius: 1000000,
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetAroundLatLng("40.71, -74.01").SetAroundRadius(search.Int32AsAroundRadius(1000000)))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setAroundLatLng("40.71, -74.01").setAroundRadius(AroundRadius.of(1000000)),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { aroundLatLng: '40.71, -74.01', aroundRadius: 1000000 },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
aroundLatLng = "40.71, -74.01",
aroundRadius = AroundRadius.of(1000000),
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['aroundLatLng' => '40.71, -74.01',
'aroundRadius' => 1000000,
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"aroundLatLng": "40.71, -74.01",
"aroundRadius": 1000000,
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(around_lat_lng: "40.71, -74.01", around_radius: 1000000)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
aroundLatLng = Some("40.71, -74.01"),
aroundRadius = Some(AroundRadius(1000000))
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(
aroundLatLng: "40.71, -74.01",
aroundRadius: SearchAroundRadius.int(1_000_000)
))
)
```
# Filter results inside a polygon
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-inside-a-polygonal-area
Use Algolia's geographical search capabilities to filter results inside a polygon.
In this tutorial, you'll learn how to filter results around a polygonal location.
This location can either be set manually or taken from the current user position.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
## Dataset
The guide uses a dataset of the 3,000+ biggest airports in the world.
```json JSON icon="braces" theme={"system"}
[
{
"objectID": "3797",
"name": "John F Kennedy Intl",
"city": "New York",
"country": "United States",
"iata_code": "JFK",
"_geoloc": {
"lat": 40.639751,
"lng": -73.778925
},
"links_count": 911
}
]
```
To tell Algolia where each record is located,
the latitude and longitude must be stored in the `_geoloc` attribute.
## Initialize the client
1. [Download the data set](https://github.com/algolia/datasets/blob/master/airports/airports.json)
2. [Set up an API client and send your data to Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api)
## Configure index settings
Even if you just want to sort by distance to a location, your textual relevance should also be good so that users can refine the search with a query.
To do that, you must configure the index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.).
The searchable attributes are: `name`, `city`, `country`, and `iata_code`.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
SearchableAttributes = new List { "name", "country", "city", "iata_code" },
CustomRanking = new List { "desc(links_count)" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: [
"name",
"country",
"city",
"iata_code",
],
customRanking: [
"desc(links_count)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetSearchableAttributes(
[]string{"name", "country", "city", "iata_code"}).SetCustomRanking(
[]string{"desc(links_count)"})))
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", "country", "city", "iata_code"))
.setCustomRanking(Arrays.asList("desc(links_count)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: {
searchableAttributes: ['name', 'country', 'city', 'iata_code'],
customRanking: ['desc(links_count)'],
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings =
IndexSettings(
searchableAttributes = listOf("name", "country", "city", "iata_code"),
customRanking = listOf("desc(links_count)"),
),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['searchableAttributes' => [
'name',
'country',
'city',
'iata_code',
],
'customRanking' => [
'desc(links_count)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"searchableAttributes": [
"name",
"country",
"city",
"iata_code",
],
"customRanking": [
"desc(links_count)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(
searchable_attributes: ["name", "country", "city", "iata_code"],
custom_ranking: ["desc(links_count)"]
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
searchableAttributes = Some(Seq("name", "country", "city", "iata_code")),
customRanking = Some(Seq("desc(links_count)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: ["name", "country", "city", "iata_code"],
customRanking: ["desc(links_count)"]
)
)
```
## Filtering inside a polygonal area
The USA can be considered as a polygon:

To filter inside this rectangle, you need to pass the latitude and longitude of all the points to the `insidePolygon` parameter:
* 42.01,-124.31,
* 48.835509470063045,-124.40453125000005
* 45.01082951668149,-65.95726562500005
* 31.247243545293433,-81.06578125000004
* 25.924152577235226,-97.68234374999997
* 32.300311895879545,-117.54828125
The empty query (`''`) returns all matching airports.
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
InsidePolygon = new List>
{
new List
{
42.01,
-124.31,
48.835509470063045,
-124.40453125000005,
45.01082951668149,
-65.95726562500005,
31.247243545293433,
-81.06578125000004,
25.924152577235226,
-97.68234374999997,
32.300311895879545,
-117.54828125,
},
},
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
insidePolygon: [
[
42.01,
-124.31,
48.835509470063045,
-124.40453125000005,
45.01082951668149,
-65.95726562500005,
31.247243545293433,
-81.06578125000004,
25.924152577235226,
-97.68234374999997,
32.300311895879545,
-117.54828125,
],
],
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetInsidePolygon(
[][]float64{
{
42.01,
-124.31,
48.835509470063045,
-124.40453125000005,
45.01082951668149,
-65.95726562500005,
31.247243545293433,
-81.06578125000004,
25.924152577235226,
-97.68234374999997,
32.300311895879545,
-117.54828125,
},
}))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setInsidePolygon(
Arrays.asList(
Arrays.asList(
42.01,
-124.31,
48.835509470063045,
-124.40453125000005,
45.01082951668149,
-65.95726562500005,
31.247243545293433,
-81.06578125000004,
25.924152577235226,
-97.68234374999997,
32.300311895879545,
-117.54828125
)
)
),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: {
insidePolygon: [
[
42.01, -124.31, 48.835509470063045, -124.40453125000005, 45.01082951668149, -65.95726562500005,
31.247243545293433, -81.06578125000004, 25.924152577235226, -97.68234374999997, 32.300311895879545,
-117.54828125,
],
],
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
insidePolygon =
listOf(
listOf(
42.01,
-124.31,
48.835509470063045,
-124.40453125000005,
45.01082951668149,
-65.95726562500005,
31.247243545293433,
-81.06578125000004,
25.924152577235226,
-97.68234374999997,
32.300311895879545,
-117.54828125,
)
)
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['insidePolygon' => [
[
42.01,
-124.31,
48.835509470063045,
-124.40453125000005,
45.01082951668149,
-65.95726562500005,
31.247243545293433,
-81.06578125000004,
25.924152577235226,
-97.68234374999997,
32.300311895879545,
-117.54828125,
],
],
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"insidePolygon": [
[
42.01,
-124.31,
48.835509470063045,
-124.40453125000005,
45.01082951668149,
-65.95726562500005,
31.247243545293433,
-81.06578125000004,
25.924152577235226,
-97.68234374999997,
32.300311895879545,
-117.54828125,
],
],
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
inside_polygon: [
[
42.01,
-124.31,
48.835509470063045,
-124.40453125000005,
45.01082951668149,
-65.95726562500005,
31.247243545293433,
-81.06578125000004,
25.924152577235226,
-97.68234374999997,
32.300311895879545,
-117.54828125
]
]
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
insidePolygon = Some(
Seq(
Seq(42.01, -124.31, 48.835509470063045, -124.40453125000005, 45.01082951668149, -65.95726562500005,
31.247243545293433, -81.06578125000004, 25.924152577235226, -97.68234374999997, 32.300311895879545,
-117.54828125)
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(insidePolygon: [[
42.01,
-124.31,
48.835509470063045,
-124.40453125000005,
45.01082951668149,
-65.95726562500005,
31.247243545293433,
-81.06578125000004,
25.924152577235226,
-97.68234374999997,
32.300311895879545,
-117.54828125,
]]))
)
```
# Filter results inside a rectangle
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-inside-a-rectangle-area
Use Algolia's geographical search capabilities to filter results inside a rectangle.
In this tutorial, you'll see how to can filter results inside a rectangular area.
This location can either be set manually or taken from the current user position.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
## Dataset
The guide uses a dataset of the 3,000+ biggest airports in the world.
```json JSON icon="braces" theme={"system"}
[
{
"objectID": "3797",
"name": "John F Kennedy Intl",
"city": "New York",
"country": "United States",
"iata_code": "JFK",
"_geoloc": {
"lat": 40.639751,
"lng": -73.778925
},
"links_count": 911
}
]
```
To tell Algolia where each record is located,
the latitude and longitude must be stored in the `_geoloc` attribute.
## Initialize the client
1. [Download the data set](https://github.com/algolia/datasets/blob/master/airports/airports.json)
2. [Set up an API client and send your data to Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api)
## Configure index settings
Even if you just want to sort by distance to a location, your textual relevance should also be good so that users can refine the search with a query.
To do that, you must configure the index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.).
The searchable attributes are: `name`, `city`, `country`, and `iata_code`.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
SearchableAttributes = new List { "name", "country", "city", "iata_code" },
CustomRanking = new List { "desc(links_count)" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: [
"name",
"country",
"city",
"iata_code",
],
customRanking: [
"desc(links_count)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetSearchableAttributes(
[]string{"name", "country", "city", "iata_code"}).SetCustomRanking(
[]string{"desc(links_count)"})))
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", "country", "city", "iata_code"))
.setCustomRanking(Arrays.asList("desc(links_count)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: {
searchableAttributes: ['name', 'country', 'city', 'iata_code'],
customRanking: ['desc(links_count)'],
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings =
IndexSettings(
searchableAttributes = listOf("name", "country", "city", "iata_code"),
customRanking = listOf("desc(links_count)"),
),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['searchableAttributes' => [
'name',
'country',
'city',
'iata_code',
],
'customRanking' => [
'desc(links_count)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"searchableAttributes": [
"name",
"country",
"city",
"iata_code",
],
"customRanking": [
"desc(links_count)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(
searchable_attributes: ["name", "country", "city", "iata_code"],
custom_ranking: ["desc(links_count)"]
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
searchableAttributes = Some(Seq("name", "country", "city", "iata_code")),
customRanking = Some(Seq("desc(links_count)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: ["name", "country", "city", "iata_code"],
customRanking: ["desc(links_count)"]
)
)
```
## Filtering inside a rectangle area
The USA can be considered as a polygon:

To filter inside this rectangle,
you need to pass the upper-left and bottom-right latitude and longitude to the `insideBoundingBox` parameter:
* 49.067996905313834, 65.73828125
* 25.905859247243498, 128.8046875
The empty query (`''`) returns all matching airports.
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(
new SearchParamsObject
{
InsideBoundingBox = new InsideBoundingBox(
new List>
{
new List { 49.067996905313834, 65.73828125, 25.905859247243498, 128.8046875 },
}
),
}
)
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
insideBoundingBox: [
[
49.067996905313834,
65.73828125,
25.905859247243498,
128.8046875,
],
],
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetInsideBoundingBox(search.ArrayOfArrayOfFloat64AsInsideBoundingBox(
[][]float64{
{49.067996905313834, 65.73828125, 25.905859247243498, 128.8046875},
})))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setInsideBoundingBox(
InsideBoundingBox.of(Arrays.asList(Arrays.asList(49.067996905313834, 65.73828125, 25.905859247243498, 128.8046875)))
),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { insideBoundingBox: [[49.067996905313834, 65.73828125, 25.905859247243498, 128.8046875]] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams =
SearchParamsObject(
insideBoundingBox =
InsideBoundingBox.of(
listOf(listOf(49.067996905313834, 65.73828125, 25.905859247243498, 128.8046875))
)
),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['insideBoundingBox' => [
[
49.067996905313834,
65.73828125,
25.905859247243498,
128.8046875,
],
],
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"insideBoundingBox": [
[
49.067996905313834,
65.73828125,
25.905859247243498,
128.8046875,
],
],
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(
inside_bounding_box: [[49.067996905313834, 65.73828125, 25.905859247243498, 128.8046875]]
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
insideBoundingBox =
Some(InsideBoundingBox(Seq(Seq(49.067996905313834, 65.73828125, 25.905859247243498, 128.8046875))))
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(insideBoundingBox: SearchInsideBoundingBox
.arrayOfArrayOfDouble([[
49.067996905313834,
65.73828125,
25.905859247243498,
128.8046875,
]])))
)
```
# Geo ranking information
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/geolocation/how-to/geo-ranking-info
The data Algolia uses for geographical ranking.
When you use Algolia's Geo feature, you can get information about each record's geographical ranking.
Use this information to detect incorrect or missing geographical data.
It can also show whether you need to adjust [precision](/doc/guides/managing-results/refine-results/geolocation/in-depth/geo-ranking-precision)
or [tweak your geographical settings](/doc/guides/managing-results/refine-results/geolocation).
To get geo-ranking data, you need to set [`getRankingInfo=true`](/doc/api-reference/api-parameters/getRankingInfo) in your query parameter.
Doing this adds two new attributes to `_rankingInfo`: `matchedGeoLocation` and `aroundLatLng`.
## `matchedGeoLocation` attribute
The `matchedGeoLocation` attribute contains geographical information,
including the location that matched when you were performing a search query (The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.).
Use it to identify the matched location and retrieve related details, such as its name.
`matchedGeoLocation` is a geographical location object that matches the query.
It consists of:
* `lat` (float): latitude of matched location
* `lng` (float): longitude of matched location
* `distance` (integer): distance between the matched location and the search location (in meters).
> [!NOTE]
>
> `lat` and `lng` are Algolia's internal values, not the original `_geoloc` on the record.
> Returned values may show fewer decimals than the values you indexed.
> For background, see [Geographical ranking precision](/doc/guides/managing-results/refine-results/geolocation/in-depth/geo-ranking-precision).
>
> Unlike `geoDistance`, the `distance` value isn't divided by geographical precision.
Here's an example of `_rankingInfo` attribute returned on a record with several geo-location
(the `matchedGeoLocation` attribute will be in `_rankingInfo` only if a geographical query is performed):
```json JSON icon=braces theme={"system"}
{
"_rankingInfo": {
"nbTypos": 0,
"firstMatchedWord": 0,
"proximityDistance": 0,
"userScore": 7,
"geoDistance": 1600,
"geoPrecision": 1,
"nbExactWords": 0,
"words": 0,
"filters": 0,
"matchedGeoLocation": {
"lat": 37.3688,
"lng": -122.036,
"distance": 1600
}
}
}
```
## `aroundLatLng` attribute
`aroundLatLng` (string, optional):
* Only returned when `aroundLatLngViaIP` is set
* Computed geographical location
* Legacy reason (`param` is a string not an object): `${lat}, ${lng}`
# How to solve the 180th meridian issue
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/geolocation/how-to/how-to-resolve-180-meridian-issue
To solve the 180th meridian issue, create two separate areas east and west of this line
The [180th meridian](https://wikipedia.org/wiki/180th_meridian) is the geographical boundary between the Eastern and Western hemispheres.
It passes through the Pacific Ocean, Russia, Alaska, and Fiji.
## The problem
When searching by location, the 180th meridian can lead to unexpected results.
If a rectangle or polygon includes the 180th meridian, the search won't include results from inside the area.
Instead, the results return everything outside the area you selected.
## The reason: longitudinal value reversal
When you cross the 180th meridian, the longitude switches from positive to negative numbers: from 180 to -180, not 181.
## The solution: create two areas
If your area crosses the meridian, split the affected area into two adjacent shapes, one east of the meridian and one west.
### Rectangles
Consider a rectangle that includes the 180th meridian: `[70, 170, 60, -170]`.
To resolve the issue, split this rectangle into two smaller ones:
* Rectangle 1: `[70, 170, 60, 180]` (east of the meridian)
* Rectangle 2: `[70, -180, 60, -170]` (west of the meridian)
Use [`insideBoundingBox`](/doc/api-reference/api-parameters/insideBoundingBox) to define these shapes with the value `[[70, 170, 60, 180], [70, -180, 60, -170]]`.
### Polygons
Consider a polygon that includes the 180th meridian: `[[70, 170], [80, 160], [90, 140], [80, 120], [70, 110], [60, 130], [70, 140]]`.
To resolve the issue, use [`insidePolygon`](/doc/api-reference/api-parameters/insidePolygon) to split this shape into two smaller ones:
* Polygon 1: `[[70, 170], [80, 160], [90, 140], [80, 120], [70, 110], [60, 130], [70, 140]]` (area east of the meridian)
* Polygon 2: `[[60, 130], [70, 140], [70, 170], [60, 180], [50, 160], [60, 150], [60, 130]]` (area west of the meridian)
# Geographical ranking precision
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/geolocation/in-depth/geo-ranking-precision
How Algolia stores, ranks, and displays geographical coordinates.
## How Algolia handles coordinate precision
Algolia uses three distinct precisions when working with geolocations:
* **Indexed precision**: the `lat`/`lng` values you sent in `_geoloc`. Echoed back unchanged in each hit.
* **Internal precision**: the values Algolia uses for geo-search. Stored at roughly 10 m resolution (4 decimal places).
* **Display precision**: how the engine prints internal values in `_rankingInfo.matchedGeoLocation`.
The original `_geoloc` in your hits is preserved at the precision you indexed.
The ranking and `matchedGeoLocation` fields use floored values.
## Geo-search precision
Algolia searches geolocations with a default of about 10 m of resolution.
Coordinates finer than that are floored to fit the grid.
Search therefore by default treats two records within roughly 10 m of each other as the same point.
You can widen this grid with [`aroundPrecision`](/doc/api-reference/api-parameters/aroundPrecision), but you can't go below the 10 m floor.
> [!NOTE]
>
> The original `_geoloc` you indexed is returned unchanged in each hit.
> Only the search and `matchedGeoLocation` use approximate values.
## What `matchedGeoLocation` returns
When you request [`getRankingInfo=true`](/doc/api-reference/api-parameters/getRankingInfo), each hit includes `_rankingInfo.matchedGeoLocation` with the `lat`, `lng`, and `distance` of the point that matched your geo-query.
These coordinates are Algolia's floored internal value, not your original `_geoloc`.
> [!NOTE]
>
> To read the coordinates exactly as you sent them, use the `_geoloc` field on the hit.
## Group results with `aroundPrecision`
Precision (using the search query parameter `aroundPrecision`) is used to group results that have more or less the same distance from a central point.
Algolia's default precision is 10 meters.
However, a 10-meter difference might not be significant: intervals of 200 meters may make more sense.
By creating 200-meter intervals, you rank all records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) that fall within that interval the same.
They're ranked higher than all records between 200 and 400 meters.
If you set `aroundPrecision` to 200, you'll have the following search results:
* Group of results in the 0-200 meters range will be the best ranked (ranked equally)
* Group of results in the 200-400 meters range will be ranked a bit lower
* Group of results in the 400-600 meters range will be ranked even lower
Another example is when users are looking for nearby recommended restaurants.
The goal is to display results that are a good mix of nearby and highly rated restaurants.
If there are two restaurants within 200 meters of a user's location:
* Restaurant A with a 5 star rating and distance of 150 meters (farther)
* Restaurant B with a 3 star rating and distance of 100 meters (closer)
If you're only sorting by geographical distance, restaurant B will be ranked higher since it's closer to the user.
But by setting `aroundPrecision` to 200, restaurant A and B will be ranked equally: the distances of 150 and 100 will be considered equal to 0 (and distances of 250 and 210 will be equal to 200, and so on).
Going further, since the results of A and B are tied under the `Geo` criterion, the search engine will examine the other criteria in the ranking formula to further sort the results.
Restaurant A will therefore be ranked higher in the result due to its higher rating.
# Deduplicate results with distinct
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/grouping
Distinct deduplicates search results that have the same value for an attribute.
The `distinct` feature deduplicates search results by grouping records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) that share the same attribute value,
so your search results show one representative record per group instead of every matching record.
This is useful when:
* **[Handling item variations](#handle-item-variations):** if you sell t-shirts and every t-shirt has 10 color variants, show one color variant per design instead of all 10. This helps users discover more products (instead of variants of the same product). You can still show the available colors in the product details.
* **[Indexing long documents](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/indexing-long-documents):** if you split a page into one record per section, a query might return multiple chunks from the same page. With `distinct`, only the best-matching chunk appears per page or section.
## Deduplicate results with distinct
To deduplicate search results, configure both:
* [`attributeForDistinct`](/doc/api-reference/api-parameters/attributeForDistinct): the attribute Algolia uses to group records.
* [`distinct`](/doc/api-reference/api-parameters/distinct): how many records to show from each group.
For example, if you set the `url` attribute as `attributeForDistinct`,
Algolia groups records with the same `url` value.
If you set `distinct` to `true`,
Algolia returns only the best matching record from each group.
You can also set `distinct` to a number to return more than one record from each group.
### Choose an attribute for grouping
Choose an attribute that has the same value for all records you want to group together.
Use an identifier instead of a display value if names, titles, or other aspects can change.
For example:
* For product variants, use a product identifier such as `product_id`, `design`, or `sku_group`.
* For job openings, use `company`.
* For long documents, use `url`, `page_id`, or `section_id`.
To define groups of records based on queries or matching filters, see [Smart Groups](/doc/guides/compositions/smart-groups/smart-groups).
## Flatten your records
To use `distinct` and `attributeForDistinct` effectively,
flatten your records:
* Create one record for each searchable item.
* Repeat the grouping attribute in each record.
The following example shows the difference between nested and flat record structures.
### Nested records
```json JSON icon=braces theme={"system"}
[
{
"objectID": "design-1",
"name": "Algolia Logo T-Shirt",
"variants": [
{
"color": "red",
"size": "S",
"sku": "algolia-logo-red-s"
},
{
"color": "blue",
"size": "M",
"sku": "algolia-logo-blue-m"
}
]
}
]
```
With this structure, when a query matches one variant, Algolia returns the whole t-shirt design with all its variants.
Each record represents a design, not an individual variant.
If you want users to search for specific variants, or if you want to show the best matching variant for each design, this structure doesn't support that use case.
### Flattened records
Instead of nested records, create one record for each variant and repeat the design information in each record:
```json JSON icon=braces theme={"system"}
[
{
"objectID": "algolia-logo-red-s",
"design": "design-1",
"name": "Algolia Logo T-Shirt",
"color": "red",
"size": "S",
"sku": "algolia-logo-red-s"
},
{
"objectID": "algolia-logo-blue-m",
"design": "design-1",
"name": "Algolia Logo T-Shirt",
"color": "blue",
"size": "M",
"sku": "algolia-logo-blue-m"
}
]
```
Each flattened record represents a single t-shirt variant.
The `design` attribute identifies variants that belong to the same design, which you can use as the attribute for `distinct`.
## Handle item variations
Consider a shop that sells t-shirts and sweatshirts in different designs and colors.
To deduplicate variants by design, create one record for each color variant.
The inventory has:
* Two t-shirt designs: A and B
* Two sweatshirt designs: C and D
Each design comes in different colors.

Each record contains the type, design, color, and thumbnail image:
```json JSON icon=braces theme={"system"}
[
{
"type": "t-shirt",
"design": "B",
"color": "blue",
"thumbnail_url": "tshirt-B-blue.png"
},
{
"type": "sweatshirt",
"design": "C",
"color": "red",
"thumbnail_url": "sweatshirt-C-red.png"
}
]
```
You can add all possible color variations to each record.
This makes all variants available for display in your UI,
for example, as color swatches below the product thumbnail.
```json JSON icon=braces theme={"system"}
[
{
"type": "t-shirt",
"design": "B",
"color": "blue",
"thumbnail_url": "tshirt-B-blue.png",
"color_variants": ["orange", "teal", "yellow", "red", "green"]
},
{
"type": "t-shirt",
"design": "B",
"color": "orange",
"thumbnail_url": "tshirt-B-orange.png",
"color_variants": ["blue", "teal", "yellow", "red", "green"]
}
]
```
Having one record per variation lets you add granular custom ranking attributes,
such as `number_of_sales`.
Use `distinct` with `design` as the `attributeForDistinct` to show fewer variants per design,
so users can browse a wider range of designs.
## Configure distinct in the dashboard
After flattening your records, configure `distinct` to group variants.
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 **Relevance essentials > Searchable attributes**.
5. Click **Add a Searchable Attribute** and add `design`, `type`, and `color` attributes.
6. Go to **Search behavior > Deduplication and Grouping**.
7. In the **Distinct** menu, select **true**.
8. In the **Attribute for Distinct** menu, select the `design` attribute.
9. Save your changes.
Now, the results include only one color variant per design.
To control which variant appears in the results,
add an attribute such as `number_of_sales` and use it in your [custom ranking](/doc/guides/managing-results/must-do/custom-ranking/how-to/configure-custom-ranking).

## Configure distinct with the API
Before deduplicating results,
restrict which attributes are searchable.
For example, searching the `thumbnail_url` and `color_variants` attributes can match queries that aren't relevant.
Set only `design`, `type`, and `color` as [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes).
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
SearchableAttributes = new List { "design", "type", "color" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: [
"design",
"type",
"color",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetSearchableAttributes(
[]string{"design", "type", "color"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setSearchableAttributes(Arrays.asList("design", "type", "color"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { searchableAttributes: ['design', 'type', 'color'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(searchableAttributes = listOf("design", "type", "color")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['searchableAttributes' => [
'design',
'type',
'color',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"searchableAttributes": [
"design",
"type",
"color",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(searchable_attributes: ["design", "type", "color"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
searchableAttributes = Some(Seq("design", "type", "color"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(searchableAttributes: ["design", "type", "color"])
)
```
Next, use the [`setSettings`](/doc/libraries/sdk/methods/search/set-settings) method to set `design` as [`attributeForDistinct`](/doc/api-reference/api-parameters/attributeForDistinct).
> [!TIP]
>
> To deduplicate all following search requests,
> set [`distinct`](/doc/api-reference/api-parameters/distinct) to `true` with `setSettings`.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings { AttributeForDistinct = "design", Distinct = new Distinct(true) }
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
attributeForDistinct: "design",
distinct: true,
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetAttributeForDistinct("design").SetDistinct(search.BoolAsDistinct(true))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setAttributeForDistinct("design").setDistinct(Distinct.of(true))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { attributeForDistinct: 'design', distinct: true },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(attributeForDistinct = "design", distinct = Distinct.of(true)),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['attributeForDistinct' => 'design',
'distinct' => true,
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"attributeForDistinct": "design",
"distinct": True,
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(attribute_for_distinct: "design", distinct: true)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
attributeForDistinct = Some("design"),
distinct = Some(Distinct(true))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(attributeForDistinct: "design", distinct: SearchDistinct.bool(true))
)
```
After setting `attributeForDistinct`,
you can also set `distinct` as a `search` method parameter:
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { Distinct = new Distinct(true) })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
distinct: true,
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetDistinct(search.BoolAsDistinct(true)))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex(
"INDEX_NAME",
new SearchParamsObject().setDistinct(Distinct.of(true)),
Hit.class
);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({ indexName: 'indexName', searchParams: { distinct: true } });
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(distinct = Distinct.of(true)),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['distinct' => true,
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"distinct": True,
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index("INDEX_NAME", Algolia::Search::SearchParamsObject.new(distinct: true))
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
distinct = Some(Distinct(true))
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams
.searchSearchParamsObject(SearchSearchParamsObject(distinct: SearchDistinct.bool(true)))
)
```
This deduplicates results only for the current search.
## See also
* [Structure ecommerce product records](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/ecommerce-records)
# Sorting results
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting
Learn how to sort by an attribute using replicas.
Although sorting occurs at each level of the [ranking formula](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria), in this guidance, "sorting" refers to **ordering records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) by a specific attribute** like release date or price.
## The challenge with sorting
Sorting exposes the difference between relevance and ranking.
Instead of ranking results based on [textual relevance](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria) and [custom ranking](/doc/guides/managing-results/must-do/custom-ranking), sorting ranks results based on attributes like price.
This attribute may not be related to relevance.
That means that any result, no matter how relevant
it is, might be the first result.
For example, if a user sorts on price from lowest to highest, a record with a low price may rank highest, even though it has less textual and business relevance than other records.
This type of sorting is called exhaustive sorting or hard sorting.
Despite being common, this behavior tends to hurt the search experience for most use cases because users don't necessarily get what they're looking for.
Returning to the preceding example: suppose a user searches "iPhone" and sorts results from lowest to highest price.
The top results would be cheaper iPhone accessories such as cases when users were actually looking for the least expensive iPhones.

To solve for this behavior when it's not explicitly required, Algolia supports two types of sorts.
## Exhaustive and relevant sorting
Algolia provides two types of sorts:
* Exhaustive or hard sorting
* Relevant sorting
Your use case determines which is a better fit.
### Exhaustive sorting
As [previously described](/doc/guides/managing-results/refine-results/sorting#the-challenge-with-sorting), [exhaustive sorting](/doc/guides/managing-results/refine-results/sorting/in-depth/exhaustive-sort) is "strict" because it reorders all results based on the sorting attributes.
It's intended for use cases that require exhaustive results or where relevance isn't crucial, like an inventory app or other use cases similar to databases.
Exhaustive sorting uses [standard replicas](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas).
### Relevant sorting
[Relevant sorting](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort) reorders only relevant results.
This behavior is a state of the art sorting experience, especially in ecommerce, marketplace, and media content discovery.
Relevant sorting uses [virtual replicas](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas).

## See also
* [Exhaustive sorting](/doc/guides/managing-results/refine-results/sorting/in-depth/exhaustive-sort)
* [Relevant sorting](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort)
# Create a replica index
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas
Replica indices let you use different ranking strategies, such as sorting.
Algolia uses one ranking strategy per index.
If you want to use different rankings for the same data,
you can use [replica indices](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas).
## Standard and virtual replicas
Standard replicas are exact copies of your index.
They let you to have a completely different configuration of your index,
while keeping the data in sync with your primary index.
[Virtual replicas](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas) are an optional feature that references a primary index instead of duplicating it.
They're more efficient than standard replicas but you can only change a subset of the index settings.
Virtual replicas are optimized for [relevant sorting](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort).
This feature isn't available on every plan.
Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included.
## Naming replica indices
To help you identify your replica indices,
adopt a naming pattern,
such as `{primaryIndex}_{sortingAttribute}_{asc_or_desc}`.
For example, `products_price_desc` is a replica index of your `products` index,
where the results are sorted by the `price` attribute in descending order.
## Create replica indices 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 **Replicas** tab, click **Create Replica Index**.
5. In the **Create a new replica** dialog,
select **Standard Replica (default)** or **Virtual Replica for Relevant Sort**,
enter a name for your replica, and click **Create replica**.
6. You can add multiple replicas at once.
7. Click **Review and Save Settings**.
## Create replica indices with the API
To create replica indices,
use the [`replicas`](/doc/api-reference/api-parameters/replicas) setting in your primary index.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings { Replicas = new List { "products_price_desc" } }
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
replicas: [
"products_price_desc",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetReplicas(
[]string{"products_price_desc"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setReplicas(Arrays.asList("products_price_desc"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { replicas: ['products_price_desc'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(replicas = listOf("products_price_desc")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['replicas' => [
'products_price_desc',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"replicas": [
"products_price_desc",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(replicas: ["products_price_desc"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
replicas = Some(Seq("products_price_desc"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(replicas: ["products_price_desc"])
)
```
To create virtual indices, use the `virtual` modifier for the replica index name:
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings { Replicas = new List { "virtual(products_price_desc)" } }
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
replicas: [
"virtual(products_price_desc)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetReplicas(
[]string{"virtual(products_price_desc)"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setReplicas(Arrays.asList("virtual(products_price_desc)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { replicas: ['virtual(products_price_desc)'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(replicas = listOf("virtual(products_price_desc)")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['replicas' => [
'virtual(products_price_desc)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"replicas": [
"virtual(products_price_desc)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(replicas: ["virtual(products_price_desc)"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
replicas = Some(Seq("virtual(products_price_desc)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(replicas: ["virtual(products_price_desc)"])
)
```
## Next steps
After creating your replica indices,
you can change their settings—for example,
by adding a [attribute for sorting](/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute).
# Rename replica indices
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/rename-a-replica-index
Rename a replica index by detaching it from the primary index first.
**You can't rename replica indices directly.**
* Before renaming a **standard replica index**, first, detach it from its primary index. You can then rename it.
* You can't rename [**virtual replicas**](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas#standard-and-virtual-replicas). You need to delete the virtual replica and create a new one.
To rename a replica index in the Algolia dashboard:
1. Go to the [Algolia dashboard](https://dashboard.algolia.com/explorer/browse/) and select the **primary index** whose replica you want to rename. Alternatively, select the **replica index** you want to rename and click the name of its associated primary index under the **Index** heading.

2. With the **primary index** selected, go to the **Replica** tab, select the replica index you want to unlink, and click **Unlink Replica**.

The index is now a regular index.
3. Select the (former) replica index and [rename the index](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/copy-move-indices#rename-indices-in-the-algolia-dashboard).
4. To reattach the renamed replica index, [create a new replica index](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas) and enter the name of the renamed replica index.
# Search in a replica index
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/search-in-a-replica-index
How to search a replica index, to create different sorting options for your users.
A [replica index](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas) is a copy of your primary index that sorts results in alternative ways.
For instance, on an ecommerce site, you might want to sort search results by default from cheapest to most expensive.
You also offer a drop-down menu to let users sort from most expensive to cheapest.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
> [!NOTE]
>
> If you're using InstantSearch,
> use the [`sortBy`](/doc/api-reference/widgets/sort-by/js) widget to let users choose their sorting strategy.
## Create replica indices
Let users sort results in various ways by [creating a replica](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas) for each sorting strategy.
That sorting strategy might order results by price, date, relevance, or any other criteria you provide.
## Switch index as required
When users change the sorting strategy, ensure you switch to the appropriate index (primary or replica).
```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 SearchInReplicaIndex
{
async Task Main(string[] args)
{
var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY"));
var query = "query";
// 1. Change the sort dynamically based on the UI events
var sortByPrice = false;
// 2. Get the index name based on sortByPrice
var indexName = sortByPrice ? "products_price_desc" : "products";
// 3. Search on dynamic index name (primary or replica)
await client.SearchSingleIndexAsync(
indexName,
new SearchParams(new SearchParamsObject { Query = "query" })
);
}
}
```
```dart Dart theme={"system"}
import 'package:algolia_client_search/algolia_client_search.dart';
void searchInReplicaIndex() async {
final client =
SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY');
// 1. Change the sort dynamically based on the UI events
final sortByPrice = false;
// 2. Get the index name based on sortByPrice
// ignore: dead_code
final indexName = sortByPrice ? "products_price_desc" : "products";
// 3. Search on dynamic index name (primary or replica)
await client.searchSingleIndex(
indexName: indexName,
searchParams: SearchParamsObject(
query: "query",
),
);
}
```
```go Go theme={"system"}
package main
import (
"github.com/algolia/algoliasearch-client-go/v4/algolia/search"
)
func searchInReplicaIndex() {
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)
}
// 1. Change the sort dynamically based on the UI events
sortByPrice := false
// 2. Get the index name based on sortByPrice
indexName := "products"
if sortByPrice {
indexName = "products_price_desc"
}
// 3. Search on dynamic index name (primary or replica)
_, err = client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
indexName).WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetQuery("query"))))
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.*;
public class searchInReplicaIndex {
public static void main(String[] args) throws Exception {
try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) {
String query = "query";
// 1. Change the sort dynamically based on the UI events
boolean sortByPrice = false;
// 2. Get the index name based on sortByPrice
String indexName = sortByPrice ? "products_price_desc" : "products";
// 3. Search on dynamic index name (primary or replica)
client.searchSingleIndex(indexName, new SearchParamsObject().setQuery("query"), Hit.class);
} 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');
// 1. Change the sort dynamically based on the UI events
const sortByPrice = false;
// 2. Get the index name based on sortByPrice
const indexName = sortByPrice ? 'products_price_desc' : 'products';
// 3. Search on dynamic index name (primary or replica)
await client.searchSingleIndex({ indexName: indexName, searchParams: { query: 'query' } });
```
```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.*
suspend fun searchInReplicaIndex() {
val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")
// 1. Change the sort dynamically based on the UI events
val sortByPrice = false
// 2. Get the index name based on sortByPrice
val indexName = if (sortByPrice) "products_price_desc" else "products"
// 3. Search on dynamic index name (primary or replica)
client.searchSingleIndex(
indexName = indexName,
searchParams = SearchParamsObject(query = "query"),
)
}
```
```php PHP theme={"system"}
searchSingleIndex(
$indexName,
['query' => 'query',
],
);
```
```python Python theme={"system"}
from algoliasearch.search.client import SearchClientSync
_client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
query = "query"
# 1. Change the sort dynamically based on the UI events
sort_by_price = False
# 2. Get the index name based on sortByPrice
index_name = "products_price_desc" if sort_by_price else "products"
# 3. Search on dynamic index name (primary or replica)
_client.search_single_index(
index_name=index_name,
search_params={
"query": "query",
},
)
```
```ruby Ruby theme={"system"}
require "algolia"
client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")
query = "query"
# 1. Change the sort dynamically based on the UI events
sort_by_price = false
# 2. Get the index name based on sortByPrice
index_name = sort_by_price ? "products_price_desc" : "products"
# 3. Search on dynamic index name (primary or replica)
client.search_single_index(index_name, Algolia::Search::SearchParamsObject.new(query: "query"))
```
```scala Scala theme={"system"}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.Duration
import algoliasearch.api.SearchClient
import algoliasearch.config.*
import algoliasearch.extension.SearchClientExtensions
import algoliasearch.search.SearchParamsObject
def searchInReplicaIndex(): Future[Unit] = {
val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY")
// 1. Change the sort dynamically based on the UI events
val sortByPrice = false
// 2. Get the index name based on sortByPrice
val indexName = if sortByPrice then "products_price_desc" else "products"
// 3. Search on dynamic index name (primary or replica)
client
.searchSingleIndex(
indexName = indexName,
searchParams = Some(
SearchParamsObject(
query = Some("query")
)
)
)
.map { response =>
println(response)
}
.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
func searchInReplicaIndex() async throws {
let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY")
// 1. Change the sort dynamically based on the UI events
let sortByPrice = false
// 2. Get the index name based on sortByPrice
let indexName = sortByPrice ? "products_price_desc" : "products"
// 3. Search on dynamic index name (primary or replica)
let response: SearchResponse = try await client.searchSingleIndex(
indexName: indexName,
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(query: "query"))
)
print(response)
}
```
# Manage multiple indices
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/set-settings-and-forward-to-replicas
Copying index settings to replicas and other indices.
It's common to have several replica indices that differ from their primary index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) by a single setting, such as the sorting strategy.
Products in an ecommerce store are a good example.
You may have a primary index sorted by ascending price, and one or more replicas that sort products by descending price, descending popularity, descending rating, and so on.
Despite these different sorting settings, **you may still want to copy settings from one index to its replicas or other indices**.
For example, if you decide to change the [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes) setting, you probably want it to apply to all indices.
The `forwardToReplicas` parameter lets you forward new settings to replicas.
From there, you can change only the settings that need to be different and keep all others synchronized.
## Example of multiple indices
You have a primary index called `products` and a `products_price_desc` replica.
Both indices have the same data and settings,
apart from the [`ranking`](/doc/api-reference/api-parameters/ranking) setting.
To add a new `description` attribute to the [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes) setting of the primary index and its replicas, use the `forwardToReplicas` parameter.
## Using the API
The [`setSettings`](/doc/libraries/sdk/methods/search/set-settings),
synonyms, and Rules methods, have a `forwardToReplicas` parameter.
Ry default, `forwardToReplicas` is `false`, so any setting, synonym,
or rule change only applies to the target index.
By setting it to `true`, you can forward these changes to all the replicas belonging to the index you target.
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
SearchableAttributes = new List { "name", "description" },
},
true
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
searchableAttributes: [
"name",
"description",
],
),
forwardToReplicas: true,
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetSearchableAttributes(
[]string{"name", "description"})).WithForwardToReplicas(true))
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", "description")),
true
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { searchableAttributes: ['name', 'description'] },
forwardToReplicas: true,
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(searchableAttributes = listOf("name", "description")),
forwardToReplicas = true,
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['searchableAttributes' => [
'name',
'description',
],
],
true,
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"searchableAttributes": [
"name",
"description",
],
},
forward_to_replicas=True,
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(searchable_attributes: ["name", "description"]),
true
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
searchableAttributes = Some(Seq("name", "description"))
),
forwardToReplicas = Some(true)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(searchableAttributes: ["name", "description"]),
forwardToReplicas: true
)
```
> [!NOTE]
>
> Settings are only forwarded to existing replicas.
> If you want to forward settings and create a replica, you first have to create the replica and forward the settings after.
> These must be separate operations.
## Using the dashboard
Whenever you make settings changes,
Algolia's dashboard prompts you to review them before saving and gives you the option to **Copy these settings to other indices and replicas**.
**Whenever you copy settings changes to indices, you replace the previous setting on the index.**
For example, if you change the ranking formula on the primary index to include a sort on an `inventory` attribute,
and then choose to copy the updated [`ranking`](/doc/api-reference/api-parameters/ranking) setting to all replicas,
you overwrite any previously set sorts on other attributes on the replicas.
When altering the ranking formula on indices with replicas,
you are recommended to do it on a per index basis.
While [`customRanking`](/doc/api-reference/api-parameters/customRanking) is part of the ranking formula,
it's a setting as well.
You can alter the custom ranking and forward the change to your replicas without worrying about overwriting the entire ranking formula.
**You usually don't need to worry about overwriting settings,
since you typically want most settings to be the same between replicas.**
Notable exceptions include [`ranking`](/doc/api-reference/api-parameters/ranking) and [`typoTolerance`](/doc/api-reference/api-parameters/typoTolerance).
### Copy synonyms and rules
You can import and export synonyms and rules [from the Algolia dashboard](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/export-import-indices).
# Sort an index by date
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/sort-an-index-by-date
How to sort your records by date.
By design, Algolia provides one [ranking strategy](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria) per [index](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/prepare-data-in-depth#algolia-index): when you want to provide different rankings for the same data, **you need to use different indices for each ranking**.
These indices are called [replicas](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas).
> [!NOTE]
>
> To set up sorting by attribute,
> you need to understand how [replica indices](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas) work.
To sort by attribute, create a replica index and then change the ranking formula of the replica.
You can do this from Algolia's dashboard or through the API.
For chronological sorting, consider [how Algolia handles dates](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#dates).
For example, you have a blog and want to create a replica to sort search results from the most recent to the oldest post.
Because Algolia doesn't interpret dates as ISO 8601 strings (such as "2008-09-15T15:53:00"),
**you must convert your dates into Unix timestamps** (numeric values such as 1221486780) before sorting them.
## Convert dates into Unix timestamps: an example
### Before
Say you have an index called `articles`:
```json JSON icon=braces theme={"system"}
[
{
"post_title": "Let's start the adventure",
"post_date": "2012-07-01",
"author_name": "Nicolas Dessaigne",
"author_image_url": "https://secure.gravatar.com/avatar/785489bc2ac2e08ae66648a8936c1101?s=40&d=mm&r=g",
"permalink": "https://blog.algolia.com/lets-start-the-adventure/",
"excerpt": "Welcome to The Algolia Blog! It's always difficult to write the first post of a blog! What should I talk about? The company, the founders, the business, the culture? And all that knowing that virtually nobody will read except diggers in a few years (hopefully)!\nLet's concentrate instead on what we'll be blogging about. Company news obviously, but not only. I expect we'll write quite a few posts about technology, algorithms, entrepreneurship, marketing, and whatever else we'll want to share with you 🙂\nAnd most important, feel free to participate in comments or by contacting us directly. We appreciate your feedback!\nWelcome to the Algolia blog!"
},
{
"post_title": "Great discussions at LeWeb'12 London",
"post_date": "2012-07-03",
"author_name": "Nicolas Dessaigne",
"author_image_url": "https://secure.gravatar.com/avatar/785489bc2ac2e08ae66648a8936c1101?s=40&d=mm&r=g",
"permalink": "https://blog.algolia.com/great-discussions-at-leweb12-london/",
"image": "https://blog.algolia.com/wp-content/uploads/2014/03/latency-360x200.png",
"excerpt": "… take long for us to decide it was the way to go, since the perception of speed is so natural that the benefit far outweighs the longer integration code. We'll now work on simplifying it!\nWe'll soon do a post about this demo. In the meantime, stay tuned!\n \n "
}
]
```
### After
You want to create a replica that sorts your data by date.
The problem is that the `post_date` attribute has dates formatted as strings, which Algolia can't process for sorting.
Before creating a replica, you must transform these dates into [Unix timestamps](https://wikipedia.org/wiki/Unix_time).
You don't have to remove or change `post_date`.
Add a `post_date_timestamp` attribute with the proper format instead.
```json JSON icon=braces theme={"system"}
[
{
"post_title": "Let's start the adventure",
"post_date": "2012-07-01",
"post_date_timestamp": 1341100800,
"author_name": "Nicolas Dessaigne",
"author_image_url": "https://secure.gravatar.com/avatar/785489bc2ac2e08ae66648a8936c1101?s=40&d=mm&r=g",
"permalink": "https://blog.algolia.com/lets-start-the-adventure/",
"excerpt": "Welcome to The Algolia Blog! It's always difficult to write the first post of a blog! What should I talk about? The company, the founders, the business, the culture? And all that knowing that virtually nobody will read except diggers in a few years (hopefully)!\nLet's concentrate instead on what we'll be blogging about. Company news obviously, but not only. I expect we'll write quite a few posts about technology, algorithms, entrepreneurship, marketing, and whatever else we'll want to share with you 🙂\nAnd most important, feel free to participate in comments or by contacting us directly. We appreciate your feedback!\nWelcome to the Algolia blog!"
},
{
"post_title": "Great discussions at LeWeb'12 London",
"post_date": "2012-07-03",
"post_date_timestamp": 1341273600,
"author_name": "Nicolas Dessaigne",
"author_image_url": "https://secure.gravatar.com/avatar/785489bc2ac2e08ae66648a8936c1101?s=40&d=mm&r=g",
"permalink": "https://blog.algolia.com/great-discussions-at-leweb12-london/",
"image": "https://blog.algolia.com/wp-content/uploads/2014/03/latency-360x200.png",
"excerpt": "… take long for us to decide it was the way to go, since the perception of speed is so natural that the benefit far outweighs the longer integration code. We'll now work on simplifying it!\nWe'll soon do a post about this demo. In the meantime, stay tuned!\n \n "
}
]
```
## Create a replica
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
Create a replica of your `articles` index.
The recommendation is to name your replica indices with a prefix or suffix describing its sorting strategy (for example, `articles_date_desc`).
**1. Create replica**
[Create a standard or virtual replica](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas) (`articles_date_desc`) from the primary index.
The primary index is likely sorted by relevance but you want to change that for `articles_date_desc`.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings { Replicas = new List { "articles_date_desc" } }
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
replicas: [
"articles_date_desc",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetReplicas(
[]string{"articles_date_desc"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setReplicas(Arrays.asList("articles_date_desc"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { replicas: ['articles_date_desc'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(replicas = listOf("articles_date_desc")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['replicas' => [
'articles_date_desc',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"replicas": [
"articles_date_desc",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(replicas: ["articles_date_desc"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
replicas = Some(Seq("articles_date_desc"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(replicas: ["articles_date_desc"])
)
```
**2. Sort the records**
Use the `post_date_timestamp` attribute to sort the `articles_date_desc` virtual index by date in descending order:
> [accordion] Sort a standard replica
>
> ```cs C# theme={"system"}
> var response = await client.SetSettingsAsync(
> "INDEX_NAME",
> new IndexSettings { Ranking = new List { "desc(post_date_timestamp)" } }
> );
> ```
>
> ```dart Dart theme={"system"}
> final response = await client.setSettings(
> indexName: "INDEX_NAME",
> indexSettings: IndexSettings(
> ranking: [
> "desc(post_date_timestamp)",
> ],
> ),
> );
> ```
>
> ```go Go theme={"system"}
> response, err := client.SetSettings(client.NewApiSetSettingsRequest(
> "INDEX_NAME",
> search.NewEmptyIndexSettings().SetRanking(
> []string{"desc(post_date_timestamp)"})))
> 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(post_date_timestamp)"))
> );
> ```
>
> ```js JavaScript theme={"system"}
> const response = await client.setSettings({
> indexName: 'theIndexName',
> indexSettings: { ranking: ['desc(post_date_timestamp)'] },
> });
> ```
>
> ```kotlin Kotlin theme={"system"}
> var response =
> client.setSettings(
> indexName = "INDEX_NAME",
> indexSettings = IndexSettings(ranking = listOf("desc(post_date_timestamp)")),
> )
> ```
>
> ```php PHP theme={"system"}
> $response = $client->setSettings(
> 'INDEX_NAME',
> ['ranking' => [
> 'desc(post_date_timestamp)',
> ],
> ],
> );
> ```
>
> ```python Python theme={"system"}
> response = client.set_settings(
> index_name="INDEX_NAME",
> index_settings={
> "ranking": [
> "desc(post_date_timestamp)",
> ],
> },
> )
> ```
>
> ```ruby Ruby theme={"system"}
> response = client.set_settings(
> "INDEX_NAME",
> Algolia::Search::IndexSettings.new(ranking: ["desc(post_date_timestamp)"])
> )
> ```
>
> ```scala Scala theme={"system"}
> val response = Await.result(
> client.setSettings(
> indexName = "INDEX_NAME",
> indexSettings = IndexSettings(
> ranking = Some(Seq("desc(post_date_timestamp)"))
> )
> ),
> Duration(100, "sec")
> )
> ```
>
> ```swift Swift theme={"system"}
> let response = try await client.setSettings(
> indexName: "INDEX_NAME",
> indexSettings: IndexSettings(ranking: ["desc(post_date_timestamp)"])
> )
> ```
> [accordion] Sort a virtual replica
>
> ```cs C# theme={"system"}
> var response = await client.SetSettingsAsync(
> "INDEX_NAME",
> new IndexSettings { CustomRanking = new List { "desc(post_date_timestamp)" } }
> );
> ```
>
> ```dart Dart theme={"system"}
> final response = await client.setSettings(
> indexName: "INDEX_NAME",
> indexSettings: IndexSettings(
> customRanking: [
> "desc(post_date_timestamp)",
> ],
> ),
> );
> ```
>
> ```go Go theme={"system"}
> response, err := client.SetSettings(client.NewApiSetSettingsRequest(
> "INDEX_NAME",
> search.NewEmptyIndexSettings().SetCustomRanking(
> []string{"desc(post_date_timestamp)"})))
> if err != nil {
> // handle the eventual error
> panic(err)
> }
> ```
>
> ```java Java theme={"system"}
> UpdatedAtResponse response = client.setSettings(
> "INDEX_NAME",
> new IndexSettings().setCustomRanking(Arrays.asList("desc(post_date_timestamp)"))
> );
> ```
>
> ```js JavaScript theme={"system"}
> const response = await client.setSettings({
> indexName: 'theIndexName',
> indexSettings: { customRanking: ['desc(post_date_timestamp)'] },
> });
> ```
>
> ```kotlin Kotlin theme={"system"}
> var response =
> client.setSettings(
> indexName = "INDEX_NAME",
> indexSettings = IndexSettings(customRanking = listOf("desc(post_date_timestamp)")),
> )
> ```
>
> ```php PHP theme={"system"}
> $response = $client->setSettings(
> 'INDEX_NAME',
> ['customRanking' => [
> 'desc(post_date_timestamp)',
> ],
> ],
> );
> ```
>
> ```python Python theme={"system"}
> response = client.set_settings(
> index_name="INDEX_NAME",
> index_settings={
> "customRanking": [
> "desc(post_date_timestamp)",
> ],
> },
> )
> ```
>
> ```ruby Ruby theme={"system"}
> response = client.set_settings(
> "INDEX_NAME",
> Algolia::Search::IndexSettings.new(custom_ranking: ["desc(post_date_timestamp)"])
> )
> ```
>
> ```scala Scala theme={"system"}
> val response = Await.result(
> client.setSettings(
> indexName = "INDEX_NAME",
> indexSettings = IndexSettings(
> customRanking = Some(Seq("desc(post_date_timestamp)"))
> )
> ),
> Duration(100, "sec")
> )
> ```
>
> ```swift Swift theme={"system"}
> let response = try await client.setSettings(
> indexName: "INDEX_NAME",
> indexSettings: IndexSettings(customRanking: ["desc(post_date_timestamp)"])
> )
> ```
# Sort by attribute
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute
Learn how to sort an index by an attribute.
By default, Algolia puts the most relevant items first in results.
However, you might want to sort results by a specific numeric or boolean attribute, like price.
To sort your search results by a specific attribute:
1. [Create a replica index](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas)
2. Configure an attribute for sorting
3. Update your user interface
## Replica indices
Think of a replica index as a duplicate of your primary index but with a different sorting of results.
Choose from two types of replicas: [standard or virtual](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas#standard-and-virtual-replicas):
* Use a standard replica for exhaustive sorting.
This method sorts results by the chosen attribute.
* Use a virtual replica for relevant sorting.
This method prioritizes relevant results while still incorporating the sorting preference for the chosen attribute.
## Configure an attribute for sorting in the Algolia dashboard
Sort indices by numeric or boolean attributes.
1. [Create a replica index](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas#create-replica-indices-in-the-dashboard).
2. Refresh the dashboard page in your browser.
3. Select the replica index.
4. On the **Configuration** tab, go to **Relevant sort** or **Ranking and Sorting**.
You'll see either option, depending on the type of replica you want to configure (virtual or standard).
5. Click **+Add a sort attribute** or **+Add sort-by attribute**
6. Type in the name of the attribute you want to sort.
Be careful here: Algolia doesn't check to see if what you type matches the name of an attribute in the index.
7. Determine the sort direction (**Ascending** or **Descending**).
8. Review and save your changes.
> [!NOTE]
>
> To rank records alphabetically by a string attribute, use [`customRanking`](/doc/api-reference/api-parameters/customRanking).
> For longer strings, calculate a numeric ranking value in advance or add more custom ranking attributes as tie-breakers.
> For more information, see [String and null or absent values](/doc/guides/managing-results/must-do/custom-ranking/how-to/configure-custom-ranking#string-and-null-or-absent-values).
## Configure an attribute for sorting with the API
1. [Create a replica index](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas#create-replica-indices-with-the-api).
2. Update the [`ranking`](/doc/api-reference/api-parameters/ranking) parameter of the replica index with the [`setSettings`](/doc/libraries/sdk/v1/methods/set-settings) method.
## Update user interface
To let users select between different rankings in your user interface,
you also need to update it—for example, by providing a [`sort-by`](/doc/api-reference/widgets/sort-by/js) widget.
## Example: relevant sort by price
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
This example applies relevant sorting to a virtual replica.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings { CustomRanking = new List { "desc(price)" } }
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
customRanking: [
"desc(price)",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetCustomRanking(
[]string{"desc(price)"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setCustomRanking(Arrays.asList("desc(price)"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { customRanking: ['desc(price)'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(customRanking = listOf("desc(price)")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['customRanking' => [
'desc(price)',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"customRanking": [
"desc(price)",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(custom_ranking: ["desc(price)"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
customRanking = Some(Seq("desc(price)"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(customRanking: ["desc(price)"])
)
```
> [!TIP]
>
> For virtual replicas, you only need to include the [`customRanking`](/doc/api-reference/api-parameters/customRanking) attribute used for sorting: `price`.
## Example: exhaustive sort by price
This example applies exhaustive sorting to a standard replica.
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
Ranking = new List
{
"desc(price)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
},
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
ranking: [
"desc(price)",
"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(price)", "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(price)", "typo", "geo", "words", "filters", "proximity", "attribute", "exact", "custom")
)
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: {
ranking: ['desc(price)', 'typo', 'geo', 'words', 'filters', 'proximity', 'attribute', 'exact', 'custom'],
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings =
IndexSettings(
ranking =
listOf(
"desc(price)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
)
),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['ranking' => [
'desc(price)',
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
'custom',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"ranking": [
"desc(price)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(
ranking: ["desc(price)", "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(price)", "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(price)",
"typo",
"geo",
"words",
"filters",
"proximity",
"attribute",
"exact",
"custom",
])
)
```
> [!TIP]
>
> For standard replicas, you need to provide **all** ranking attributes.
# UI sorting widgets
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/use-sorting-ui-widget
How to sort in InstantSearch with UI widgets
Using replicas with different sorting strategies provides a way for users to change sorting on the frontend.
For example, if you have an ecommerce website, and by default, want to sort search results from cheapest to most expensive.
You might want to provide a drop-down menu or toggle switch to let users sort from most expensive to cheapest.
Because you have one replica index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) per sorting strategy,
you need to **change what index Algolia must search into when users change the sorting method**.
While you could [handle it yourself](/doc/guides/managing-results/refine-results/sorting/how-to/search-in-a-replica-index), it's easier to use the InstantSearch [`sortBy`](/doc/api-reference/widgets/sort-by/js) and [`relevantSort`](/doc/api-reference/widgets/relevant-sort/js) widgets.
## Set up replicas
Before you implement the search, you need to have replicas for each sorting strategy you want to provide.
For more information, see [Sort by attribute](/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute).
## Sorting widget
### Sort with `sortBy`
Dropdowns are a familiar widget, common in ecommerce scenarios and power user workflows.
[InstantSearch](/doc/guides/building-search-ui/what-is-instantsearch/js) provides sorting dropdowns with the [`sortBy`](/doc/api-reference/widgets/sort-by/js) widget.
```js JavaScript theme={"system"}
search.addWidgets([
instantsearch.widgets.sortBy({
container: "#sort-by-container",
items: [
{ value: "products", label: "Most relevant" },
{ value: "products_price_desc", label: "Highest price" },
],
}),
]);
```
```jsx React theme={"system"}
;
```
```vue Vue theme={"system"}
```
### Relevance sort toggle with `relevantSort`
If you're using [Relevant sort](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort) you can use the [`relevantSort`](/doc/api-reference/widgets/relevant-sort/js) widget to let users toggle between relevant and regular sorting.
The [`relevantSort`](/doc/api-reference/widgets/relevant-sort/js) widget comes with recommended patterns when using Relevant sort, such as displaying the number of sorted results out of the total number of results, and letting users see more results.
```js JavaScript theme={"system"}
search.addWidgets([
instantsearch.widgets.relevantSort({
container: "#relevant-sort",
templates: {
button({ isRelevantSorted }) {
return isRelevantSorted ? "See all results" : "See relevant results";
},
},
}),
]);
```
```jsx React theme={"system"}
const TextComponent = ({ isRelevantSorted }) => (
{isRelevantSorted
? 'We removed some search results to show you the most relevant ones'
: 'Currently showing all results'}
);
const ButtonTextComponent = ({ isRelevantSorted }) => (
{isRelevantSorted ? 'See all results' : 'See relevant results'}
);
```
```vue Vue theme={"system"}
We removed some search results to show you the most relevant ones
Currently showing all results
See all results
See relevant results
```
# Exhaustive sorting
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/exhaustive-sort
Learn how to exhaustively sort by an attribute using standard replicas.
Exhaustive sorting refers to strictly reordering results by an attribute.
For example, you can exhaustively sort results from lowest to highest price,
regardless of [textual relevance](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria)
and [custom ranking](/doc/guides/managing-results/must-do/custom-ranking).
It's intended for use cases requiring strict sorting or where relevance isn't essential,
like an inventory app or a database.
An alternative to exhaustive sorting is [relevant sorting](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort).
## Effect on ranking formula
In Algolia, each of your indices has a ranking formula of two or three parts:
* [Sort-by attributes](/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute) (optional)
* [Textual ranking criteria](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria)
* [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking), also known as business ranking criteria
### Sort-by attribute
> [!NOTE]
>
> Only add a [sort-by attribute](/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute) to [replica indices](#backend-implementation).
If a sort-by attribute is present, Algolia first orders results by the sort-by attribute's value.
For example, suppose you have an index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) where you've set "price (ascending)" as a sort-by attribute.
For the search query (The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.) `iphone`, Algolia first selects all records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) that match `iphone` (and alternatives like prefixes, typos, and synonyms) in the [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes).
Then, it orders the results from lowest to highest price.
* After sorting by the sort-by attribute's value (price), Algolia applies its default [textual ranking criteria](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria) to records with the same price.
* If records have the same values for all textual ranking criteria, Algolia orders them by [custom ranking](/doc/guides/managing-results/must-do/custom-ranking).
In summary, **textual ranking criteria come after your selected sort-by attribute.**
With exhaustive sorting,
textual ranking criteria have little effect if the sort-by attribute values are sufficiently granular or unique.
For example, consider exhaustively sorting on a timestamp attribute down to the millisecond.
If none of your records have the same timestamp value,
the timestamp is the only attribute that plays a role in ranking.
If this is the intended behavior, no further changes are needed.
Otherwise, reduce the timestamp granularity or use [relevant sorting](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort) instead.
To learn more, see:
* [Sort by attribute](/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute)
* [Sort an index by date](/doc/guides/managing-results/refine-results/sorting/how-to/sort-an-index-by-date)
## Backend implementation
To ensure a fast search experience, the engine sorts your data at indexing time.
Therefore, each of your indices can only be sorted in one way.
However, to allow for multiple sort orders, Algolia uses replica indices.
### Exhaustive sorting uses standard index replicas
A replica is a copy of one of your indices,
with the same data and synchronized data updates, but can have unique settings.
The index from which you copy a replica's data is the replica's primary index.
**If you want to configure an exhaustive sort-by, use [standard replicas](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas#standard-and-virtual-replicas).**
For each attribute you want to sort by,
create a replica index,
which contains a copy of all the data in your primary index.
## Frontend implementation
Replica indices manage the backend of sorting, but you still need to implement the frontend with
custom logic or [InstantSearch's SortBy UI widget](/doc/guides/managing-results/refine-results/sorting/how-to/use-sorting-ui-widget).

## Sorting and optional filters
[Optional filters](/doc/api-reference/api-parameters/optionalFilters) are applied *after* the sort-by attribute.
Therefore, you can't use rules to boost or bury records with indices with sort-by attributes.
# Relevant sorting
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort
Learn about the relevant sorting feature and how you can use it to provide more relevant results for users.
Use relevant sorting to only show the most relevant results for a user's search instead of displaying all records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) (exhaustive sorting).
This feature isn't available on every plan.
Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included.
## Examples
These examples illustrate the difference between [exhaustive](/doc/guides/managing-results/refine-results/sorting/in-depth/exhaustive-sort) and relevant sorting.
Exhaustive sorting returns and sorts all results that match the search term `iphone`:
here, a list of phone cases appear at the top of the results.
For the same search term, relevant sorting has applied a virtual replica's [custom ranking](/doc/guides/managing-results/must-do/custom-ranking) criteria to ensure that iPhone devices appear at the top of the results.
### Exhaustive sorting

### Relevant sorting

## Effect of relevant sorting on the ranking formula
Algolia's [ranking formula](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria) isn't overridden when you use relevant sorting. Instead, the relevant sorting algorithm:
1. Sorts results using the ranking formula of the primary index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.).
2. Selects the most relevant results. You can determine how strict this should be by adjusting [`relevancyStrictness`](/doc/api-reference/api-parameters/relevancyStrictness) on a [virtual replica](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas#standard-and-virtual-replicas).
3. Orders the selected results according to the primary index's [`sortBy`](/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute) attributes (if present) and the virtual replica's [`customRanking`](/doc/api-reference/api-parameters/customRanking).
4. Settle remaining ties according to the [filter score](/doc/guides/managing-results/refine-results/filtering/in-depth/filter-scoring).
5. Returns the relevant results.
The ranking formula determines which results are most relevant to the search query (The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.),
but custom ranking determines the order of results.
This means that parameters that affect the ranking formula don't affect result ordering.
If multiple results have the same value for a sorting attribute,
the other sorting attributes are used to decide which one comes first.
For example, if you sort according to price and then popularity,
and three results have the same cost,
the engine will prioritize them according to their popularity score.
For more information, see:
* [Sort by attribute](/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute)
* [Sort an index by date](/doc/guides/managing-results/refine-results/sorting/how-to/sort-an-index-by-date)
## Relevant sorting considerations
Relevant sorting interacts with other features in ways you might not expect.
### Other re-ranking features can affect results
Other re-ranking features, such as rules, apply after relevant sorting and will modify sort order.
For example, if [Personalization](/doc/guides/personalization/classic-personalization/what-is-personalization) promotes expensive items for a particular user based on their past actions,
that user might see expensive items at the top of the results, even if they've chosen to *relevant sort* from lowest to highest price.
To ensure predictable behavior, don't use the following features or parameters with relevant sorting:
* [`distinct`](/doc/api-reference/api-parameters/distinct) > 1
* [Dynamic Re-ranking](/doc/guides/algolia-ai/re-ranking)
* [Personalization](/doc/guides/personalization/classic-personalization/personalizing-results)
### Facets count
When relevant sorting is enabled, Algolia still computes each facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) on all results, not just those defined by the relevant sort.
A [UI banner](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/add-banners) detailing the behavior of the relevant sort may help manage user expectations.
### Enabling relevant sorting reduces the number of results
Essentially, that's the point: it gives users more relevant results, not all results ([exhaustive sorting](/doc/guides/managing-results/refine-results/sorting/in-depth/exhaustive-sort)).
For example, you search for `chromebook` on an ecommerce site and get Chromebook computers at the top of your results (so far, so good).
However, if you then sort by ascending price (because you want a cheaper computer),
the top (cheapest) listed items might be "Chromebook power adapters".
With a relevant sort, you get fewer results but only see the relevant items (computers).
Using a virtual replica even with `relevancyStrictness` set to 0 can't mimic the exhaustive sorting of a standard replica because it doesn't use the same algorithm. Be aware that a virtual replica:
* Doesn't duplicate the data. It sorts at query time.
* Can only retrieve a maximum of 20k results, so there's no guarantee that it will fetch the "deepest" hits.
> [!TIP]
>
> When using virtual replicas, use `nbSortedHits` rather than `nbHits` to get the number of results.
### Relevant sort doesn't apply when browsing an index
When you use the [Browse](/doc/libraries/sdk/methods/search/browse) method on a virtual replica index, records are returned in the same order as the primary index.
This means that relevant sort parameters, such as [relevancy strictness](/doc/api-reference/api-parameters/relevancyStrictness), don't have any effect.
## How to configure relevant sorting
### Create a virtual replica index
If you want to configure relevant sorting,
you first need to create a [virtual replica index](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas#standard-and-virtual-replicas) for each attribute you want to sort by.
Virtual replicas provide an alternative "view" of the primary index.
Virtual replicas reuse the primary's ranking formula to return relevant results.
For that reason, you can't change the [`ranking`](/doc/api-reference/api-parameters/ranking)
on virtual replicas but can use [`customRanking`](/doc/api-reference/api-parameters/customRanking)
to define the sort criteria.
For more information, see:
* [Understanding replicas](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas)
* [What is the effect of a replica on the number of records?](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas-impact-on-pricing)
### Connect your UI to your index
Virtual replica indices manage the backend of sorting, but you still need to implement the frontend.
You can do this with either custom logic or [InstantSearch](/doc/guides/building-search-ui/what-is-instantsearch/js).
If you're using InstantSearch, use the [`sortBy` UI widget](/doc/guides/managing-results/refine-results/sorting/how-to/use-sorting-ui-widget).

To ensure users understand how relevant sort works and how they can control their experience:
* Indicate that sorting removed some results
* Offer them the option of displaying more.
You can do this with the `relevantSort` widget:

For more details, see:
* [`sortBy` widget](/doc/api-reference/widgets/sort-by/js)
* [`relevantSort` widget](/doc/api-reference/widgets/relevant-sort/js)
# Understanding replicas
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/replicas
What replicas are and how to handle them.
Algolia has one ranking formula per index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.).
Every index has a unique sorting strategy but you can't change it at query time.
This is because pre-sorting during indexing instead of at query time leads to a considerable performance boost.
**The key use of replica indices is they let you provide different rankings for the same data.**
Algolia lets you automatically replicate the content of one index (the primary) onto other indices (standard and virtual replicas) and synchronize content changes.
## Standard and virtual replicas
Standard or virtual replicas are optimized for different use cases:
* [**Exhaustive sorting**](/doc/guides/managing-results/refine-results/sorting/how-to/sort-by-attribute).
Use standard replicas if you want to show everything that matches,
strictly sorted by a chosen attribute.
For example, on a news site sort articles by publication date.
* [**Relevant sorting**](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort).
Use virtual replicas to prioritize relevant results while still incorporating the sorting preference for the selected attribute.
For example, searching for "notebook" should show laptops first, not notebook accessories.
This feature isn't available on every plan.
Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included.
### Similarities
* You can [search a replica index](/doc/guides/managing-results/refine-results/sorting/how-to/search-in-a-replica-index) as you would any primary index.
* You can't add, update, or delete records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in replica indices.
* Both replica types synchronize with any updates made to records in the primary index. Every time you add, update, or delete records in your primary index, your replica is automatically modified. You can't alter this automatic data synchronization.
#### Index settings
Replicas start with the same settings as their primary index, but you can change them.
For example, to sort:
* By price
* In ascending order
* In descending order
* By most viewed
Algolia applies one sorting strategy to each index.
You can only sort by attributes with **boolean or numerical values**.
If you want to sort by dates, [convert the dates to Unix timestamps](/doc/guides/managing-results/refine-results/sorting/how-to/sort-an-index-by-date).
To keep replica settings in sync with your primary index, set the primary index's `forwardToReplicas` parameter to `true` with the [`setSettings`](/doc/libraries/sdk/v1/methods/set-settings) method.
### Differences
| | Standard replicas | Virtual replicas |
| - | - | - |
| **Records** | [Increases](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas-impact-on-pricing) the number of records since it's a *copy* of its primary index | Doesn't increase the number of records since it's just a *view* of its primary index. It does slightly increase the primary index's *size* (but by less than 10 MB per million records) |
| **Usage** | Optimized for exhaustive sorting | Optimized for relevant sorting |
| **Number of replicas per index** | Unlimited | 20 |
| **Supported index settings** | All | [A subset](#supported-index-settings-for-virtual-replicas) |
| **Synonyms** | Supports all synonym types | Supports most synonym types, except [placeholders](/doc/guides/managing-results/optimize-search-results/adding-synonyms#placeholders) |
| **[API response](/doc/rest-api/search/search-single-index) that returns the number of hits** | `nbHits` | `nbSortedHits` |
## Manage replicas
For more information about managing replica indices, see:
* [Create a replica index](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas)
* [Manage multiple indices](/doc/guides/managing-results/refine-results/sorting/how-to/set-settings-and-forward-to-replicas)
* [Delete replica indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/delete-indices#delete-indices-with-replicas)
## Supported index settings for virtual replicas
Virtual replicas support a *subset* of the index settings.
### Supported settings
* [`advancedSyntax`](/doc/api-reference/api-parameters/advancedSyntax)
* [`advancedSyntaxFeatures`](/doc/api-reference/api-parameters/advancedSyntaxFeatures)
* [`allowTyposOnNumericTokens`](/doc/api-reference/api-parameters/allowTyposOnNumericTokens)
* [`alternativesAsExact`](/doc/api-reference/api-parameters/alternativesAsExact)
* [`aroundPrecision`](/doc/api-reference/api-parameters/aroundPrecision)
* [`attributeCriteriaComputedByMinProximity`](/doc/api-reference/api-parameters/attributeCriteriaComputedByMinProximity)
* [`attributesToHighlight`](/doc/api-reference/api-parameters/attributesToHighlight)
* [`attributesToRetrieve`](/doc/api-reference/api-parameters/attributesToRetrieve)
* [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet)
* [`distinct`](/doc/api-reference/api-parameters/distinct)
* [`enablePersonalization`](/doc/api-reference/api-parameters/enablePersonalization)
* [`enableRules`](/doc/api-reference/api-parameters/enableRules)
* [`exactOnSingleWordQuery`](/doc/api-reference/api-parameters/exactOnSingleWordQuery)
* [`highlightPostTag`](/doc/api-reference/api-parameters/highlightPostTag)
* [`highlightPreTag`](/doc/api-reference/api-parameters/highlightPreTag)
* [`hitsPerPage`](/doc/api-reference/api-parameters/hitsPerPage)
* [`ignorePlurals`](/doc/api-reference/api-parameters/ignorePlurals)
* [`maxFacetHits`](/doc/api-reference/api-parameters/maxFacetHits)
* [`maxValuesPerFacet`](/doc/api-reference/api-parameters/maxValuesPerFacet)
* [`minProximity`](/doc/api-reference/api-parameters/minProximity)
* [`minWordSizefor1Typo`](/doc/api-reference/api-parameters/minWordSizefor1Typo)
* [`minWordSizefor2Typos`](/doc/api-reference/api-parameters/minWordSizefor2Typos)
* [`paginationLimitedTo`](/doc/api-reference/api-parameters/paginationLimitedTo)
* [`queryLanguages`](/doc/api-reference/api-parameters/queryLanguages)
* [`queryType`](/doc/api-reference/api-parameters/queryType)
* [`removeStopWords`](/doc/api-reference/api-parameters/removeStopWords)
* [`replaceSynonymsInHighlight`](/doc/api-reference/api-parameters/replaceSynonymsInHighlight)
* [`responseFields`](/doc/api-reference/api-parameters/responseFields)
* [`restrictHighlightAndSnippetArrays`](/doc/api-reference/api-parameters/restrictHighlightAndSnippetArrays)
* [`snippetEllipsisText`](/doc/api-reference/api-parameters/snippetEllipsisText)
* [`sortFacetValuesBy`](/doc/api-reference/api-parameters/sortFacetValuesBy)
* [`typoTolerance`](/doc/api-reference/api-parameters/typoTolerance)
* [`unretrievableAttributes`](/doc/api-reference/api-parameters/unretrievableAttributes)
### Unsupported settings
You can't change the following parameters for virtual indices.
Instead, change them on the corresponding primary index or create a standard replica.
* [`attributeForDistinct`](/doc/api-reference/api-parameters/attributeForDistinct)
* [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting)
* [`camelCaseAttributes`](/doc/api-reference/api-parameters/camelCaseAttributes)
* [`customNormalization`](/doc/api-reference/api-parameters/customNormalization)
* [`decompoundedAttributes`](/doc/api-reference/api-parameters/decompoundedAttributes)
* [`disableExactOnAttributes`](/doc/api-reference/api-parameters/disableExactOnAttributes)
* [`disablePrefixOnAttributes`](/doc/api-reference/api-parameters/disablePrefixOnAttributes)
* [`disableTypoToleranceOnAttributes`](/doc/api-reference/api-parameters/disableTypoToleranceOnAttributes)
* [`disableTypoToleranceOnWords`](/doc/api-reference/api-parameters/disableTypoToleranceOnWords)
* [`indexLanguages`](/doc/api-reference/api-parameters/indexLanguages)
* [`keepDiacriticsOnCharacters`](/doc/api-reference/api-parameters/keepDiacriticsOnCharacters)
* [`numericAttributesForFiltering`](/doc/api-reference/api-parameters/numericAttributesForFiltering)
* [`optionalWords`](/doc/api-reference/api-parameters/optionalWords)
* [`ranking`](/doc/api-reference/api-parameters/ranking)
* [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes)
* [`separatorsToIndex`](/doc/api-reference/api-parameters/separatorsToIndex)
* [`userData`](/doc/api-reference/api-parameters/userData)
# What is the effect of a replica on the number of records?
Source: https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/replicas-impact-on-pricing
Find out how replicas impact the number of records. Standard replicas increase the number of records but virtual replicas don't.
## Number of records
Algolia counts the number of records differently depending on the [replica type](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas).
This means the count differs between standard and virtual replicas.
### Standard replicas
Standard replica indices "replicate" or copy the primary index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) records.
Standard replicas have the same content but can have their own configuration.
For example, say your primary index has 1,000 records.
If you create two standard replicas, you end up with 3,000 records in total.
The total number of records for an index equals the records in its primary index.
Multiply that by the replica count to get the total.
* total records size = number of records in primary index \* (1 + number of standard replicas).
### Virtual replicas
Virtual replicas are a "view" of their primary indices.
They don't increase the number of records.
## Indexing operations
Only operations on the primary index count as indexing operations.
This applies to both standard and virtual replicas.
* You send data to a primary index, or update existing data. Algolia then automatically forwards the changes to your replica indices. These changes don't add anything to your operation count.
* The `forwardToReplicas` setting forwards settings from your primary index to your replicas without adding extra operation counts.
* Adding settings directly to your standard or virtual replicas count as one operation.
* Adding synonyms and Rules to your standard or virtual replicas count as operations.
## What happens when you detach a replica from its primary?
You can turn a standard or virtual replica into a regular index by
[un-linking it from its primary index](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/delete-indices#unlink-replica-indices).
After that, all its indexing operations count toward your monthly operations total.
# Relevance overview
Source: https://www.algolia.com/doc/guides/managing-results/relevance-overview
Learn how to achieve strong relevance and improve it.
[**Relevance**](/doc/guides/managing-results/relevance-overview/in-depth/defining-relevance) is finding results that match a search query (The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.) and ranking them so the best-matched results appear at the top of the list.
* To **find results**, Algolia uses [text-based](/doc/guides/managing-results/relevance-overview/in-depth/textual-matching) comparisons like typos and stop words, and non-textual factors such as filter (A filter is a condition that limits which records Algolia returns. Filters often use one or more facet-value pairs, such as brand:Apple AND color:red. You can also filter by numeric values, dates, tags, booleans, or geographic constraints.) criteria and geolocation.
* **Ranking**, which determines the order of results, relies on a [tie-breaking algorithm](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria) to present the most relevant items first.
Good relevance is vital because it directly affects the quality of user's search results.
In ecommerce, poorly ranked results decrease user satisfaction and conversion rate (CVR) (The percentage of searches with `clickAnalytics` set to `true` that result in at least one conversion event.).
Some common indications that you need to improve relevance are:
* **Too many irrelevant results**. Relevance suffers when searching large text attributes. Consider searching smaller, targeted attributes instead.
* **Important content is buried**. You haven't implemented custom ranking or failed to set some key attributes as searchable.
> [card] [Algolia Academy](https://academy.algolia.com/pages/contentcatalog/training/0199aad2-7472-71b5-9d8b-711feb5d698b/overview)
>
> Learn more about: Configuring Textual Relevance via the Dashboard
> [card] [Algolia Academy](https://academy.algolia.com/pages/contentcatalog/training/0199c715-19fe-7c72-b416-45bd89089083/overview)
>
> Learn more about: Configuring Textual Relevance via the API
## How to improve relevance
If you don't configure anything else, change these two settings:
* Choose a good set of [**searchable attributes**](/doc/guides/managing-results/must-do/searchable-attributes).
* Apply [**custom ranking**](/doc/guides/managing-results/must-do/custom-ranking) to adapt Algolia to your needs.
### Further relevance optimizations
Algolia has some key features to let you further enhance relevance:
* Use [**rules**](/doc/guides/managing-results/rules/rules-overview) to override relevance in some situations. For example, for seasonal product promotions.
* Use [**synonyms**](/doc/guides/managing-results/optimize-search-results/adding-synonyms) if your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) contain terms that users may be unfamiliar with.
* Offer [**facets**](/doc/guides/managing-results/refine-results/faceting) so users can fine-tune the relevance of their results.
* Change [**natural language settings**](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp) to help anticipate user errors (such as misspellings) and to optimize for a multi-lingual audience.
* Use **Personalization** ([classic](/doc/guides/personalization/classic-personalization/what-is-personalization) or [advanced](/doc/guides/personalization/advanced-personalization/what-is-advanced-personalization)) to show returning users results that align with their established preferences, like works by a specific author or clothing from a particular brand.
With any change, use [search analytics](/doc/guides/search-analytics/overview) for insights into what your users search for, and which results they select.
Consider [A/B testing](/doc/guides/ab-testing/what-is-ab-testing) to create two alternative search experiences, put them both live and see which performs best.
## Enhance the user experience
Algolia also offers features that enhance the user experience:
* [Sorting](/doc/guides/managing-results/refine-results/sorting)
* [Grouping](/doc/guides/managing-results/refine-results/grouping)
* [Geolocation](/doc/guides/managing-results/refine-results/geolocation)
* [Prefix searching](/doc/guides/managing-results/optimize-search-results/override-search-engine-defaults/in-depth/prefix-searching)
* [Multi-lingual support](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp).
## See also
For more information, see:
* [Implementation checklist](/doc/guides/going-to-production/implementation-checklist)
* [Empty or insufficient results](/doc/guides/managing-results/optimize-search-results/empty-or-insufficient-results)
* [What is search relevance? (blog)](https://www.algolia.com/blog/product/what-is-search-relevance)
* [Search engine relevance (blog)](https://www.algolia.com/blog/product/defining-what-a-search-index-is-and-the-speed-and-relevance-of-a-search-engine-index/)
# Relevance
Source: https://www.algolia.com/doc/guides/managing-results/relevance-overview/in-depth/defining-relevance
Relevance is about finding and ranking results from your index in a way that matches the users' needs and expectations.
Relevance is about finding and ranking results from your index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) in a way that matches the users' needs and expectations.
Relevance is a balance between finding all the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) that match a search and ranking them so that the most important results are returned first.
What important means depends on your and your users' goals.
While finding all matches is essential, if the best match is somewhere among hundreds of results, your search isn't relevant.
## Finding (intelligent matching)
Relevance makes sure that a search finds **all records that match a query**.
Relevance is an **intelligent matching** process that takes into account [typo tolerance](/doc/guides/managing-results/optimize-search-results/typo-tolerance), [partial word matching](/doc/guides/managing-results/optimize-search-results/override-search-engine-defaults/in-depth/adjust-exact-settings), the spatial distance between matching words, the number of attributes that match, [synonyms](/doc/guides/managing-results/optimize-search-results/adding-synonyms), [Rules](/doc/guides/managing-results/rules/rules-overview), [natural language characteristics](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp) (like stop words and plurals), [geolocation](/doc/guides/managing-results/refine-results/geolocation#enabling-geo-search-by-adding-geolocation-data-to-records), and many other aspects of what users expect from a modern search experience.
## Ranking (putting the best at the top)
Finding records is only part of the story.
Ranking ensures that the records most closely related to the search appear first, while less related records are shown further down the list of results.
* **Textual relevance** is about how well a record matches the search terms. Records that are a better match are ranked higher.
* **Custom ranking.** If several records have equal textual relevance, Algolia lets you determine the order of results with custom metrics appropriate for your organization.
> [!NOTE]
>
> Algolia's [ranking criteria](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria) define where a record will sit in the list of results for a query.
> If you want to find out why a record is ranked the way it is, you can [do this in the dashboard or with the API](/doc/guides/managing-results/troubleshooting/troubleshooting-relevance#the-engine’s-ranking-decisions).
## Promote your content
Relevant results help promote your content and give users what they're looking for through the use of:
* [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking)
* [Merchandising](/doc/guides/managing-results/rules/merchandising-and-promoting)
* [Personalization](/doc/guides/personalization/classic-personalization/what-is-personalization)
* [Analytics](/doc/guides/search-analytics/overview).
When a search matches the user's [intent](/doc/guides/managing-results/rules/detecting-intent), your relevance is good.
## Discovery
Users may not always be actively searching for specific items: sometimes, they **discover** them intentionally or unexpectedly.
Discovery is a user-driven process where they browse and define what they need with each new query.
A well-designed frontend UI/UX helps a user discover what they want.
## UI/UX
The UI/UX should allow users to find results, navigate them, discover more content, and refine their search.
A good UI/UX incorporates rapid searching, highlighting key terms in results, faceting, infinite scrolling, and more.
## See also
* [Relevance overview](/doc/guides/managing-results/relevance-overview)
* [Search is more than textual matching](/doc/guides/managing-results/relevance-overview/in-depth/textual-matching)
* [The speed and relevance of a search engine (blog)](https://www.algolia.com/blog/product/defining-what-a-search-index-is-and-the-speed-and-relevance-of-a-search-engine-index/)
* [What is search relevance? (blog)](https://www.algolia.com/blog/product/what-is-search-relevance/)
* [Mobile search and discovery (blog)](https://www.algolia.com/blog/ux/mobile-search-ux-best-practices/)
# The eight ranking criteria
Source: https://www.algolia.com/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria
Discover the eight tie-breaking criteria that Algolia uses to rank your results.
Algolia's **eight ranking criteria** help define what's both [textually and business relevant](/doc/guides/managing-results/relevance-overview/in-depth/defining-relevance#ranking-putting-the-best-at-the-top).
## Typo
Algolia is [typo-tolerant](/doc/guides/managing-results/optimize-search-results/typo-tolerance).
The Typo criterion in the ranking formula ensures the following ranking of search results for queries:
1. Queries without typos (exact matches)
2. Queries with one typo
3. Queries with two typos
### Typo versus Exact versus Words
Algolia's [Exact](#exact), [Words](#words-if-applicable), and Typo ranking criteria are all used to determine how well a record matches a query.
However, they differ in the following ways:
| Criterion | Behavior | Helpful when |
| - | - | - |
| **Typo** | Returns results for queries that match all words in a record, including words with typos. However, matches without typos in the query terms rank higher | Users make typing errors |
| **Exact** | Returns results for queries that match all words in a record. The ranking favors records where the query sequence matches the record's word order | Users type the query as written, such as the name of a well-known product or company |
| **Words** | Returns results for queries that match some words in a record. | Misinterpretation of user input is more common such as in a [voice-to-text search](/doc/guides/solutions/ecommerce/voice-search) |
## Geo (if applicable)
If you're using the [geo-search feature](/doc/guides/managing-results/refine-results/geolocation),
results are ranked by distance, from the closest to the farthest.
The [`aroundPrecision`](/doc/api-reference/api-parameters/aroundPrecision) parameter sets the precision of this ranking.
## Words (if applicable)
This criterion only breaks ties if you're using the [`optionalWords` setting](/doc/guides/managing-results/optimize-search-results/empty-or-insufficient-results#create-a-list-of-optional-words).
If you're not using `optionalWords`,
this criterion will have the same value for each result.
By default, Algolia only matches results that contain all the query's words.
With `optionalWords`,
you can declare some words as optional.
The Words criterion ranks them by the number of matching words typed by users (not the number of times the word appears in the record).
For example, if a user typed two words,
the maximum score for this criterion is 2—even if a record contains this word 10 times.
## Filters
If a query uses [filters](/doc/api-reference/api-parameters/filters) or [optional filters](/doc/api-reference/api-parameters/optionalFilters),
this criterion ranks records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) according to the **number of matching filters** so that records that don't match a filter score 0 and records that match two filters score 2.
You can adjust the scoring with:
* [Filter scoring](/doc/guides/managing-results/refine-results/filtering/in-depth/filter-scoring#score-calculation), you can assign different scores to each filter.
* Using [sumOrFiltersScores](/doc/guides/managing-results/refine-results/filtering/in-depth/filter-scoring#accumulate-scores-with-sumorfiltersscores) to accumulate the scores of disjunctive (OR) matches to create a **total score**.
The Filters criterion can be helpful when defining relevance—for example, when [promoting results](/doc/guides/managing-results/rules/merchandising-and-promoting).
## Proximity
For a query with two or more words,
Proximity calculates how near those words are to each other in the matching record.
This criterion prioritizes records that have words closer to each other.
For example, consider two records with an `actor_name` attribute:
`George Clooney` and `George Timothy Clooney`.
If a user searches for "george clooney",
`George Clooney` has a better proximity distance (the two query words are 1 word apart) compared to `George Timothy Clooney` (the two query words are 2 words apart).
## Attribute
This criterion only considers [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes):
attributes at the top of the `searchableAttributes` list rank higher than lower ones.
The [order](/doc/guides/managing-results/must-do/searchable-attributes/how-to/configuring-searchable-attributes-the-right-way#word-position) of matches within the attribute itself is also important.
By default, records with matched words closer to the beginning of a given attribute rank higher.
## Exact
Records with words that match query terms without typos will rank higher.
The more matching words in a record's attribute,
the higher that record's rank.
By default:
* **An exact match** is when a complete word in a query,
without typos, matches a word in an attribute.
Single-word queries are only exact if they match a single-word attribute.
* **An inexact match** has typos or only matches a prefix.
Synonym matching and singular/plural matching are considered exact.
Thus, a word is considered an exact match if its synonym matches a query.
You can [change the settings for the Exact criterion](/doc/guides/managing-results/optimize-search-results/override-search-engine-defaults/in-depth/adjust-exact-settings).
## Custom
This criterion takes into account your [custom ranking](/doc/guides/managing-results/must-do/custom-ranking) attributes.
With multiple custom ranking attributes, the behavior is the same as other criteria:
a criterion is only used if there is a tie between all the previous criteria.
## Attribute and proximity combinations
When Proximity appears before Attribute in the ranking,
the calculation of Attribute ranking is different than if Proximity appears after Attribute.
This is called the **best-matched attribute**.
> [!NOTE]
>
> This ordering is a subtle distinction.
> We recommend keeping the default ranking since Proximity usually leads to better identification of the best-matched attribute.
### Best-matched attribute
To determine the best-matched attribute,
Algolia uses two methods:
* **Closest in proximity.** Ranking is based on how close two or more query terms are to each other.
* **Best position.** Considers words near the beginning of an attribute to be better than those towards the end.
Algolia's default ranking formula puts Proximity before Attribute,
which has a subtle but important effect on computing the best-matched attribute:
attributes with matched terms **closest** to each other will rank highest.
If you put Proximity after Attribute or remove the Proximity criterion,
the best-matched attributes are those whose matched terms are in the **best position**.
### Example
For example, consider an index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.) with two searchable attributes, `profession` and `full-name`, and the following two records:
```json JSON icon=braces theme={"system"}
[
{
"profession": "Singer and comedian",
"full-name": "Jerry Lewis",
"objectID": "3"
},
{
"profession": "Born a singer",
"full-name": "Jerry Singer",
"objectID": "1"
}
]
```
Consider the search query "Jerry Singer".
The default ranking formula order is **Proximity before Attribute**.
In this case, the record containing the two words "jerry" and "singer" in closest proximity ranks higher (regardless of attribute order).
For the two example records:
* `objectID` 1 has the query words side-by-side in the full-name attribute
* `objectID` 3 has the query words in different attributes.
Because the former has a better Proximity, it's ranked first.
However, if you put **Proximity after Attribute**,
the ranking is based on the **best position** of the matched terms in the searchable attributes (`profession` and `full-name`).
With the query "jerry singer",
the term "singer" appears in `profession` before `full-name`.
# Search is more than textual matching
Source: https://www.algolia.com/doc/guides/managing-results/relevance-overview/in-depth/textual-matching
Understand that a good search experience is more than textual matching.
Search isn't just a letter-by-letter comparison.
Compare searches on these sentences ([records](/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records)):
1. To be or not to be that is the question
2. This was the best of times the worst of times
3. Ask not what your country asks of you but what you can do for your country
4. For a long time I used to go to bed early
5. Shakespeare and Dickens are both famous British authors
6. By George, I should've known that! I'll be a dickens!
7. An accomplished woman almost always knows more than we men, though her knowledge is of a different sort
* A search for "ask" returns one result: sentence 3.
* A search for "time" returns two result: sentences 2 and 4.
* A search for "be" returns four results: sentence 1 and 6 "be", sentence 2 "**be**st", and sentence 4 "**be**d".
## Where textual matching doesn't work
Consider these searches on the preceding sentences:
* "qestion" ("question" mistyped). No sentences are returned.
* "be". What sentence should come first? The ones that match exactly (the word "be")? Or some other criteria?
* "literary quotes"? Textual matching doesn't know anything about these sentences - are these political or literary quotes? The quotes need to be classified, tagged as coming from novels, politicians, authors, countries.
All these queries require something more.
## Going beyond textual matching
Simple textual matching won't solve every search problem: it's only the starting point.
## Add structure
By adding more information to the preceding sentences (records), their relevance can be improved:
1. "To be or not to be, that is the question", shakespeare, british, hamlet, theater
2. "It was the best of times, it was the worst of times", dickens, british, tale of two cities, novel
3. "Ask not what your country asks of you but what you can do for your country", kennedy, american, speech, politics
4. "For a long time I used to go to bed early", proust, french, recherche, novel
5. "Shakespeare and Dickens are both famous British authors", georgette, british, history of british literature, essay
6. "By George, I should've known that! I'll be a dickens!", landly, british, my fair lady, theater
7. "An accomplished woman almost always knows more than we men, though her knowledge is of a different sort.", george elliot, british, middlemarch, novel
## Going further
A good search experience also needs to consider [filtering](/doc/guides/managing-results/refine-results/filtering), [attribute priorities](/doc/guides/managing-results/must-do/searchable-attributes), [custom ranking](/doc/guides/managing-results/must-do/custom-ranking), [handling typos](/doc/guides/managing-results/optimize-search-results/typo-tolerance), [synonyms](/doc/guides/managing-results/optimize-search-results/adding-synonyms), and more.
Insights from [user actions](/doc/guides/sending-events), [analytics](/doc/guides/search-analytics/overview), [personalization](/doc/guides/personalization/classic-personalization/what-is-personalization), and [A/B testing](/doc/guides/ab-testing/what-is-ab-testing) can also help you fine-tune your search experience to better meet the needs of your users.
# Detect intent
Source: https://www.algolia.com/doc/guides/managing-results/rules/detecting-intent
Use Rules to detect user intent by looking at the keywords they use in their queries.
**Keywords** in a search query (The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.) are crucial terms that show user intent.
They vary depending on the data in your index (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.).
For example, if a user types "red":
* On a clothing site, they're probably looking for red clothes.
* On a movie site, "red" might not refer to color.
Once you understand what specific keywords mean in queries,
[use rules to adjust the search results](/doc/guides/managing-results/rules/rules-overview#rules-responding-to-user-queries) users get when they use those keywords.
For example, you can:
* Dynamically filter
* Change the entire query
* Category searches
* Dynamically change search parameters.
## Dynamically filter based on the query
Dynamic filtering applies filters based on keywords.
For example, if someone searches for "cheap toaster 800w",
you could create a rule to filter on toasters priced under \$25 with an 800w power rating.
For more information, see:
* [Add filters based on the query](/doc/guides/managing-results/rules/detecting-intent/how-to/applying-a-custom-filter-for-a-specific-query)
* [Dynamic filtering with Rules](/doc/guides/managing-results/rules/detecting-intent/how-to/applying-a-filter-if-the-query-match-a-facet-value)
* [Merchandising](/doc/guides/managing-results/rules/merchandising-and-promoting)
## Change the entire query
Sometimes, users may search using informal terms not in your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.).
For example, "iwatch" instead of "apple watch".
To handle this, you can create rules to replace an entire query with an alternative set of words.
For example, to replace queries containing "iwatch" with an "apple watch" query:
```json JSON icon=braces theme={"system"}
{
"conditions": [
{
"anchoring": "is",
"pattern": "iwatch",
"alternatives": false
}
],
"consequence": {
"params": {
"query": "apple watch"
}
}
}
```
To help determine these informal terms, review your [Search analytics](/doc/guides/search-analytics/overview).
## Category searches
You can use rules to recognize [categories](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/tutorial/generate-sitemap-from-index#create-a-sitemap-for-categories) in queries and refine the results based on that.
For example, on a document library site, in the query "article ref21," "article" is a category.
* Without a rule, the search probably fails since it searches for the terms "article" and "ref21" but it's unlikely that your records contain the category name "article".
* With a rule that [recognizes and removes](/doc/guides/managing-results/rules/detecting-intent/how-to/detecting-keywords) the "article" category, the search succeeds ("ref21" replaces "article ref21").
## Dynamically change search parameters
After parsing a query, you can use a rule to determine which [parameters best apply to the search](/doc/guides/managing-results/rules/detecting-intent/how-to/applying-search-parameters-for-a-specific-query).
For example, you can turn typo tolerance on or off, change geolocation settings, search for only certain types of attributes, or add filters.
# Add filters based on the query
Source: https://www.algolia.com/doc/guides/managing-results/rules/detecting-intent/how-to/applying-a-custom-filter-for-a-specific-query
Applying a custom filter for a specific query
Sometimes, specific terms can act as cues that you can use to filter search results.
You can use positive, negative, or numerical filters:
* [**Positive filters**](#positive-filters) include a specific subset of matching records in the results. For example, if a user types "diet" on a restaurant website, return every record that has "low-carb" or "low-fat".
* [**Negative filters**](#negative-filters) exclude matching records from results. For example, if a user types "gluten-free" on a restaurant website, you could filter out any gluten-containing meal.
* [**Numerical filters**](#numerical-filters) convert text queries into a numerical range. For example, if a user types "cheap" on a website for kitchen appliances, you could filter out anything costing more than \$50.
## Positive filters
If you want to filter out every non-diet-friendly meal whenever user's search queries contain the term "diet",
you could use the [`_tags`](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-tags) attribute to categorize meals depending on their individual qualities:
```json JSON icon=braces theme={"system"}
[
{
"name": "Chicken Stuffed Baked Avocados",
"restaurant": "The Hive",
"_tags": ["low-carb"]
},
{
"name": "Spinach Quiche",
"restaurant": "Bert's Inn",
"_tags": ["low-carb", "vegetarian"]
},
{
"name": "Pizza Chicken Bake",
"restaurant": "Millbrook Deli",
"_tags": ["cheese"]
},
{
"name": "Strawberry Sorbet",
"restaurant": "The Hive",
"_tags": ["low-fat", "vegetarian", "vegan"]
}
]
```
When users include the term "diet" in their search, you want to automatically return every record that has "low-carb" or "low-fat" in their `_tags` attribute.
Because `_tags` is already optimized for filtering, you don't have to set it as an attribute for faceting.
You can directly create a new Rule that detects the term "diet" in a query and applies a positive filter on tags "low-carb" and "low-fat".
> [!TIP]
>
> To use the term "diet" only for filtering and not as a search term,
> add a consequence in your rule to remove the word from your query.
### Using the API
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
To add a rule, use the [`saveRule`](/doc/libraries/sdk/v1/methods/save-rule) method.
When creating a rule, you must define a condition and a consequence.
```cs C# theme={"system"}
var response = await client.SaveRuleAsync(
"INDEX_NAME",
"diet-rule",
new Rule
{
ObjectID = "diet-rule",
Conditions = new List
{
new Condition { Pattern = "diet", Anchoring = Enum.Parse("Contains") },
},
Consequence = new Consequence
{
Params = new ConsequenceParams
{
Filters = "'low-carb' OR 'low-fat'",
Query = new ConsequenceQuery(
new ConsequenceQueryObject
{
Edits = new List
{
new Edit { Type = Enum.Parse("Remove"), Delete = "diet" },
},
}
),
},
},
}
);
```
```dart Dart theme={"system"}
final response = await client.saveRule(
indexName: "INDEX_NAME",
objectID: "diet-rule",
rule: Rule(
objectID: "diet-rule",
conditions: [
Condition(
pattern: "diet",
anchoring: Anchoring.fromJson("contains"),
),
],
consequence: Consequence(
params: ConsequenceParams(
filters: "'low-carb' OR 'low-fat'",
query: ConsequenceQueryObject(
edits: [
Edit(
type: EditType.fromJson("remove"),
delete: "diet",
),
],
),
),
),
),
);
```
```go Go theme={"system"}
response, err := client.SaveRule(client.NewApiSaveRuleRequest(
"INDEX_NAME", "diet-rule",
search.NewEmptyRule().SetObjectID("diet-rule").SetConditions(
[]search.Condition{*search.NewEmptyCondition().SetPattern("diet").SetAnchoring(search.Anchoring("contains"))}).SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetFilters("'low-carb' OR 'low-fat'").SetQuery(search.ConsequenceQueryObjectAsConsequenceQuery(
search.NewEmptyConsequenceQueryObject().SetEdits(
[]search.Edit{*search.NewEmptyEdit().SetType(search.EditType("remove")).SetDelete("diet")})))))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.saveRule(
"INDEX_NAME",
"diet-rule",
new Rule()
.setObjectID("diet-rule")
.setConditions(Arrays.asList(new Condition().setPattern("diet").setAnchoring(Anchoring.CONTAINS)))
.setConsequence(
new Consequence().setParams(
new ConsequenceParams()
.setFilters("'low-carb' OR 'low-fat'")
.setQuery(new ConsequenceQueryObject().setEdits(Arrays.asList(new Edit().setType(EditType.REMOVE).setDelete("diet"))))
)
)
);
```
```js JavaScript theme={"system"}
const response = await client.saveRule({
indexName: 'indexName',
objectID: 'diet-rule',
rule: {
objectID: 'diet-rule',
conditions: [{ pattern: 'diet', anchoring: 'contains' }],
consequence: {
params: { filters: "'low-carb' OR 'low-fat'", query: { edits: [{ type: 'remove', delete: 'diet' }] } },
},
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.saveRule(
indexName = "INDEX_NAME",
objectID = "diet-rule",
rule =
Rule(
objectID = "diet-rule",
conditions =
listOf(
Condition(
pattern = "diet",
anchoring = Anchoring.entries.first { it.value == "contains" },
)
),
consequence =
Consequence(
params =
ConsequenceParams(
filters = "'low-carb' OR 'low-fat'",
query =
ConsequenceQueryObject(
edits =
listOf(
Edit(
type = EditType.entries.first { it.value == "remove" },
delete = "diet",
)
)
),
)
),
),
)
```
```php PHP theme={"system"}
$response = $client->saveRule(
'INDEX_NAME',
'diet-rule',
['objectID' => 'diet-rule',
'conditions' => [
['pattern' => 'diet',
'anchoring' => 'contains',
],
],
'consequence' => ['params' => ['filters' => "'low-carb' OR 'low-fat'",
'query' => ['edits' => [
['type' => 'remove',
'delete' => 'diet',
],
],
],
],
],
],
);
```
```python Python theme={"system"}
response = client.save_rule(
index_name="INDEX_NAME",
object_id="diet-rule",
rule={
"objectID": "diet-rule",
"conditions": [
{
"pattern": "diet",
"anchoring": "contains",
},
],
"consequence": {
"params": {
"filters": "'low-carb' OR 'low-fat'",
"query": {
"edits": [
{
"type": "remove",
"delete": "diet",
},
],
},
},
},
},
)
```
```ruby Ruby theme={"system"}
response = client.save_rule(
"INDEX_NAME",
"diet-rule",
Algolia::Search::Rule.new(
algolia_object_id: "diet-rule",
conditions: [Algolia::Search::Condition.new(pattern: "diet", anchoring: "contains")],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(
filters: "'low-carb' OR 'low-fat'",
query: Algolia::Search::ConsequenceQueryObject.new(
edits: [Algolia::Search::Edit.new(type: "remove", delete: "diet")]
)
)
)
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.saveRule(
indexName = "INDEX_NAME",
objectID = "diet-rule",
rule = Rule(
objectID = "diet-rule",
conditions = Some(
Seq(
Condition(
pattern = Some("diet"),
anchoring = Some(Anchoring.withName("contains"))
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
filters = Some("'low-carb' OR 'low-fat'"),
query = Some(
ConsequenceQueryObject(
edits = Some(
Seq(
Edit(
`type` = Some(EditType.withName("remove")),
delete = Some("diet")
)
)
)
)
)
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.saveRule(
indexName: "INDEX_NAME",
objectID: "diet-rule",
rule: Rule(
objectID: "diet-rule",
conditions: [SearchCondition(pattern: "diet", anchoring: SearchAnchoring.contains)],
consequence: SearchConsequence(params: SearchConsequenceParams(
filters: "'low-carb' OR 'low-fat'",
query: SearchConsequenceQuery
.searchConsequenceQueryObject(SearchConsequenceQueryObject(edits: [SearchEdit(
type: SearchEditType.remove,
delete: "diet"
)]))
))
)
)
```
### Using the dashboard
You can add rules from the Algolia dashboard using either the Visual Editor or the Manual Editor.
> [tab] Visual Editor
>
> 1. In the Algolia dashboard, select your application and index, open **Rules**, and select **New rule > Visual Editor**.
> 2. Click **Set query condition(s)**, select **contains**, and enter `diet`.
> Click **Apply**.
> 3. In **Consequences**, select **Filter results**.
> Select `_tags` as the attribute and `low-carb` as the value.
> Add `low-fat` as another value and join the values with **Or**.
> Click **Apply**.
> 4. Click **Add Consequence**, expand **More consequences**, and select **Remove word**.
> Enter `diet` in **Word(s)** and click **Apply**.
> 5. Review the results, click **Review and Publish**, and click **Publish**.
> [tab] Manual Editor
>
> 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.).
>
> On the left sidebar, select **Search**, select your index, and open [**Rules**](https://dashboard.algolia.com/rules).
> Select **Create your first rule** or **New rule**, then select **Manual Editor**.
> 2. In **Condition(s)**, keep **Query contains** and enter `diet`.
> 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**.
> Enter `{ "filters": "'low-carb' OR 'low-fat'" }`.
> 4. Click **Add consequence** again and select **Remove Word**.
> Enter `diet`.
> 5. Save your changes.
## Negative filters
To exclude gluten-containing foods from the search results when a user searches for gluten-free meals, do the following:
1. Create an `allergens` attribute (with "gluten" as one of the potential values).
2. Create a rule that filters out records with "gluten" in that attribute.
### Example records
```json JSON icon=braces theme={"system"}
[
{
"name": "Pasta Bolognese",
"restaurant": "Millbrook Deli",
"allergens": ["eggs", "lactose"]
},
{
"name": "Breakfast Waffles",
"restaurant": "The Hive",
"allergens": ["gluten", "lactose"]
}
]
```
### Using the API
Set `allergens` as an [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) in your index:
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings { AttributesForFaceting = new List { "allergens" } }
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
attributesForFaceting: [
"allergens",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetAttributesForFaceting(
[]string{"allergens"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings().setAttributesForFaceting(Arrays.asList("allergens"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'INDEX_NAME',
indexSettings: { attributesForFaceting: ['allergens'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(attributesForFaceting = listOf("allergens")),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['attributesForFaceting' => [
'allergens',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"attributesForFaceting": [
"allergens",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(attributes_for_faceting: ["allergens"])
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
attributesForFaceting = Some(Seq("allergens"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(attributesForFaceting: ["allergens"])
)
```
* Use the [`saveRule`](/doc/libraries/sdk/v1/methods/save-rule) method to create a rule that detects the term "gluten-free" in a query and applies a negative filter on facet value `allergens:gluten`.
* Add a consequence in your rule to remove the word "gluten-free" from your query. This way, it won't be used as a search term, only for filtering purposes.
```cs C# theme={"system"}
var response = await client.SaveRuleAsync(
"INDEX_NAME",
"gluten-free-rule",
new Rule
{
ObjectID = "gluten-free-rule",
Conditions = new List
{
new Condition { Pattern = "gluten-free", Anchoring = Enum.Parse("Contains") },
},
Consequence = new Consequence
{
Params = new ConsequenceParams
{
Filters = "NOT allergens:gluten",
Query = new ConsequenceQuery(
new ConsequenceQueryObject
{
Edits = new List
{
new Edit { Type = Enum.Parse("Remove"), Delete = "gluten-free" },
},
}
),
},
},
}
);
```
```dart Dart theme={"system"}
final response = await client.saveRule(
indexName: "INDEX_NAME",
objectID: "gluten-free-rule",
rule: Rule(
objectID: "gluten-free-rule",
conditions: [
Condition(
pattern: "gluten-free",
anchoring: Anchoring.fromJson("contains"),
),
],
consequence: Consequence(
params: ConsequenceParams(
filters: "NOT allergens:gluten",
query: ConsequenceQueryObject(
edits: [
Edit(
type: EditType.fromJson("remove"),
delete: "gluten-free",
),
],
),
),
),
),
);
```
```go Go theme={"system"}
response, err := client.SaveRule(client.NewApiSaveRuleRequest(
"INDEX_NAME", "gluten-free-rule",
search.NewEmptyRule().SetObjectID("gluten-free-rule").SetConditions(
[]search.Condition{*search.NewEmptyCondition().SetPattern("gluten-free").SetAnchoring(search.Anchoring("contains"))}).SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetFilters("NOT allergens:gluten").SetQuery(search.ConsequenceQueryObjectAsConsequenceQuery(
search.NewEmptyConsequenceQueryObject().SetEdits(
[]search.Edit{*search.NewEmptyEdit().SetType(search.EditType("remove")).SetDelete("gluten-free")})))))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.saveRule(
"INDEX_NAME",
"gluten-free-rule",
new Rule()
.setObjectID("gluten-free-rule")
.setConditions(Arrays.asList(new Condition().setPattern("gluten-free").setAnchoring(Anchoring.CONTAINS)))
.setConsequence(
new Consequence().setParams(
new ConsequenceParams()
.setFilters("NOT allergens:gluten")
.setQuery(new ConsequenceQueryObject().setEdits(Arrays.asList(new Edit().setType(EditType.REMOVE).setDelete("gluten-free"))))
)
)
);
```
```js JavaScript theme={"system"}
const response = await client.saveRule({
indexName: 'indexName',
objectID: 'gluten-free-rule',
rule: {
objectID: 'gluten-free-rule',
conditions: [{ pattern: 'gluten-free', anchoring: 'contains' }],
consequence: {
params: { filters: 'NOT allergens:gluten', query: { edits: [{ type: 'remove', delete: 'gluten-free' }] } },
},
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.saveRule(
indexName = "INDEX_NAME",
objectID = "gluten-free-rule",
rule =
Rule(
objectID = "gluten-free-rule",
conditions =
listOf(
Condition(
pattern = "gluten-free",
anchoring = Anchoring.entries.first { it.value == "contains" },
)
),
consequence =
Consequence(
params =
ConsequenceParams(
filters = "NOT allergens:gluten",
query =
ConsequenceQueryObject(
edits =
listOf(
Edit(
type = EditType.entries.first { it.value == "remove" },
delete = "gluten-free",
)
)
),
)
),
),
)
```
```php PHP theme={"system"}
$response = $client->saveRule(
'INDEX_NAME',
'gluten-free-rule',
['objectID' => 'gluten-free-rule',
'conditions' => [
['pattern' => 'gluten-free',
'anchoring' => 'contains',
],
],
'consequence' => ['params' => ['filters' => 'NOT allergens:gluten',
'query' => ['edits' => [
['type' => 'remove',
'delete' => 'gluten-free',
],
],
],
],
],
],
);
```
```python Python theme={"system"}
response = client.save_rule(
index_name="INDEX_NAME",
object_id="gluten-free-rule",
rule={
"objectID": "gluten-free-rule",
"conditions": [
{
"pattern": "gluten-free",
"anchoring": "contains",
},
],
"consequence": {
"params": {
"filters": "NOT allergens:gluten",
"query": {
"edits": [
{
"type": "remove",
"delete": "gluten-free",
},
],
},
},
},
},
)
```
```ruby Ruby theme={"system"}
response = client.save_rule(
"INDEX_NAME",
"gluten-free-rule",
Algolia::Search::Rule.new(
algolia_object_id: "gluten-free-rule",
conditions: [Algolia::Search::Condition.new(pattern: "gluten-free", anchoring: "contains")],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(
filters: "NOT allergens:gluten",
query: Algolia::Search::ConsequenceQueryObject.new(
edits: [Algolia::Search::Edit.new(type: "remove", delete: "gluten-free")]
)
)
)
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.saveRule(
indexName = "INDEX_NAME",
objectID = "gluten-free-rule",
rule = Rule(
objectID = "gluten-free-rule",
conditions = Some(
Seq(
Condition(
pattern = Some("gluten-free"),
anchoring = Some(Anchoring.withName("contains"))
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
filters = Some("NOT allergens:gluten"),
query = Some(
ConsequenceQueryObject(
edits = Some(
Seq(
Edit(
`type` = Some(EditType.withName("remove")),
delete = Some("gluten-free")
)
)
)
)
)
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.saveRule(
indexName: "INDEX_NAME",
objectID: "gluten-free-rule",
rule: Rule(
objectID: "gluten-free-rule",
conditions: [SearchCondition(pattern: "gluten-free", anchoring: SearchAnchoring.contains)],
consequence: SearchConsequence(params: SearchConsequenceParams(
filters: "NOT allergens:gluten",
query: SearchConsequenceQuery
.searchConsequenceQueryObject(SearchConsequenceQueryObject(edits: [SearchEdit(
type: SearchEditType.remove,
delete: "gluten-free"
)]))
))
)
)
```
### Using the dashboard
You can add rules from the Algolia dashboard using either the Visual Editor or the Manual Editor.
Before creating the rule, open **Configuration > Filtering and Faceting > Facets** for your index and add the `allergens` attribute.
> [tab] Visual Editor
>
> 1. In the Algolia dashboard, select your application and index, open **Rules**, and select **New rule > Visual Editor**.
> 2. Click **Set query condition(s)**, select **contains**, and enter `gluten-free`.
> Click **Apply**.
> 3. In **Consequences**, select **Filter results**.
> Select `allergens` as the attribute, **is not** as the operator, and `gluten` as the value.
> Click **Apply**.
> 4. Click **Add Consequence**, expand **More consequences**, and select **Remove word**.
> Enter `gluten-free` in **Word(s)** and click **Apply**.
> 5. Review the results, click **Review and Publish**, and click **Publish**.
> [tab] Manual Editor
>
> 1. Open **Rules**, select **Create your first rule** or **New rule**, and select **Manual Editor**.
> 2. In **Condition(s)**, keep **Query** selected, select **Contains**, and enter `gluten-free`.
> 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**.
> Enter `{ "filters": "NOT allergens:gluten" }`.
> 4. Click **Add consequence** again and select **Remove Word**.
> Enter `gluten-free`.
> 5. Save your changes.
## Numerical filters
Consider the query "cheap toaster 800w".
You can use Rules to filter the results by "toaster" and "prices under \$10" so that the only textual search is the remaining term,
"800w", which could further be used to limit the results with that wattage.
### Rule
If `query = "cheap toaster"` then `price < 10` and `product_type:toaster`.
> [!NOTE]
>
> This requires two rules because each query term applies a different filter.
### Using the API
```cs C# theme={"system"}
var response = await client.SaveRuleAsync(
"INDEX_NAME",
"cheap",
new Rule
{
ObjectID = "cheap",
Conditions = new List
{
new Condition { Pattern = "cheap", Anchoring = Enum.Parse("Contains") },
},
Consequence = new Consequence
{
Params = new ConsequenceParams
{
Query = new ConsequenceQuery(
new ConsequenceQueryObject { Remove = new List { "cheap" } }
),
Filters = "price < 10",
},
},
}
);
```
```dart Dart theme={"system"}
final response = await client.saveRule(
indexName: "INDEX_NAME",
objectID: "cheap",
rule: Rule(
objectID: "cheap",
conditions: [
Condition(
pattern: "cheap",
anchoring: Anchoring.fromJson("contains"),
),
],
consequence: Consequence(
params: ConsequenceParams(
query: ConsequenceQueryObject(
remove: [
"cheap",
],
),
filters: "price < 10",
),
),
),
);
```
```go Go theme={"system"}
response, err := client.SaveRule(client.NewApiSaveRuleRequest(
"INDEX_NAME", "cheap",
search.NewEmptyRule().SetObjectID("cheap").SetConditions(
[]search.Condition{*search.NewEmptyCondition().SetPattern("cheap").SetAnchoring(search.Anchoring("contains"))}).SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetQuery(search.ConsequenceQueryObjectAsConsequenceQuery(
search.NewEmptyConsequenceQueryObject().SetRemove(
[]string{"cheap"}))).SetFilters("price < 10")))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.saveRule(
"INDEX_NAME",
"cheap",
new Rule()
.setObjectID("cheap")
.setConditions(Arrays.asList(new Condition().setPattern("cheap").setAnchoring(Anchoring.CONTAINS)))
.setConsequence(
new Consequence().setParams(
new ConsequenceParams().setQuery(new ConsequenceQueryObject().setRemove(Arrays.asList("cheap"))).setFilters("price < 10")
)
)
);
```
```js JavaScript theme={"system"}
const response = await client.saveRule({
indexName: 'indexName',
objectID: 'cheap',
rule: {
objectID: 'cheap',
conditions: [{ pattern: 'cheap', anchoring: 'contains' }],
consequence: { params: { query: { remove: ['cheap'] }, filters: 'price < 10' } },
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.saveRule(
indexName = "INDEX_NAME",
objectID = "cheap",
rule =
Rule(
objectID = "cheap",
conditions =
listOf(
Condition(
pattern = "cheap",
anchoring = Anchoring.entries.first { it.value == "contains" },
)
),
consequence =
Consequence(
params =
ConsequenceParams(
query = ConsequenceQueryObject(remove = listOf("cheap")),
filters = "price < 10",
)
),
),
)
```
```php PHP theme={"system"}
$response = $client->saveRule(
'INDEX_NAME',
'cheap',
['objectID' => 'cheap',
'conditions' => [
['pattern' => 'cheap',
'anchoring' => 'contains',
],
],
'consequence' => ['params' => ['query' => ['remove' => [
'cheap',
],
],
'filters' => 'price < 10',
],
],
],
);
```
```python Python theme={"system"}
response = client.save_rule(
index_name="INDEX_NAME",
object_id="cheap",
rule={
"objectID": "cheap",
"conditions": [
{
"pattern": "cheap",
"anchoring": "contains",
},
],
"consequence": {
"params": {
"query": {
"remove": [
"cheap",
],
},
"filters": "price < 10",
},
},
},
)
```
```ruby Ruby theme={"system"}
response = client.save_rule(
"INDEX_NAME",
"cheap",
Algolia::Search::Rule.new(
algolia_object_id: "cheap",
conditions: [Algolia::Search::Condition.new(pattern: "cheap", anchoring: "contains")],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(
query: Algolia::Search::ConsequenceQueryObject.new(remove: ["cheap"]),
filters: "price < 10"
)
)
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.saveRule(
indexName = "INDEX_NAME",
objectID = "cheap",
rule = Rule(
objectID = "cheap",
conditions = Some(
Seq(
Condition(
pattern = Some("cheap"),
anchoring = Some(Anchoring.withName("contains"))
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
query = Some(
ConsequenceQueryObject(
remove = Some(Seq("cheap"))
)
),
filters = Some("price < 10")
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.saveRule(
indexName: "INDEX_NAME",
objectID: "cheap",
rule: Rule(
objectID: "cheap",
conditions: [SearchCondition(pattern: "cheap", anchoring: SearchAnchoring.contains)],
consequence: SearchConsequence(params: SearchConsequenceParams(
filters: "price < 10",
query: SearchConsequenceQuery
.searchConsequenceQueryObject(SearchConsequenceQueryObject(remove: ["cheap"]))
))
)
)
```
### Using the dashboard
You can configure this example from the Algolia dashboard using either the Visual Editor or the Manual Editor.
#### Preparation
1. Go to the [Algolia dashboard](https://dashboard.algolia.com/explorer/browse) and select your Algolia application.
2. On the left sidebar, select **Search**.
3. Select your Algolia index and open the **Configuration** tab.
4. On the **Filtering and Facets** page,
click **Add an attribute** and select the `product_type` attribute. Repeat to add `price`.
> [tab] Visual Editor
>
> In the Visual Editor, set up separate rules for `toaster` and `cheap`.
>
> 1. In the Algolia dashboard, select your application and index, open **Rules**, and select **New rule > Visual Editor**.
> 2. Click **Set query condition(s)**, select **contains**, and enter `toaster`.
> Click **Apply**.
> 3. In **Consequences**, select **Filter results**.
> Select `product_type` as the attribute and `toaster` as the value.
> Click **Apply**.
> 4. Click **Add Consequence**, expand **More consequences**, and select **Remove word**.
> Enter `toaster` in **Word(s)**, then click **Apply**.
> 5. Review the results, click **Review and Publish**, and click **Publish**.
>
> Repeat these steps to create the second rule.
> Use `cheap` for the condition and removed word, and filter on `price < 10`.
> [tab] Manual Editor
>
> In the Manual Editor, set up separate rules for `toaster` and `cheap`.
>
> 1. Open [**Rules**](https://dashboard.algolia.com/rules), select **Create your first rule** or **New rule**, and select **Manual Editor**.
> 2. In **Condition(s)**, keep **Query** selected, select **Contains**, and enter `toaster`.
> 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**.
> Enter `{ "filters": "product_type:toaster" }`.
> 4. Click **Add consequence** again and select **Remove Word**.
> Enter `toaster`.
> 5. Save your changes.
>
> Repeat these steps to create the second rule.
> Use `cheap` for the condition and removed word, and enter `{ "filters": "price<10" }` as the query parameter.
## See also
* [Faceting](/doc/guides/managing-results/refine-results/faceting)
* [Filtering](/doc/guides/managing-results/refine-results/filtering)
* [Merchandising](/doc/guides/managing-results/rules/merchandising-and-promoting)
# Dynamic filtering with Rules
Source: https://www.algolia.com/doc/guides/managing-results/rules/detecting-intent/how-to/applying-a-filter-if-the-query-match-a-facet-value
Use rules to apply facet and numeric filters automatically based on search queries.
To help users find what they're looking for straight from the search box,
you can use Algolia Rules to automatically apply a filter to results if the query matches a facet value.
## Before you begin
Add the records from the [example dataset](#example-dataset) to an Algolia index.
Configure `color`, `product_type`, and `brand` as
[attributes for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard).
## How to create a dynamic filtering rule
In this example for a product catalog,
you create a rule that displays products with the "red" value in the `color` facet if the query contains the word "red".
With the [example dataset](#example-dataset),
this rule limits results to records with "red" in the `color` attribute.
It ignores "red" in other attributes such as `brand`.
| Query | Results |
| - | - |
| red | FM, Clothing, LondonLook, Red, 21.99 The Mandal 800w, Toaster, Black & Decker, Red, 149.99 |
### Create the rule in the dashboard
You can add this rule from the Algolia dashboard using either the Visual Editor or the Manual Editor.
> [tab] Visual Editor
>
> 1. In the Algolia dashboard, select your application and index, open **Rules**, and select **New rule > Visual Editor**.
> 2. Click **Set query condition(s)**, select **contains**, enter `red`, and click **Apply**.
> 3. In **Consequences**, select **Filter results**.
> Select the `color` attribute and the `red` value, then click **Apply**.
> To boost red products while keeping other colors in the results, use **Boost categories** instead.
> 4. Click **Add Consequence**, expand **More consequences**, and select **Remove word**.
> Enter `red` in **Word(s)** and click **Apply**.
> 5. Review the results, click **Review and Publish**, and click **Publish**.
> [tab] Manual Editor
>
> 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.).
>
> On the left sidebar, select **Search**, select your index, and open [**Rules**](https://dashboard.algolia.com/rules).
> Select **Create your first rule** or **New rule**, then select **Manual Editor**.
> 2. In **Condition(s)**, keep **Query** selected, select **Contains**, and enter `red`.
> 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**.
> Enter `{"filters":"color:red"}`.
> To promote red products without excluding products of other colors, see [Optional filters](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/how-to-promote-with-optional-filters).
> 4. Click **Add consequence** and select **Remove Word**.
> Enter `red`.
> 5. Save your changes.
### Create the rule with the API
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
This example uses the [`filters`](/doc/api-reference/api-parameters/filters) parameter.
With this approach, you need one rule per filter value.
If you have 10 color options, you need 10 rules, one for each color.
```cs C# theme={"system"}
var response = await client.SaveRuleAsync(
"INDEX_NAME",
"red-color",
new Rule
{
ObjectID = "red-color",
Conditions = new List
{
new Condition { Pattern = "red", Anchoring = Enum.Parse("Contains") },
},
Consequence = new Consequence
{
Params = new ConsequenceParams
{
Query = new ConsequenceQuery(
new ConsequenceQueryObject { Remove = new List { "red" } }
),
Filters = "color:red",
},
},
}
);
```
```dart Dart theme={"system"}
final response = await client.saveRule(
indexName: "INDEX_NAME",
objectID: "red-color",
rule: Rule(
objectID: "red-color",
conditions: [
Condition(
pattern: "red",
anchoring: Anchoring.fromJson("contains"),
),
],
consequence: Consequence(
params: ConsequenceParams(
query: ConsequenceQueryObject(
remove: [
"red",
],
),
filters: "color:red",
),
),
),
);
```
```go Go theme={"system"}
response, err := client.SaveRule(client.NewApiSaveRuleRequest(
"INDEX_NAME", "red-color",
search.NewEmptyRule().SetObjectID("red-color").SetConditions(
[]search.Condition{*search.NewEmptyCondition().SetPattern("red").SetAnchoring(search.Anchoring("contains"))}).SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetQuery(search.ConsequenceQueryObjectAsConsequenceQuery(
search.NewEmptyConsequenceQueryObject().SetRemove(
[]string{"red"}))).SetFilters("color:red")))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.saveRule(
"INDEX_NAME",
"red-color",
new Rule()
.setObjectID("red-color")
.setConditions(Arrays.asList(new Condition().setPattern("red").setAnchoring(Anchoring.CONTAINS)))
.setConsequence(
new Consequence().setParams(
new ConsequenceParams().setQuery(new ConsequenceQueryObject().setRemove(Arrays.asList("red"))).setFilters("color:red")
)
)
);
```
```js JavaScript theme={"system"}
const response = await client.saveRule({
indexName: 'indexName',
objectID: 'red-color',
rule: {
objectID: 'red-color',
conditions: [{ pattern: 'red', anchoring: 'contains' }],
consequence: { params: { query: { remove: ['red'] }, filters: 'color:red' } },
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.saveRule(
indexName = "INDEX_NAME",
objectID = "red-color",
rule =
Rule(
objectID = "red-color",
conditions =
listOf(
Condition(
pattern = "red",
anchoring = Anchoring.entries.first { it.value == "contains" },
)
),
consequence =
Consequence(
params =
ConsequenceParams(
query = ConsequenceQueryObject(remove = listOf("red")),
filters = "color:red",
)
),
),
)
```
```php PHP theme={"system"}
$response = $client->saveRule(
'INDEX_NAME',
'red-color',
['objectID' => 'red-color',
'conditions' => [
['pattern' => 'red',
'anchoring' => 'contains',
],
],
'consequence' => ['params' => ['query' => ['remove' => [
'red',
],
],
'filters' => 'color:red',
],
],
],
);
```
```python Python theme={"system"}
response = client.save_rule(
index_name="INDEX_NAME",
object_id="red-color",
rule={
"objectID": "red-color",
"conditions": [
{
"pattern": "red",
"anchoring": "contains",
},
],
"consequence": {
"params": {
"query": {
"remove": [
"red",
],
},
"filters": "color:red",
},
},
},
)
```
```ruby Ruby theme={"system"}
response = client.save_rule(
"INDEX_NAME",
"red-color",
Algolia::Search::Rule.new(
algolia_object_id: "red-color",
conditions: [Algolia::Search::Condition.new(pattern: "red", anchoring: "contains")],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(
query: Algolia::Search::ConsequenceQueryObject.new(remove: ["red"]),
filters: "color:red"
)
)
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.saveRule(
indexName = "INDEX_NAME",
objectID = "red-color",
rule = Rule(
objectID = "red-color",
conditions = Some(
Seq(
Condition(
pattern = Some("red"),
anchoring = Some(Anchoring.withName("contains"))
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
query = Some(
ConsequenceQueryObject(
remove = Some(Seq("red"))
)
),
filters = Some("color:red")
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.saveRule(
indexName: "INDEX_NAME",
objectID: "red-color",
rule: Rule(
objectID: "red-color",
conditions: [SearchCondition(pattern: "red", anchoring: SearchAnchoring.contains)],
consequence: SearchConsequence(params: SearchConsequenceParams(
filters: "color:red",
query: SearchConsequenceQuery
.searchConsequenceQueryObject(SearchConsequenceQueryObject(remove: ["red"]))
))
)
)
```
## Example: one rule per facet
Use one rule to detect values of the `color` facet and apply the corresponding filter.
For example, the query `red` applies `color:red`, while `black` applies `color:black`.
This avoids creating a separate rule for each color.
| Query | Results |
| - | - |
| red | FM, Clothing, LondonLook, Red, 21.99 |
| red | The Mandal 800w, Toaster, Black & Decker, Red, 149.99 |
| black | Will Carpenter, T-shirt, Red or Dead, Black, 199.99 |
### Create the rule in the dashboard
You can add this rule from the Algolia dashboard using either the Visual Editor or the Manual Editor.
> [tab] Visual Editor
>
> 1. Create a rule with **Visual Editor**.
> 2. Click **Set query condition(s)** and select **contains**.
> Select **Match any value of a facet** and choose `color`.
> Click **Apply**.
> 3. In **Consequences**, expand **More consequences** and select **Filter / Boost matching attribute**.
> Select `color` in **Facet**.
> The facet must be present in every query condition.
> 4. Choose **Filter** to keep only matching records or **Boost** to promote them without excluding other records.
> Set **Score** from 1 to 50 to control the filter score.
> If your plan doesn't support optional filters in rules, select **Filter facets** to filter the results.
> 5. Under **Filter type**, select **Conjunctive** to require all matching values or **Disjunctive** to accept any of them.
> Click **Apply**.
> 6. Review the results for different color queries, click **Review and Publish**, and click **Publish**.
> [tab] Manual Editor
>
> 1. Create a rule with the Manual Editor.
> See [how to create a dynamic filtering rule](#how-to-create-a-dynamic-filtering-rule).
> 2. In **Condition(s)**, click the box next to **Query contains** and select **Add Facet "color"**.
> 3. In **Consequence(s)**, click **Add consequence** and select **Filter/Boost Matching Attributes**.
> Click **Filter** and select **Add Facet "color"**.
> 4. Save your changes.
### Create the rule with the API
```cs C# theme={"system"}
var response = await client.SaveRuleAsync(
"INDEX_NAME",
"color-facets",
new Rule
{
ObjectID = "color-facets",
Conditions = new List { new Condition { Pattern = "{facet:color}" } },
Consequence = new Consequence
{
Params = new ConsequenceParams
{
AutomaticFacetFilters = new AutomaticFacetFilters(
new List { new AutomaticFacetFilter { Facet = "color" } }
),
},
},
}
);
```
```dart Dart theme={"system"}
final response = await client.saveRule(
indexName: "INDEX_NAME",
objectID: "color-facets",
rule: Rule(
objectID: "color-facets",
conditions: [
Condition(
pattern: "{facet:color}",
),
],
consequence: Consequence(
params: ConsequenceParams(
automaticFacetFilters: [
AutomaticFacetFilter(
facet: "color",
),
],
),
),
),
);
```
```go Go theme={"system"}
response, err := client.SaveRule(client.NewApiSaveRuleRequest(
"INDEX_NAME", "color-facets",
search.NewEmptyRule().SetObjectID("color-facets").SetConditions(
[]search.Condition{*search.NewEmptyCondition().SetPattern("{facet:color}")}).SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetAutomaticFacetFilters(search.ArrayOfAutomaticFacetFilterAsAutomaticFacetFilters(
[]search.AutomaticFacetFilter{*search.NewEmptyAutomaticFacetFilter().SetFacet("color")}))))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.saveRule(
"INDEX_NAME",
"color-facets",
new Rule()
.setObjectID("color-facets")
.setConditions(Arrays.asList(new Condition().setPattern("{facet:color}")))
.setConsequence(
new Consequence().setParams(
new ConsequenceParams().setAutomaticFacetFilters(
AutomaticFacetFilters.ofListOfAutomaticFacetFilter(Arrays.asList(new AutomaticFacetFilter().setFacet("color")))
)
)
)
);
```
```js JavaScript theme={"system"}
const response = await client.saveRule({
indexName: 'indexName',
objectID: 'color-facets',
rule: {
objectID: 'color-facets',
conditions: [{ pattern: '{facet:color}' }],
consequence: { params: { automaticFacetFilters: [{ facet: 'color' }] } },
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.saveRule(
indexName = "INDEX_NAME",
objectID = "color-facets",
rule =
Rule(
objectID = "color-facets",
conditions = listOf(Condition(pattern = "{facet:color}")),
consequence =
Consequence(
params =
ConsequenceParams(
automaticFacetFilters =
AutomaticFacetFilters.ofListOfAutomaticFacetFilter(
listOf(AutomaticFacetFilter(facet = "color"))
)
)
),
),
)
```
```php PHP theme={"system"}
$response = $client->saveRule(
'INDEX_NAME',
'color-facets',
['objectID' => 'color-facets',
'conditions' => [
['pattern' => '{facet:color}',
],
],
'consequence' => ['params' => ['automaticFacetFilters' => [
['facet' => 'color',
],
],
],
],
],
);
```
```python Python theme={"system"}
response = client.save_rule(
index_name="INDEX_NAME",
object_id="color-facets",
rule={
"objectID": "color-facets",
"conditions": [
{
"pattern": "{facet:color}",
},
],
"consequence": {
"params": {
"automaticFacetFilters": [
{
"facet": "color",
},
],
},
},
},
)
```
```ruby Ruby theme={"system"}
response = client.save_rule(
"INDEX_NAME",
"color-facets",
Algolia::Search::Rule.new(
algolia_object_id: "color-facets",
conditions: [Algolia::Search::Condition.new(pattern: "{facet:color}")],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(
automatic_facet_filters: [Algolia::Search::AutomaticFacetFilter.new(facet: "color")]
)
)
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.saveRule(
indexName = "INDEX_NAME",
objectID = "color-facets",
rule = Rule(
objectID = "color-facets",
conditions = Some(
Seq(
Condition(
pattern = Some("{facet:color}")
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
automaticFacetFilters = Some(
AutomaticFacetFilters(
Seq(
AutomaticFacetFilter(
facet = "color"
)
)
)
)
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.saveRule(
indexName: "INDEX_NAME",
objectID: "color-facets",
rule: Rule(
objectID: "color-facets",
conditions: [SearchCondition(pattern: "{facet:color}")],
consequence: SearchConsequence(
params: SearchConsequenceParams(automaticFacetFilters: SearchAutomaticFacetFilters
.arrayOfSearchAutomaticFacetFilter([SearchAutomaticFacetFilter(facet: "color")]))
)
)
)
```
To show the filters applied by this rule as selected options in your search UI,
see [Show automatically applied filters](/doc/guides/solutions/ecommerce/filtering-and-navigation/tutorials/auto-selected-facets).
## Numerical dynamic filtering
For the query `cheap toaster 800w`, use one rule to filter by `product_type:toaster`
and another to filter by `price < 15`.
Remove `toaster` and `cheap` from the query so the search uses `800w` to find matching products.
With the [example dataset](#example-dataset),
the query returns the Essentials 800w toaster, priced at \$14.99.
| Query | Results |
| - | - |
| cheap toaster 800w | Essentials 800w, Toaster, Daewoo, Black, 14.99 |
### Create the rules in the dashboard
You can add these rules from the Algolia dashboard using either the Visual Editor or the Manual Editor.
> [tab] Visual Editor
>
> 1. Open **Rules** and select **New rule > Visual Editor**.
> 2. Click **Set query condition(s)**, select **contains**, enter `toaster`, and click **Apply**.
> 3. In **Consequences**, select **Filter results**.
> Select `product_type` as the attribute and `toaster` as the value, then click **Apply**.
> 4. Click **Add Consequence**, expand **More consequences**, and select **Remove word**.
> Enter `toaster` in **Word(s)** and click **Apply**.
> 5. Review the results, click **Review and Publish**, and click **Publish**.
>
> Repeat these steps to create the second rule.
> Use `cheap` for the condition and removed word, and filter on `price < 15`.
> [tab] Manual Editor
>
> 1. Open **Rules**, select **New rule**, and select **Manual Editor**.
> 2. In **Condition(s)**, keep **Query** selected, select **Contains**, and enter `toaster`.
> 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**.
> Enter `{"filters":"product_type:toaster"}`.
> 4. Click **Add consequence** and select **Remove Word**.
> Enter `toaster`.
> 5. Save your changes.
>
> Repeat these steps to create the second rule.
> Use `cheap` for the condition and removed word, and enter `{"filters":"price < 15"}` as the query parameter.
### Create the rules with the API
```cs C# theme={"system"}
var response = await client.SaveRulesAsync(
"INDEX_NAME",
new List
{
new Rule
{
ObjectID = "toaster",
Conditions = new List
{
new Condition { Pattern = "toaster", Anchoring = Enum.Parse("Contains") },
},
Consequence = new Consequence
{
Params = new ConsequenceParams
{
Query = new ConsequenceQuery(
new ConsequenceQueryObject { Remove = new List { "toaster" } }
),
Filters = "product_type:toaster",
},
},
},
new Rule
{
ObjectID = "cheap",
Conditions = new List
{
new Condition { Pattern = "cheap", Anchoring = Enum.Parse("Contains") },
},
Consequence = new Consequence
{
Params = new ConsequenceParams
{
Query = new ConsequenceQuery(
new ConsequenceQueryObject { Remove = new List { "cheap" } }
),
Filters = "price < 15",
},
},
},
}
);
```
```dart Dart theme={"system"}
final response = await client.saveRules(
indexName: "INDEX_NAME",
rules: [
Rule(
objectID: "toaster",
conditions: [
Condition(
pattern: "toaster",
anchoring: Anchoring.fromJson("contains"),
),
],
consequence: Consequence(
params: ConsequenceParams(
query: ConsequenceQueryObject(
remove: [
"toaster",
],
),
filters: "product_type:toaster",
),
),
),
Rule(
objectID: "cheap",
conditions: [
Condition(
pattern: "cheap",
anchoring: Anchoring.fromJson("contains"),
),
],
consequence: Consequence(
params: ConsequenceParams(
query: ConsequenceQueryObject(
remove: [
"cheap",
],
),
filters: "price < 15",
),
),
),
],
);
```
```go Go theme={"system"}
response, err := client.SaveRules(client.NewApiSaveRulesRequest(
"INDEX_NAME",
[]search.Rule{*search.NewEmptyRule().SetObjectID("toaster").SetConditions(
[]search.Condition{*search.NewEmptyCondition().SetPattern("toaster").SetAnchoring(search.Anchoring("contains"))}).SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetQuery(search.ConsequenceQueryObjectAsConsequenceQuery(
search.NewEmptyConsequenceQueryObject().SetRemove(
[]string{"toaster"}))).SetFilters("product_type:toaster"))), *search.NewEmptyRule().SetObjectID("cheap").SetConditions(
[]search.Condition{*search.NewEmptyCondition().SetPattern("cheap").SetAnchoring(search.Anchoring("contains"))}).SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetQuery(search.ConsequenceQueryObjectAsConsequenceQuery(
search.NewEmptyConsequenceQueryObject().SetRemove(
[]string{"cheap"}))).SetFilters("price < 15")))}))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.saveRules(
"INDEX_NAME",
Arrays.asList(
new Rule()
.setObjectID("toaster")
.setConditions(Arrays.asList(new Condition().setPattern("toaster").setAnchoring(Anchoring.CONTAINS)))
.setConsequence(
new Consequence().setParams(
new ConsequenceParams()
.setQuery(new ConsequenceQueryObject().setRemove(Arrays.asList("toaster")))
.setFilters("product_type:toaster")
)
),
new Rule()
.setObjectID("cheap")
.setConditions(Arrays.asList(new Condition().setPattern("cheap").setAnchoring(Anchoring.CONTAINS)))
.setConsequence(
new Consequence().setParams(
new ConsequenceParams().setQuery(new ConsequenceQueryObject().setRemove(Arrays.asList("cheap"))).setFilters("price < 15")
)
)
)
);
```
```js JavaScript theme={"system"}
const response = await client.saveRules({
indexName: 'INDEX_NAME',
rules: [
{
objectID: 'toaster',
conditions: [{ pattern: 'toaster', anchoring: 'contains' }],
consequence: { params: { query: { remove: ['toaster'] }, filters: 'product_type:toaster' } },
},
{
objectID: 'cheap',
conditions: [{ pattern: 'cheap', anchoring: 'contains' }],
consequence: { params: { query: { remove: ['cheap'] }, filters: 'price < 15' } },
},
],
});
```
```kotlin Kotlin theme={"system"}
var response =
client.saveRules(
indexName = "INDEX_NAME",
rules =
listOf(
Rule(
objectID = "toaster",
conditions =
listOf(
Condition(
pattern = "toaster",
anchoring = Anchoring.entries.first { it.value == "contains" },
)
),
consequence =
Consequence(
params =
ConsequenceParams(
query = ConsequenceQueryObject(remove = listOf("toaster")),
filters = "product_type:toaster",
)
),
),
Rule(
objectID = "cheap",
conditions =
listOf(
Condition(
pattern = "cheap",
anchoring = Anchoring.entries.first { it.value == "contains" },
)
),
consequence =
Consequence(
params =
ConsequenceParams(
query = ConsequenceQueryObject(remove = listOf("cheap")),
filters = "price < 15",
)
),
),
),
)
```
```php PHP theme={"system"}
$response = $client->saveRules(
'INDEX_NAME',
[
['objectID' => 'toaster',
'conditions' => [
['pattern' => 'toaster',
'anchoring' => 'contains',
],
],
'consequence' => ['params' => ['query' => ['remove' => [
'toaster',
],
],
'filters' => 'product_type:toaster',
],
],
],
['objectID' => 'cheap',
'conditions' => [
['pattern' => 'cheap',
'anchoring' => 'contains',
],
],
'consequence' => ['params' => ['query' => ['remove' => [
'cheap',
],
],
'filters' => 'price < 15',
],
],
],
],
);
```
```python Python theme={"system"}
response = client.save_rules(
index_name="INDEX_NAME",
rules=[
{
"objectID": "toaster",
"conditions": [
{
"pattern": "toaster",
"anchoring": "contains",
},
],
"consequence": {
"params": {
"query": {
"remove": [
"toaster",
],
},
"filters": "product_type:toaster",
},
},
},
{
"objectID": "cheap",
"conditions": [
{
"pattern": "cheap",
"anchoring": "contains",
},
],
"consequence": {
"params": {
"query": {
"remove": [
"cheap",
],
},
"filters": "price < 15",
},
},
},
],
)
```
```ruby Ruby theme={"system"}
response = client.save_rules(
"INDEX_NAME",
[
Algolia::Search::Rule.new(
algolia_object_id: "toaster",
conditions: [Algolia::Search::Condition.new(pattern: "toaster", anchoring: "contains")],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(
query: Algolia::Search::ConsequenceQueryObject.new(remove: ["toaster"]),
filters: "product_type:toaster"
)
)
),
Algolia::Search::Rule.new(
algolia_object_id: "cheap",
conditions: [Algolia::Search::Condition.new(pattern: "cheap", anchoring: "contains")],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(
query: Algolia::Search::ConsequenceQueryObject.new(remove: ["cheap"]),
filters: "price < 15"
)
)
)
]
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.saveRules(
indexName = "INDEX_NAME",
rules = Seq(
Rule(
objectID = "toaster",
conditions = Some(
Seq(
Condition(
pattern = Some("toaster"),
anchoring = Some(Anchoring.withName("contains"))
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
query = Some(
ConsequenceQueryObject(
remove = Some(Seq("toaster"))
)
),
filters = Some("product_type:toaster")
)
)
)
),
Rule(
objectID = "cheap",
conditions = Some(
Seq(
Condition(
pattern = Some("cheap"),
anchoring = Some(Anchoring.withName("contains"))
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
query = Some(
ConsequenceQueryObject(
remove = Some(Seq("cheap"))
)
),
filters = Some("price < 15")
)
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.saveRules(
indexName: "INDEX_NAME",
rules: [
Rule(objectID: "toaster", conditions: [SearchCondition(
pattern: "toaster",
anchoring: SearchAnchoring.contains
)], consequence: SearchConsequence(params: SearchConsequenceParams(
filters: "product_type:toaster",
query: SearchConsequenceQuery
.searchConsequenceQueryObject(SearchConsequenceQueryObject(remove: ["toaster"]))
))),
Rule(
objectID: "cheap",
conditions: [SearchCondition(pattern: "cheap", anchoring: SearchAnchoring.contains)],
consequence: SearchConsequence(params: SearchConsequenceParams(
filters: "price < 15",
query: SearchConsequenceQuery
.searchConsequenceQueryObject(SearchConsequenceQueryObject(remove: ["cheap"]))
))
),
]
)
```
## Filter by brand
Users can enter `apple headphones` to search for Apple headphones.
The rule applies a filter for the Apple brand and removes the matched brand value from the query,
leaving `headphones` as the search term.
This extends [one rule per facet](#example-one-rule-per-facet)
by also removing the matched brand value from the query.
With the [example dataset](#example-dataset), the query returns Airpods Max.
It excludes the Sony headphones, even though their `color` attribute contains `Apple`.
| Query | Results |
| - | - |
| apple headphones | Airpods Max, Headphones, Apple, Gray, 548.99 |
### Create the rule in the dashboard
You can add this rule from the Algolia dashboard using either the Visual Editor or the Manual Editor.
> [tab] Visual Editor
>
> 1. Open **Rules** and select **New rule > Visual Editor**.
> 2. Click **Set query condition(s)** and select **contains**.
> Select **Match any value of a facet** and choose `brand`.
> Clear the checkbox for matching plurals, synonyms, and typos, then click **Apply**.
> 3. In **Consequences**, expand **More consequences** and select **Filter / Boost matching attribute**.
> Select `brand` in **Facet**, choose **Filter**, and click **Apply**.
> 4. Click **Add Consequence**, expand **More consequences**, and select **Remove word**.
> Select `{facet:brand}` in **Word(s)** and click **Apply**.
> 5. Review the results, click **Review and Publish**, and click **Publish**.
> [tab] Manual Editor
>
> 1. Create a rule with the Manual Editor.
> 2. In **Condition(s)**, click the box next to **Query contains** and select **Add Facet "brand"**.
> Clear **Apply to plurals, synonyms and typos** to ensure precise brand matching.
> 3. In **Consequence(s)**, click **Add consequence** and select **Filter/Boost Matching Attributes**.
> Click **Filter** and select **Add Facet "brand"**.
> 4. Click **Add consequence** and select **Remove word**.
> Select **Remove `{facet:brand}`**.
> 5. Save your changes.
### Create the rule with the API
```cs C# theme={"system"}
var response = await client.SaveRuleAsync(
"INDEX_NAME",
"tagged-brand-rule",
new Rule
{
Conditions = new List
{
new Condition
{
Pattern = "brand: {facet:brand}",
Anchoring = Enum.Parse("Contains"),
Alternatives = false,
},
},
Consequence = new Consequence
{
Params = new ConsequenceParams
{
AutomaticFacetFilters = new AutomaticFacetFilters(
new List { new AutomaticFacetFilter { Facet = "brand" } }
),
Query = new ConsequenceQuery(
new ConsequenceQueryObject
{
Remove = new List { "brand:", "{facet:brand}" },
}
),
},
},
Description = "filter on brand: {brand}",
ObjectID = "tagged-brand-rule",
}
);
```
```dart Dart theme={"system"}
final response = await client.saveRule(
indexName: "INDEX_NAME",
objectID: "tagged-brand-rule",
rule: Rule(
conditions: [
Condition(
pattern: "brand: {facet:brand}",
anchoring: Anchoring.fromJson("contains"),
alternatives: false,
),
],
consequence: Consequence(
params: ConsequenceParams(
automaticFacetFilters: [
AutomaticFacetFilter(
facet: "brand",
),
],
query: ConsequenceQueryObject(
remove: [
"brand:",
"{facet:brand}",
],
),
),
),
description: "filter on brand: {brand}",
objectID: "tagged-brand-rule",
),
);
```
```go Go theme={"system"}
response, err := client.SaveRule(client.NewApiSaveRuleRequest(
"INDEX_NAME", "tagged-brand-rule",
search.NewEmptyRule().SetConditions(
[]search.Condition{
*search.NewEmptyCondition().SetPattern("brand: {facet:brand}").SetAnchoring(search.Anchoring("contains")).SetAlternatives(false),
}).
SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetAutomaticFacetFilters(search.ArrayOfAutomaticFacetFilterAsAutomaticFacetFilters(
[]search.AutomaticFacetFilter{
*search.NewEmptyAutomaticFacetFilter().SetFacet("brand"),
},
)).SetQuery(search.ConsequenceQueryObjectAsConsequenceQuery(
search.NewEmptyConsequenceQueryObject().SetRemove(
[]string{"brand:", "{facet:brand}"}))))).SetDescription("filter on brand: {brand}").SetObjectID("tagged-brand-rule")))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.saveRule(
"INDEX_NAME",
"tagged-brand-rule",
new Rule()
.setConditions(
Arrays.asList(new Condition().setPattern("brand: {facet:brand}").setAnchoring(Anchoring.CONTAINS).setAlternatives(false))
)
.setConsequence(
new Consequence().setParams(
new ConsequenceParams()
.setAutomaticFacetFilters(
AutomaticFacetFilters.ofListOfAutomaticFacetFilter(Arrays.asList(new AutomaticFacetFilter().setFacet("brand")))
)
.setQuery(new ConsequenceQueryObject().setRemove(Arrays.asList("brand:", "{facet:brand}")))
)
)
.setDescription("filter on brand: {brand}")
.setObjectID("tagged-brand-rule")
);
```
```js JavaScript theme={"system"}
const response = await client.saveRule({
indexName: 'indexName',
objectID: 'tagged-brand-rule',
rule: {
conditions: [{ pattern: 'brand: {facet:brand}', anchoring: 'contains', alternatives: false }],
consequence: {
params: { automaticFacetFilters: [{ facet: 'brand' }], query: { remove: ['brand:', '{facet:brand}'] } },
},
description: 'filter on brand: {brand}',
objectID: 'tagged-brand-rule',
},
});
```
```kotlin Kotlin theme={"system"}
var response =
client.saveRule(
indexName = "INDEX_NAME",
objectID = "tagged-brand-rule",
rule =
Rule(
conditions =
listOf(
Condition(
pattern = "brand: {facet:brand}",
anchoring = Anchoring.entries.first { it.value == "contains" },
alternatives = false,
)
),
consequence =
Consequence(
params =
ConsequenceParams(
automaticFacetFilters =
AutomaticFacetFilters.ofListOfAutomaticFacetFilter(
listOf(AutomaticFacetFilter(facet = "brand"))
),
query = ConsequenceQueryObject(remove = listOf("brand:", "{facet:brand}")),
)
),
description = "filter on brand: {brand}",
objectID = "tagged-brand-rule",
),
)
```
```php PHP theme={"system"}
$response = $client->saveRule(
'INDEX_NAME',
'tagged-brand-rule',
['conditions' => [
['pattern' => 'brand: {facet:brand}',
'anchoring' => 'contains',
'alternatives' => false,
],
],
'consequence' => ['params' => ['automaticFacetFilters' => [
['facet' => 'brand',
],
],
'query' => ['remove' => [
'brand:',
'{facet:brand}',
],
],
],
],
'description' => 'filter on brand: {brand}',
'objectID' => 'tagged-brand-rule',
],
);
```
```python Python theme={"system"}
response = client.save_rule(
index_name="INDEX_NAME",
object_id="tagged-brand-rule",
rule={
"conditions": [
{
"pattern": "brand: {facet:brand}",
"anchoring": "contains",
"alternatives": False,
},
],
"consequence": {
"params": {
"automaticFacetFilters": [
{
"facet": "brand",
},
],
"query": {
"remove": [
"brand:",
"{facet:brand}",
],
},
},
},
"description": "filter on brand: {brand}",
"objectID": "tagged-brand-rule",
},
)
```
```ruby Ruby theme={"system"}
response = client.save_rule(
"INDEX_NAME",
"tagged-brand-rule",
Algolia::Search::Rule.new(
conditions: [
Algolia::Search::Condition.new(pattern: "brand: {facet:brand}", anchoring: "contains", alternatives: false)
],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(
automatic_facet_filters: [Algolia::Search::AutomaticFacetFilter.new(facet: "brand")],
query: Algolia::Search::ConsequenceQueryObject.new(remove: ["brand:", "{facet:brand}"])
)
),
description: "filter on brand: {brand}",
algolia_object_id: "tagged-brand-rule"
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.saveRule(
indexName = "INDEX_NAME",
objectID = "tagged-brand-rule",
rule = Rule(
conditions = Some(
Seq(
Condition(
pattern = Some("brand: {facet:brand}"),
anchoring = Some(Anchoring.withName("contains")),
alternatives = Some(false)
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
automaticFacetFilters = Some(
AutomaticFacetFilters(
Seq(
AutomaticFacetFilter(
facet = "brand"
)
)
)
),
query = Some(
ConsequenceQueryObject(
remove = Some(Seq("brand:", "{facet:brand}"))
)
)
)
)
),
description = Some("filter on brand: {brand}"),
objectID = "tagged-brand-rule"
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.saveRule(
indexName: "INDEX_NAME",
objectID: "tagged-brand-rule",
rule: Rule(
objectID: "tagged-brand-rule",
conditions: [SearchCondition(
pattern: "brand: {facet:brand}",
anchoring: SearchAnchoring.contains,
alternatives: false
)],
consequence: SearchConsequence(params: SearchConsequenceParams(
query: SearchConsequenceQuery.searchConsequenceQueryObject(SearchConsequenceQueryObject(remove: [
"brand:",
"{facet:brand}",
])),
automaticFacetFilters: SearchAutomaticFacetFilters
.arrayOfSearchAutomaticFacetFilter([SearchAutomaticFacetFilter(facet: "brand")])
)),
description: "filter on brand: {brand}"
)
)
```
## Example dataset
The examples on this page use the following product catalog records:
```json JSON icon=braces theme={"system"}
[
{
"product_name": "FM",
"product_type": "T-shirt",
"brand": "LondonLook",
"color": "Red",
"price": 21.99
},
{
"product_name": "Caveman",
"product_type": "T-shirt",
"brand": "Red or Dead",
"color": "Brown",
"price": 9.99
},
{
"product_name": "iPhone 13 Pro Max",
"product_type": "Phone",
"brand": "Apple",
"color": "Blue",
"price": 1824.61
},
{
"product_name": "Will Carpenter",
"product_type": "T-shirt",
"brand": "Red or Dead",
"color": "Black",
"price": 199.99
},
{
"product_name": "The Mandal 800w",
"product_type": "Toaster",
"brand": "Black & Decker",
"color": "Red",
"price": 149.99
},
{
"product_name": "Essentials 800w",
"product_type": "Toaster",
"brand": "Daewoo",
"color": "Black",
"price": 14.99
},
{
"product_name": "Airpods Max",
"product_type": "Headphones",
"brand": "Apple",
"color": "Gray",
"price": 548.99
},
{
"product_name": "WH-CH520",
"product_type": "Headphones",
"brand": "Sony",
"color": "Apple",
"price": 39.99
}
]
```
## See also
* [Adding filters based on the query](/doc/guides/managing-results/rules/detecting-intent/how-to/applying-a-custom-filter-for-a-specific-query)
* [Faceting](/doc/guides/managing-results/refine-results/faceting#conjunctive-and-disjunctive-faceting)
# Enhance search results with query-based rules
Source: https://www.algolia.com/doc/guides/managing-results/rules/detecting-intent/how-to/applying-search-parameters-for-a-specific-query
Use rules to enhance search results based on user queries.
You can use rules to enhance the user experience based on what they search for.
For example, if you're building an app to search for airports,
Algolia's [geolocation feature](/doc/guides/managing-results/refine-results/geolocation) helpfully ranks results by proximity.
However, the nearest airport isn't necessarily the best choice.
In Paris, a search based on proximity might rank Le Bourget, a small airport for private jets, over large airports like Charles de Gaulle or Orly.
As a solution, you could prioritize other factors over [proximity](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria#geo-if-applicable).
For example, you could rank airports by their number of airline connections.
However, if you also set a geolocation parameter like [`aroundLatLngViaIP`](/doc/api-reference/api-parameters/aroundLatLngViaIP),
proximity remains the primary factor in determining the order of search results.
A better solution is to create a rule so that when users search for a specific city or country,
it turns off the `aroundLatLngViaIP` parameter for that query.
## Before you begin
Before creating the rule, ensure that:
* `nb_airline_connections` is the [custom ranking](/doc/guides/managing-results/must-do/custom-ranking) attribute
* `city` and `country` are set as [attributes for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard), so you can detect whenever a query matches them
## Create the rule
### Using the dashboard
This example assumes you're using the [sample dataset](#example-dataset).
You can add these rules from the Algolia dashboard using either the Visual Editor or the Manual Editor.
#### Custom ranking and attributes for faceting
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, click **Ranking and Sorting**.
5. Click **Add custom ranking attribute** and select the `nb_airline_connections` attribute from the drop-down menu.
6. Click **Facets**, under Filtering and faceting, then click **Add an Attribute** and select `country` and `city` from the drop-down menu.
7. Save your changes.
> [tab] Visual Editor
>
> After configuring custom ranking and attributes for faceting, create a rule for each facet:
>
> 1. Open **Rules** and select **New rule > Visual Editor**.
> 2. Click **Set query condition(s)** and select **contains**.
> Select **Match any value of a facet**, choose `country`, and click **Apply**.
> 3. In **Consequences**, expand **More consequences** and select **Apply search parameters**.
> Enter `{ "aroundLatLngViaIP": false }` in **Value** and click **Apply**.
> 4. Click **Review and Publish**, then **Publish**.
>
> Repeat these steps to create the second rule, but choose `city` instead of `country` as the facet.
> [tab] Manual Editor
>
> In the Manual Editor, create a separate rule for the `country` and `city` facets.
>
> 1. Open [**Rules**](https://dashboard.algolia.com/rules), select **Create your first rule** or **New rule**, and select **Manual Editor**.
> 2. In **Condition(s)**, keep **Query** selected, select **Contains**, and select `country`.
> The input displays `{facet:country}`.
> 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**.
> Enter `{ "aroundLatLngViaIP": false }`.
> 4. Save your changes.
>
> Repeat these steps to create the second rule, but select `city` so the condition displays `{facet:city}`.
### Using the API
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
Before creating the rule,
set custom ranking and the [attributes for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting).
This example assumes you're using the [sample dataset](#example-dataset).
#### Custom ranking and attributes for faceting
```cs C# theme={"system"}
var response = await client.SetSettingsAsync(
"INDEX_NAME",
new IndexSettings
{
CustomRanking = new List { "desc(nb_airline_liaisons)" },
AttributesForFaceting = new List { "city, country" },
}
);
```
```dart Dart theme={"system"}
final response = await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
customRanking: [
"desc(nb_airline_liaisons)",
],
attributesForFaceting: [
"city, country",
],
),
);
```
```go Go theme={"system"}
response, err := client.SetSettings(client.NewApiSetSettingsRequest(
"INDEX_NAME",
search.NewEmptyIndexSettings().SetCustomRanking(
[]string{"desc(nb_airline_liaisons)"}).SetAttributesForFaceting(
[]string{"city, country"})))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.setSettings(
"INDEX_NAME",
new IndexSettings()
.setCustomRanking(Arrays.asList("desc(nb_airline_liaisons)"))
.setAttributesForFaceting(Arrays.asList("city, country"))
);
```
```js JavaScript theme={"system"}
const response = await client.setSettings({
indexName: 'theIndexName',
indexSettings: { customRanking: ['desc(nb_airline_liaisons)'], attributesForFaceting: ['city, country'] },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.setSettings(
indexName = "INDEX_NAME",
indexSettings =
IndexSettings(
customRanking = listOf("desc(nb_airline_liaisons)"),
attributesForFaceting = listOf("city, country"),
),
)
```
```php PHP theme={"system"}
$response = $client->setSettings(
'INDEX_NAME',
['customRanking' => [
'desc(nb_airline_liaisons)',
],
'attributesForFaceting' => [
'city, country',
],
],
);
```
```python Python theme={"system"}
response = client.set_settings(
index_name="INDEX_NAME",
index_settings={
"customRanking": [
"desc(nb_airline_liaisons)",
],
"attributesForFaceting": [
"city, country",
],
},
)
```
```ruby Ruby theme={"system"}
response = client.set_settings(
"INDEX_NAME",
Algolia::Search::IndexSettings.new(
custom_ranking: ["desc(nb_airline_liaisons)"],
attributes_for_faceting: ["city, country"]
)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.setSettings(
indexName = "INDEX_NAME",
indexSettings = IndexSettings(
customRanking = Some(Seq("desc(nb_airline_liaisons)")),
attributesForFaceting = Some(Seq("city, country"))
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.setSettings(
indexName: "INDEX_NAME",
indexSettings: IndexSettings(
attributesForFaceting: ["city, country"],
customRanking: ["desc(nb_airline_liaisons)"]
)
)
```
#### Create the rules
Use the [`batch-rules`](/doc/libraries/sdk/v1/methods/batch-rules) method to create rules that detect matches in facets `city` and `country`, and change the search parameters accordingly.
```cs C# theme={"system"}
var response = await client.SaveRulesAsync(
"INDEX_NAME",
new List
{
new Rule
{
ObjectID = "country",
Conditions = new List
{
new Condition
{
Pattern = "{facet:country}",
Anchoring = Enum.Parse("Contains"),
},
},
Consequence = new Consequence
{
Params = new ConsequenceParams { AroundLatLngViaIP = false },
},
},
new Rule
{
ObjectID = "city",
Conditions = new List
{
new Condition
{
Pattern = "{facet:city}",
Anchoring = Enum.Parse("Contains"),
},
},
Consequence = new Consequence
{
Params = new ConsequenceParams { AroundLatLngViaIP = false },
},
},
}
);
```
```dart Dart theme={"system"}
final response = await client.saveRules(
indexName: "INDEX_NAME",
rules: [
Rule(
objectID: "country",
conditions: [
Condition(
pattern: "{facet:country}",
anchoring: Anchoring.fromJson("contains"),
),
],
consequence: Consequence(
params: ConsequenceParams(
aroundLatLngViaIP: false,
),
),
),
Rule(
objectID: "city",
conditions: [
Condition(
pattern: "{facet:city}",
anchoring: Anchoring.fromJson("contains"),
),
],
consequence: Consequence(
params: ConsequenceParams(
aroundLatLngViaIP: false,
),
),
),
],
);
```
```go Go theme={"system"}
response, err := client.SaveRules(client.NewApiSaveRulesRequest(
"INDEX_NAME",
[]search.Rule{*search.NewEmptyRule().SetObjectID("country").SetConditions(
[]search.Condition{*search.NewEmptyCondition().SetPattern("{facet:country}").SetAnchoring(search.Anchoring("contains"))}).SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetAroundLatLngViaIP(false))), *search.NewEmptyRule().SetObjectID("city").SetConditions(
[]search.Condition{*search.NewEmptyCondition().SetPattern("{facet:city}").SetAnchoring(search.Anchoring("contains"))}).SetConsequence(
search.NewEmptyConsequence().SetParams(
search.NewEmptyConsequenceParams().SetAroundLatLngViaIP(false)))}))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
UpdatedAtResponse response = client.saveRules(
"INDEX_NAME",
Arrays.asList(
new Rule()
.setObjectID("country")
.setConditions(Arrays.asList(new Condition().setPattern("{facet:country}").setAnchoring(Anchoring.CONTAINS)))
.setConsequence(new Consequence().setParams(new ConsequenceParams().setAroundLatLngViaIP(false))),
new Rule()
.setObjectID("city")
.setConditions(Arrays.asList(new Condition().setPattern("{facet:city}").setAnchoring(Anchoring.CONTAINS)))
.setConsequence(new Consequence().setParams(new ConsequenceParams().setAroundLatLngViaIP(false)))
)
);
```
```js JavaScript theme={"system"}
const response = await client.saveRules({
indexName: 'INDEX_NAME',
rules: [
{
objectID: 'country',
conditions: [{ pattern: '{facet:country}', anchoring: 'contains' }],
consequence: { params: { aroundLatLngViaIP: false } },
},
{
objectID: 'city',
conditions: [{ pattern: '{facet:city}', anchoring: 'contains' }],
consequence: { params: { aroundLatLngViaIP: false } },
},
],
});
```
```kotlin Kotlin theme={"system"}
var response =
client.saveRules(
indexName = "INDEX_NAME",
rules =
listOf(
Rule(
objectID = "country",
conditions =
listOf(
Condition(
pattern = "{facet:country}",
anchoring = Anchoring.entries.first { it.value == "contains" },
)
),
consequence = Consequence(params = ConsequenceParams(aroundLatLngViaIP = false)),
),
Rule(
objectID = "city",
conditions =
listOf(
Condition(
pattern = "{facet:city}",
anchoring = Anchoring.entries.first { it.value == "contains" },
)
),
consequence = Consequence(params = ConsequenceParams(aroundLatLngViaIP = false)),
),
),
)
```
```php PHP theme={"system"}
$response = $client->saveRules(
'INDEX_NAME',
[
['objectID' => 'country',
'conditions' => [
['pattern' => '{facet:country}',
'anchoring' => 'contains',
],
],
'consequence' => ['params' => ['aroundLatLngViaIP' => false,
],
],
],
['objectID' => 'city',
'conditions' => [
['pattern' => '{facet:city}',
'anchoring' => 'contains',
],
],
'consequence' => ['params' => ['aroundLatLngViaIP' => false,
],
],
],
],
);
```
```python Python theme={"system"}
response = client.save_rules(
index_name="INDEX_NAME",
rules=[
{
"objectID": "country",
"conditions": [
{
"pattern": "{facet:country}",
"anchoring": "contains",
},
],
"consequence": {
"params": {
"aroundLatLngViaIP": False,
},
},
},
{
"objectID": "city",
"conditions": [
{
"pattern": "{facet:city}",
"anchoring": "contains",
},
],
"consequence": {
"params": {
"aroundLatLngViaIP": False,
},
},
},
],
)
```
```ruby Ruby theme={"system"}
response = client.save_rules(
"INDEX_NAME",
[
Algolia::Search::Rule.new(
algolia_object_id: "country",
conditions: [Algolia::Search::Condition.new(pattern: "{facet:country}", anchoring: "contains")],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(around_lat_lng_via_ip: false)
)
),
Algolia::Search::Rule.new(
algolia_object_id: "city",
conditions: [Algolia::Search::Condition.new(pattern: "{facet:city}", anchoring: "contains")],
consequence: Algolia::Search::Consequence.new(
params: Algolia::Search::ConsequenceParams.new(around_lat_lng_via_ip: false)
)
)
]
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.saveRules(
indexName = "INDEX_NAME",
rules = Seq(
Rule(
objectID = "country",
conditions = Some(
Seq(
Condition(
pattern = Some("{facet:country}"),
anchoring = Some(Anchoring.withName("contains"))
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
aroundLatLngViaIP = Some(false)
)
)
)
),
Rule(
objectID = "city",
conditions = Some(
Seq(
Condition(
pattern = Some("{facet:city}"),
anchoring = Some(Anchoring.withName("contains"))
)
)
),
consequence = Consequence(
params = Some(
ConsequenceParams(
aroundLatLngViaIP = Some(false)
)
)
)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response = try await client.saveRules(
indexName: "INDEX_NAME",
rules: [
Rule(objectID: "country", conditions: [SearchCondition(
pattern: "{facet:country}",
anchoring: SearchAnchoring.contains
)], consequence: SearchConsequence(params: SearchConsequenceParams(aroundLatLngViaIP: false))),
Rule(
objectID: "city",
conditions: [SearchCondition(pattern: "{facet:city}", anchoring: SearchAnchoring.contains)],
consequence: SearchConsequence(params: SearchConsequenceParams(aroundLatLngViaIP: false))
),
]
)
```
## Query time
If a user query exactly matches a city or a country **in** your dataset,
the rule sets `aroundLatLngViaIP` to `false` overriding any other parameters in the query.
For example, if a user searches for "Paris" or "China", matching airports are ranked by `nb_airline_connections`.
When not searching for either a city or a country, airports are ranked by distance from the user's location.
```cs C# theme={"system"}
var response = await client.SearchSingleIndexAsync(
"INDEX_NAME",
new SearchParams(new SearchParamsObject { AroundLatLngViaIP = true })
);
```
```dart Dart theme={"system"}
final response = await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchParamsObject(
aroundLatLngViaIP: true,
),
);
```
```go Go theme={"system"}
response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest(
"INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams(
search.NewEmptySearchParamsObject().SetAroundLatLngViaIP(true))))
if err != nil {
// handle the eventual error
panic(err)
}
```
```java Java theme={"system"}
SearchResponse response = client.searchSingleIndex("INDEX_NAME", new SearchParamsObject().setAroundLatLngViaIP(true), Hit.class);
```
```js JavaScript theme={"system"}
const response = await client.searchSingleIndex({
indexName: 'indexName',
searchParams: { aroundLatLngViaIP: true },
});
```
```kotlin Kotlin theme={"system"}
var response =
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = SearchParamsObject(aroundLatLngViaIP = true),
)
```
```php PHP theme={"system"}
$response = $client->searchSingleIndex(
'INDEX_NAME',
['aroundLatLngViaIP' => true,
],
);
```
```python Python theme={"system"}
response = client.search_single_index(
index_name="INDEX_NAME",
search_params={
"aroundLatLngViaIP": True,
},
)
```
```ruby Ruby theme={"system"}
response = client.search_single_index(
"INDEX_NAME",
Algolia::Search::SearchParamsObject.new(around_lat_lng_via_ip: true)
)
```
```scala Scala theme={"system"}
val response = Await.result(
client.searchSingleIndex(
indexName = "INDEX_NAME",
searchParams = Some(
SearchParamsObject(
aroundLatLngViaIP = Some(true)
)
)
),
Duration(100, "sec")
)
```
```swift Swift theme={"system"}
let response: SearchResponse = try await client.searchSingleIndex(
indexName: "INDEX_NAME",
searchParams: SearchSearchParams.searchSearchParamsObject(SearchSearchParamsObject(aroundLatLngViaIP: true))
)
```
## Example dataset
```json JSON icon=braces theme={"system"}
[
{
"name": "Orly",
"city": "Paris",
"country": "France",
"_geoloc": { "lat": 48.725278, "lng": 2.359444 },
"nb_airline_connections": 404
},
{
"name": "Charles De Gaulle",
"city": "Paris",
"country": "France",
"_geoloc": { "lat": 49.012779, "lng": 2.55 },
"nb_airline_connections": 1041
},
{
"name": "Le Bourget",
"city": "Paris",
"country": "France",
"_geoloc": { "lat": 48.961487, "lng": 2.436966 },
"nb_airline_connections": 0
},
{
"name": "Ben Gurion",
"city": "Tel-Aviv",
"country": "Israel",
"_geoloc": { "lat": 32.011389, "lng": 34.886667 },
"nb_airline_connections": 271
},
{
"name": "Haifa",
"city": "Haifa",
"country": "Israel",
"_geoloc": { "lat": 32.809444, "lng": 35.043056 },
"nb_airline_connections": 4
},
{
"name": "Pudong",
"city": "Shanghai",
"country": "China",
"_geoloc": { "lat": 31.143378, "lng": 121.805214 },
"nb_airline_connections": 825
},
{
"name": "Hongqiao Intl",
"city": "Shanghai",
"country": "China",
"_geoloc": { "lat": 31.197875, "lng": 121.336319 },
"nb_airline_connections": 411
}
]
```
## See also
* [Geographical search with InstantSearch.js](/doc/guides/building-search-ui/ui-and-ux-patterns/geo-search/js)
* [Send and update your data](/doc/guides/sending-and-managing-data/send-and-update-your-data)
# Detect keywords
Source: https://www.algolia.com/doc/guides/managing-results/rules/detecting-intent/how-to/detecting-keywords
Detect keywords and reserved words in the query using Algolia Rules
You can use rules to detect a specific word and modify a search query (The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.) parameter because of this word.
## Limit the search to only a subset of attributes
A good example of altering a query is with an online document library that allows keyword searches inside documents.
If a user types in the word "article ref21", they're probably looking for an article whose title or ID contains "ref21".\_
### Rule
*If query = "article ref21" then remove article and search for an article whose title or ID contains 'ref21'*
### Using the API
To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).
```cs C# theme={"system"}
var response = await client.SaveRuleAsync(
"INDEX_NAME",
"article-rule",
new Rule
{
ObjectID = "article-rule",
Conditions = new List