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

{hit.product_type}

${hit.price}

); } export default function App() { return (
Product type
); } ``` 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. ![Image that compares average calculations: left side arithmetic average, right side Bayesian average.](https://mintcdn.com/algolia/u8QjGPGZbKOqOFEr/images/guides/solutions/solutions-bayesian-average-ranking.jpg?fit=max\&auto=format\&n=u8QjGPGZbKOqOFEr\&q=85\&s=4ca8982dac5d2da02af0b9247dcf90d9) 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: ![Screenshot of a settings interface showing a 'CUSTOM RANKING' attribute named 'bayes\_average' set to 'Descending' order.](https://mintcdn.com/algolia/u8QjGPGZbKOqOFEr/images/guides/solutions/solutions-bayesian-average-one-attribute.jpg?fit=max\&auto=format\&n=u8QjGPGZbKOqOFEr\&q=85\&s=82b872b632966c8225e3a8d50b507b82) ### 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: ![Two custom ranking attributes: bayes\_average and profit\_margin](https://mintcdn.com/algolia/u8QjGPGZbKOqOFEr/images/guides/solutions/solutions-bayesian-average-two-attributes.jpg?fit=max\&auto=format\&n=u8QjGPGZbKOqOFEr\&q=85\&s=67c56159ce9eee9e32bad70bf0a5f8b3) ### 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. ![Ranking and sorting in the Algolia dashboard, showing custom ranking attribute configuration](https://mintcdn.com/algolia/AACpGv43fS_1tsyy/images/guides/indexing/custom-ranking-attribute.jpg?fit=max\&auto=format\&n=AACpGv43fS_1tsyy\&q=85\&s=520d03349ace24602c708bd704c95dfc) ### 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`. ![Select attributes you want to add as custom ranking](https://mintcdn.com/algolia/QUuhkPGiow1bP-ae/images/guides/relevance/custom-ranking.png?fit=max\&auto=format\&n=QUuhkPGiow1bP-ae\&q=85\&s=92e048d5b53fe9223874fff6f48971e6) 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) ![Screenshot of a drop-down menu showing 'searchable' selected for the 'city' attribute under 'Attributes for faceting'.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/tutorials/attributes-for-faceting-dashboard.jpg?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=65743f2d20cf97c747124128cd537bdb) 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: ![Screenshot of a map showing the United States with a polygonal filter drawn over it, highlighting a specific region.](https://mintcdn.com/algolia/AACpGv43fS_1tsyy/images/guides/geo-search/filter-inside-polygon.png?fit=max\&auto=format\&n=AACpGv43fS_1tsyy\&q=85\&s=737da5678fcd7b4a948c1b0fc1a8ee52) 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: ![Screenshot of a map showing a red rectangular selection box over the United States, with 'États-Unis' labeled inside the box.](https://mintcdn.com/algolia/AACpGv43fS_1tsyy/images/guides/geo-search/filter-inside-rectangle.png?fit=max\&auto=format\&n=AACpGv43fS_1tsyy\&q=85\&s=4a8cb412c35f97103f678e4db6c62e26) 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. ![Diagram that shows t-shirts and sweatshirts grouped by design, with several color variants for each.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/distinct/distinct.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=e1097a0be7aab4e34f5e2d678df0caa0) 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). ![Screenshot of search results showing one clothing variant for each design, with color swatches below each item.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/distinct/distinct-2.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=3f49e025effdbaa5fbc6a24551a372f9) ## 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. ![Results for an iPhone search, sorted from lowest to highest price](https://mintcdn.com/algolia/uAYFrBCMSmYQz381/images/guides/sorting/hard-sort.jpg?fit=max\&auto=format\&n=uAYFrBCMSmYQz381\&q=85\&s=55dc34a6c30ac3e9cf65362f1ff0204c) 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). ![Results for an iPhone search, sorted from lowest to highest price using Relevant sort](https://mintcdn.com/algolia/uAYFrBCMSmYQz381/images/guides/sorting/relevant-sort.jpg?fit=max\&auto=format\&n=uAYFrBCMSmYQz381\&q=85\&s=72398ebf28939d7b8abbb3140c8c5145) ## 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. ![Find the associated primary index of a replica you want to rename](https://mintcdn.com/algolia/uAYFrBCMSmYQz381/images/guides/sorting/find-primary.png?fit=max\&auto=format\&n=uAYFrBCMSmYQz381\&q=85\&s=c57e524a38754318a8f51e3b5ec358e1) 2. With the **primary index** selected, go to the **Replica** tab, select the replica index you want to unlink, and click **Unlink Replica**. ![Screenshot of a 'Replicas' tab showing a list of replica indices with a replica index name label and a link icon.](https://mintcdn.com/algolia/uAYFrBCMSmYQz381/images/guides/sorting/unlink-replica.png?fit=max\&auto=format\&n=uAYFrBCMSmYQz381\&q=85\&s=503cdee59f6efdce4f00fcf9b8a59346) 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"} ``` # 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). ![Screenshot of a 'SortBy' drop-down menu showing options for 'Relevance', 'Price ascending', and 'Price desc.'.](https://mintcdn.com/algolia/uAYFrBCMSmYQz381/images/guides/sorting/sortby-widget.png?fit=max\&auto=format\&n=uAYFrBCMSmYQz381\&q=85\&s=b85befa7f2afb5b15acd7d4972d61946) ## 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 ![Screenshot of a search results page showing 'Exhaustive sort - Lowest price' with three Dynex™ iPhone cases listed, each with a price of \$2.99.](https://mintcdn.com/algolia/uAYFrBCMSmYQz381/images/guides/sorting/hard-sort.jpg?fit=max\&auto=format\&n=uAYFrBCMSmYQz381\&q=85\&s=55dc34a6c30ac3e9cf65362f1ff0204c) ### Relevant sorting ![Screenshot of a search results page for 'iPhone' showing 63 relevant results sorted by 'Relevant sort - Lowest price' with phone listings.](https://mintcdn.com/algolia/uAYFrBCMSmYQz381/images/guides/sorting/relevant-sort.jpg?fit=max\&auto=format\&n=uAYFrBCMSmYQz381\&q=85\&s=72398ebf28939d7b8abbb3140c8c5145) ## 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). ![Screenshot of a 'SortBy' drop-down menu showing options: 'Relevance', 'Price ascending', and 'Price desc.'.](https://mintcdn.com/algolia/uAYFrBCMSmYQz381/images/guides/sorting/sortby-widget.png?fit=max\&auto=format\&n=uAYFrBCMSmYQz381\&q=85\&s=b85befa7f2afb5b15acd7d4972d61946) 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: ![Screenshot of a search interface with 'iPhone' in the search box, showing 63 relevant results sorted by 'Relevant sort - Lowest price'.](https://mintcdn.com/algolia/uAYFrBCMSmYQz381/images/guides/sorting/relevant-sort-widget.jpg?fit=max\&auto=format\&n=uAYFrBCMSmYQz381\&q=85\&s=14f2703649e1d90d86fea3f77edb7e16) 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 { new Condition { Pattern = "article", Anchoring = Enum.Parse("StartsWith") }, }, Consequence = new Consequence { Params = new ConsequenceParams { Query = new ConsequenceQuery( new ConsequenceQueryObject { Edits = new List { new Edit { Type = Enum.Parse("Remove"), Delete = "article" }, }, } ), RestrictSearchableAttributes = new List { "title", "book_id" }, }, }, } ); ``` ```dart Dart theme={"system"} final response = await client.saveRule( indexName: "INDEX_NAME", objectID: "article-rule", rule: Rule( objectID: "article-rule", conditions: [ Condition( pattern: "article", anchoring: Anchoring.fromJson("startsWith"), ), ], consequence: Consequence( params: ConsequenceParams( query: ConsequenceQueryObject( edits: [ Edit( type: EditType.fromJson("remove"), delete: "article", ), ], ), restrictSearchableAttributes: [ "title", "book_id", ], ), ), ), ); ``` ```go Go theme={"system"} response, err := client.SaveRule(client.NewApiSaveRuleRequest( "INDEX_NAME", "article-rule", search.NewEmptyRule().SetObjectID("article-rule").SetConditions( []search.Condition{*search.NewEmptyCondition().SetPattern("article").SetAnchoring(search.Anchoring("startsWith"))}).SetConsequence( search.NewEmptyConsequence().SetParams( search.NewEmptyConsequenceParams().SetQuery(search.ConsequenceQueryObjectAsConsequenceQuery( search.NewEmptyConsequenceQueryObject().SetEdits( []search.Edit{ *search.NewEmptyEdit().SetType(search.EditType("remove")).SetDelete("article"), }), )).SetRestrictSearchableAttributes( []string{"title", "book_id"}))))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.saveRule( "INDEX_NAME", "article-rule", new Rule() .setObjectID("article-rule") .setConditions(Arrays.asList(new Condition().setPattern("article").setAnchoring(Anchoring.STARTS_WITH))) .setConsequence( new Consequence().setParams( new ConsequenceParams() .setQuery(new ConsequenceQueryObject().setEdits(Arrays.asList(new Edit().setType(EditType.REMOVE).setDelete("article")))) .setRestrictSearchableAttributes(Arrays.asList("title", "book_id")) ) ) ); ``` ```js JavaScript theme={"system"} const response = await client.saveRule({ indexName: 'indexName', objectID: 'article-rule', rule: { objectID: 'article-rule', conditions: [{ pattern: 'article', anchoring: 'startsWith' }], consequence: { params: { query: { edits: [{ type: 'remove', delete: 'article' }] }, restrictSearchableAttributes: ['title', 'book_id'], }, }, }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.saveRule( indexName = "INDEX_NAME", objectID = "article-rule", rule = Rule( objectID = "article-rule", conditions = listOf( Condition( pattern = "article", anchoring = Anchoring.entries.first { it.value == "startsWith" }, ) ), consequence = Consequence( params = ConsequenceParams( query = ConsequenceQueryObject( edits = listOf( Edit( type = EditType.entries.first { it.value == "remove" }, delete = "article", ) ) ), restrictSearchableAttributes = listOf("title", "book_id"), ) ), ), ) ``` ```php PHP theme={"system"} $response = $client->saveRule( 'INDEX_NAME', 'article-rule', ['objectID' => 'article-rule', 'conditions' => [ ['pattern' => 'article', 'anchoring' => 'startsWith', ], ], 'consequence' => ['params' => ['query' => ['edits' => [ ['type' => 'remove', 'delete' => 'article', ], ], ], 'restrictSearchableAttributes' => [ 'title', 'book_id', ], ], ], ], ); ``` ```python Python theme={"system"} response = client.save_rule( index_name="INDEX_NAME", object_id="article-rule", rule={ "objectID": "article-rule", "conditions": [ { "pattern": "article", "anchoring": "startsWith", }, ], "consequence": { "params": { "query": { "edits": [ { "type": "remove", "delete": "article", }, ], }, "restrictSearchableAttributes": [ "title", "book_id", ], }, }, }, ) ``` ```ruby Ruby theme={"system"} response = client.save_rule( "INDEX_NAME", "article-rule", Algolia::Search::Rule.new( algolia_object_id: "article-rule", conditions: [Algolia::Search::Condition.new(pattern: "article", anchoring: "startsWith")], consequence: Algolia::Search::Consequence.new( params: Algolia::Search::ConsequenceParams.new( query: Algolia::Search::ConsequenceQueryObject.new( edits: [Algolia::Search::Edit.new(type: "remove", delete: "article")] ), restrict_searchable_attributes: ["title", "book_id"] ) ) ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.saveRule( indexName = "INDEX_NAME", objectID = "article-rule", rule = Rule( objectID = "article-rule", conditions = Some( Seq( Condition( pattern = Some("article"), anchoring = Some(Anchoring.withName("startsWith")) ) ) ), consequence = Consequence( params = Some( ConsequenceParams( query = Some( ConsequenceQueryObject( edits = Some( Seq( Edit( `type` = Some(EditType.withName("remove")), delete = Some("article") ) ) ) ) ), restrictSearchableAttributes = Some(Seq("title", "book_id")) ) ) ) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.saveRule( indexName: "INDEX_NAME", objectID: "article-rule", rule: Rule( objectID: "article-rule", conditions: [SearchCondition(pattern: "article", anchoring: SearchAnchoring.startsWith)], consequence: SearchConsequence(params: SearchConsequenceParams( restrictSearchableAttributes: ["title", "book_id"], query: SearchConsequenceQuery .searchConsequenceQueryObject(SearchConsequenceQueryObject(edits: [SearchEdit( type: SearchEditType.remove, delete: "article" )])) )) ) ) ``` ### Using the dashboard You can add this rule from the Algolia dashboard using either the Visual Editor or the Manual Editor. The `title` and `id` attributes must already be in your index's `searchableAttributes`. > [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 `article`, and click **Apply**. > 3. In **Consequences**, expand **More consequences** and select **Apply search parameters**. > Enter `{ "restrictSearchableAttributes": ["title", "id"] }` in **Value** and click **Apply**. > 4. Click **Add Consequence**, expand **More consequences**, and select **Remove word**. > Enter `article` in **Word(s)** and click **Apply**. > 5. Preview the results for `article ref21`, click **Review and Publish**, and click **Publish**. > [tab] Manual Editor > > 1. In the Algolia dashboard, select **Search**, select 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.), 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 `article`. > 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**. > Enter `{ "restrictSearchableAttributes": ["title", "id"] }`. > 4. Click **Add consequence** again and select **Remove Word**. > Enter `article`. > 5. Save your changes. # Merchandising Source: https://www.algolia.com/doc/guides/managing-results/rules/merchandising-and-promoting Merchandise search results, facets, and category pages with Algolia. Apply rules to influence the ranking of individual records or whole categories. Merchandising is the promotion of specific products or categories of products. Depending on your goals, merchandising lets you: * Offer recommendations to your users * Highlight your best products * Follow trends * Improve conversion rate (CVR) (The percentage of searches with `clickAnalytics` set to `true` that result in at least one conversion event.) Algolia offers multiple merchandising capabilities, all powered by rules. In the Algolia dashboard, use the Visual Editor for most merchandising rules. It supports the same conditions and consequences as the Manual Editor and previews their effect on search results. Use the Manual Editor when you prefer to enter rule details directly, including JSON values, and don't need a preview. For more control and dynamic compositions of your results feed, see [Smart Groups](/doc/guides/compositions/smart-groups/smart-groups). For more information, see: * [Rules overview](/doc/guides/managing-results/rules/rules-overview) * [What can I do with the Visual Editor?](https://support.algolia.com/hc/en-us/articles/4406981935761) ## Merchandising Studio This feature isn't available on every plan. Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. The [Merchandising Studio](https://www.algolia.com/industries-and-solutions/ecommerce/digital-merchandising/) is an optional feature designed for digital merchandisers who prefer a no-code approach. It allows ecommerce users to focus on search merchandising, category merchandising, and facet merchandising. > [card] [Algolia Academy](https://academy.algolia.com/pages/contentcatalog/training/0199aac7-4dc8-7736-9a66-99cc095d6360/overview) > > Learn more about: Merchandising Studio: Overview ## Promote hits [Promoting a hit](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/promote-hits) means you want it to appear first in results for a specific 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.). For example: * A bookstore may want to recommend a Harry Potter box set whenever the term "Harry Potter" appears in the search. * You may want to manage new releases for the latest iPhone. With custom ranking, you've placed best-selling items at the top of your search results. Unfortunately, this doesn't work well with new releases, which don't have any sales yet (meaning they're probably listed last). With rules, you can force new releases to the top of the results list without changing your custom ranking. You can promote up to 300 hits in a single rule. For more information, see: * [Promoting hits](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/promote-hits) * [Pin blocks](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/pin-blocks) * [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking) ## Hide hits You can [hide hits](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/how-to-hide-hits) if required. 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.) may contain records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) relevant only to a specific search. For example, you have a website where people can search for dishes from restaurants with a home delivery service If someone types "gluten free", you don't want to show items with gluten (even though gluten is in the search query). Hiding hits lets you remove all gluten items from the results. ## Redirect to URL You can [add a redirect](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/redirects) to a specific URL. For example, consider that you find in your search analytics that a lot of your users are searching for "help" but don't get any results. You can add a redirect for this query to your website's FAQ or support page. This improves the user experience and reduces the no-results rate. Other examples include searching for a category. For example the query "TV" redirects users to the corresponding category page. Or, searching for a specific product or brand redirects users to a seasonal promotional page for it. ## Display banners You can [display a banner](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/add-banners) to promote an item, a group of products, a business-wide sale, and so on. Using the same logic as hits promotion (looking for specific keywords in a query), you can create a rule with the necessary data to display a banner: the text, HTML, CSS, and associated images. ## Dynamically promote filters You can use a rule to promote products based on [user intent](/doc/guides/managing-results/rules/detecting-intent). For example, if a user appears interested in an important product category, you can apply a dynamic 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 promote relevant items. You can also generate optional filters based on the user's behavior. ### Use optional filters Optional filters let you favor some filters over others. They behave like regular filters, except that with optional filtering, **results that don't match a filter aren't excluded altogether**: they're only ranked lower in the results. For example, if you know a user is interested in the brand Samsung, you can promote that brand with optional filters: when users type "phone", all Samsung phones will be returned first, followed by all other phones. For more information, see: * [Promote records with optional filters](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/how-to-promote-with-optional-filters) * [Optional filters](/doc/guides/managing-results/rules/merchandising-and-promoting/in-depth/optional-filters) ## Combine rules You can use the merchandising techniques individually or [in combination](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/combining-multiple-consequences): one condition can have more than one consequence. For example, if a user types "Harry Potter", you can simultaneously: * Promote movies over books * Set the most recent Harry Potter box set as the first result * Add filters for J. K. Rowling, children's novels, or the fantasy genre. # Promote results with rules Source: https://www.algolia.com/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/promote-hits Use rules to promote (pin) specific search results or display a banner at the top of your search results. To promote groups of results dynamically, see [Smart Groups](/doc/guides/compositions/smart-groups/smart-groups). To keep pinned items together when one of them becomes unavailable, see [Keep pinned items together with pin blocks](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/pin-blocks). > [!NOTE] > > If `typoTolerance` is `min` or `strict`, > promoted results might not appear if they contain more typos than the top results. ## Promote a single items For example, a book store wants to recommend a Harry Potter box set whenever the words "Harry Potter" form part of a search. ### Rule If 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.) is `Harry Potter`, promote the Harry Potter box set. > [!NOTE] > > By default, you can pin up to 300 records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) per rule. ### With 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", "promote-harry-potter-box-set", new Rule { ObjectID = "promote-harry-potter-box-set", Conditions = new List { new Condition { Pattern = "Harry Potter", Anchoring = Enum.Parse("Contains") }, }, Consequence = new Consequence { Promote = new List { new Promote(new PromoteObjectID { ObjectID = "HP-12345", Position = 0 }), }, }, } ); ``` ```dart Dart theme={"system"} final response = await client.saveRule( indexName: "INDEX_NAME", objectID: "promote-harry-potter-box-set", rule: Rule( objectID: "promote-harry-potter-box-set", conditions: [ Condition( pattern: "Harry Potter", anchoring: Anchoring.fromJson("contains"), ), ], consequence: Consequence( promote: [ PromoteObjectID( objectID: "HP-12345", position: 0, ), ], ), ), ); ``` ```go Go theme={"system"} response, err := client.SaveRule(client.NewApiSaveRuleRequest( "INDEX_NAME", "promote-harry-potter-box-set", search.NewEmptyRule().SetObjectID("promote-harry-potter-box-set").SetConditions( []search.Condition{*search.NewEmptyCondition().SetPattern("Harry Potter").SetAnchoring(search.Anchoring("contains"))}).SetConsequence( search.NewEmptyConsequence().SetPromote( []search.Promote{*search.PromoteObjectIDAsPromote( search.NewEmptyPromoteObjectID().SetObjectID("HP-12345").SetPosition(0))})))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.saveRule( "INDEX_NAME", "promote-harry-potter-box-set", new Rule() .setObjectID("promote-harry-potter-box-set") .setConditions(Arrays.asList(new Condition().setPattern("Harry Potter").setAnchoring(Anchoring.CONTAINS))) .setConsequence(new Consequence().setPromote(Arrays.asList(new PromoteObjectID().setObjectID("HP-12345").setPosition(0)))) ); ``` ```js JavaScript theme={"system"} const response = await client.saveRule({ indexName: 'indexName', objectID: 'promote-harry-potter-box-set', rule: { objectID: 'promote-harry-potter-box-set', conditions: [{ pattern: 'Harry Potter', anchoring: 'contains' }], consequence: { promote: [{ objectID: 'HP-12345', position: 0 }] }, }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.saveRule( indexName = "INDEX_NAME", objectID = "promote-harry-potter-box-set", rule = Rule( objectID = "promote-harry-potter-box-set", conditions = listOf( Condition( pattern = "Harry Potter", anchoring = Anchoring.entries.first { it.value == "contains" }, ) ), consequence = Consequence(promote = listOf(PromoteObjectID(objectID = "HP-12345", position = 0))), ), ) ``` ```php PHP theme={"system"} $response = $client->saveRule( 'INDEX_NAME', 'promote-harry-potter-box-set', ['objectID' => 'promote-harry-potter-box-set', 'conditions' => [ ['pattern' => 'Harry Potter', 'anchoring' => 'contains', ], ], 'consequence' => ['promote' => [ ['objectID' => 'HP-12345', 'position' => 0, ], ], ], ], ); ``` ```python Python theme={"system"} response = client.save_rule( index_name="INDEX_NAME", object_id="promote-harry-potter-box-set", rule={ "objectID": "promote-harry-potter-box-set", "conditions": [ { "pattern": "Harry Potter", "anchoring": "contains", }, ], "consequence": { "promote": [ { "objectID": "HP-12345", "position": 0, }, ], }, }, ) ``` ```ruby Ruby theme={"system"} response = client.save_rule( "INDEX_NAME", "promote-harry-potter-box-set", Algolia::Search::Rule.new( algolia_object_id: "promote-harry-potter-box-set", conditions: [Algolia::Search::Condition.new(pattern: "Harry Potter", anchoring: "contains")], consequence: Algolia::Search::Consequence.new( promote: [Algolia::Search::PromoteObjectID.new(algolia_object_id: "HP-12345", position: 0)] ) ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.saveRule( indexName = "INDEX_NAME", objectID = "promote-harry-potter-box-set", rule = Rule( objectID = "promote-harry-potter-box-set", conditions = Some( Seq( Condition( pattern = Some("Harry Potter"), anchoring = Some(Anchoring.withName("contains")) ) ) ), consequence = Consequence( promote = Some( Seq( PromoteObjectID( objectID = "HP-12345", position = 0 ) ) ) ) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.saveRule( indexName: "INDEX_NAME", objectID: "promote-harry-potter-box-set", rule: Rule( objectID: "promote-harry-potter-box-set", conditions: [SearchCondition(pattern: "Harry Potter", anchoring: SearchAnchoring.contains)], consequence: SearchConsequence(promote: [SearchPromote.searchPromoteObjectID(SearchPromoteObjectID( objectID: "HP-12345", position: 0 ))]) ) ) ``` ### With the Visual 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.). 2. On the left sidebar, select **Search**. 3. Select your Algolia 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.). 4. Select [**Rules**](https://dashboard.algolia.com/rules) from the left sidebar. 5. Select **Create your first Rule** or **New rule**. 6. Click **Visual Editor**. 7. Under **Conditions**: 1. Click **Set query condition(s)**. 2. In **Your search**, type `Harry Potter` and click **Apply**. 8. Under **Consequences**: 1. Click **Pin items**. 2. In **Pinned items**, select the item you want to promote (for example, `HP-12345`) and click **Apply**. 9. **Review and Publish** your rule. ### With the Manual Editor 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. 4. Select [**Rules**](https://dashboard.algolia.com/rules) from the left sidebar. 5. Select **Create your first Rule** or **New rule**. 6. Click **Manual Editor**. 7. In the **Condition(s)** section, enter `Harry Potter` in the **Query** field. 8. In the **Consequence(s)** section: 1. Click **Add consequence** and select **Pin an item**. 2. Find and select the item you want to promote. For example, `HP-12345`. 9. **Save** your rule. ## Promote the newest release For example, you've placed "best-selling items" at the top of your search results by using [custom ranking](/doc/guides/managing-results/must-do/custom-ranking). But the newest release? Set up a rule that promotes the newest iPhone for searches containing `iPhone`, while keeping other phones sorted by most-sold. ### Rule If query is `iphone`, promote the newest iPhone release. ### With the API ```cs C# theme={"system"} var response = await client.SaveRuleAsync( "INDEX_NAME", "Promote-iPhone-X", new Rule { ObjectID = "Promote-iPhone-X", Conditions = new List { new Condition { Pattern = "iPhone", Anchoring = Enum.Parse("Contains") }, }, Consequence = new Consequence { Promote = new List { new Promote(new PromoteObjectID { ObjectID = "iPhone-12345", Position = 0 }), }, }, } ); ``` ```dart Dart theme={"system"} final response = await client.saveRule( indexName: "INDEX_NAME", objectID: "Promote-iPhone-X", rule: Rule( objectID: "Promote-iPhone-X", conditions: [ Condition( pattern: "iPhone", anchoring: Anchoring.fromJson("contains"), ), ], consequence: Consequence( promote: [ PromoteObjectID( objectID: "iPhone-12345", position: 0, ), ], ), ), ); ``` ```go Go theme={"system"} response, err := client.SaveRule(client.NewApiSaveRuleRequest( "INDEX_NAME", "Promote-iPhone-X", search.NewEmptyRule().SetObjectID("Promote-iPhone-X").SetConditions( []search.Condition{*search.NewEmptyCondition().SetPattern("iPhone").SetAnchoring(search.Anchoring("contains"))}).SetConsequence( search.NewEmptyConsequence().SetPromote( []search.Promote{*search.PromoteObjectIDAsPromote( search.NewEmptyPromoteObjectID().SetObjectID("iPhone-12345").SetPosition(0))})))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.saveRule( "INDEX_NAME", "Promote-iPhone-X", new Rule() .setObjectID("Promote-iPhone-X") .setConditions(Arrays.asList(new Condition().setPattern("iPhone").setAnchoring(Anchoring.CONTAINS))) .setConsequence(new Consequence().setPromote(Arrays.asList(new PromoteObjectID().setObjectID("iPhone-12345").setPosition(0)))) ); ``` ```js JavaScript theme={"system"} const response = await client.saveRule({ indexName: 'indexName', objectID: 'Promote-iPhone-X', rule: { objectID: 'Promote-iPhone-X', conditions: [{ pattern: 'iPhone', anchoring: 'contains' }], consequence: { promote: [{ objectID: 'iPhone-12345', position: 0 }] }, }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.saveRule( indexName = "INDEX_NAME", objectID = "Promote-iPhone-X", rule = Rule( objectID = "Promote-iPhone-X", conditions = listOf( Condition( pattern = "iPhone", anchoring = Anchoring.entries.first { it.value == "contains" }, ) ), consequence = Consequence( promote = listOf(PromoteObjectID(objectID = "iPhone-12345", position = 0)) ), ), ) ``` ```php PHP theme={"system"} $response = $client->saveRule( 'INDEX_NAME', 'Promote-iPhone-X', ['objectID' => 'Promote-iPhone-X', 'conditions' => [ ['pattern' => 'iPhone', 'anchoring' => 'contains', ], ], 'consequence' => ['promote' => [ ['objectID' => 'iPhone-12345', 'position' => 0, ], ], ], ], ); ``` ```python Python theme={"system"} response = client.save_rule( index_name="INDEX_NAME", object_id="Promote-iPhone-X", rule={ "objectID": "Promote-iPhone-X", "conditions": [ { "pattern": "iPhone", "anchoring": "contains", }, ], "consequence": { "promote": [ { "objectID": "iPhone-12345", "position": 0, }, ], }, }, ) ``` ```ruby Ruby theme={"system"} response = client.save_rule( "INDEX_NAME", "Promote-iPhone-X", Algolia::Search::Rule.new( algolia_object_id: "Promote-iPhone-X", conditions: [Algolia::Search::Condition.new(pattern: "iPhone", anchoring: "contains")], consequence: Algolia::Search::Consequence.new( promote: [Algolia::Search::PromoteObjectID.new(algolia_object_id: "iPhone-12345", position: 0)] ) ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.saveRule( indexName = "INDEX_NAME", objectID = "Promote-iPhone-X", rule = Rule( objectID = "Promote-iPhone-X", conditions = Some( Seq( Condition( pattern = Some("iPhone"), anchoring = Some(Anchoring.withName("contains")) ) ) ), consequence = Consequence( promote = Some( Seq( PromoteObjectID( objectID = "iPhone-12345", position = 0 ) ) ) ) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.saveRule( indexName: "INDEX_NAME", objectID: "Promote-iPhone-X", rule: Rule( objectID: "Promote-iPhone-X", conditions: [SearchCondition(pattern: "iPhone", anchoring: SearchAnchoring.contains)], consequence: SearchConsequence(promote: [SearchPromote.searchPromoteObjectID(SearchPromoteObjectID( objectID: "iPhone-12345", position: 0 ))]) ) ) ``` ### With the Visual Editor 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. 4. Select [**Rules**](https://dashboard.algolia.com/rules) from the left sidebar. 5. Select **Create your first Rule** or **New rule**. 6. Click **Visual Editor**. 7. Under **Conditions**: 1. Click **Set query condition(s)**. 2. In **Your search**, type `iPhone` and click **Apply**. 8. Under **Consequences**: 1. Click **Pin item**. 2. In **Pinned items**, select the item you want to promote (for example, `iPhone-12345`) and then click **Apply**. 9. **Review and Publish** your rule. ### With the Manual Editor 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. 4. Select [**Rules**](https://dashboard.algolia.com/rules) from the left sidebar. 5. Select **Create your first Rule** or **New rule**. 6. Click **Manual Editor**. 7. In the **Condition(s)** section, enter `iPhone` in the **Query** field and change the drop-down from **Is** to **Contains**. 8. In the **Consequence(s)** section: 1. Click **Add consequence** and select **Pin an item**. 2. Find and select the item you want to promote (for example, `iPhone-12345`). 9. **Save** your rule. ## Promote several results For example, you're running a promotion on the newest Apple products. Set up a rule that promotes the newest Apple releases at the top of results for searches containing `apple`. ### Rule If query is `apple`, promote the newest Apple releases. ### With the API ```cs C# theme={"system"} var response = await client.SaveRuleAsync( "INDEX_NAME", "Promote-Apple-Newest", new Rule { ObjectID = "Promote-Apple-Newest", Conditions = new List { new Condition { Pattern = "apple", Anchoring = Enum.Parse("Is") }, }, Consequence = new Consequence { Promote = new List { new Promote( new PromoteObjectIDs { ObjectIDs = new List { "iPhone-12345", "watch-123" }, Position = 0, } ), }, }, } ); ``` ```dart Dart theme={"system"} final response = await client.saveRule( indexName: "INDEX_NAME", objectID: "Promote-Apple-Newest", rule: Rule( objectID: "Promote-Apple-Newest", conditions: [ Condition( pattern: "apple", anchoring: Anchoring.fromJson("is"), ), ], consequence: Consequence( promote: [ PromoteObjectIDs( objectIDs: [ "iPhone-12345", "watch-123", ], position: 0, ), ], ), ), ); ``` ```go Go theme={"system"} response, err := client.SaveRule(client.NewApiSaveRuleRequest( "INDEX_NAME", "Promote-Apple-Newest", search.NewEmptyRule().SetObjectID("Promote-Apple-Newest").SetConditions( []search.Condition{*search.NewEmptyCondition().SetPattern("apple").SetAnchoring(search.Anchoring("is"))}).SetConsequence( search.NewEmptyConsequence().SetPromote( []search.Promote{*search.PromoteObjectIDsAsPromote( search.NewEmptyPromoteObjectIDs().SetObjectIDs( []string{"iPhone-12345", "watch-123"}).SetPosition(0))})))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.saveRule( "INDEX_NAME", "Promote-Apple-Newest", new Rule() .setObjectID("Promote-Apple-Newest") .setConditions(Arrays.asList(new Condition().setPattern("apple").setAnchoring(Anchoring.IS))) .setConsequence( new Consequence().setPromote( Arrays.asList(new PromoteObjectIDs().setObjectIDs(Arrays.asList("iPhone-12345", "watch-123")).setPosition(0)) ) ) ); ``` ```js JavaScript theme={"system"} const response = await client.saveRule({ indexName: 'indexName', objectID: 'Promote-Apple-Newest', rule: { objectID: 'Promote-Apple-Newest', conditions: [{ pattern: 'apple', anchoring: 'is' }], consequence: { promote: [{ objectIDs: ['iPhone-12345', 'watch-123'], position: 0 }] }, }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.saveRule( indexName = "INDEX_NAME", objectID = "Promote-Apple-Newest", rule = Rule( objectID = "Promote-Apple-Newest", conditions = listOf( Condition( pattern = "apple", anchoring = Anchoring.entries.first { it.value == "is" }, ) ), consequence = Consequence( promote = listOf( PromoteObjectIDs(objectIDs = listOf("iPhone-12345", "watch-123"), position = 0) ) ), ), ) ``` ```php PHP theme={"system"} $response = $client->saveRule( 'INDEX_NAME', 'Promote-Apple-Newest', ['objectID' => 'Promote-Apple-Newest', 'conditions' => [ ['pattern' => 'apple', 'anchoring' => 'is', ], ], 'consequence' => ['promote' => [ ['objectIDs' => [ 'iPhone-12345', 'watch-123', ], 'position' => 0, ], ], ], ], ); ``` ```python Python theme={"system"} response = client.save_rule( index_name="INDEX_NAME", object_id="Promote-Apple-Newest", rule={ "objectID": "Promote-Apple-Newest", "conditions": [ { "pattern": "apple", "anchoring": "is", }, ], "consequence": { "promote": [ { "objectIDs": [ "iPhone-12345", "watch-123", ], "position": 0, }, ], }, }, ) ``` ```ruby Ruby theme={"system"} response = client.save_rule( "INDEX_NAME", "Promote-Apple-Newest", Algolia::Search::Rule.new( algolia_object_id: "Promote-Apple-Newest", conditions: [Algolia::Search::Condition.new(pattern: "apple", anchoring: "is")], consequence: Algolia::Search::Consequence.new( promote: [Algolia::Search::PromoteObjectIDs.new(object_ids: ["iPhone-12345", "watch-123"], position: 0)] ) ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.saveRule( indexName = "INDEX_NAME", objectID = "Promote-Apple-Newest", rule = Rule( objectID = "Promote-Apple-Newest", conditions = Some( Seq( Condition( pattern = Some("apple"), anchoring = Some(Anchoring.withName("is")) ) ) ), consequence = Consequence( promote = Some( Seq( PromoteObjectIDs( objectIDs = Seq("iPhone-12345", "watch-123"), position = 0 ) ) ) ) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.saveRule( indexName: "INDEX_NAME", objectID: "Promote-Apple-Newest", rule: Rule( objectID: "Promote-Apple-Newest", conditions: [SearchCondition(pattern: "apple", anchoring: SearchAnchoring.`is`)], consequence: SearchConsequence(promote: [SearchPromote.searchPromoteObjectIDs(SearchPromoteObjectIDs( objectIDs: ["iPhone-12345", "watch-123"], position: 0 ))]) ) ) ``` ### With the Visual Editor 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. 4. Select [**Rules**](https://dashboard.algolia.com/rules) from the left sidebar. 5. Select **Create your first Rule** or **New rule**. 6. Click **Visual Editor**. 7. Under **Conditions**: 1. Click **Set query condition(s)**. 2. In **Your search**, type `apple` and click **Apply**. 8. Under **Consequences**: 1. Click **Pin items**. 2. Click **Pin multiple items**. 3. One per line, enter the object IDs of the items you want to pin, for example, `iPhone-12345` and `watch-123`, in the input field and then click **Pin items**. 4. Click **Apply**. 9. **Review and Publish** your rule. > [!NOTE] > > You can't promote more than one item with the Manual Editor. ## Promote results matching active filters For example, you only want to promote items if they match a user's active 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.), such as stock availability, color, or category. This prevents irrelevant items such as out-of-stock products from appearing. ### Rule Create a rule with a consequence that pins the item only if it matches active filters. ### With the API To pin an item only if it matches active filters, set [`filterPromotes`](/doc/rest-api/search/save-rule#body-consequence) to `true` in the rule's consequence. ```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 EnableFilterPromote { async Task Main(string[] args) { var condition = new Condition { Anchoring = Anchoring.Is, Pattern = "{facet:brand}" }; var consequence = new Consequence { FilterPromotes = true }; var rule = new Rule { ObjectID = "rule_with_filterPromotes", Conditions = [condition], Consequence = consequence, }; } } ``` ```dart Dart theme={"system"} import 'package:algolia_client_search/algolia_client_search.dart'; void enableFilterPromote() async { final condition = Condition( pattern: "{facet:brand}", anchoring: Anchoring.is_, ); final consequence = Consequence(filterPromotes: true); // ignore: unused_local_variable final rule = Rule( objectID: "rule_with_filterPromotes", conditions: [condition], consequence: consequence); } ``` ```go Go theme={"system"} package main import ( "fmt" "github.com/algolia/algoliasearch-client-go/v4/algolia/search" ) func enableFilterPromote() { condition := search.NewCondition(). SetPattern("{facet:brand}"). SetAnchoring(search.ANCHORING_IS) rule := search.NewRule( "rule_with_filterPromotes", *search.NewConsequence().SetFilterPromotes(true), ).SetEnabled(true).SetConditions([]search.Condition{*condition}) fmt.Printf("Rule: %#v\n", rule) } ``` ```java Java theme={"system"} package com.algolia; import com.algolia.config.*; import com.algolia.model.search.*; import java.util.List; public class enableFilterPromote { public static void main(String[] args) throws Exception { Condition condition = new Condition().setAnchoring(Anchoring.IS).setPattern("{facet:brand}"); Consequence consequence = new Consequence().setFilterPromotes(true); Rule rule = new Rule().setObjectID("rule_with_filterPromotes").setConditions(List.of(condition)).setConsequence(consequence); } } ``` ```js JavaScript theme={"system"} import type { Condition, Consequence } from 'algoliasearch'; const condition: Condition = { anchoring: 'is', pattern: '{facet:brand}', }; const consequence: Consequence = { filterPromotes: true, }; ``` ```kotlin Kotlin theme={"system"} import com.algolia.client.configuration.* import com.algolia.client.extensions.* import com.algolia.client.model.search.* import com.algolia.client.transport.* suspend fun enableFilterPromote() { val condition = Condition(pattern = "{facet:brand}", anchoring = Anchoring.Is) val consequence = Consequence(filterPromotes = true) val rule = Rule( objectID = "rule_with_filterPromotes", conditions = listOf(condition), consequence = consequence, ) } ``` ```php PHP theme={"system"} setAnchoring((new Anchoring())::IS) ->setPattern('{facet:brand}') ; $consequence = (new Consequence()) ->setFilterPromotes(true) ; $rule = (new Rule()) ->setObjectID('rule_with_filterPromotes') ->setConditions([$condition]) ->setConsequence($consequence); ``` ```python Python theme={"system"} from algoliasearch.search.models.anchoring import Anchoring from algoliasearch.search.models.condition import Condition from algoliasearch.search.models.consequence import Consequence from algoliasearch.search.models.rule import Rule condition = Condition( anchoring=Anchoring.IS, pattern="{facet:brand}", ) consequence = Consequence( filter_promotes=True, ) rule = Rule( enabled=True, object_id="rule_with_filterPromotes", conditions=[condition], consequence=consequence, ) ``` ```ruby Ruby theme={"system"} condition = Algolia::Search::Condition.new( anchoring: Algolia::Search::Anchoring::IS, pattern: "{facet:brand}" ) consequence = Algolia::Search::Consequence.new( filter_promotes: true ) rule = Algolia::Search::Rule.new( enabled: true, object_id: "rule_with_filterPromotes", conditions: [condition], consequence: consequence ) ``` ```scala Scala theme={"system"} import scala.concurrent.ExecutionContext.Implicits.global import algoliasearch.api.SearchClient import algoliasearch.config.* import algoliasearch.extension.SearchClientExtensions import algoliasearch.search.{Anchoring, Condition, Consequence, Rule} def enableFilterPromote(): Unit = { val condition = Condition( pattern = Some("{facet:brand}"), anchoring = Some(Anchoring.Is) ) val consequence = Consequence( filterPromotes = Some(true) ) val rule = Rule( objectID = "rule_with_filterPromotes", conditions = Some(Seq(condition)), consequence = consequence ) } ``` ```swift Swift theme={"system"} import Foundation #if os(Linux) // For linux interop import FoundationNetworking #endif import AlgoliaCore import AlgoliaSearch func enableFilterPromote() async throws { let condition = SearchCondition( pattern: "{facet:brand}", anchoring: .is ) let consequence = SearchConsequence( filterPromotes: true ) let rule = Rule(objectID: "rule_with_filterPromotes", conditions: [condition], consequence: consequence) print(rule) } ``` ### With the Visual Editor 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. 4. Select [**Rules**](https://dashboard.algolia.com/rules) from the left sidebar. 5. Select **Create your first Rule** or **New rule**. 6. Click **Visual Editor**. 7. Under **Conditions**: 1. Click **Set query condition(s)**. 2. In **Your search**, enter the item you want to promote (for example, `shoes`) and click **Apply**. 8. Under **Consequences**: 1. Click **Pin items**. 2. In **Pinned items**, select the item you want to promote. 3. Keep **Must match active filters** turned on and click **Apply**. 9. **Review and Publish** your rule. ### With the Manual Editor 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. 4. Select [**Rules**](https://dashboard.algolia.com/rules) from the left sidebar. 5. Select **Create your first Rule** or **New rule**. 6. Click **Manual Editor**. 7. In the **Condition(s)** section, enter the query (for example, `shoes`) in the **Query** field. 8. In the **Consequence(s)** section: 1. Click **Add consequence** and select **Pin an item**. 2. Find and select the item you want to promote (for example, `brogue-12345`). 9. In the **Additional settings** section, keep the checkbox **Pinned items must match active filters to be displayed** selected. 10. **Save** your rule. # Rules overview Source: https://www.algolia.com/doc/guides/managing-results/rules/rules-overview How to use Algolia's Rules feature to create flexible and dynamic search results. Search needs to be flexible. Product promotions should be seamless: search setting adjustments or record updates aren't needed. If one of your products isn't appearing where you want it to in your search results, you can reposition it. Rules let you make precise and (if desired) temporary modifications to your search results. Relevance and 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.) settings provide the general structure for your search results. **Rules let you optimize and dynamically adjust that structure.** > [!NOTE] > > Rules are no substitute for a well-configured index. > Make sure you [adjust your general relevance](/doc/guides/managing-results/relevance-overview) before adding rules. ## Index rules and composition rules Algolia uses two different types of rules: index rules and composition rules. This section describes index rules. For more information about composition rules, see [Smart Groups](/doc/guides/compositions/smart-groups/smart-groups). ## What are rules used for? Rules let you make precise, predetermined changes to your search results. For example, you can pin or hide items, boost or bury categories, or 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.) results based on the query. You can also enable rules for a fixed time, making rules a great way of implementing sales or promotions. **Rules are typically used either for merchandising or fine-tuning your search and relevance.** For more information about typical uses for rules, see [Merchandising](/doc/guides/managing-results/rules/merchandising-and-promoting) and [Detecting intent](/doc/guides/managing-results/rules/detecting-intent). > [!NOTE] > > The maximum number of rules per index depends on your [Algolia plan](https://www.algolia.com/pricing). ## Rule structure Three elements define a rule: conditions, consequences, and a validity period. Of these three elements, only a consequence is mandatory. **When a rule's condition is satisfied, Algolia applies its consequences**. If a rule doesn't have any conditions (a [conditionless rule](#conditionless-rules)), Algolia always applies its consequences. For more information, see: * [Implement rules](/doc/guides/managing-results/rules/rules-overview/in-depth/implementing-rules) * [Troubleshoot rules](/doc/guides/managing-results/rules/rules-overview/in-depth/debugging-rules) ## Conditions A rule can have up to 25 conditions. A rule with several conditions activates when any condition is satisfied. In other words, conditions have an `or` relationship to one another. Conditions may contain a pattern string, anchoring, context, or filters. To determine if a rule's consequences apply: 1. Algolia compares the user's query to the condition's pattern. It bases the comparison on the condition's anchoring. Values for anchoring are: `is`, `contains`, `starts with`, and `ends with`. The anchoring determines what part of the query the pattern must match: all of it, part of it, the beginning, or the end. If you enable [`alternatives`](/doc/rest-api/search/save-rule#body-condition) in the condition, user queries can match the condition's pattern, even if the query is a plural, synonym, or typo of the pattern. Without alternatives, queries must precisely match the condition's pattern and anchoring. 2. It compares the condition's filters to the [filters](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-string) applied to the search. 3. It compares the condition's context to the search's [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts). If a [single condition includes a pattern string, filters, and a context](#pattern-filters-and-context), a search must match all three for the condition to be satisfied. With three conditions, a rule activates if any is met. If a condition uses `is` anchoring with an empty pattern, Algolia applies the rule if users haven't yet entered a query. If a condition doesn't include a pattern, anchoring, context, or filters, it's a conditionless rule, and Algolia applies it to every search. Rules can respond to different types of conditions: * [Query patterns](#rules-responding-to-user-queries) * [Filters](#rules-responding-to-applied-filters) * [Context-only](#context-only-rules) * [Conditionless](#conditionless-rules). ### Example conditions #### Using a pattern with is anchoring Trigger a rule with this condition whenever the query string is the word `sale` and nothing else. If the query contains other words, the rule isn't triggered. ```json JSON icon=braces theme={"system"} { "pattern": "sale", "anchoring": "is" } ``` #### A pattern with contains anchoring Trigger a rule with this condition whenever the query string contains the word `featured`. The query can include other words. ```json JSON icon=braces theme={"system"} { "pattern": "featured", "anchoring": "contains" } ``` #### A context Trigger a rule when the search includes `mobile` in the [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts). ```json JSON icon=braces theme={"system"} { "context": "mobile" } ``` #### Filters Trigger a rule when the filter `{ "filters": "category:TV" }` applies to the query. ```json JSON icon=braces theme={"system"} { "filters": "category:TV" } ``` > [!NOTE] > > The filters condition rejects an `OR` combination of different attributes. > For example, `"filters": "brand:Guess OR color:orange"` returns an error. > You also can't use numeric filters in conditions. #### Pattern, filters, and context Trigger a rule when: * The query contains the word `featured` * [`filters`](/doc/api-reference/api-parameters/filters) includes `category:TV` * [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts) includes `mobile`. ```json JSON icon=braces theme={"system"} { "pattern": "featured", "anchoring": "contains", "filters": "category:TV", "context": "mobile" } ``` #### An empty string pattern Trigger a rule when there is an empty search. The rule turns off as soon as a user starts typing. ```json JSON icon=braces theme={"system"} { "pattern": "", "anchoring": "is" } ``` ## Consequences Though conditions aren't required, rules must have at least one consequence. Consequences change the search results. Examples of consequences include: * **Pin an item**. Insert an item at a specific position. If the item is already in the results, it moves to that position. * **Hide an item**. Remove a specific result from the list of results. * **Add a query parameter**. Add a query parameter to your user's search. For example, you could decrease the [`aroundRadius`](/doc/api-reference/api-parameters/aroundRadius) if your user's query includes the words "near me". * **Remove word**. Remove a specific word from your user's 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.) (for search, not display purposes). * **Replace word**. Replace a word from your user's search query with another word (for search, not display purposes). * **Replace query**. Replace the entire user search query with another query (for search, not display purposes). * **Return custom data**. Add custom JSON data to the search response. * **Filter/Boost matching attributes**. Apply [`filters`](/doc/api-reference/api-parameters/filters) or [`optionalFilters`](/doc/api-reference/api-parameters/optionalFilters) matching the query to the results. Use the **Visual Editor** to configure rules with guided controls and preview their effect on search results. The **Manual Editor** offers a more technical workflow for configuring rule conditions, consequences, and search parameters directly. Both editors use the [Rules API](/doc/libraries/sdk/methods/search/save-rules). The Visual Editor supports custom data, search parameters, query edits, matching-attribute filters, and merchandising actions such as pinning, hiding, and displaying banners. Custom data and search parameters use JSON inputs in both editors. For more information, see: * [Rules consequences](/doc/guides/managing-results/rules/rules-overview/in-depth/implementing-rules#consequence—adapt-the-results) * [Merchandising](/doc/guides/managing-results/rules/merchandising-and-promoting) ## Validity period If you want to apply a rule temporarily, you can set a validity period. The validity period determines how long a rule remains active. Use this to ensure promotions and sales end automatically. ## Rules responding to user queries Rules can parse user queries and apply consequences if the query matches the rule's condition. Queries can match with conditions in the following ways: * `is`: the entire query matches the condition string. * `contains`: the entire query contains the condition string. * `startsWith`: the query starts with the condition string. * `endsWith`: the query ends with the condition string. To [trigger a rule on an empty query](/doc/guides/managing-results/rules/detecting-intent), use `is` anchoring and set the condition's pattern to an empty string. ## Rules responding to applied filters If a rule's condition includes filters, Algolia applies that rule's consequences only if the filters in a search's query parameters exactly match the condition's filters. For example, Algolia only triggers a rule with the condition `{"filters": "category:TV"}` if the `category` value is `TV`. Other filters like `"category:Smartphone"` or `"category:TV OR category:Smartphone"` don't trigger the rule. **The primary goal of this condition is to create rules that trigger on specific category pages or when a user applies specific filters.** For example, you can define a rule with the condition `{"filters": "category:TV"}` and a consequence to promote specific items. The rule triggers when a user lands on the TV category page. For more information, see [Filters can trigger rules](/doc/guides/managing-results/rules/rules-overview/in-depth/implementing-rules#filters-can-trigger-rules). > [!NOTE] > > To use an attribute in a rule condition with filters, > you must first [declare it in `attributesForFaceting`](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting). ## Context-only rules If a rule's condition only includes a context, the consequence only applies if the rule's context exactly matches a value in the [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts) parameter of a user's search. ### What are contexts? Contexts provide information about your user's search environment: for example, what section of a website they're currently visiting or what device they're using. A context can be any string value that doesn't include whitespace characters. The search condition it specifies must be identifiable at search time. You need to implement logic for sending contexts with queries. You can pass contexts through the [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts) search parameter. ### Implementation To create a rule with a context condition, you must: 1. Create the rule (with the dashboard or the API). 2. Conditionally assign its associated context to your user's searches. To clarify the process, consider the example of an electronics store. By looking at [analytics tags](/doc/guides/search-analytics/guides/segments), it's clear that mobile users rarely find what they're searching for on the first page of results. Testing indicates this is because vague searches for accessories like "chargers" or "cases" return laptop chargers and cases first. If mobile users search for cases, you can assume that they prefer phone cases over laptop cases. If they search for chargers, phone chargers should show up first. To remedy this, add a rule that promotes all items with the "phone" tag but only for users searching from a mobile platform. To learn more, see [Using rules to customize search results by device](/doc/guides/managing-results/rules/rules-overview/how-to/customize-search-results-by-platform). ## Conditionless rules Rules without conditions apply to every search. They're an effective way of temporarily modifying search for a predefined time, for example, for seasonal promotions. For more information, see [Add default search parameters with rules](/doc/guides/managing-results/rules/rules-overview/how-to/add-default-search-parameters-with-rules). You can create conditionless rules from the Algolia dashboard or with one of the API clients. ### Using the dashboard You can create a conditionless 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**. > If you're using the Merchandising Studio Visual Merchandiser instead, select **Actions > Create new rule**. > 2. In **Conditions**, select **Create conditionless rule**, review its scope, and click **Apply**. > A conditionless rule applies to every search. Leaving a query field empty instead creates an empty-query condition. > 3. Select the consequences you want to apply. > Optional: select **Add a date range** to limit when the rule applies. > 4. Click **Review and Publish**, review the rule, and click **Publish**. > [tab] Manual Editor > > 1. In the Algolia dashboard, select your application and index, open [**Rules**](https://dashboard.algolia.com/rules), and select **New rule > Manual Editor**. > 2. In **Condition(s)**, remove the default condition. > 3. In **Consequence(s)**, add the consequences you want to apply. > To limit when the rule applies, select a time range in **Additional Settings > Timeframe in UTC**. > To forward the rule to replicas or other indices, select **Copy this rule to other indices** and enter their names. > 4. Save your changes. ### Using the API To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install). To create a conditionless rule with an API client, use the [`saveRule`](/doc/libraries/sdk/methods/search/save-rule) method on a rule object but without a condition. ```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 UseConditionlessRule { async Task Main(string[] args) { var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")); var objectID = "a-rule-id"; var rule = new Rule { ObjectID = objectID, Consequence = new Consequence( /* Set relevant consequence */ ), // Set validity (optional) Validity = [new TimeRange { From = 1688774400L, Until = 1738972800L }], }; await client.SaveRuleAsync("INDEX_NAME", objectID, rule); } } ``` ```dart Dart theme={"system"} import 'package:algolia_client_search/algolia_client_search.dart'; void useConditionlessRule() async { final client = SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY'); final objectID = "a-rule-id"; final rule = Rule( objectID: objectID, consequence: Consequence( // Set relevant consequence ), // Set validity (optional) validity: [TimeRange(from: 1688774400, until: 1738972800)]); await client.saveRule( indexName: "INDEX_NAME", objectID: objectID, rule: rule, ); } ``` ```go Go theme={"system"} package main import ( "github.com/algolia/algoliasearch-client-go/v4/algolia/search" "github.com/algolia/algoliasearch-client-go/v4/algolia/utils" ) func useConditionlessRule() { 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) } objectID := "a-rule-id" rule := search.NewRule( objectID, *search.NewConsequence( /* Set relevant consequence */ ), ). // Set validity (optional) SetValidity( []search.TimeRange{{ From: utils.ToPtr(int64(1_688_774_400)), Until: utils.ToPtr(int64(1_738_972_800)), }}, ) _, err = client.SaveRule(client.NewApiSaveRuleRequest( "INDEX_NAME", objectID, rule)) if err != nil { panic(err) } } ``` ```java Java theme={"system"} package com.algolia; import com.algolia.api.SearchClient; import com.algolia.config.*; import com.algolia.model.search.*; import java.util.List; public class useConditionlessRule { public static void main(String[] args) throws Exception { try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) { String objectID = "a-rule-id"; Rule rule = new Rule().setObjectID(objectID).setConsequence(new Consequence(/* Set relevant consequence */)); // Set validity (optional) rule.setValidity(List.of(new TimeRange().setFrom(1688774400L).setUntil(1738972800L))); client.saveRule("INDEX_NAME", objectID, rule); } catch (Exception e) { System.out.println("An error occurred: " + e.getMessage()); } } } ``` ```js JavaScript theme={"system"} import { algoliasearch } from 'algoliasearch'; import type { Rule } from 'algoliasearch'; const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY'); const objectID = 'a-rule-id'; const rule: Rule = { objectID, consequence: { // Set relevant consequence }, }; // Set validity (optional) rule.validity = [ { from: 1688774400, until: 1738972800, }, ]; await client.saveRule({ indexName: 'indexName', objectID: objectID, rule: rule }); ``` ```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 useConditionlessRule() { val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY") val objectID = "a-rule-id" val rule = Rule( objectID = objectID, consequence = Consequence( // Set relevant consequence ), // Set validity (optional) validity = listOf(TimeRange(from = 1_688_774_400, until = 1_738_972_800)), ) client.saveRule(indexName = "INDEX_NAME", objectID = objectID, rule = rule) } ``` ```php PHP theme={"system"} setObjectID($objectID) ->setConsequence(new Consequence(/* Set relevant consequence */)) ; // Set validity (optional) $rule->setValidity( [ (new TimeRange()) ->setFrom(1688774400) ->setUntil(1738972800), ] ); $client->saveRule( 'INDEX_NAME', $objectID, $rule, ); ``` ```python Python theme={"system"} from algoliasearch.search.client import SearchClientSync from algoliasearch.search.models.consequence import Consequence from algoliasearch.search.models.rule import Rule from algoliasearch.search.models.time_range import TimeRange _client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY") object_id = "a-rule-id" rule = Rule( object_id=object_id, consequence=Consequence( # Set relevant consequence ), ) # Set validity (optional) rule.validity = [ TimeRange( var_from=1688774400, until=1738972800, ), ] _client.save_rule( index_name="INDEX_NAME", object_id=object_id, rule=rule, ) ``` ```ruby Ruby theme={"system"} require "algolia" client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY") object_id = "a-rule-id" rule = Algolia::Search::Rule.new( object_id: object_id, consequence: Algolia::Search::Consequence.new # Set relevant consequence ) # Set validity (optional) rule.validity = [ Algolia::Search::TimeRange.new( from: 1688774400, until: 1738972800 ) ] client.save_rule("INDEX_NAME", algolia_object_id, rule) ``` ```scala Scala theme={"system"} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import algoliasearch.api.SearchClient import algoliasearch.config.* import algoliasearch.extension.SearchClientExtensions import algoliasearch.search.{Consequence, Rule, TimeRange} def useConditionlessRule(): Future[Unit] = { val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY") val objectID = "a-rule-id"; val rule = Rule( objectID = objectID, consequence = Consequence( // Set relevant consequence ), // Set validity (optional) validity = Some( Seq( TimeRange( from = Some(1_688_774_400), until = Some(1_738_972_800) ) ) ) ) client .saveRule( indexName = "INDEX_NAME", objectID = objectID, rule = rule ) .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 useConditionlessRule() async throws { let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY") let objectID = "a-rule-id" let rule = Rule( objectID: objectID, consequence: SearchConsequence( // Set relevant consequence ), // Set validity (optional) validity: [ SearchTimeRange( from: 1_688_774_400, until: 1_738_972_800 ), ] ) try await client.saveRule(indexName: "INDEX_NAME", objectID: objectID, rule: rule) } ``` ## Context-only and conditionless rules deactivate consequences Since conditionless and context-only rules don't have a query as a condition, these consequences are deactivated. | Consequence | Why is it deactivated? | | - | - | | Pin an item | The same item would always appear in the same position, usually at the top, for all queries and all users. Instead, consider [adding a banner](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/add-banners) to promote items you want to pin. | | Remove word, Replace word, Replace query | Since there's no query, there are no words to remove or replace. | | Filter/Boost matching attributes | Rules that filter and boost facets require users to type a facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) attribute into the query. Since there's no query or facet in the condition, there's no filter for the rule to boost. | > [!NOTE] > > These limitations only apply to **context-only** and **conditionless** rules. > Other rules can use the full range of consequences. ## Choose a rule editor Use the Visual Editor for most rules. It supports the same conditions and consequences as the Manual Editor and lets you preview their effect on search results. Choose another option when you need a different workflow. | Option | Use it when | | - | - | | **Visual Editor** | You want a guided workflow and an instant preview of how a rule affects search results. | | **Manual Editor** | You want to enter rule details directly, including JSON values, and don't need an instant preview. | | **Rules API** | You want to create or update rules in bulk, automate rule management, or build a custom interface. | ## Rule tags Create and manage rule tags in the [Algolia dashboard](https://dashboard.algolia.com/rules) or Merchandising Studio. On the **Rules** page, click **Manage tags**. A rule tag includes: | Attribute | Required | Description | | - | - | - | | Name | Yes | Unique within the index. | | Description | No | Describes the tag's purpose. | | Color | No | Color shown for the tag in the dashboard. | By default, indices may contain up to 100 rule tags. After you create a rule tag, you can add it to individual rules in the **Visual Editor** and **Manual Editor**. # Add default search parameters with rules Source: https://www.algolia.com/doc/guides/managing-results/rules/rules-overview/how-to/add-default-search-parameters-with-rules Learn how to use an API client or the dashboard to add search parameters using always active rules. You can create rules to add default parameters to every search made to the associated 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.). This is most useful when applied with a [validity period](/doc/guides/managing-results/rules/rules-overview#validity-period). If you want to add always active parameters for an indefinite period of time, change your [index's settings](/doc/api-reference/api-parameters). This page shows you how to create a rule that sets: * The [`aroundRadius`](/doc/api-reference/api-parameters/aroundRadius) search parameter, for all queries, to 1,000 meters. * The validity period of this rule to one month. This is an example of a [conditionless rule](/doc/guides/managing-results/rules/rules-overview#conditionless-rules). ## Using the API To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install). To create a rule with one of the API clients, use the [`saveRule`](/doc/libraries/sdk/v1/methods/save-rule) method using a rule object without a condition. ```cs C# theme={"system"} var response = await client.SaveRuleAsync( "INDEX_NAME", "a-rule-id", new Rule { ObjectID = "a-rule-id", Consequence = new Consequence { Params = new ConsequenceParams { AroundRadius = new AroundRadius(1000) }, }, Validity = new List { new TimeRange { From = 1577836800L, Until = 1577836800L }, }, } ); ``` ```dart Dart theme={"system"} final response = await client.saveRule( indexName: "INDEX_NAME", objectID: "a-rule-id", rule: Rule( objectID: "a-rule-id", consequence: Consequence( params: ConsequenceParams( aroundRadius: 1000, ), ), validity: [ TimeRange( from: 1577836800, until: 1577836800, ), ], ), ); ``` ```go Go theme={"system"} response, err := client.SaveRule(client.NewApiSaveRuleRequest( "INDEX_NAME", "a-rule-id", search.NewEmptyRule().SetObjectID("a-rule-id").SetConsequence( search.NewEmptyConsequence().SetParams( search.NewEmptyConsequenceParams().SetAroundRadius(search.Int32AsAroundRadius(1000)))).SetValidity( []search.TimeRange{*search.NewEmptyTimeRange().SetFrom(1577836800).SetUntil(1577836800)}))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.saveRule( "INDEX_NAME", "a-rule-id", new Rule() .setObjectID("a-rule-id") .setConsequence(new Consequence().setParams(new ConsequenceParams().setAroundRadius(AroundRadius.of(1000)))) .setValidity(Arrays.asList(new TimeRange().setFrom(1577836800L).setUntil(1577836800L))) ); ``` ```js JavaScript theme={"system"} const response = await client.saveRule({ indexName: 'indexName', objectID: 'a-rule-id', rule: { objectID: 'a-rule-id', consequence: { params: { aroundRadius: 1000 } }, validity: [{ from: 1577836800, until: 1577836800 }], }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.saveRule( indexName = "INDEX_NAME", objectID = "a-rule-id", rule = Rule( objectID = "a-rule-id", consequence = Consequence(params = ConsequenceParams(aroundRadius = AroundRadius.of(1000))), validity = listOf(TimeRange(from = 1577836800L, until = 1577836800L)), ), ) ``` ```php PHP theme={"system"} $response = $client->saveRule( 'INDEX_NAME', 'a-rule-id', ['objectID' => 'a-rule-id', 'consequence' => ['params' => ['aroundRadius' => 1000, ], ], 'validity' => [ ['from' => 1577836800, 'until' => 1577836800, ], ], ], ); ``` ```python Python theme={"system"} response = client.save_rule( index_name="INDEX_NAME", object_id="a-rule-id", rule={ "objectID": "a-rule-id", "consequence": { "params": { "aroundRadius": 1000, }, }, "validity": [ { "from": 1577836800, "until": 1577836800, }, ], }, ) ``` ```ruby Ruby theme={"system"} response = client.save_rule( "INDEX_NAME", "a-rule-id", Algolia::Search::Rule.new( algolia_object_id: "a-rule-id", consequence: Algolia::Search::Consequence.new(params: Algolia::Search::ConsequenceParams.new(around_radius: 1000)), validity: [Algolia::Search::TimeRange.new(from: 1577836800, _until: 1577836800)] ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.saveRule( indexName = "INDEX_NAME", objectID = "a-rule-id", rule = Rule( objectID = "a-rule-id", consequence = Consequence( params = Some( ConsequenceParams( aroundRadius = Some(AroundRadius(1000)) ) ) ), validity = Some( Seq( TimeRange( from = Some(1577836800L), until = Some(1577836800L) ) ) ) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.saveRule( indexName: "INDEX_NAME", objectID: "a-rule-id", rule: Rule( objectID: "a-rule-id", consequence: SearchConsequence(params: SearchConsequenceParams(aroundRadius: SearchAroundRadius .int(1000))), validity: [SearchTimeRange(from: Int64(1_577_836_800), until: Int64(1_577_836_800))] ) ) ``` ## Using the dashboard You can create 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. Select **Create conditionless rule** and click **Apply**. > 3. In **Consequences**, expand **More consequences** and select **Apply search parameters**. > Enter `{ "aroundRadius": 1000 }` in **Value** and click **Apply**. > 4. Select **Add a date range**, set the start and end dates one month apart, and click **Apply**. > 5. Click **Review and Publish** and review the rule. > To forward the rule to replicas or other indices, select them under **Copy rule to**. > Click **Publish**. > [tab] Manual Editor > > 1. In the Algolia dashboard, select your application and index, open [**Rules**](https://dashboard.algolia.com/rules), and select **New rule > Manual Editor**. > 2. In **Condition(s)**, remove the default condition. > 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**. > Enter `{ "aroundRadius": 1000 }`. > 4. In **Additional Settings > Timeframe in UTC**, select a time range of one month. > 5. To forward the rule to replicas or other indices, select **Copy this rule to other indices** and enter their names. > Save your changes. > [!NOTE] > > In JSON, you must write string values inside quotation marks, and number values and booleans without quotations. > For more information about JSON syntax, > see [Algolia records](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record). # Customize search results by device Source: https://www.algolia.com/doc/guides/managing-results/rules/rules-overview/how-to/customize-search-results-by-platform Learn how to customize your search results based on whether your user is on a mobile or desktop device. Search needs to be flexible. Depending on the user's context, results can vary. For example, because some products are more likely to be bought or viewed from phones than desktops, you may want to promote items based on the user's device. To do this: 1. Identify your user's device. 2. Assign a `mobile` context when users search from mobile devices. A context is a string passed as a search parameter to InstantSearch or one of the Algolia API clients. 3. Create a contextual rule that acts on the `mobile` context. > [!NOTE] > > To help understand user behavior across devices, pair your device-specific experience with analytics. > You can even use the same context string to tag search queries for analytics. > These [`analyticsTags`](/doc/guides/search-analytics/guides/segments) help you group search analytics into different segments. ## Example scenario Suppose you work for a movie database company. Research shows that mobile users, unlike desktop users, tend to see a film in theaters after it has appeared in their search results. To capitalize on this, you partner with a movie theater chain and decide to promote newly released movies to your mobile users. The records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in your Algolia 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.) include a `release_date` attribute, in [Unix timestamp](https://www.unixtimestamp.com/) format, this is useful for identifying newly released movies. ```json JSON icon=braces theme={"system"} { "title": "Fargo", "year": 1996, "director": "Joel Coen", "release_date": 841795200, "rating": 93 } ``` By creating a mobile context and contextual rules, you can promote recent movies to mobile users. ## Identify your user's device Before assigning a mobile context to searches, write a function that detects what device the app is running on, and return "desktop" or "mobile" depending on what it finds: ```js JavaScript icon=code theme={"system"} const getPlatform = () => { // const isMobile = ... // Your logic to determine whether a user is on a mobile or desktop device return isMobile ? "mobile" : "desktop"; }; ``` ## Assign context You can assign context with InstantSearch or one of the Algolia API clients. ### Assign context with InstantSearch Set the user's device context at the beginning of their search session by adding it to the `searchParameters` setting of your InstantSearch instance [during initialization](/doc/api-reference/widgets/instantsearch/js). ```js JavaScript theme={"system"} const platformTag = getPlatform(); instantsearch.widgets.configure({ ruleContexts: [platformTag], }); ``` ```swift Swift theme={"system"} let platform = getPlatform() let searcher = HitsSearcher(appId: "YourAppId", apiKey: "ALGOLIA_API_KEY", indexName: "YourIndexName") searcher.request.query.ruleContexts = [platform] ``` ```jsx React theme={"system"} import { Configure } from 'react-instantsearch'; const platformTag = getPlatform() ``` ```vue Vue theme={"system"} ``` ```kotlin Android theme={"system"} val platform: String = getPlatform() val query = query { ruleContexts { +platform } } val searcher = HitsSearcher(movies, query) ``` ### Assign context with an API client To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install). Pass the user's device context as a search parameter whenever you search your index. ```cs C# theme={"system"} namespace Algolia; using System; using System.Collections.Generic; using System.Net.Http; using System.Text.Json; using Algolia.Search.Clients; using Algolia.Search.Http; using Algolia.Search.Models.Search; class SearchWithRuleContexts { private static string GetPlatformTag() { return ""; // Implement your logic here } async Task Main(string[] args) { var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")); var platformTag = GetPlatformTag(); var searchParams = new SearchParams( new SearchParamsObject { Query = "SEARCH_QUERY", RuleContexts = [platformTag] } ); await client.SearchSingleIndexAsync("INDEX_NAME", searchParams); } } ``` ```dart Dart theme={"system"} import 'package:algolia_client_search/algolia_client_search.dart'; String getPlatformTag() { return ""; // Implement your logic here } void searchWithRuleContexts() async { final client = SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY'); final platformTag = getPlatformTag(); final searchParams = SearchParamsObject( query: "SEARCH_QUERY", ruleContexts: [platformTag]); await client.searchSingleIndex( indexName: "INDEX_NAME", searchParams: searchParams, ); } ``` ```go Go theme={"system"} package main import ( "github.com/algolia/algoliasearch-client-go/v4/algolia/search" ) func getPlatformTag() (string, error) { return "", nil // Implement your logic here } func searchWithRuleContexts() { 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) } platformTag, err := getPlatformTag() if err != nil { panic(err) } searchParams := search.SearchParamsObjectAsSearchParams( search.NewSearchParamsObject(). SetQuery("SEARCH_QUERY"). SetRuleContexts([]string{platformTag}), ) _, 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.util.List; public class searchWithRuleContexts { private static String getPlatformTag() { return ""; // Implement your logic here } public static void main(String[] args) throws Exception { try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) { String platformTag = getPlatformTag(); SearchParams searchParams = new SearchParamsObject().setQuery("SEARCH_QUERY").setRuleContexts(List.of(platformTag)); 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 getPlatformTag = () => { return ''; // Implement your logic here }; const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY'); const platformTag = getPlatformTag(); const searchParams: SearchParams = { query: 'SEARCH_QUERY', ruleContexts: [platformTag], }; 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.* val getPlatformTag: () -> String = { "" // Implement your logic here } suspend fun searchWithRuleContexts() { val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY") val platformTag = getPlatformTag() val searchParams = SearchParamsObject(query = "SEARCH_QUERY", ruleContexts = listOf(platformTag)) client.searchSingleIndex(indexName = "INDEX_NAME", searchParams = searchParams) } ``` ```php PHP theme={"system"} setQuery('SEARCH_QUERY') ->setRuleContexts([$platformTag]) ; $client->searchSingleIndex( 'INDEX_NAME', $searchParams, ); ``` ```python Python theme={"system"} from algoliasearch.search.client import SearchClientSync from algoliasearch.search.models.search_params import SearchParams def _get_platform_tag() -> str: # Implement your logic here return "" _client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY") # get the buyer account information platform_tag = _get_platform_tag() search_params = SearchParams( query="SEARCH_QUERY", rule_contexts=[platform_tag], ) _client.search_single_index( index_name="INDEX_NAME", search_params=search_params, ) ``` ```ruby Ruby theme={"system"} require "algolia" def get_platform_tag # Implement your logic here "" end client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY") # get the buyer account information platform_tag = get_platform_tag search_params = Algolia::Search::SearchParamsObject.new( query: "SEARCH_QUERY", rule_contexts: [platform_tag] ) client.search_single_index("INDEX_NAME", search_params) ``` ```scala Scala theme={"system"} import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global import algoliasearch.api.SearchClient import algoliasearch.config.* import algoliasearch.extension.SearchClientExtensions import algoliasearch.search.SearchParamsObject val getPlatformTag: String = { "" // Implement your logic here } def searchWithRuleContexts(): Future[Unit] = { val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY") val platformTag = getPlatformTag val searchParams = SearchParamsObject( query = Some("SEARCH_QUERY"), ruleContexts = Some(Seq(platformTag)) ) 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 let getPlatformTag = { "" } // Implement your logic here func searchWithRuleContexts() async throws { let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY") let platformTag = getPlatformTag() let searchParams = SearchSearchParams.searchSearchParamsObject( SearchSearchParamsObject(query: "SEARCH_QUERY", ruleContexts: [platformTag]) ) let response: SearchResponse = try await client.searchSingleIndex( indexName: "INDEX_NAME", searchParams: searchParams ) print(response) } ``` ## Create a contextual rule You can create a mobile contextual rule with an API client or from the Algolia dashboard. ### Using the API Use the [`saveRule`](/doc/libraries/sdk/methods/search/save-rule) method to create a context-dependent rule. For example, to promote movies released after January 1, 2020, for mobile users: ```cs C# theme={"system"} var response = await client.SaveRuleAsync( "INDEX_NAME", "a-rule-id", new Rule { ObjectID = "a-rule-id", Conditions = new List { new Condition { Context = "mobile" } }, Consequence = new Consequence { Params = new ConsequenceParams { Filters = "release_date >= 1577836800" }, }, } ); ``` ```dart Dart theme={"system"} final response = await client.saveRule( indexName: "INDEX_NAME", objectID: "a-rule-id", rule: Rule( objectID: "a-rule-id", conditions: [ Condition( context: "mobile", ), ], consequence: Consequence( params: ConsequenceParams( filters: "release_date >= 1577836800", ), ), ), ); ``` ```go Go theme={"system"} response, err := client.SaveRule(client.NewApiSaveRuleRequest( "INDEX_NAME", "a-rule-id", search.NewEmptyRule().SetObjectID("a-rule-id").SetConditions( []search.Condition{*search.NewEmptyCondition().SetContext("mobile")}).SetConsequence( search.NewEmptyConsequence().SetParams( search.NewEmptyConsequenceParams().SetFilters("release_date >= 1577836800"))))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.saveRule( "INDEX_NAME", "a-rule-id", new Rule() .setObjectID("a-rule-id") .setConditions(Arrays.asList(new Condition().setContext("mobile"))) .setConsequence(new Consequence().setParams(new ConsequenceParams().setFilters("release_date >= 1577836800"))) ); ``` ```js JavaScript theme={"system"} const response = await client.saveRule({ indexName: 'indexName', objectID: 'a-rule-id', rule: { objectID: 'a-rule-id', conditions: [{ context: 'mobile' }], consequence: { params: { filters: 'release_date >= 1577836800' } }, }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.saveRule( indexName = "INDEX_NAME", objectID = "a-rule-id", rule = Rule( objectID = "a-rule-id", conditions = listOf(Condition(context = "mobile")), consequence = Consequence(params = ConsequenceParams(filters = "release_date >= 1577836800")), ), ) ``` ```php PHP theme={"system"} $response = $client->saveRule( 'INDEX_NAME', 'a-rule-id', ['objectID' => 'a-rule-id', 'conditions' => [ ['context' => 'mobile', ], ], 'consequence' => ['params' => ['filters' => 'release_date >= 1577836800', ], ], ], ); ``` ```python Python theme={"system"} response = client.save_rule( index_name="INDEX_NAME", object_id="a-rule-id", rule={ "objectID": "a-rule-id", "conditions": [ { "context": "mobile", }, ], "consequence": { "params": { "filters": "release_date >= 1577836800", }, }, }, ) ``` ```ruby Ruby theme={"system"} response = client.save_rule( "INDEX_NAME", "a-rule-id", Algolia::Search::Rule.new( algolia_object_id: "a-rule-id", conditions: [Algolia::Search::Condition.new(context: "mobile")], consequence: Algolia::Search::Consequence.new( params: Algolia::Search::ConsequenceParams.new(filters: "release_date >= 1577836800") ) ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.saveRule( indexName = "INDEX_NAME", objectID = "a-rule-id", rule = Rule( objectID = "a-rule-id", conditions = Some( Seq( Condition( context = Some("mobile") ) ) ), consequence = Consequence( params = Some( ConsequenceParams( filters = Some("release_date >= 1577836800") ) ) ) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.saveRule( indexName: "INDEX_NAME", objectID: "a-rule-id", rule: Rule( objectID: "a-rule-id", conditions: [SearchCondition(context: "mobile")], consequence: SearchConsequence(params: SearchConsequenceParams(filters: "release_date >= 1577836800")) ) ) ``` ### Using the dashboard You can create 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)**. > Turn **Query** off, turn **Context** on, enter `mobile`, and click **Apply**. > 3. In **Consequences**, select **Filter results**. > Select `release_date`, set the operator to `>=`, enter `1577836800`, and click **Apply**. > 4. Click **Review and Publish**, review the rule, and click **Publish**. > [tab] Manual Editor > > 1. In the Algolia dashboard, select your application and index, open [**Rules**](https://dashboard.algolia.com/rules), and select **New rule > Manual Editor**. > 2. In **Condition(s)**, turn **Query** off, turn **Context** on, and enter `mobile`. > 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**. > To add 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.) for movies released since January 1, 2020, enter `{ "filters": "release_date >= 1577836800" }`. > 4. Save your changes. # Debug rules Source: https://www.algolia.com/doc/guides/managing-results/rules/rules-overview/in-depth/debugging-rules How to debug rules. If you have many rules, it can be difficult to understand which rules apply to a given 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.). Algolia provides tools to help you with this. The [`getRankingInfo`](/doc/api-reference/api-parameters/getRankingInfo) parameter adds extra information about rules: * **Promoted hits** are labeled with a `"promoted": true` field inside the `_rankingInfo` object. This field is omitted from regular hits. * The list of **applied Rules** is available inside the `appliedRules` top-level field. This field is omitted when no rule has been applied (even if `enableRules` is true). The `params` response field also has search parameters after Rule application, and as such reflects the side-effects of the rules. > [!NOTE] > > When in doubt, you can always run the same query with `enableRules=false` and compare the results you get with and without rules. ## Improve accuracy of rules Sometimes, creating rules can lead to lower search accuracy. The following table lists common causes for a lower-than-expected search accuracy when applying rules. | Cause of less accurate results | Solution | | - | - | | Not adding a "remove word" consequence when turning search phrases into query parameters | [Add a remove word consequence](/doc/guides/managing-results/rules/detecting-intent/how-to/applying-a-custom-filter-for-a-specific-query#positive-filters) after any add query parameter consequence | | Not creating an optional filter as a facet | If you want to use an attribute as an optional filter, [declare it as a facet](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting) | | Not taking advantage of "add search parameter" consequences | See some [common uses for this consequence](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/rules-query-parameters) | # Implement rules Source: https://www.algolia.com/doc/guides/managing-results/rules/rules-overview/in-depth/implementing-rules Learn how to add index rules to curate your search results. **Use the dashboard to familiarize yourself with rules**. The dashboard provides a responsive interface to create and configure your rules. And importantly, test your choices using the dashboard's live search interface. You may never need to go beyond the dashboard if your rules are manageable in the UI or follow a defined pattern. In this way, you can consider the dashboard as a way to manage static rules—rules that remain in place for some time or whose effect is global and constant. Open the **Rules** page for a selected 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.) and select **New rule**. Use the Visual Editor for most rules. Choose the Manual Editor when you prefer to enter rule details directly, including JSON values, and don't need a preview. Both editors support the same conditions and consequences. To automate or manage rules in bulk, use the Rules API. ## Coding rules with the API clients Coding rules using the Rules API becomes necessary when you need more programmatic control. This move from static to more dynamic rules can arise for several reasons—like when you can't manage rules one-by-one or when you want to start building (and deleting) rules based on the content of your database. If your rules change with every new update of your index, or if you offer a frontend to manage your rules, use the API. You must set up your index for the API and then set up the rules at indexing time. ## How index rules works **Algolia adapts its behavior according to a rule's consequences**. Rules are if-then configurations or condition/consequence pairs: the consequence applies to the returned results if the condition is satisfied. For example, a rule with the condition "user's query contains the word red" and the consequence "filter on the facet color:red". And that's only one kind of consequence. As described in the overview and presented in technical detail below— Different kinds of consequences adapt the search and alter the relevance. Rules are processed at indexing time to prepare the index so that when someone performs a search, the data is ready for the if-then matching. Keep in mind: * If the `alternatives` setting of a rule is `false`, the condition must match the entire word (as a whole word) for the rule to be triggered. * All text matching is case insensitive. * Conditions can match on multiple phrases. * A rule can have multiple conditions and multiple consequences. > [!NOTE] > > Rules don't noticeably affect search performance. > Most of the work happens during indexing. ## Turning rules on and off Turn a rule off by setting its `enabled` flag to `false`. Rules that have been turned off remain in the index. They're still searchable, but ignored at query time. This is an alternative to deleting a rule when you wish to turn it off temporarily. For even more fine-grained control, you can specify **validity windows** on rules (with the `validity` attribute). Rules with validity windows only apply during the specified time frames: they're ignored the rest of the time. Rules with no time windows always apply. Like with the `enabled` flag, rules with validity windows remain searchable all the time. ## API overview ### Pre-processing and post-processing As suggested, rules allow fine-tuning results for queries matching specific patterns. They do so in two complementary ways: 1. **Query pre-processing.** Rules may alter the query parameters—not only the query string but also features such filters and facets—before the query is processed. 2. **Results post-processing.** Rules might cause results (hits) to be ranked differently for specific queries. They can also add user data to the results. Rules are complementary to the traditional ranking and textual relevance settings: while settings act globally on every search to an index, rules act selectively. ### Conditions and consequences The general syntax is as follows: ```jsonc JSON icon=braces theme={"system"} { "conditions": [ { // What the query must match for the rule to be applied. }, ], "consequence": { // How the query will be modified if the rule is applied. } } ``` #### Condition: what the query must match Conditions identify query strings matching a specific pattern. If the query string matches all the rule's conditions, it activates the rule for that search. More precisely, a condition consists of: * An optional **query pattern** acting on the whole text query string, that is, the `query` search parameter. * Optional **anchoring** defining how the rule's query pattern compares to user queries. * Optional **filters**, which must match the filters applied with the query. These can be [`filters`](/doc/api-reference/api-parameters/filters) or [`facetFilters`](/doc/api-reference/api-parameters/facetFilters), but not [`numericFilters`](/doc/api-reference/api-parameters/numericFilters), [`tagFilters`](/doc/api-reference/api-parameters/tagFilters), nor [`optionalFilters`](/doc/api-reference/api-parameters/optionalFilters). * An optional **context** must match a context supplied at query time with the [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts) parameter * Optionally enabled **alternatives**, which indicates whether synonyms and plurals of the query pattern are treated as matches. #### Consequence: modify the query A rule's consequence can be one or more actions: * [Add query parameters](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/rules-query-parameters) * [Automatic facet filter](#automatic-facet-filter) * [Automatic optional facet filter](#automatic-optional-facet-filter) * [Remove or replace a word from the query string](#remove-or-replace-a-word-from-the-query-string) * Replace the query string entirely * Promote or hide specific hits * Return user data ## Conditions: adapt the query A rule can have any number of conditions: * If a rule doesn't have any conditions, it applies to every search. * If a rule has more than one condition, it applies when any condition matches. For more information about multiple conditions, see [rule structure](/doc/guides/managing-results/rules/rules-overview#conditions) ### Rule query pattern The query pattern is the most crucial part of the rule's condition. It consists of a sequence of tokens, treated as a phrase. All tokens must appear contiguously and in the specified order. The allowed token types are: * **Literal**: matches a word. The matching is case insensitive. If a rule's `alternatives` setting is `true`, then matching considers typo tolerance, plurals, and synonyms. If you set [`ignorePlurals`](/doc/api-reference/api-parameters/ignorePlurals) to `true` or [`synonyms`](/doc/api-reference/api-parameters/synonyms) to `false`, your `alternatives` setting is ignored. * **Facet value placeholder**: matches any value of a given facet in the same index. You must declare the facet in `attributesForFaceting`. Matching is case insensitive. Contrary to literals, facet values may be phrases. The pattern is implicitly a phrase. The order of words matters: `foo bar` and `bar foo` aren't identical patterns. The pattern also has an **anchoring type**, depending on whether its boundaries (beginning, end) must coincide with the boundaries of the query string: * **starts with**: the pattern must match the words at the beginning of the query string, but there may be extra words at the end * **ends with**: the pattern must match the words at the end of the query string, but there may be extra words at the beginning * **contains**: the pattern must match any contiguous sequence of words of the query string. #### Escape characters within a pattern To include special characters like colons (`:)`, braces (`{`, `}`), or backslashes (`\`) in a query pattern, you need to escape them. For example, to match the pattern `thrones:episode`, write it as: ```json JSON icon=braces theme={"system"} "pattern": "thrones\\:episode" ``` The first backslash escapes the colon for Algolia, and the second is required by JSON syntax. ### Filters can trigger rules [`filters`](/doc/api-reference/api-parameters/filters) or [`facetFilters`](/doc/api-reference/api-parameters/facetFilters) can trigger rules. The primary goal of this condition is to create rules that are triggered on specific category pages or when a user applies specific filters. If a rule's condition has only filters and not query pattern nor context, Algolia applies the rule's consequences. Algolia applies the rule's consequences if a condition only has filters: no query patterns or context. If a condition also has query patterns or context, those must match for the rule to be triggered. > [!NOTE] > > To use an attribute in a condition with filters, > [declare it in `attributesForFaceting`](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting). #### A single rule per filter value Just like only a single rule can be triggered per word in the query, only a single rule can be triggered per applied filter. For example: * Rule A has the filter `brand:Nike` as its condition * Rule B has the filter `brand:Nike` and the query "shoes" as its condition * If the query is "shoes" and the filter `brand:Nike` is applied, only rule B is triggered. * If the query is anything other than "shoes" and the filter `brand:Nike` is applied, only rule A is triggered. This follows the same [rule matching algorithm](/doc/guides/managing-results/rules/rules-overview/in-depth/rule-matching-algorithm) applied to rules on queries and contexts. #### Matches must be exact A rule with filter values in the condition triggers only when the search contains that value just for that filter. For example, if a rule's condition contains `"filters": "brand:Nike"`, this rule triggers if **only** the value `Nike` is selected in the filter `brand`. Algolia doesn't trigger the rule for a query filtered on `brand:Nike OR brand:Supreme`. If a condition has multiple filters, all applied filters must match the condition with no additional filter values to trigger the rule. For example, if the rule condition is `"filters": "brand:Nike AND color:red"`, then Algolia triggers this rule if only the value `Nike` is selected in the `brand` filter and only the value `red` is selected in the `color` filter. The rule isn't triggered if only `Nike` is selected without `red`, and it doesn't trigger if `brand:Nike` and `color:red OR color:blue` are selected. #### Only the filtering attributes in the condition matter If a rule's condition includes the filter `brand:Nike` and no other filters, Algolia only considers the `brand` attribute's applied filter values to decide whether to trigger the rule. For example, if a query filters on `brand:Nike AND color:red`, that rule doesn't consider the value of the `color` filter because it's not part of the condition. Algolia triggers the rule since `brand` is filtered on `Nike`. #### Multiple filtering attributes If a condition has multiple filters, the following applies: * Multiple values for the same attribute are grouped with an `OR` condition * Values for different attributes are grouped with an `AND` condition. That means Algolia interprets a condition with `brand:Supreme`, `brand:Nike`, `gender:men` as `(brand:Supreme OR brand:Nike) AND (gender:men)`. In other words, Algolia triggers the rule if a user selects the same set of values (`Supreme`, `Nike` and `men`) in the filters on the frontend. These conditions only hold if the frontend filters work with the same logic. For example, if a user filters on `Nike`, `Supreme` for brand, and `red` for color, the filtering logic follows the `AND of ORs` pattern: `(brand:Nike OR brand:Supreme) AND color:red`. Thus the filters are compatible with filtering conditions possible on rules. Filters in rules' conditions aren't compatible with `ORs of ANDs`, `ANDs of ANDs` or `ORs of ORs` logic. For example, consider a real estate search where a user filters for a two-bedroom apartment in Paris with a balcony and fireplace. In this use case, the desired logic would be `ANDs of ANDs`: `bedrooms:2 AND (amenities:balcony AND amenities:fireplace) AND location:Paris`. You can't create a rule with a condition to match this filtering. ### Context To narrow, customize, or make rules query-independent, you can have a rule respond to the [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts) parameter. The contexts found in the `ruleContexts` of user searches are compared to the contexts you assign to your rules. Contexts passed as parameters during your search: doing so activates all rules that share the context you passed. **Activation means the rule is considered not to have been applied**. If your rule's condition only contains a context, the rules consequence is applied to search results. If your rule also contains a query pattern, that pattern must match for the consequence to be applied. For example, you can define a set of rules with the context "homepage" and pass the same context value along with any search that originates from your homepage. Now you have a set of rules that apply to the homepage search, giving a more customized experience. A rule can have one context at most, but a search may have multiple contexts: these are treated as disjunctive (OR). When one or more contexts are specified at query time, rules matching any of those contexts are activated. General rules are always activated. The context's primary goal is to conditionally enable rules for specific search environments. If present, the context is a string that must be passed at query time in the `ruleContexts` search parameter with the same value for the rule to be triggered. Matching is case-sensitive. The `ruleContexts` parameter lets you enable multiple contexts simultaneously. > [!TIP] > > A rule context must consist only of alphanumeric characters, hyphens, and underscores. ## Consequence—adapt the results A rule's consequence can be one or more actions. ### Add query parameters Any number of valid search parameters are supported. These parameters are literals (constants). This consequence lets you add or modify any search settings that can be changed when the query is sent to Algolia. Some settings that affect search, like searchable attributes, can only be modified at indexing time, meaning you can't change them at query time. [Any parameter](/doc/api-reference/api-parameters) with `search` as part of its scope can be changed at query time. One common example of a parameter that's useful to change at query time is `filters`. For example, when someone searches "tablet", you want to show not only tablets (products with 'tablets' in their `categories` attribute) but also laptops used as tablets (products with 'hybrids' in their `categories` attribute). You can create 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. In **Conditions**, click **Set query condition(s)**, select **contains**, enter `tablet`, and click **Apply**. > 3. In **Consequences**, select **Filter results**. > Select `categories` as the attribute and `tablets` as its value. > Add `hybrids` as another value, join the values with **Or**, and click **Apply**. > 4. Review the results, click **Review and Publish**, and click **Publish**. > [tab] Manual Editor > > 1. In the Algolia dashboard, select your application and index, open **Rules**, and select **New rule > Manual Editor**. > 2. In **Condition(s)**, keep **Query** selected, select **Contains**, and enter `tablet`. > 3. In **Consequence(s)**, click **Add consequence** and select **Add Query Parameter**. > Enter `{ "filters": "categories:tablets OR categories:hybrids" }`. > 4. Save your changes. You can also [add filters with the API](/doc/guides/managing-results/rules/detecting-intent/how-to/applying-a-custom-filter-for-a-specific-query#using-the-api). ### Automatic facet filter Automatically transform a word or group of words into a facet filter if it matches a value in a given facet. A user enters "red", which is also a value in the color facet. You can set up a rule to transform red to behave like a filter. Another approach would be to use the filters parameter (using the "add query parameters" rule) but, with this approach, you must have one rule per filter value. If you have 10 color values in your color facet, you must create 10 rules, one for each color. It's more powerful to use automatic faceting: instead of creating one rule for each color, you create one rule per facet. Whether users types "blue" or "red", this single rule will apply, it also applies to new colors added to the facet. When the pattern matches multiple values of the same facet, filters can be either **conjunctive** (`AND`, default) or **disjunctive** (`OR`). By default, the filters are combined with an `AND` operator. However, if you specify the filter to be disjunctive, an `OR` operator is used instead. In both cases, relationships to other filters in the query will always be conjunctive (`AND`). You can also specify a score for the created filter, mainly useful for optional facet filters (see below). For example, to automatically filter on the `brand` facet, using `OR` in case of multiple occurrences: ```json JSON icon=braces theme={"system"} { "conditions": [ { "pattern": "{facet:brand}" } ], "consequence": { "params": { "automaticFacetFilters": [ { "facet": "brand", "disjunctive": "true" } ] } } } ``` And if you want to reverse the logic to exclude the captured facet for example, you can use the `negative` field and set it to `true`. ### Automatic optional facet filter Same as `automaticFacetFilters`, but the filters are optional. Behaves like [`optionalFilters`](/doc/api-reference/api-parameters/optionalFilters). ### Remove or replace a word from the query string You can remove or replace words 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 `foo bar`, and click **Apply**. > 3. In **Consequences**, expand **More consequences** and select **Remove word**. > In **Word(s)**, select or enter `foo` and click **Apply**. > You can select more than one word or a facet placeholder from the condition. > 4. Click **Add consequence**, expand **More consequences**, and select **Replace word**. > Set **Original word** to `bar` and **New word** to `baz`, then click **Apply**. > To replace more words, use **Add another** in the same panel. > 5. Review the search results, click **Review and Publish**, and click **Publish**. > [tab] Manual Editor > > 1. In the Algolia dashboard, select your application and index, open **Rules**, and select **New rule > Manual Editor**. > 2. In **Condition(s)**, keep **Query** selected, select **Contains**, and enter `foo bar`. > 3. In **Consequence(s)**, click **Add consequence**, select **Remove Word**, and enter `foo`. > 4. Click **Add consequence** again and select **Replace Word**. > Set the original word to `bar` and its replacement to `baz`. > 5. Save your changes. The rule changes the query used for search, not the text users entered in their search box. Removing or replacing words is achieved by specifying a list of **edits** to be applied to the query string. An edit can either: * Remove a word (or facet placeholder) * Replace a word (or facet placeholder) with another word For example, the following rule specifies that when matching `foo bar`, Algolia should remove the word `foo` and the word `bar` should be replaced with `baz`: ```json JSON icon=braces theme={"system"} { "condition": [ { "pattern": "foo bar" } ], "consequence": { "params": { "query": { "edits": [ { "type": "remove", "delete": "foo" }, { "type": "replace", "delete": "bar", "insert": "baz" } ] } } } } ``` > [!NOTE] > > Words that are removed or replaced from the query string are still highlighted or showing in the snippets in the query's results, > so it's not misleading for users. ### Entirely replace the query string You can replace a query 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. Select **Set query condition(s)** to define the query that triggers the rule. > 3. In **Consequences**, expand **More consequences** and select **Replace query**. > Enter the new text in **Replacement query** and click **Apply**. > Leave the field empty to search with an empty query. > 4. Review the search results, click **Review and Publish**, and click **Publish**. > [tab] Manual Editor > > 1. In the Algolia dashboard, select your application and index, open **Rules**, and select **New rule > Manual Editor**. > 2. In **Condition(s)**, define the query that triggers the rule. > 3. In **Consequence(s)**, click **Add consequence** and select **Replace Query**. > Enter the new text, or leave the field empty to search with an empty query. > 4. Save your changes. To entirely replace the query string, specify a new string for the `query` parameter, as you would do for any other search parameters: ```json JSON icon=braces theme={"system"} { "condition": [ { "pattern": "original query that will be replaced" } ], "consequence": { "params": { "query": "new text to be searched" } } } ``` > [!NOTE] > > You can't combine full query replacement with removing or replacing individual words in the same rule. > But you can remove and replace individual words in the same rule. > This can affect how later rules apply. > For more information, see [Matching algorithm](/doc/guides/managing-results/rules/rules-overview/in-depth/rule-matching-algorithm). ### Promote or hide specific hits One or more objects from the same index, identified by their `objectID`, are promoted to specific positions in the results list or removed from them. This promotion works seamlessly with Algolia's built-in pagination, mingling promoted hits with regular hits and removing hidden hits while keeping pages the same size. For more information, see [Hit promotion's effect on relevance](#hit-promotion’s-effect-on-relevance). ### Return user data User data is returned in a dedicated `userData` array. If multiple rules are applied, each set of user data is appended to this array. User data can be used to display specific information that doesn't affect pagination. For more information, see [Add banners to results With `userData`](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/add-banners). ## Hit promotion's effect on relevance You can only promote objects coming from the same index. Promoted objects have to be explicitly identified by their `objectID`. A promoted object will always be considered a hit, even if it doesn't match the query. If it would have matched the query, it's removed from its original position and inserted at its promoted position, even if the original position would have been better than the promoted position. In other words, promoted hits can also be "demoted". For performance reasons, promoted positions can be between 0 and 300. Inside the same rule, each promoted object must have a different promoted position. If promoted objects from two distinct rules are triggered for the same query: * Any duplicates are merged using the best position. * If the resulting positions conflict between distinct records (A record is a searchable object in an Algolia index. Each record consists of named attributes.), records are shifted down until a free slot is found. * All regular hits are shifted down until all promoted records get as close to their promoted position as possible (except conflicts between records, as stated before). Hidden records are removed from the hits. The following hits are shifted up so that pagination works seamlessly. ## Inject user data User data lets you inject data inside the results that aren't in the index, and as such, don't compete with other hits for pagination. A typical use-case would be to display a banner on top of the result list. User data can be any JSON object. The API doesn't interpret user data. If you want to keep your analytics data for longer, [upgrade your plan](https://www.algolia.com/pricing) or contact the [Algolia support](https://support.algolia.com/hc/en-us/requests/new) team. # Rules matching algorithm Source: https://www.algolia.com/doc/guides/managing-results/rules/rules-overview/in-depth/rule-matching-algorithm Learn about the precedence order for applying multiple rules. 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.) has many rules, you need to be careful not to create conflicts, since **rules may contradict or override each other.** To reduce conflicts, Algolia applies the following **rules matching algorithm**: 1. Sort all matching rules with a [tie-breaking algorithm](#rules-precedence). 2. Compare the sorted rules to check for conflicts. If there is a conflict, exclude rules with the lowest precedence. 3. Apply the remaining rules 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.). ## Rules precedence Algolia uses a tie-breaking algorithm to determine the precedence of all rules that apply to a query. The next criterion is only considered if there's a tie in the preceding criteria. More specific rules typically have higher precedence. The tie-breaking algorithm that determines rule precedence applies these criteria: 1. **Position**. The match closest to the beginning of the query string wins. 2. **Match length**. The longest match wins. The match length is determined by the number of words matched from the query string and the number of filters matched from the query parameters. 3. **Anchoring**. Rules with anchoring patterns are ranked in this order: `is` > `starts with` > `ends with` > `contains`. 4. **Context**. Rules with context have a higher priority than other rules. 5. **Filters**. Rules with filters have a higher priority than other rules. 6. **Literals over placeholders**. If a word matches a literal and a facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) value, the literal takes precedence. Matches with alternatives, such as synonyms and plurals, don't take precedence over matches with facet values. 7. **Temporary over permanent**. If the temporary and permanent rules match, the temporary rule takes precedence. 8. **Rule ID**. If there are still conflicts after all other criteria have been applied, the rule with the smallest rule ID (`objectID`) wins, in [lexicographical order](https://www.educative.io/answers/what-is-a-lexicographic-order) wins. This final criterion guarantees to break any remaining ties. It often only applies when there are duplicate rules. Rules apply from the beginning of the query to its end. The precedence algorithm also applies to rules with multiple conditions. Each condition is evaluated as if it were an independent rule. > [!NOTE] > > For more information about rules applied to recommendations, > see [Recommend rules](/doc/guides/algolia-recommend/how-to/rules#recommend-rules-precedence-algorithm). ## Conflicting conditions After ranking rules by precedence, only conflicting rules with the highest precedence are retained. Conflicting rules with lower precedence are excluded. ### Overlapping pattern conditions Rules with the [pattern](/doc/guides/managing-results/rules/rules-overview/in-depth/implementing-rules#rule-query-pattern) condition might conflict with each other due to overlapping patterns. **Rules with overlapping patterns are excluded.** Consider the query `Enchanted forest adventure`, and an index with several rules. Each rule matches a different pattern. The rules are ranked by precedence and only rules A and D are retained. The pattern of rules B and C overlaps with rule A, which leads to their exclusion. ```txt Text theme={"system"} `pattern` [enchanted] [forest] [adventure] Rule A [------------------] KEPT Rule B [------------------] EXCLUDED: overlaps with rule A Rule C [------] EXCLUDED: overlaps with rule A Rule D [---------] KEPT ``` Multiple rules can still match the same query, provided they match **a distinct set of words.** > [!NOTE] > > Rules with an **empty query** pattern don't overlap with any other pattern. > They will all be applied, as long as there isn't any other conflict. ### Intersecting filter conditions For rules with intersecting [`filters`](/doc/guides/managing-results/rules/rules-overview/in-depth/implementing-rules#filters-can-trigger-rules) conditions, the rule with the lowest precedence is excluded. ## Conflicting consequences Two rules might try to apply the same consequences, for example, pinning different records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) to the first position. Consider the following example: * **Rule 1:** if a query contains "Shakespeare", then promote the record "The Lost Shakespeare Diaries", with objectID "1", to the first result. * **Rule 2:** if a query contains the phrase "how much is", then promote the record "Full Price List", with objectID "2", to the first result. What happens when the query is "how much is Shakespeare"? The conditions of rules 1 and 2 are different. Yet the consequences overlap: both rules try to pin two different records to the first position. In this case, the consequences apply depending on the `objectID` of the promoted record. The record with the lowest `objectID` (in lexicographical order) wins. Subsequent promotions are shifted down by one position. In the previous example, the record "The Lost Shakespeare Diaries" is promoted to the first result, since it has a smaller `objectID`. The record "Full Price List" is then pinned to the second position. ## Edge cases If a **rule removes a word from the query string**, all subsequent rules that would have been triggered by this word are ignored. If a **rule replaces the query string entirely**, all subsequent rules are ignored. ### Context-only rules For rules that have only a context as a condition, the following applies: * They have a higher priority than general rules and rules without conditions. * They always trigger in this context and never conflict with other rules. ### Rules without conditions For rules that don't have any conditions, the following applies: * They have a higher priority than general rules. * They always trigger and never conflict with other rules. ## Effect of promotions on relevance Only records from the same index can be promoted. Promoted records have to be explicitly identified by their `objectID`. A promoted record is included in search results, even if it doesn't match the query. If it matches the query, it's removed from its original position and inserted at its promoted position, even if the original position was better than the promoted position. Promoted positions are restricted to a range of 0 to 300. Inside the same rule, each promoted record must have a unique promoted position. If promoted records from two distinct rules are triggered for the same query, the following applies: * Duplicates are merged, using the best position. * If the resulting positions conflict between distinct records, records are shifted down until a free slot is found. * All regular search results are shifted down to ensure that all promoted records are as close to their promoted position as possible. Hidden records are removed from the search results. The following hits are shifted up so that pagination works seamlessly. ## User data You can use rules to inject custom data into the search results, such as [banners](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/add-banners). Since this data isn't part of the index, it doesn't interfere with things like pagination. User data can be any JSON object. It's not interpreted by the API. # Manage indices Source: https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices Learn how to copy, rename, or delete your indices and understand the effect on analytics. Learn how you can build a sitemap from your index for SEO. You can rename, copy, and delete indices, and export or import their settings. You can perform many of these tasks in the Algolia dashboard or with the API. To copy an index between different Algolia applications, use the API. Analytics data is associated with each index. For more information about how renaming, copying, and deleting indices affects their analytics data, see [Indices and analytics](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/indices-analytics). ## Name your indices Choose a unique, readable name for each index. Don't include sensitive or personally identifiable information (PII), such as usernames, user IDs, or email addresses. Index names appear in network requests, so **consider them publicly available**. ## Indices and applications Common uses for several indices include: * Query Suggestions or sorting. See [Choosing between one or more indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/one-or-more-indices). * Several Algolia applications, such as testing, staging, and production. See [Manage your Algolia applications](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-your-apps). ## Create sitemaps from your indices Search engines like Google and Bing can find content from your Algolia indices. This includes product pages or category pages. To help them, [create sitemaps](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/tutorial/generate-sitemap-from-index). ## See also * [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) * [Send your data to an index](/doc/guides/sending-and-managing-data/send-and-update-your-data) * [Export and import indices and settings](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/export-import-indices) * [Copy and move indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/copy-move-indices) * [Delete indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/delete-indices) # Indices and analytics Source: https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/indices-analytics Learn the impact of renaming, deleting or copying an index on analytics Algolia **stores analytics data on dedicated servers**, separate from the indices. Algolia collects analytics data in parallel with search, in a separate process. This means collecting analytics never slows down search. Indices and analytics are separate data sets. Because of this, copying, moving, or deleting an index doesn't affect the analytics data. **Algolia refers to analytics data by the name of its index.** This link never changes. ## Effect of renaming indices on analytics **Changing the name of an index doesn't change the name of the index's analytics.** The analytics belonging to the original index keep their original name. Algolia starts a new set of analytics under the new name. The old and new analytics aren't merged. To preserve an index's analytics history, you need to keep using the old name. For more information, see [Copy and move (rename) indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/copy-move-indices). ## Effect of deleting indices on analytics **Deleting an index doesn't delete the index's analytics.** The analytics data remains available under the deleted index's name. You can use the index's name to export the analytics data using the [Analytics API](/doc/rest-api/analytics). For more information, see [Delete indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/delete-indices) ## Copying an index **Copying an index doesn't copy its analytics data.** > [!WARNING] > > Be careful if you copy over an existing index. > The analytics data stays linked to the index's name. For more information, see [Copy and move (rename) indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/copy-move-indices). # Choosing between one or more indices Source: https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/one-or-more-indices Learn when to use one index or several indices, depending on your relevance strategy and the user experience you want to create. Should you create one large index for all your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.), or create multiple indices? It depends on your ranking strategy and which user experience you want to create. The number of indices you can create depends on your [plan](https://www.algolia.com/pricing). ## One index or multiple indices for federated search Federated search describes searching multiple types of data sources. One data source may be your product catalog. Another data source may be a support FAQ, or Query Suggestions. Depending on how you want to organize and present the search results, you can create one index for all data sources or separate indices for each data source. For more information, see [Multi-index search with InstantSearch.js](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/js). ### Multiple indices for federated search Using multiple indices is useful when: * You need to configure each index independently. For example, one index for movies, another for actors. With federated search, you can show results from both indices in the same interface, allowing users to search for both actors and movies. * You want to **use different ranking strategies** for the same data. For example, showing highest-rated and showing most recent movies. For a step-by-step example, see [Autocomplete with Federated Search](/doc/guides/solutions/ecommerce/search/autocomplete/federated-search). ### Single index for federated search Using a single index is useful when you want to use a **single ranking strategy**. Users can search for anything, see movies and actors in the same results list, ranked by the same relevance configuration. ## Multiple indices for Query Suggestions [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) suggest popular search queries to users to help them search faster. Since Query Suggestions are stored in a separate index, you'd use **multiple indices**: one for your content, and one for the Query Suggestions. ## Multiple indices for different ranking strategies Each Algolia index can have one ranking strategy. If you want to let users switch between different ranking strategies, for example, to allow sorting by price, you can use **replica indices**—one index per sorting strategy. A [replica index](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas) is a copy of the main index with the same records, but a different ranking strategy. To learn more, see [Create replica indices](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas). ## Multiple indices for different environments If you use different environments, for example, staging and production environments, it's best to use separate indices for each environment. This prevents your [Analytics](/doc/guides/search-analytics/overview) from getting skewed by test queries, prevents indexing accidents, and keeps each environment more secure. You can use common prefixes to distinguish the different environments, for example, `staging_products` and `prod_products`. To reduce the number of indexing operations, you can reduce the number of records in your staging environments, for example, only using 10% of your records. To learn more, see [Manage your Algolia applications](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-your-apps). # Copy and move indices Source: https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/copy-move-indices Learn how to copy, move, or rename indices including their records, settings, synonyms, and rules. You can copy (duplicate) or move (rename) indices, including their associated records (A record is a searchable object in an Algolia index. Each record consists of named attributes.), settings, synonyms, and rules, using the Algolia dashboard or one of the API clients. > [!NOTE] > > Copying or moving 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.) is resource-intensive and [rate-limited](https://support.algolia.com/hc/en-us/articles/4406975251089#when-do-we-trigger-the-rate-limit). ## Move or rename indices You can move or rename an index within the same 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.) in the Algolia dashboard or with an API client. > [!NOTE] > > Moving or renaming an index leaves its existing analytics associated with the original index name. > Algolia starts a new set of analytics under the new name. > For more information, > see [Indices and analytics](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/indices-analytics). ### Rename indices in the Algolia dashboard In the Algolia dashboard, you can't rename replica indices. You also can't rename primary indices that have replicas. 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. 4. Select **Manage index > Rename**. > [!WARNING] > > If the destination index already exists, this operation overwrites it. 5. Enter the current and new index names and click **Rename**. ### Rename indices with an API client To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install). To rename or move an index with the API, you can use: * API clients: [`operationIndex`](/doc/rest-api/search/operation-index) * Algolia CLI: [`algolia indices move`](/doc/tools/cli/commands/indices/move) You can't move an index between different Algolia applications but you can create a copy and delete the original index. For more information, see [Copy indices between apps](#copy-indices-between-different-applications). > [!WARNING] > > If the destination index already exists, this operation overwrites it. > To avoid overwriting existing indices, > check if an index exists with [`indexExists`](/doc/libraries/sdk/methods/search/index-exists). ```cs C# theme={"system"} var response = await client.OperationIndexAsync( "", new OperationIndexParams { Operation = Enum.Parse("Move"), Destination = "", } ); ``` ```dart Dart theme={"system"} final response = await client.operationIndex( indexName: "", operationIndexParams: OperationIndexParams( operation: OperationType.fromJson("move"), destination: "", ), ); ``` ```go Go theme={"system"} response, err := client.OperationIndex(client.NewApiOperationIndexRequest( "", search.NewEmptyOperationIndexParams().SetOperation(search.OperationType("move")).SetDestination(""))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.operationIndex( "", new OperationIndexParams().setOperation(OperationType.MOVE).setDestination("") ); ``` ```js JavaScript theme={"system"} const response = await client.operationIndex({ indexName: '', operationIndexParams: { operation: 'move', destination: '' }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.operationIndex( indexName = "", operationIndexParams = OperationIndexParams( operation = OperationType.entries.first { it.value == "move" }, destination = "", ), ) ``` ```php PHP theme={"system"} $response = $client->operationIndex( '', ['operation' => 'move', 'destination' => '', ], ); ``` ```python Python theme={"system"} response = client.operation_index( index_name="", operation_index_params={ "operation": "move", "destination": "", }, ) ``` ```ruby Ruby theme={"system"} response = client.operation_index( "", Algolia::Search::OperationIndexParams.new(operation: "move", destination: "") ) ``` ```scala Scala theme={"system"} val response = Await.result( client.operationIndex( indexName = "", operationIndexParams = OperationIndexParams( operation = OperationType.withName("move"), destination = "" ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.operationIndex( indexName: "", operationIndexParams: OperationIndexParams( operation: OperationType.move, destination: "" ) ) ``` If the source index doesn't exist, Algolia ignores the move operation. ### Rename indices with replicas [Renaming replica indices](/doc/guides/managing-results/refine-results/sorting/how-to/rename-a-replica-index) requires several steps. **You can't move a source index with replicas.** You can move an index to a destination index with replicas. Algolia replaces the destination index's data with the source index's data, then copies it to the destination's replicas. ### Indices used as a Recommend data source Don't rename an index used as a data source for Recommend models. Renaming it breaks the relationship between recommended records and their source. Instead: 1. [Copy the index.](#copy-indices) 2. To keep the events you already collected, train a new model on the copied index using the original index as an additional [events source](/doc/guides/algolia-recommend/how-to/set-up#train-the-recommend-models). 3. After training, switch your apps and services to use the new index. 4. [Delete the old index](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/delete-indices) and the old model (from the [Algolia dashboard](https://dashboard.algolia.com/recommend/models)). ## Copy indices You can copy an index in the Algolia dashboard or with an API client. Copying an index works as follows: * Copying an index duplicates the records and configuration, including synonyms and rules of an existing index. * Copying an index [doesn't create a replica](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas). The new index is an independent copy. * To [copy an index between two Algolia applications](#copy-indices-between-different-applications), use the Algolia CLI. > [!NOTE] > > Copying an index [doesn't copy its analytics data](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/indices-analytics). ### Copy indices in the Algolia dashboard 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 the Algolia index that you want to copy. 4. Select **Manage index > Duplicate**. 5. Select **Create a new index** or **Overwrite an existing index** and enter the destination index name. 6. If you're overwriting an existing index, enter `DUPLICATE` to confirm. 7. Click **Duplicate**. ### Copy indices with an API client To copy indices, you can use: * API clients: [`operationIndex`](/doc/rest-api/search/operation-index) * Algolia CLI: [`algolia indices copy`](/doc/tools/cli/commands/indices/copy) To copy only settings, synonyms, or rules, specify them in the `scope` parameter. Omit `scope` to copy the entire index. When copying an index with the API, Algolia doesn't copy the `enableReRanking`, `mode`, or `replicas` settings. #### Copy entire indices To copy an index including records, settings, synonyms, and rules: ```cs C# theme={"system"} var response = await client.OperationIndexAsync( "", new OperationIndexParams { Operation = Enum.Parse("Copy"), Destination = "", } ); ``` ```dart Dart theme={"system"} final response = await client.operationIndex( indexName: "", operationIndexParams: OperationIndexParams( operation: OperationType.fromJson("copy"), destination: "", ), ); ``` ```go Go theme={"system"} response, err := client.OperationIndex(client.NewApiOperationIndexRequest( "", search.NewEmptyOperationIndexParams().SetOperation(search.OperationType("copy")).SetDestination(""))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.operationIndex( "", new OperationIndexParams().setOperation(OperationType.COPY).setDestination("") ); ``` ```js JavaScript theme={"system"} const response = await client.operationIndex({ indexName: '', operationIndexParams: { operation: 'copy', destination: '' }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.operationIndex( indexName = "", operationIndexParams = OperationIndexParams( operation = OperationType.entries.first { it.value == "copy" }, destination = "", ), ) ``` ```php PHP theme={"system"} $response = $client->operationIndex( '', ['operation' => 'copy', 'destination' => '', ], ); ``` ```python Python theme={"system"} response = client.operation_index( index_name="", operation_index_params={ "operation": "copy", "destination": "", }, ) ``` ```ruby Ruby theme={"system"} response = client.operation_index( "", Algolia::Search::OperationIndexParams.new(operation: "copy", destination: "") ) ``` ```scala Scala theme={"system"} val response = Await.result( client.operationIndex( indexName = "", operationIndexParams = OperationIndexParams( operation = OperationType.withName("copy"), destination = "" ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.operationIndex( indexName: "", operationIndexParams: OperationIndexParams( operation: OperationType.copy, destination: "" ) ) ``` > [!WARNING] > > If the destination index already exists, this operation overwrites it. > To avoid overwriting an index, check whether it exists with [`indexExists`](/doc/libraries/sdk/methods/search/index-exists). #### Copy index settings, synonyms, or rules To copy parts of an index, use the `scope` parameter of the [`operationIndex`](/doc/rest-api/search/operation-index) method. For example, to copy only the synonyms and settings between indices, but not records or rules: ```cs C# theme={"system"} var response = await client.OperationIndexAsync( "", new OperationIndexParams { Operation = Enum.Parse("Move"), Destination = "", Scope = new List { Enum.Parse("Rules"), Enum.Parse("Settings"), }, } ); ``` ```dart Dart theme={"system"} final response = await client.operationIndex( indexName: "", operationIndexParams: OperationIndexParams( operation: OperationType.fromJson("move"), destination: "", scope: [ ScopeType.fromJson("rules"), ScopeType.fromJson("settings"), ], ), ); ``` ```go Go theme={"system"} response, err := client.OperationIndex(client.NewApiOperationIndexRequest( "", search.NewEmptyOperationIndexParams().SetOperation(search.OperationType("move")).SetDestination("").SetScope( []search.ScopeType{search.ScopeType("rules"), search.ScopeType("settings")}))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.operationIndex( "", new OperationIndexParams() .setOperation(OperationType.MOVE) .setDestination("") .setScope(Arrays.asList(ScopeType.RULES, ScopeType.SETTINGS)) ); ``` ```js JavaScript theme={"system"} const response = await client.operationIndex({ indexName: '', operationIndexParams: { operation: 'move', destination: '', scope: ['rules', 'settings'] }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.operationIndex( indexName = "", operationIndexParams = OperationIndexParams( operation = OperationType.entries.first { it.value == "move" }, destination = "", scope = listOf( ScopeType.entries.first { it.value == "rules" }, ScopeType.entries.first { it.value == "settings" }, ), ), ) ``` ```php PHP theme={"system"} $response = $client->operationIndex( '', ['operation' => 'move', 'destination' => '', 'scope' => [ 'rules', 'settings', ], ], ); ``` ```python Python theme={"system"} response = client.operation_index( index_name="", operation_index_params={ "operation": "move", "destination": "", "scope": [ "rules", "settings", ], }, ) ``` ```ruby Ruby theme={"system"} response = client.operation_index( "", Algolia::Search::OperationIndexParams.new( operation: "move", destination: "", scope: ["rules", "settings"] ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.operationIndex( indexName = "", operationIndexParams = OperationIndexParams( operation = OperationType.withName("move"), destination = "", scope = Some(Seq(ScopeType.withName("rules"), ScopeType.withName("settings"))) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.operationIndex( indexName: "", operationIndexParams: OperationIndexParams( operation: OperationType.move, destination: "", scope: [ScopeType.rules, ScopeType.settings] ) ) ``` If you specify `scope`, Algolia replaces the selected settings, synonyms, or rules in the destination index. Records, settings, synonyms, and rules outside the selected scopes remain unchanged. For example, if you use the `settings` scope, Algolia copies only the settings to the destination index and leaves that index's records, synonyms, and rules unchanged. ### Copy indices with replicas Copying a source index that has replicas copies the index's data, but not its replicas. The destination index won't have replicas. **You can't copy to a destination index with replicas.** ### Copy indices between different applications To copy an index between different Algolia applications, [use the Algolia CLI](/doc/tools/cli/examples#copy-indices-between-applications). # Delete indices Source: https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/delete-indices Delete indices and their configuration, or clear records while keeping settings, rules, and synonyms. You can either delete 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.) or [clear it](#clear-indices) to delete only its records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). > [!NOTE] > > Deleting an index doesn't delete the index's analytics. > The analytics data remains available under the deleted index's name. > For more information, see [Indices and analytics](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/indices-analytics). ## Before you begin Deleting an index removes that index and its records, rules, settings, and synonyms. Before you delete or clear an index, [export its records and configuration](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/export-import-indices) to create a backup. > [!WARNING] > > If you delete an index by mistake, contact [Algolia support](https://support.algolia.com/hc/en-us/requests/new). > They may be able to restore it, > but recovery isn't guaranteed. To run the API examples, [install the latest API client](/doc/libraries/sdk/install). Use an API key with the [permissions](/doc/guides/security/api-keys#rights-and-restrictions) required by each method. * Deleting or clearing an index requires `deleteIndex`. * If you list indices, also include `listIndexes`. * If you unlink replicas, also include `editSettings`. Before running CLI commands, [install and configure the Algolia CLI](/doc/tools/cli/get-started) for the 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.) that contains your indices. ## Delete indices in the Algolia dashboard 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. 4. Select **Manage index > Delete**. 5. Type `DELETE` to confirm and click **Delete**. ## Delete indices with the API Delete an index with either of the following: * Algolia CLI: [`algolia indices delete`](/doc/tools/cli/commands/indices/delete). Deletes one or more indices. Add `--include-replicas` to delete their replicas too. * API clients: [`deleteIndex`](/doc/libraries/sdk/methods/search/delete-index) ```cs C# theme={"system"} var response = await client.DeleteIndexAsync("INDEX_NAME"); ``` ```dart Dart theme={"system"} final response = await client.deleteIndex( indexName: "INDEX_NAME", ); ``` ```go Go theme={"system"} response, err := client.DeleteIndex(client.NewApiDeleteIndexRequest( "INDEX_NAME")) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} DeletedAtResponse response = client.deleteIndex("INDEX_NAME"); ``` ```js JavaScript theme={"system"} const response = await client.deleteIndex({ indexName: 'theIndexName' }); ``` ```kotlin Kotlin theme={"system"} var response = client.deleteIndex(indexName = "INDEX_NAME") ``` ```php PHP theme={"system"} $response = $client->deleteIndex( 'INDEX_NAME', ); ``` ```python Python theme={"system"} response = client.delete_index( index_name="INDEX_NAME", ) ``` ```ruby Ruby theme={"system"} response = client.delete_index("INDEX_NAME") ``` ```scala Scala theme={"system"} val response = Await.result( client.deleteIndex( indexName = "INDEX_NAME" ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.deleteIndex(indexName: "INDEX_NAME") ``` ### Delete several indices To delete replica indices with an API client, first [unlink them from their primary indices](#delete-indices-with-replicas). To delete more than one index: 1. Select the indices to delete. To find available indices, use [`listIndices`](/doc/libraries/sdk/methods/search/list-indices). 2. Use [`multipleBatch`](/doc/libraries/sdk/methods/search/multiple-batch) to delete the selected indices with a single request. ```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 DeleteMultipleIndices { async Task Main(string[] args) { // You need an API key with `deleteIndex` var client = new SearchClient(new SearchConfig("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")); // List all indices var indices = await client.ListIndicesAsync(); // Primary indices don't have a `primary` key var primaryIndices = indices.Items.Where(item => item.Primary == null).ToList(); var replicaIndices = indices.Items.Where(item => item.Primary != null).ToList(); // Delete primary indices first if (primaryIndices.Count > 0) { var requests = primaryIndices .Select(index => new MultipleBatchRequest(Search.Models.Search.Action.Delete, index.Name)) .ToList(); await client.MultipleBatchAsync(new BatchParams { Requests = requests }); Console.WriteLine("Deleted primary indices."); } // Now, delete replica indices if (replicaIndices.Count > 0) { var requests = replicaIndices .Select(index => new MultipleBatchRequest(Search.Models.Search.Action.Delete, index.Name)) .ToList(); await client.MultipleBatchAsync(new BatchParams { Requests = requests }); Console.WriteLine("Deleted replica indices."); } } } ``` ```dart Dart theme={"system"} import 'package:algolia_client_search/algolia_client_search.dart'; void deleteMultipleIndices() async { // You need an API key with `deleteIndex` final client = SearchClient(appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY'); // List all indices final indices = await client.listIndices(); // Primary indices don't have a `primary` key final primaryIndices = indices.items.where((element) => element.primary == null).toList(); final replicaIndices = indices.items.where((element) => element.primary != null).toList(); // Delete primary indices first if (primaryIndices.isNotEmpty) { final List requests = primaryIndices .map((element) => MultipleBatchRequest( action: Action.delete, indexName: element.name)) .toList(); await client.multipleBatch( batchParams: BatchParams( requests: requests, ), ); print("Deleted primary indices."); } // Now, delete replica indices if (replicaIndices.isNotEmpty) { final List requests = replicaIndices .map((element) => MultipleBatchRequest( action: Action.delete, indexName: element.name)) .toList(); await client.multipleBatch( batchParams: BatchParams( requests: requests, ), ); print("Deleted replica indices."); } } ``` ```go Go theme={"system"} package main import ( "fmt" "github.com/algolia/algoliasearch-client-go/v4/algolia/search" ) func deleteMultipleIndices() { // You need an API key with `deleteIndex` 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) } // List all indices indices, err := client.ListIndices(client.NewApiListIndicesRequest()) if err != nil { panic(err) } // Primary indices don't have a `primary` key primaryIndices := make([]search.FetchedIndex, 0, len(indices.Items)) replicaIndices := make([]search.FetchedIndex, 0, len(indices.Items)) for _, index := range indices.Items { if index.Primary == nil { primaryIndices = append(primaryIndices, index) } else { replicaIndices = append(replicaIndices, index) } } // Delete primary indices first if len(primaryIndices) > 0 { requests := make([]search.MultipleBatchRequest, 0, len(primaryIndices)) for _, index := range primaryIndices { requests = append(requests, search.MultipleBatchRequest{ Action: "delete", IndexName: index.Name, }) } _, err = client.MultipleBatch(client.NewApiMultipleBatchRequest( search.NewEmptyBatchParams().SetRequests(requests))) if err != nil { panic(err) } fmt.Println("Deleted primary indices.") } // Now, delete replica indices if len(replicaIndices) > 0 { requests := make([]search.MultipleBatchRequest, 0, len(replicaIndices)) for _, index := range primaryIndices { requests = append(requests, search.MultipleBatchRequest{ Action: "delete", IndexName: index.Name, }) } _, err = client.MultipleBatch(client.NewApiMultipleBatchRequest( search.NewEmptyBatchParams().SetRequests(requests))) if err != nil { panic(err) } fmt.Println("Deleted replica indices.") } } ``` ```java Java theme={"system"} package com.algolia; import com.algolia.api.SearchClient; import com.algolia.config.*; import com.algolia.model.search.*; import java.util.List; public class deleteMultipleIndices { public static void main(String[] args) throws Exception { // You need an API key with `deleteIndex` try (SearchClient client = new SearchClient("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY")) { // List all indices ListIndicesResponse indices = client.listIndices(); // Primary indices don't have a `primary` key List primaryIndices = indices .getItems() .stream() .filter(item -> item.getPrimary() == null) .toList(); List replicaIndices = indices .getItems() .stream() .filter(item -> item.getPrimary() != null) .toList(); // Delete primary indices first if (!primaryIndices.isEmpty()) { List requests = primaryIndices .stream() .map(index -> new MultipleBatchRequest().setAction(Action.DELETE).setIndexName(index.getName())) .toList(); client.multipleBatch(new BatchParams().setRequests(requests)); System.out.println("Deleted primary indices."); } // Now, delete replica indices if (!replicaIndices.isEmpty()) { List requests = replicaIndices .stream() .map(index -> new MultipleBatchRequest().setAction(Action.DELETE).setIndexName(index.getName())) .toList(); client.multipleBatch(new BatchParams().setRequests(requests)); System.out.println("Deleted replica indices."); } } catch (Exception e) { System.out.println("An error occurred: " + e.getMessage()); } } } ``` ```js JavaScript theme={"system"} import { algoliasearch } from 'algoliasearch'; import type { MultipleBatchRequest } from 'algoliasearch'; // You need an API key with `deleteIndex` const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY'); // List all indices const indices = await client.listIndices(); // Primary indices don't have a `primary` key const primaryIndices = indices.items.filter((item) => item.primary == null); const replicaIndices = indices.items.filter((item) => item.primary != null); // Delete primary indices first if (primaryIndices.length > 0) { const requests: MultipleBatchRequest[] = primaryIndices.map((index) => ({ action: 'delete', indexName: index.name, })); await client.multipleBatch({ requests: requests }); console.log('Deleted primary indices.'); } // Now, delete replica indices if (replicaIndices.length > 0) { const requests: MultipleBatchRequest[] = replicaIndices.map((index) => ({ action: 'delete', indexName: index.name, })); await client.multipleBatch({ requests: requests }); console.log('Deleted replica indices.'); } ``` ```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 deleteMultipleIndices() { // You need an API key with `deleteIndex` val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY") // List all indices val indices = client.listIndices() // Primary indices don't have a `primary` key val primaryIndices = indices.items.filter { it.primary == null } val replicaIndices = indices.items.filter { it.primary != null } // Delete primary indices first if (primaryIndices.isNotEmpty()) { val requests = primaryIndices.map { MultipleBatchRequest(action = Action.Delete, indexName = it.name) } client.multipleBatch(batchParams = BatchParams(requests = requests)) println("Deleted primary indices.") } // Now, delete replica indices if (replicaIndices.isNotEmpty()) { val requests = replicaIndices.map { MultipleBatchRequest(action = Action.Delete, indexName = it.name) } client.multipleBatch(batchParams = BatchParams(requests = requests)) println("Deleted replica indices.") } } ``` ```php PHP theme={"system"} listIndices(); $indices = $indicesResponse['items']; // Primary indices don't have a `primary` key $primaryIndices = array_filter($indices, function ($index) { return !isset($index['primary']); }); $replicaIndices = array_filter($indices, function ($index) { return isset($index['primary']); }); // Delete primary indices first if (!empty($primaryIndices)) { $requests = array_map(function ($index) { return (new MultipleBatchRequest()) ->setAction((new Action())::DELETE) ->setIndexName($index['name']) ; }, $primaryIndices); $client->multipleBatch( ['requests' => $requests, ], ); echo "Deleted primary indices.\n"; } // Now, delete replica indices if (!empty($replicaIndices)) { $requests = array_map(function ($index) { return [ 'action' => 'delete', 'indexName' => $index['name'], ]; }, $replicaIndices); $client->multipleBatch( ['requests' => $requests, ], ); echo "Deleted replica indices.\n"; } ``` ```python Python theme={"system"} from algoliasearch.search.client import SearchClientSync # You need an API key with `deleteIndex` _client = SearchClientSync("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY") # List all indices indices = _client.list_indices() # Primary indices don't have a `primary` key primary_indices = [index for index in indices.items if index.primary is None] replica_indices = [index for index in indices.items if index.primary is not None] # Delete primary indices first if primary_indices: requests = [ {"action": "delete", "indexName": index.name} for index in primary_indices ] _client.multiple_batch( batch_params={ "requests": requests, }, ) print("Deleted primary indices.") # Now, delete replica indices if replica_indices: requests = [ {"action": "delete", "indexName": index.name} for index in replica_indices ] _client.multiple_batch( batch_params={ "requests": requests, }, ) print("Deleted replica indices.\n") ``` ```ruby Ruby theme={"system"} require "algolia" # You need an API key with `deleteIndex` client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY") # List all indices indices = client.list_indices # Primary indices don't have a `primary` key primary_indices, replica_indices = indices.items.partition { |index| index.primary.nil? }.map(&:to_a) # Delete primary indices first if primary_indices.any? requests = primary_indices.map { |index| Algolia::Search::BatchRequest.new(action: Algolia::Search::Action::DELETE, indexName: index.name) } client.multiple_batch(Algolia::Search::BatchParams.new(requests: requests)) print("Deleted primary indices.") end # Now, delete replica indices if replica_indices.any? requests = replica_indices.map { |index| Algolia::Search::BatchRequest.new(action: Algolia::Search::Action::DELETE, indexName: index.name) } client.multiple_batch(Algolia::Search::BatchParams.new(requests: requests)) print("Deleted replica indices.\n") end ``` ```scala Scala theme={"system"} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import algoliasearch.api.SearchClient import algoliasearch.config.* import algoliasearch.extension.SearchClientExtensions import algoliasearch.search.{Action, BatchParams, MultipleBatchRequest} def deleteMultipleIndices(): Future[Unit] = { // You need an API key with `deleteIndex` val client = SearchClient(appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY") client .listIndices( ) .flatMap { indices => val (primaryIndices, replicaIndices) = indices.items.partition(_.primary.isEmpty) val deletePrimary = if (primaryIndices.nonEmpty) { val requests = primaryIndices.map(index => MultipleBatchRequest(Action.Delete, None, index.name)) client .multipleBatch( batchParams = BatchParams( requests = requests ) ) .map { _ => println("Deleted primary indices.") } } else Future.unit val deleteReplica = if (replicaIndices.nonEmpty) { val requests = replicaIndices.map(index => MultipleBatchRequest(Action.Delete, None, index.name)) client .multipleBatch( batchParams = BatchParams( requests = requests ) ) .map { _ => println("Deleted replica indices.") } } else Future.unit for { _ <- deletePrimary _ <- deleteReplica } yield () } .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 deleteMultipleIndices() async throws { do { // You need an API key with `deleteIndex` let client = try SearchClient(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY") // List all indices let indices = try await client.listIndices() // Primary indices don't have a `primary` key let primaryIndices = indices.items.filter { $0.primary == nil } let replicaIndices = indices.items.filter { $0.primary != nil } // Delete primary indices first if primaryIndices.isEmpty == false { let requests = primaryIndices.map { MultipleBatchRequest( action: .delete, indexName: $0.name ) } try await client.multipleBatch(batchParams: BatchParams(requests: requests)) print("Deleted primary indices.") } // Now, delete replica indices if replicaIndices.isEmpty == false { let requests = replicaIndices.map { MultipleBatchRequest( action: .delete, indexName: $0.name ) } try await client.multipleBatch(batchParams: BatchParams(requests: requests)) print("Deleted replica indices.") } } catch { print(error) } } ``` > [!NOTE] > > In a `multipleBatch` request body, > set the `requests` property to an array of operations. > Don't nest another `requests` property inside it. > An extra wrapper can cause an error such as "Requests attribute must be an array." ## Delete indices with replicas Deleting a primary index with an API client makes its replicas independent indices. To delete a replica index with an API client, first unlink it from its primary index. In the Algolia dashboard, you can delete replicas without manually unlinking them. The Algolia CLI `algolia indices delete` command automatically unlinks replica indices before deleting them. ### Unlink replica indices The [`replicas`](/doc/api-reference/api-parameters/replicas) setting contains the list of replicas for a primary index. To unlink a replica, remove its entry from this list with the [`setSettings`](/doc/libraries/sdk/methods/search/set-settings) method. Keep the entries for all other replicas. Setting `replicas` to an empty list (`[]`) unlinks all replicas. ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { Replicas = new List { "" } } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( replicas: [ "", ], ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetReplicas( []string{""}))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings("INDEX_NAME", new IndexSettings().setReplicas(Arrays.asList(""))); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { replicas: [''] } }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings(replicas = listOf("")), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['replicas' => [ '', ], ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "replicas": [ "", ], }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings("INDEX_NAME", Algolia::Search::IndexSettings.new(replicas: [""])) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( replicas = Some(Seq("")) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings(replicas: [""]) ) ``` Wait for the settings update on the primary index to complete with [`waitForTask`](/doc/libraries/sdk/methods/search/wait-for-task). Then delete the unlinked index [in the Algolia dashboard](#delete-indices-in-the-algolia-dashboard) or [with the API](#delete-indices-with-the-api). ## Clear indices Clearing an index deletes only its records and keeps its settings, synonyms, and rules. The index doesn't return any results until you add records again. Clearing a primary index also clears the records from its replicas. ### Clear indices in the Algolia dashboard 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. 4. Select **Manage index > Clear**. 5. Type `CLEAR` to confirm and click **Clear**. ### Clear indices with the API To replace records without interrupting search, use [`replaceAllObjects`](/doc/libraries/sdk/methods/search/replace-all-objects) instead of clearing the index first. To clear an index and delete all records, use either of the following: * API clients: [`clearObjects`](/doc/libraries/sdk/methods/search/clear-objects) * Algolia CLI: [`algolia indices clear`](/doc/tools/cli/commands/indices/clear) ```cs C# theme={"system"} var response = await client.ClearObjectsAsync("INDEX_NAME"); ``` ```dart Dart theme={"system"} final response = await client.clearObjects( indexName: "INDEX_NAME", ); ``` ```go Go theme={"system"} response, err := client.ClearObjects(client.NewApiClearObjectsRequest( "INDEX_NAME")) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.clearObjects("INDEX_NAME"); ``` ```js JavaScript theme={"system"} const response = await client.clearObjects({ indexName: 'theIndexName' }); ``` ```kotlin Kotlin theme={"system"} var response = client.clearObjects(indexName = "INDEX_NAME") ``` ```php PHP theme={"system"} $response = $client->clearObjects( 'INDEX_NAME', ); ``` ```python Python theme={"system"} response = client.clear_objects( index_name="INDEX_NAME", ) ``` ```ruby Ruby theme={"system"} response = client.clear_objects("INDEX_NAME") ``` ```scala Scala theme={"system"} val response = Await.result( client.clearObjects( indexName = "INDEX_NAME" ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.clearObjects(indexName: "INDEX_NAME") ``` # Export and import indices and settings Source: https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/how-to/export-import-indices Export and your records and settings from your Algolia index from the dashboard or the API. An Algolia index has records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) and configuration data, including settings, synonyms, and rules. Exporting your records and configuration can be useful when you need to: * Back up your index. * Track changes to the index configuration (for example, with a version control system). * Apply the same configuration to multiple indices. For example, in staging and production environments, or if you use different indices for different geographical regions. ## Export and import records To **export your records** as a JSON file, use one of these options: * Algolia CLI: [`algolia objects browse`](/doc/tools/cli/commands/objects/browse) * API clients: [`browse`](/doc/libraries/sdk/v1/methods/browse) You can't export records from the Algolia dashboard. For information about importing or **uploading your records**, see [Send and update your data](/doc/guides/sending-and-managing-data/send-and-update-your-data). ## Export and import index configurations Your index configuration includes: * Settings, such as: * [Searchable attributes](/doc/guides/managing-results/must-do/searchable-attributes) * [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking) * [Typo tolerance](/doc/guides/managing-results/optimize-search-results/typo-tolerance/in-depth/configuring-typo-tolerance) * [Stop words](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp/how-to/customize-stop-words) * [Synonyms](/doc/guides/managing-results/optimize-search-results/adding-synonyms) * [Rules](/doc/guides/managing-results/rules/rules-overview) ### Export index configuration in the Algolia dashboard To export the configuration of an index from the Algolia dashboard as a JSON file: 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. Select **Manage index > Export Configuration**. 5. Select what you want to export: **Settings**, **Synonyms**, or **Rules**. 6. Click **Export Configuration** to download a JSON file with the index configuration. ### Export index configuration with the Algolia CLI With the **Algolia CLI**, you can export an index configuration with the [`algolia indices config export`](/doc/tools/cli/commands/indices/config/export) command. This creates a JSON file that you can [import](#import-index-configuration-in-the-algolia-dashboard) in the dashboard, for example, for another index. To export your settings, rules, or synonyms individually, use these commands: * [`algolia settings get`](/doc/tools/cli/commands/settings/get) * [`algolia rules browse`](/doc/tools/cli/commands/rules/browse) * [`algolia synonyms browse`](/doc/tools/cli/commands/synonyms/browse) Check the [Algolia CLI examples](/doc/tools/cli/examples#export-your-algolia-data) for more information. ### Export index configuration with the API With the API clients, you can export your index settings, rules, and synonyms, using these methods: * [`getSettings`](/doc/libraries/sdk/v1/methods/get-settings) * [`exportRules`](/doc/libraries/sdk/v1/methods/export-rules) * [`exportSynonyms`](/doc/libraries/sdk/v1/methods/export-synonyms) ### Import index configuration in the Algolia dashboard 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. 4. Select **Manage index > Import Configuration**. 5. Select the JSON file you want to import. 6. Choose what you want to import: **Settings**, **Synonyms**, or **Rules**. 7. Type `IMPORT` to confirm and click **Import Configuration**. > [!WARNING] > > Importing settings **replaces all existing index settings** with the imported ones. > Synonyms and rules are replaced if a synonym or rule with the same `objectID` already exists. > Otherwise, new synonyms or rules are added. ### Import index configuration with the Algolia CLI To import the configuration of an index with the Algolia CLI, use the [`algolia indices config import`](/doc/tools/cli/commands/indices/config/import) command. The command expects a JSON file with `"settings"`, `"rules"`, or `"synonyms"` keys, which you can get by exporting an index configuration from the dashboard or with the Algolia CLI. To import settings, rules, or synonyms individually, use these commands: * [`algolia settings import`](/doc/tools/cli/commands/settings/import) * [`algolia rules import`](/doc/tools/cli/commands/rules/import) * [`algolia synonyms import`](/doc/tools/cli/commands/synonyms/import) Check the [Algolia CLI examples](/doc/tools/cli/examples#import-your-algolia-data) for more information. ### Import index configuration with the API With the API clients, you can use these methods for importing settings, rules, or synonyms into your index: * [`setSettings`](/doc/libraries/sdk/v1/methods/set-settings) * [`saveRule`](/doc/libraries/sdk/v1/methods/save-rule) * [`saveSynonym`](/doc/libraries/sdk/v1/methods/save-synonym) # Generate a sitemap from an Algolia index Source: https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/tutorial/generate-sitemap-from-index Learn how to generate sitemaps from your Algolia indices dynamically. A [sitemap](https://www.sitemaps.org/index.html) is an XML file that describes all pages of your website that are available for crawling by a search engine. Sitemaps contain the URLs of your pages along with metadata, such as when a page was last updated, how often a page is updated, or how important a page is relative to other pages. Sitemaps play a significant role in search engine optimization (SEO), especially when you're using Algolia in your frontend. Because facets are flexible, Algolia can power navigation in addition to search results pages, which lets you implement dynamic category pages based 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.). These are great candidates to add to your sitemap. ## Before you begin This tutorial assumes that you installed [Node.js](https://nodejs.org/en/) in your environment, and you understand how to create and run Node.js scripts. You also need an Algolia account. If you don't have one already, you can [create an account](https://dashboard.algolia.com/users/sign_up) for free. ### Dataset For this tutorial, you'll use an ecommerce dataset where each result is a product. All records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) have a `categories` attribute with one or more categories. [Download the dataset](https://github.com/algolia/datasets/blob/master/ecommerce/bestbuy_seo.json) and import it into 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.). For more information, see [Import with the dashboard](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-from-the-dashboard). ### Install dependencies Install the [`algolia-sitemap`](https://github.com/algolia/algolia-sitemap) as a dependency. This helper library lets you dynamically generate sitemaps from your Algolia indices. ```sh Command line icon=square-terminal theme={"system"} npm install algolia-sitemap ``` ## Create a sitemap of all the records in your index First, create a sitemap with all your products to let search engines know where to find them. You need your Algolia application ID and your [API key](https://dashboard.algolia.com/account/api-keys/). The API key must have [`browse` permission](/doc/guides/security/api-keys#access-control-list-acl). ```js JavaScript icon=code theme={"system"} // sitemap.js const sitemap = require("algolia-sitemap"); // You need an API key with `browse` permission const algoliaConfig = { appId: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY", indexName: "YourIndexName", }; sitemap({ algoliaConfig, // ... }); ``` To the `sitemap` function, you need to add a `hitsToParams` callback function. This function turns a record from your index into a sitemap entry. The return value of the `hitsToParams` function must be an object with attributes that match an `` entry of a sitemap file: * `loc` (required): the URL of the page for the record * `lastmod`: the last modified date (ISO 8601) * `priority`: the priority of this page compared to other pages on your site (between 0 and 1) * `changefreq`: describes how often the page is likely to change * `alternates`: alternate versions of this link * `alternates.languages`: an array of enabled languages for this link * `alternates.hitToURL`: a function that transforms a language into a URL To learn more, see [Sitemaps XML format](https://www.sitemaps.org/protocol.html). In this tutorial, you'll create a simple sitemap, only specifying the `loc` attribute. ```js JavaScript icon=code theme={"system"} // sitemap.js // Turn a record into a sitemap entry function hitToParams({ url }) { return { loc: url }; } sitemap({ algoliaConfig, hitToParams, // The URL of the sitemaps directory sitemapLoc: "https://example.com/sitemaps", // The directory with all sitemaps (default: `sitemaps`) outputFolder: "sitemaps", }); ``` In your project directory, create a `sitemaps/` directory. Now, you can run the `sitemap.js` script: ```sh Command line icon=square-terminal theme={"system"} node sitemap.js ``` The `/sitemaps` directory now has two types of files: * The `sitemap-index.xml` file with links to each sitemap file * The `sitemap.0.xml` file with links to your products You can validate your sitemaps with [external tools](https://www.xml-sitemaps.com/validate-xml-sitemap.html). To inspect the sitemap, you can use a tool like [`xmllint`](https://xmllint.com/). ```sh Command line icon=square-terminal theme={"system"} xmllint --format sitemaps/sitemap.0.xml ``` ## Create a sitemap for categories Add your category pages to the sitemap. If you're using the sample dataset, use the `categories` attribute for the URL: ```json JSON icon=braces theme={"system"} { "categories": ["Mobile Phones", "Phones & Tablets"] } ``` In this example, the product belongs to two categories. Usually, category pages have URLs like the following: `https://example.com/{CATEGORY_NAME}`. You need to modify the `hitToParams` callback function to return an array of all categories that belong to a given record. Since categories apply to many records, you need to make sure to add them to your sitemaps only once. With ES6, you can use a [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). ```js JavaScript icon=code theme={"system"} // sitemap.js // Turn categories into sitemap entries const alreadyAdded = {}; function hitToParams({ categories }) { const newCategories = categories.filter( (category) => category !== null && category !== undefined && !alreadyAdded[category], ); if (!newCategories.length) return; const locs = newCategories.map((category) => { alreadyAdded[category] = category; return { loc: `https://example.com/${encodeURI(category)}` }; }); return locs; } // ... ``` Check each record to see if it contains categories that you didn't yet add to the sitemap, and add them. This lets you save all your category pages to your sitemap. ## Create a sitemap for categories and all records You can modify the code for adding category pages to your sitemap to create a combined sitemap for both records and categories. ```js JavaScript icon=code theme={"system"} // sitemap.js // ... function hitToParams({ categories, url }) { const newCategories = categories.filter( (category) => category !== null && category !== undefined && !alreadyAdded[category], ); if (!newCategories.length) return; const locs = []; newCategories.forEach((category) => { alreadyAdded[category] = category; alreadyAdded[url] = url; locs.push( ...[{ loc: `https://example.com/${encodeURI(category)}` }, { loc: url }], ); }); return locs; } // ... ``` ## Notify search engines of sitemap changes Finally, you can let search engines know that your sitemap has changed. Most search engines have a `ping` mechanism to inform them of a new sitemap, so you can perform this directly from your script. ```js JavaScript icon=code theme={"system"} const endpoints = [ "http://www.google.com/webmasters/sitemaps/ping?sitemap=http://example.com/sitemap.xml", "http://www.bing.com/webmaster/ping.aspx?siteMap=http://example.com/sitemap.xml", ]; Promise.all(endpoints.map(fetch)).then(() => { console.log("Done"); }); ``` For more information, see: * [Submit your sitemap to Google](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) * [Bing sitemaps](https://www.bing.com/webmasters/help/Sitemaps-3b5cf6ed) # Manage your Algolia applications Source: https://www.algolia.com/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-your-apps Set up different Algolia applications for your production, testing, or other environments. Set up different Algolia applications to keep environments and projects separate. ## Manage environments If you have several environments, such as development, staging, and production, you can choose: * [Use one index for each environment in the same Algolia application.](#one-index-for-each-environment-in-the-same-algolia-application) * [Use one Algolia application for each environment.](#separate-algolia-applications-for-each-environment) > [!NOTE] > > You can use different datasets outside production. > For example, index a small part of your production data to cut down on indexing. ### One index for each environment in the same Algolia application If you're using a single 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.), you can separate your production from your development and testing environments. Create separate indices to do this. To distinguish the different environments, make sure to name your indices consistently, for example, by using prefixes or suffixes: `dev_index`, `staging_index`, `prod_index`. #### Control access to your indices Indices on the same Algolia application share the same application ID (A unique alphanumeric string that identifies an Algolia application.). To **control access to the index**, create an Algolia user for each environment with different search and admin API keys. > [!NOTE] > > To create a dedicated user per environment, you need the Team Advanced Permissions feature. > Team Advanced Permissions is available as part of the Enterprise add-on to your [pricing plan](https://dashboard.algolia.com/info/plans/). To create a new team member with access to all indices of a single environment: 1. Sign in to the [Algolia dashboard](https://dashboard.algolia.com/). 2. On the left sidebar, select Settings **Settings**. 3. In the **Team and Access** section, click [**Team**](https://dashboard.algolia.com/account/teams). 4. Click **Add Team Member** and select the application to which you want to grant access. 5. Select the **Index restrictions** option and add the indices to which you want to grant access. Use `"*"` as a wildcard, for example, `dev_*` or `staging_*`, to grant access to all indices with a common prefix. You can further refine the permissions in the **Give them permissions** section. This gives your team members access to individual indices in your application. You can repeat the same operation for all your environments. The only things your environments share are the number of records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) and operations, since they belong to the same Algolia application. ### Separate Algolia applications for each environment If you need full separation between your development, staging, and production environments, create an Algolia application for each. **Separate Algolia applications each have their own pricing plan.** The Free plan allows only one application. To isolate your environments, use a [paid plan](https://www.algolia.com/pricing/) for each extra application. Create new Algolia applications on the [**Applications**](https://dashboard.algolia.com/account/applications) page in the Algolia dashboard. > [!NOTE] > > A dedicated cluster of servers is available as part of the Enterprise add-on to your [pricing plan](https://dashboard.algolia.com/info/plans/). ## Manage your Algolia applications Manage your Algolia applications from the dashboard. For example, to delete an unwanted Algolia application: 1. Go to the [**Applications**](https://dashboard.algolia.com/account/applications) page in the Algolia dashboard. 2. Click **Application actions** and select **Delete**. If you don't own this application, select **Leave** instead. 3. Confirm your request. > [!NOTE] > > There's no API for managing applications. > Use the dashboard instead. ## See also * [Choose between one or more indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/one-or-more-indices) # Prepare your records for indexing Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data How to format and structure your attributes and records to improve search results with Algolia. > [card] [Algolia Academy](https://academy.algolia.com/pages/contentcatalog/training/0199aaf1-7fc0-7f95-83cb-320d8af18e71/overview) > > Learn more about: Structuring Data for Optimal Search ## Algolia records An Algolia record is a collection of attributes where each attribute has a name and a value (a key-value pair). Here is an example record with four [different kinds of attributes](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record) (string, integer, array, and boolean): ```json JSON icon=braces theme={"system"} { "title": "Blackberry and blueberry pie", "description": "A delicious pie recipe that combines blueberries and blackberries.", "image": "https://yourdomain.com/blackberry-blueberry-pie.jpg", "likes": 1128, "sales": 284, "categories": ["pie", "dessert", "sweet"], "gluten_free": false } ``` Your records should only include information that helps with searching, showing results, sorting, and [relevance](/doc/guides/managing-results/relevance-overview). > [!NOTE] > > Attributes don't have to follow a specific schema (pattern)—they can differ for each record. ## Unique record identifiers Each record in an index has a unique `objectID`. Set it yourself so you can match the record to its source data. If you don't set one, Algolia generates it for you, and you need to find it in the index before you can [update or delete a record](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/incremental-updates). ## Searchable attributes All attributes are searchable by default. To make searching more relevant, [set only some attributes as searchable](/doc/guides/managing-results/must-do/searchable-attributes). Textual, descriptive attributes, such as summaries, brands, or colors, can be helpful as searchable attributes. For instance, in a recipe app, to help users search for "blueberry pie", you need an attribute containing those words, such as a `title` attribute. ## Structuring records When adding data to your records, be selective. For example, if you're working with a product line, you don't need to send every piece of information about your products: only what serves the purposes of search. Include all the necessary information to find products, rank them, and display them on your website or app. Building records involves: * Extracting valuable data * Reworking the data to remove unnecessary elements * Adding or computing extra information that improves the chances of finding the most relevant results. ### Simplify your records When making an Algolia index, make your records as simple as possible. Each record should have enough information for users to find it. When users search for something, Algolia returns records as results that match their search. **Each record should contain all the information users need to find it and display its content**. > [!NOTE] > > Don't worry about relational database principles, > such as not repeating data or creating hierarchical structures with primary and foreign keys. For more information, see: * [Reduce record size](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/reducing-object-size) * [Data relationships](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/handling-data-relationships) * [Index long pages](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/indexing-long-documents) * [Structure ecommerce product records](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/ecommerce-records) ### Rework your data Think about what needs to happen if you want to create a movie UI for users to: * Search and view titles, synopsis, and distribution details * Display (but not search) images and country of release * Filter on genre or a range of dates * Rank based on review scores * You don't care about technical information, such as how long the movie is. Assuming your data comes from a relational database, with the information you need in different tables, you need to query the data from these tables. After fetching it, a record for one movie may look like the following: ```json JSON icon=braces theme={"system"} [ { "title": "Spirited Away", "synopsis": "During her family's move to the suburbs, a sullen 10-year-old girl wanders into a world ruled by gods, witches, and spirits, and where humans are changed into beasts.", "director": "Hayao Miyazaki", "cast": [ { "name": "Rumi Hiiragi", "birth_date": "August 1, 1987", "birth_place": "Tokyo, Japan" }, { "name": "Miyu Irino", "birth_date": "February 19, 1988", "birth_place": "Tokyo, Japan" }, { "name": "Mari Natsuki", "birth_date": "May 2, 1952", "birth_place": "Tokyo, Japan" } ], "release_year": 2001, "country": "Japan", "genres": ["Animation", "Adventure", "Family", "Fantasy", "Mystery"], "runtime": 125, "aspect_ratio": "1.85:1", "content_rating": "PG", "review_scores": { "imdb": 8.6, "rotten_tomatoes": { "critics": 97, "audience": 96 } }, "images": [ "https://example.com/spirited-away/image1.jpg", "https://example.com/spirited-away/image2.jpg" ] } ] ``` The example contains several kinds of content, some useful for a search experience, some that aren't, and others that require reformatting. For example, you don't need to keep `runtime` or `aspect_ratio`. While they're helpful in other contexts, they have little value when searching, filtering, ranking, or displaying search results. While the names of the voice actors in the `cast` attribute are helpful, you don't need their birth date and place. Therefore, you can remove them and just keep the names. **This process [removes noise and saves room in records](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/reducing-object-size) for more valuable data**. ```json JSON icon=braces theme={"system"} { "cast": ["Rumi Hiiragi", "Miyu Irino", "Mari Natsuki"] } ``` ### Attributes for searching Attributes for searching are the ones that contain the terms that your users look for. If you want to search for a movie by title, plot, genre, or cast, you need attributes that contain these terms. In the preceding example, such attributes are `title`, `synopsis`, `director`, `cast`, and `genres`. Algolia lets you define in which specific attributes to search, using the [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes) parameter. By default, the engine searches within the entire record, but you want to adjust this: this is better for performance and lets you remove noise. Don't search attributes that aren't textually relevant or might generate false positives, like `images`, `release_year`, `review_scores`, or `country`. For example, when searching for "japan", English-speaking users may want to find movies that either have the term in the title or take place in Japan rather than Japanese movies. You can therefore set `title`, `synopsis`, `director`, `cast`, and `genres` as `searchableAttributes` and leave out the rest for displaying, filtering, and custom ranking. You can add extra data to make your record more discoverable. For example, some users may look for a movie by its original title or translation in their language. Unless the translations are in the record, searching for these terms wouldn't return any results, so it's a good idea to retrieve them and add them to your objects. You can fetch them from your database if you have them or a third-party source such as an API or a website. ```json JSON icon=braces theme={"system"} { "display_title": "Spirited Away", "original_title": "千と千尋の神隠し", "alternative_title": [ "Le voyage de Chihiro", "El viaje de Chihiro", "Chihiros Reise ins Zauberland" ] } ``` ### Attributes for display Display attributes are those attributes that can be useful to show in the search results. They can include: * Titles * Descriptions * Attributes used for filtering and custom ranking, such as the number of likes or categories * Images. To display images in your results, you need an image URL attribute in your records. This way, Algolia can return them in search results for you to display on the frontend. Some display attributes, such as `title` and `description`, can also be searchable. Others, such as `image` or `likes`, shouldn't be set as searchable. > [!NOTE] > > You can't store images directly in Algolia. > Instead, store the image on a content delivery network (CDN) or web server and add the image URL to a field in your records. > When you retrieve a record from Algolia, use this URL to display the image in your app. ### Attributes for filtering Sometimes, you might want users to find a specific subset (category) of your records. For example, they may want to find all movies by director Hayao Miyazaki, find new adventure movies to watch or look for the best motion pictures of the past year. You can do this by setting some attributes as [filters](/doc/guides/managing-results/refine-results/filtering) to narrow down search results. The following are examples of attributes that you can filter: * Boolean attributes (like whether an item is public) * Lists (categories, tags) * Numeric attributes (price, rounded rating) * Normalized text (colors, types, or enumerated types). For example, you could use `director`, `cast`, `country`, `content_rating`, and `genres` and display them as refinement lists in your search experience, and `release_year` to display a range slider. Declare them with the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter. Filterable attributes can be anything, but normalize your data to ensure consistency. For example, if you have attribute `genres` with the term "Animation" in one record and "Animated picture" in another, these would result in two different facet values. For more information, see: * [Filtering](/doc/guides/managing-results/refine-results/filtering) * [Faceting](/doc/guides/managing-results/refine-results/faceting) * [Declare attributes for faceting with the API](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting) A good rule of thumb is to add attributes based on how users want to fine-tune their search. If you have a movie reviews website, users may want to refine on review score or popularity. ### Custom ranking attributes Custom ranking makes Algolia's search results more relevant for your users by including your business metrics. It's a good idea to start thinking about such metrics when fetching your data and structuring your records. For example, if a user looks for "james bond", all James Bond movies would match the same. Without anything else to break the tie, Algolia falls back on the `objectID` in alphanumeric order, which isn't helpful. A better way to break ties is to compare meaningful information. For movies, you could use the `review_scores` attribute. However, in the preceding example, you have several scores, you may want to compute them into a global one and use them in custom ranking. The computed attribute would look like this: ```json JSON icon=braces theme={"system"} { "computed_score": 201.6 } ``` > [!NOTE] > > Custom ranking attributes must be either numeric or boolean. For more information, see: * [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking) * [Create custom ranking attributes](/doc/guides/managing-results/must-do/custom-ranking/how-to/configure-custom-ranking) * [Control precision of custom ranking metrics](/doc/guides/managing-results/must-do/custom-ranking/how-to/controlling-custom-ranking-metrics-precision) ## Advanced data formatting Algolia provides a vast collection of settings to help with relevance, and these work in combination with how you format your content. For example, whether to use one or several attributes for a single piece of information, including long or short descriptions (or both), repeat the same words in the title and description, and how to use custom ranking attributes. # Create nested attributes Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/creating-and-using-nested-attributes Learn about nested attributes, and when to use them. With nested attributes, you can add subcategories to your attributes. For example, instead of having a single attribute `price`, you can set up different prices as subcategories: `price.net`, `price.gross`, `price.margin`. To refer to nested attributes, separate parent and child attribute with a `.`. To create nested attributes, add them as nested JSON objects to your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). For example: ```json JSON icon=braces theme={"system"} [ { "country": "CA", // price is a nested attribute "price": { "net": 2.30, "gross": 2.62 } }, { "country": "US", // price is a nested attribute "price": { "net": 1.99, "gross": 1.75 } } ] ``` ## Example: filtering nested attributes If you want to filter a search using nested attributes, use the [`filters`](/doc/api-reference/api-parameters/filters) parameter in your search request and separate the parent and child attribute by a `.`. For example, with the JavaScript API client: ```cs C# theme={"system"} var response = await client.SearchSingleIndexAsync( "INDEX_NAME", new SearchParams(new SearchParamsObject { Filters = "country:US AND price.gross < 2.0" }) ); ``` ```dart Dart theme={"system"} final response = await client.searchSingleIndex( indexName: "INDEX_NAME", searchParams: SearchParamsObject( filters: "country:US AND price.gross < 2.0", ), ); ``` ```go Go theme={"system"} response, err := client.SearchSingleIndex(client.NewApiSearchSingleIndexRequest( "INDEX_NAME").WithSearchParams(search.SearchParamsObjectAsSearchParams( search.NewEmptySearchParamsObject().SetFilters("country:US AND price.gross < 2.0")))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} SearchResponse response = client.searchSingleIndex( "INDEX_NAME", new SearchParamsObject().setFilters("country:US AND price.gross < 2.0"), Hit.class ); ``` ```js JavaScript theme={"system"} const response = await client.searchSingleIndex({ indexName: 'indexName', searchParams: { filters: 'country:US AND price.gross < 2.0' }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.searchSingleIndex( indexName = "INDEX_NAME", searchParams = SearchParamsObject(filters = "country:US AND price.gross < 2.0"), ) ``` ```php PHP theme={"system"} $response = $client->searchSingleIndex( 'INDEX_NAME', ['filters' => 'country:US AND price.gross < 2.0', ], ); ``` ```python Python theme={"system"} response = client.search_single_index( index_name="INDEX_NAME", search_params={ "filters": "country:US AND price.gross < 2.0", }, ) ``` ```ruby Ruby theme={"system"} response = client.search_single_index( "INDEX_NAME", Algolia::Search::SearchParamsObject.new(filters: "country:US AND price.gross < 2.0") ) ``` ```scala Scala theme={"system"} val response = Await.result( client.searchSingleIndex( indexName = "INDEX_NAME", searchParams = Some( SearchParamsObject( filters = Some("country:US AND price.gross < 2.0") ) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response: SearchResponse = try await client.searchSingleIndex( indexName: "INDEX_NAME", searchParams: SearchSearchParams .searchSearchParamsObject(SearchSearchParamsObject(filters: "country:US AND price.gross < 2.0")) ) ``` > [!NOTE] > > This example combines two conditions on one nested object, which works because `price` holds a single object. > When a nested attribute is an array of objects, each condition is evaluated independently and can match a different element. > See [Filters and arrays of objects](/doc/guides/managing-results/refine-results/filtering/in-depth/arrays-of-objects). ## Where you can use nested attributes You can use nested attributes wherever you might use a regular attribute, such as [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes) and [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting). Just make sure you use the appropriate dot notation such as `price.country` to refer to them. There's no limit on the number of nested attributes apart from [the default restrictions on record size](https://support.algolia.com/hc/en-us/articles/4406981897617-Is-there-a-size-limit-for-my-index-records). The maximum nesting depth is 256 levels, which is more than enough for most use cases. For example, you could use something like `price.net.us.ca`. # Make measures and dimensions searchable Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/dimensional-data Transform dimension and measure formats to make them searchable. Users enter dimensions and measures in many different ways. For example, users might search for `30 x 50` or `30mm x 50mm`, while your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) may use a different format like `30x50mm`. Algolia's features support some formatting variations but may not handle all dimension formats reliably. This is because: * **User query formats vary.** A query like `30mm x 50mm` won't necessarily match a record with `30x50mm`. * **Unit differences cause mismatches.** Queries may include `"`, `inches`, `mm`, `cm`, or `ft`, while records might use only one format. * **Typo tolerance has limits.** While [typo tolerance](/doc/guides/managing-results/optimize-search-results/typo-tolerance) can match slight variations such as `30by50` or `30 x 50`, but not different units or separators. * **AI doesn't consistently interpret dimensions.** Although [NeuralSearch](/doc/guides/ai-relevance/neuralsearch/get-started) can identify dimensions, it doesn't do so consistently, due to the ambiguity of the input. To improve matching for dimension-based searches, standardize and expand these values using a transformation function before sending your data to Algolia. ## Transform data into dimension-friendly formats To address this issue, pre-process your data with a transformation function like the one below. For each record you pass to it, the `transform` function returns a transformed record or `undefined` if no dimensions are found. To run this function, create a [Push to Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push) connector, using the following transformation code. ```js JavaScript icon=code expandable theme={"system"} const DIMENSIONS_RE = /\b\d+(?:[.,]\d+)?\s*(?:(?:mm|cm|m|in(?:ch(?:es)?)?|ft|'|["″])?(?:\s*(?:x|\*|by)\s*\d+(?:[.,]\d+)?\s*(?:mm|cm|m|in(?:ch(?:es)?)?|ft|'|["″])?){1,2}|(?:mm|cm|m|in(?:ch(?:es)?)?|ft|'|["″]))\b/gi; const PART_RE = /(\d+(?:[.,]\d+)?)(?:\s*(mm|cm|m|in(?:ch(?:es)?)?|ft|["″'])?)?/gi; // Convert any raw unit symbol to a standard form function normalizeUnit(raw, fallback) { if (!raw) return fallback.toLowerCase(); switch (raw.toLowerCase()) { case '"': case "″": case "in": case "inch": case "inches": return "in"; case "'": case "ft": return "ft"; default: return raw.toLowerCase(); } } // Return every spelling for a unit function unitForms(unit) { if (unit === "in") return ["in", '"', "inch", "inches"]; if (unit === "ft") return ["ft", "'"]; return [unit]; } function dimensionKeywords(attr, fallbackUnit) { if (!attr) return []; const out = new Set(); const entries = [...attr.matchAll(DIMENSIONS_RE)].map((m) => m[0]); // Extract all matching dimensions for (const entry of entries) { // Extract number and unit pairs const parts = []; let match; while ((match = PART_RE.exec(entry)) !== null) { const number = match[1]; const unit = normalizeUnit(match[2], fallbackUnit); parts.push({ n: number, u: unit }); } if (!parts.length) continue; // Single numbers and number plus unit forms parts.forEach((p) => { out.add(p.n); unitForms(p.u).forEach((f) => out.add(p.n + f)); }); // Combined variants when there's more than one part if (parts.length > 1) { const nums = parts.map((p) => p.n); // ["30","50"] const nu = parts.map((p) => p.n + p.u); // ["30mm","50mm"] // Join dimensions without spaces (for example, 30mm50mm), including inch variants const tight = nu.join(""); out.add(tight); if (tight.includes("in")) { unitForms("in").forEach((f) => out.add(tight.replace(/in/g, f))); } ["x", "by"].forEach((sep) => { // Raw numbers joined out.add(nums.join(sep)); if (sep === "by") out.add(nums.join(` ${sep} `)); // Numbers and units joined const joinedTight = nu.join(sep); const joinedSpc = nu.join(` ${sep} `); [joinedTight, joinedSpc].forEach((s) => { out.add(s); if (s.includes("in")) unitForms("in").forEach((f) => out.add(s.replace(/in\b/g, f))); }); }); } } return [...out]; } /** * Transforms a record by extracting dimension keyword variants. * @param {SourceRecord} record - A record from your dataset. * @param {Helper} helper - A helper for accessing secrets and metadata. * @returns {SourceRecord|undefined} - The transformed record, or undefined if no dimensions are found. */ async function transform(record, helper) { record.dimension_keywords = dimensionKeywords(record.name, "mm"); return record; } ``` ### Customization You can customize the function to support other measurement units or non-standard formats. To add new units (for example, `yd`, `mil`, `µm`, `kg`) or handle alternative patterns (for example, `D30`, `H50`, `Ø20mm x 40mm`), update the following: * **`DIMENSIONS_RE`.** Extend the regular expression to detect new unit symbols or structural patterns. Consider using AI-assisted tools to build and test regular expressions. * **`normalizeUnit(raw, fallback)`.** Map any new unit symbol or abbreviation to a standard form. For example, 'yard' and 'yd' become 'yd'. * **`dimensionKeywords()`.** The function defaults to `mm` if it doesn't find a unit. To change this default (for example, to `cm` or `in`), update the second argument in the `dimensionKeywords()` call. * **`unitForms(unit)`.** Add alternative spellings and symbols for each unit. For example, `["yd", "yard", "yards"]`. ## How the transformation function works The function improves search by extracting keyword variants from dimension patterns, by performing the following steps: * [Identify dimensions](#identify-dimensions) with a regular expression * [Extract numbers and units](#extract-numbers-and-units) * [Generate variants](#generate-variants) * [Attach keywords](#attach-keywords-to-an-attribute) to a new attribute ## Identify dimensions The function uses the `DIMENSIONS_RE` regular expression to detect one-part, two-part, or three-part dimensions, such as: * One part: `600mm`, `2.4m` * Two-part: `30x50`, `3"x6"`, `20mm x 30mm` * Three-part: `245x148x65mm`, `30mm x 50mm x 2m` It recognizes various separators (`x`, `*`, `by`) and units (including `mm`, `"`, `inches`, and `ft`). Spaces are optional. > [!TIP] > > The regular expression handles a wide range of edge cases, > but test it against your data to confirm it captures the formats you use. ### Extract numbers and units For each match, the function extracts the numbers and their associated units. The function standardizes unit variants like `"`, `inch`, and `inches` to `in`. ### Generate variants Each detected dimension expands into these keyword-friendly formats: * Bare numbers: `30`, `50`, `2` * Normalized units: `30mm`, `2m`, `3in` * Commonly-accepted synonyms: `30in`, `30"`, `30inch`, `30inches` * Joined forms: * Without spacing: `30mm50mm` * With separators: `30x50`, `30 mm by 50 mm`, `30mmx50mm` For example, from `30mm x 50mm`, the function generates: ```js JavaScript icon=code theme={"system"} [ "30", "50", "30mm", "50mm", "30x50", "30 x 50", "30mmx50mm", "30 mm x 50 mm", "30 by 50", "30mm by 50mm", "30mm50mm", ]; ``` ### Attach keywords to an attribute The function adds a new attribute, `dimension_keywords`, to each record it processes. ## Add dimension keywords to your index To use the generated keywords in Algolia: * With the `taskID` generated by the Push to Algolia connector, send your data to Algolia with the Ingestion API [`pushTask`](/doc/rest-api/ingestion/push-task) method, making sure each one includes the `dimension_keywords` attribute. * Configure `dimension_keywords` as a [searchable attribute](/doc/guides/managing-results/must-do/searchable-attributes) 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.) settings. ## See also * [Transform your data with code](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code) * [Searching in hyphenated attributes](/doc/guides/managing-results/optimize-search-results/typo-tolerance/how-to/how-to-search-in-hyphenated-attributes) # Structure ecommerce product records Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/ecommerce-records Find the optimal structure for your Algolia records for products In ecommerce, you often deal with *base products* and their *variants*. A base product, such as a t-shirt, can have variants with different colors and sizes. You can store this data in Algolia in three ways: * **Variant-level records:** one Algolia record for each product variation. * **Product-level records:** one Algolia record for each base product. The record contains all variants. * **Variation-group-level records:** one Algolia record for each variation of a specific attribute, generally the `color`. Each record contains variations sharing this attribute value. ## Record structure examples The following example shows records for a t-shirt in two colors. Each color comes in two sizes. With the **variant-level** records model, each variant is a separate record: ```json JSON icon=braces theme={"system"} [ { "objectID": "v-neck-t-shirt-white-m", "name": "V-neck t-shirt", "category": "Sport", "color": "White", "size": "M", "image": "v-neck-t-shirt-white.jpg", "price": 19.99 }, { "objectID": "v-neck-t-shirt-white-l", "name": "V-neck t-shirt", "category": "Sport", "color": "White", "size": "L", "image": "v-neck-t-shirt-white.jpg", "price": 19.99 }, { "objectID": "v-neck-t-shirt-blue-m", "name": "V-neck t-shirt", "category": "Sport", "color": "Blue", "size": "M", "image": "v-neck-t-shirt-blue.jpg", "price": 22.99 }, { "objectID": "v-neck-t-shirt-blue-l", "name": "V-neck t-shirt", "category": "Sport", "color": "Blue", "size": "L", "image": "v-neck-t-shirt-blue.jpg", "price": 22.99 } ] ``` With the **product-level** records model, all variants of a base product are in a single record. The common attributes sit at the top level. The varying attributes, such as color, size, images, and prices, sit in a `variants` array. Each array item corresponds to a unique variant: ```json JSON icon=braces theme={"system"} { "objectID": "v-neck-t-shirt", "name": "V-neck t-shirt", "category": "Sport", "variants": [ { "variantID": "v-neck-t-shirt-white-m", "color": "White", "size": "M", "image": "v-neck-t-shirt-white.jpg", "price": 19.99 }, { "variantID": "v-neck-t-shirt-white-l", "color": "White", "size": "L", "image": "v-neck-t-shirt-white.jpg", "price": 19.99 }, { "variantID": "v-neck-t-shirt-blue-m", "color": "Blue", "size": "M", "image": "v-neck-t-shirt-blue.jpg", "price": 22.99 }, { "variantID": "v-neck-t-shirt-blue-l", "color": "Blue", "size": "L", "image": "v-neck-t-shirt-blue.jpg", "price": 22.99 } ] } ``` With the **variation-group-level** records model, variants that share the same value of a given attribute, typically the color, sit in a single record. For the t-shirt example, there are two records, one for each color. The common attributes sit at the top level. The varying attributes, such as size and price, sit in a `variants` array. Each array item corresponds to a unique variant: ```json JSON icon="braces" theme={"system"} [ { "objectID": "v-neck-t-shirt-white", "name": "V-neck t-shirt", "category": "Sport", "color": "White", "image": "v-neck-t-shirt-white.jpg", "variants": [ { "variantID": "v-neck-t-shirt-white-m", "size": "M", "price": 19.99 }, { "variantID": "v-neck-t-shirt-white-l", "size": "L", "price": 19.99 } ] }, { "objectID": "v-neck-t-shirt-blue", "name": "V-neck t-shirt", "category": "Sport", "color": "Blue", "image": "v-neck-t-shirt-blue.jpg", "variants": [ { "variantID": "v-neck-t-shirt-blue-m", "size": "M", "price": 22.99 }, { "variantID": "v-neck-t-shirt-blue-l", "size": "L", "price": 22.99 } ] } ] ``` ## Choose your record model When choosing between the three indexing models, consider whether you use Algolia AI or merchandising features. **Use the variation-group-level or product-level record model if you use Algolia AI or merchandising features.** These models can work better when your variants look alike: * Algolia groups [click and conversion events](/doc/guides/sending-events) from each variant by `objectID`, meaning by base product or by variation-group. This lets Algolia re-rank or personalize results at the product or variation-group level, not just for specific variants. * [Merchandising](/doc/guides/managing-results/rules/merchandising-and-promoting) at the product-level or variation-group-level is simpler when you have many variants. For example, you can [promote or hide products or variations](/doc/guides/managing-results/rules/rules-overview/in-depth/implementing-rules#promote-or-hide-specific-hits) rather than individual variants. * [Search analytics](/doc/guides/search-analytics/overview) groups results by product or variation, which makes them simpler to analyze. **Use the variant-level record model if you don't use Algolia AI or merchandising.** This model is more granular and better suited for precise control: * Results match the most relevant variant directly. For example, a search for "red shoes" returns only variants with "red" in their data. In contrast, the product model requires more frontend work to display the correct variant. * Faceting is more accurate. 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.) like `color: green AND size: 40` returns only matching variants, not entire products with partial matches. In the product model the two conditions can match different variants, so the results can include products where no single variant satisfies both. See [Filters and arrays of objects](/doc/guides/managing-results/refine-results/filtering/in-depth/arrays-of-objects). * You can update individual variant attributes, such as price or stock. The product model requires resending the full list of variants, because it doesn't support partial updates. > [!TIP] > > The variation-group-level model is a good compromise between the two other models. > > It offers better AI and merchandising features than the variant-level model, while staying more granular than the product-level model. > For example, using `color` as the grouping attribute means a search for `red shoes` returns only records with "red" in their data. > This lets you show the matching image without extra frontend work. ### Capabilities by record model | | Variant-level | Product-level | Variation-group-level | | - | - | - | - | | AI features | PER VARIANT | PER PRODUCT | PER VARIATION GROUP | | Rules and merchandising | PER VARIANT | PER PRODUCT | PER VARIATION GROUP | | Search analytics | PER VARIANT | PER PRODUCT | PER VARIATION GROUP | | Textual relevance | OPTIMIZED | SUPPORTED | OPTIMIZED FOR THE VARIATION | | Faceting support | OPTIMIZED | Requires frontend work (see below) | OPTIMIZED FOR THE VARIATION | | Variant-powered PLP | OPTIMIZED | Requires frontend work (see below) | OPTIMIZED FOR THE VARIATION | | Granular variant update | SUPPORTED | Not supported | Not supported | | Average record size | Depending on further choices (see [Average record size](#average-record-size-for-the-variant-level-model)) | OPTIMIZED | OPTIMIZED | | Number of records | HIGH | OPTIMIZED | Depends on the number of variants of each variation | #### Average record size for the variant-level model The average record size for the variant-level model depends on what you want to show on your results, for example, if you want to include color swatches or images carousels. To display those, each of your variant records needs to have information about their siblings, which increases the average record size. ## Results display ### Group records per product Depending on your desired end-user experience and catalog size, showing one result per product, instead of listing each variant separately, can offer a cleaner, friendlier interface. With the **product-level** record model, you get one result per product by default. The **variant-level** model requires using the [distinct](/doc/guides/managing-results/refine-results/grouping) feature to group variants under a single result. To do this, include a shared product ID in each variant record and set the distinct parameter on that attribute: ```jsonc JSON icon=braces theme={"system"} { "objectID": "v-neck-t-shirt-white-m" "name": "V-neck t-shirt", "baseProductID": "v-neck-t-shirt", // ... } ``` The **variation-group-level** model gives you the choice: * By default, you get one result per variation (for example, one per color). * Or you can use the distinct feature the same way as for the variant-level model and display one result per product. ### Facets The **variant-level** record model lets users refine results. In this model, Algolia returns only the variants that match a selected facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).), with no extra frontend logic required. This also works well for the **variation-group-level** model when it's based on color. Images are often the same for all variants of a given color, so a record's variants tend to share the same image. This means you don't need extra frontend logic here either. In contrast, the **product-level** model always returns the entire product, including all variants. To display only the matching variants, you need to post-process the search results in your InstantSearch code: ```js JavaScript icon=code theme={"system"} hits({ // ... transformItems(items, { results }) { return items.map((item) => { const colorFacets = results._state.disjunctiveFacetsRefinements['variants.color'] || []; let selectedVariant; if (colorFacets.length > 0) { selectedVariant = item.variants.find(variant => { return colorFacets.includes(variant.color); }); } else { selectedVariant = item.variants[0]; } item.image = selectedVariant.image; item.price = selectedVariant.price; item.url = selectedVariant.url; return item; }); }, }); ``` # Handle data relationships Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/handling-data-relationships Model simple and complex data relationships, hierarchies, and nesting. To index relational data into Algolia, create a data structure that supports your desired search experience. Algolia's schemaless nature gives you the flexibility to structure your data in the way that best fits your users' needs. Consider a marketplace for third-parties to sell second-hand games. You could build your search experience by displaying: * Sellers and their full game catalogs * Games titles and a list of people who sell them * Classified ads, ranked by business relevance (such as seller's popularity) Depending on the experience, you need to represent data relationships differently. Because Algolia is schemaless, you can tailor your data structure to support the search experience you want to create. ## Data source When you're fetching from a relational database like as MySQL, you usually have one entity per table and join tables for many-to-many mappings. After making a `JOIN` request, serialized data might look like this: ```json JSON icon=braces theme={"system"} [ { "id": 1, "name": "Alice", "popularity": 1000, "total_sales": 13, "country": "France", "games": [ { "id": 1, "title": "Grand Theft Auto V" }, { "id": 2, "title": "The Legend of Zelda: Ocarina of Time" } ] }, { "id": 2, "name": "Bob", "popularity": 900, "total_sales": 42, "country": "United States", "games": [ { "id": 3, "title": "Mass Effect 3" } ] }, { "id": 3, "name": "Mass Effect Fan", "popularity": 800, "total_sales": 50, "country": "United States", "games": [ { "id": 1, "title": "Grand Theft Auto V" } ] } ] ``` ## Display sellers and their catalog A nested data structure would work well to create a search experience where you would display a list of sellers and their associated games. From there, you could streamline the data by removing unnecessary attributes, and index it as follows: ```json JSON icon=braces theme={"system"} [ { "name": "Alice", "popularity": 1000, "country": "France", "games": [ "Grand Theft Auto V", "The Legend of Zelda: Ocarina of Time" ] }, { "name": "Bob", "popularity": 900, "country": "United States", "games": [ "Mass Effect 3" ] }, { "name": "Mass Effect Fan", "popularity": 800, "country": "United States", "games": [ "Grand Theft Auto V" ] } ] ``` ### Searchable attributes This experience focuses on sellers, which means you might want to search seller data in priority. For example, when a user searches for "mass effect", you may want seller Mass Effect Fan to show up before sellers who sell Mass Effect 3, even if Mass Effect Fan doesn't sell this game. To do so, you can create an order of priority in your [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes), by setting `name` first and `games` second. ## Displaying game titles and their sellers You can use the same structure to create a search experience where you would display a list of games and their associated sellers. To achieve this, all you would need to do is to reverse the relationship: instead of nesting games per seller, you would nest sellers per game. ```json JSON icon=braces theme={"system"} [ { "name": "Grand Theft Auto V", "sellers": [ { "name": "Alice", "popularity": 1000, "country": "France" }, { "name": "Mass Effect Fan", "popularity": 800, "country": "United States" } ] }, { "name": "The Legend of Zelda: Ocarina of Time", "sellers": [ { "name": "Alice", "popularity": 1000, "country": "France" } ] }, { "name": "Mass Effect 3", "sellers": [ { "name": "Bob", "popularity": 900, "country": "United States" } ] } ] ``` ### Searchable attributes This experience focuses on games, meaning you might want to search game data in priority. For example, when a user searches for "mass effect", you may want game Mass Effect 3 to show up before seller Mass Effect Fan, especially if they don't sell that game. For this, you can create an order of priority in your [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes), by setting `name` first and `sellers.name` second. ## Display classified ads ranked by business relevance The first two experiences focus on exhaustiveness. No matter what users are looking for, they should get all possible options (all sellers per game or all games per seller). Another approach to building search is to be relevance-oriented: instead of showing all possibilities, you only show the best ones. For example, if users search for "grand theft auto", they may not need to see all classified ads for Grand Theft Auto V. Instead, you could only show them the ones from the most popular sellers. In this case, you need to split your data and use a flat structure. ```json JSON icon=braces theme={"system"} [ { "game_title": "Grand Theft Auto V", "seller_name": "Alice", "seller_popularity": 1000, "seller_country": "France" }, { "game_title": "Grand Theft Auto V", "seller_name": "Mass Effect Fan", "seller_popularity": 800, "seller_country": "United States" }, { "game_title": "The Legend of Zelda: Ocarina of Time", "seller_name": "Alice", "seller_popularity": 1000, "seller_country": "France" }, { "game_title": "Mass Effect 3", "seller_name": "Bob", "seller_popularity": 900, "seller_country": "United States" } ] ``` By breaking up games by seller, you can add more granular popularity attributes to each record. This way, you ensure that the game with the best seller ranks better by using the [`customRanking`](/doc/api-reference/api-parameters/customRanking) attribute. This structure involves data duplication, so you can use Algolia's [distinct feature](/doc/guides/managing-results/refine-results/grouping) to deduplicate records on `game_title`. For more information, see: * [Create custom ranking attributes](/doc/guides/managing-results/must-do/custom-ranking/how-to/configure-custom-ranking) * [Index long pages](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/indexing-long-documents) * [Filters and arrays of objects](/doc/guides/managing-results/refine-results/filtering/in-depth/arrays-of-objects) ### Searchable attributes This experience focuses on classified ads, so you may want to prioritize searching on game data. For example, when a user searches for "mass effect", you may want ads for game Mass Effect 3 to show up before ads by user Mass Effect Fan. For this, you can create an order of priority in your [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes), by setting `game_title` first and `seller_name` second. # Split long pages into smaller records Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/indexing-long-documents Split long pages into smaller records by heading, section, paragraph, or another logical boundary. Content-heavy documentation pages can contain thousands of words under a single URL. If you index a full page as one record, a query can match unrelated parts of the page and return a result that's too broad. To improve relevance, split long pages into smaller records. For example, create one record for each heading, section, paragraph, or another logical boundary. Smaller records let Algolia match queries to the most relevant part of a page instead of treating the whole page as one large block of content. When splitting content into records, preserve the page structure. Include attributes such as the page title, section title, URL, and content. These attributes let you distinguish page-level matches from section-level matches and send users directly to the relevant part of the page. Splitting long pages also helps you stay within Algolia's [record size limits](/doc/guides/scaling/algolia-service-limits#application-record-and-index-limits). Long documentation pages or knowledge base articles might be too large to fit into single records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). > [!NOTE] > > If you're using the Algolia Crawler and a record exceeds its size limit, > use the [`helpers.splitContentIntoRecords()`](/doc/tools/crawler/apis/configuration/actions#param-helpers-split-content-into-records) helper to split the page into smaller records. ## Example records for a long page Each record includes page-level and section-level attributes, such as `page_id`, `title`, `section`, `url`, and `content`. Page-level records can omit `section`. ```json JSON icon=braces theme={"system"} [ { "page_id": "indexing-long-documents", "title": "Split long pages into smaller records", "url": "https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/indexing-long-documents", "content": "Content-heavy documentation pages can contain thousands of words under a single URL..." }, { "page_id": "indexing-long-documents", "title": "Split long pages into smaller records", "section": "Example records for a long page", "url": "https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/indexing-long-documents#example-records-for-a-long-page", "content": "Each record includes page-level and section-level attributes..." }, { "page_id": "indexing-long-documents", "title": "Split long pages into smaller records", "section": "Deduplicate records in search results", "url": "https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/indexing-long-documents#deduplicate-records-in-search-results", "content": "A single query might return more than one record from the same page..." } ] ``` ## Deduplicate records in search results A single query might return more than one record from the same page. To show only the best matching record for each page, set [`attributeForDistinct`](/doc/api-reference/api-parameters/attributeForDistinct) to `page_id` and enable `distinct`. Algolia groups records with the same `page_id` and returns the best matching record from each group. For more information, see [Results deduplication with distinct](/doc/guides/managing-results/refine-results/grouping). # Create parent-child relationships Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/parent-child-relationships The indexing technique for maintaining parent-child relationships between records when you migrate data to Algolia's JSON format. If your source data has parent-child relationships between records and you want to maintain those relationships when you migrate to Algolia's JSON format, this guide outlines the recommended indexing process. Use parent-child relationships when you want to: * [Filter](/doc/guides/managing-results/refine-results/filtering) on children * Update parents without affecting children * Index a tree-like data structure * Model a [one-to-many](https://wikipedia.org/wiki/One-to-many_\(data_model\)) relationship. ## Model relationships in your JSON file To model a parent-child relationship in [the records of your Algolia JSON data](/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records): * **Create one record per child**, and add a unique reference to their parent as a key-value pair (`parentID` in the example) * **Don't create records for parents**. ### Dataset ```json JSON icon=braces theme={"system"} [ { "parentID":"ABC", "parentName":"Parent", "name":"Child One" }, { "parentID":"ABC", "parentName":"Parent", "name":"Child Two" } ] ``` ## Implementation notes Since this structure doesn't have parent records, you can only query children. To retrieve the most relevant child per parent (`parentID` in the preceding example), use [Algolia's distinct feature](/doc/guides/managing-results/refine-results/grouping). For more information, see: * [Index relational data](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/handling-data-relationships) * [Prepare your data for indexing](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/prepare-data-in-depth) * [Filter attributes with rules](/doc/guides/managing-results/rules/rules-overview/in-depth/implementing-rules#only-the-filtering-attributes-in-the-condition-matter) * [Nested attributes](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/creating-and-using-nested-attributes) # Reduce record size Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/how-to/reducing-object-size How to reduce the size of your records by removing unused or unnecessary data. To ensure good performance, there's a limit on record size of 10 KB to 100 KB—based on [your plan](https://www.algolia.com/pricing) (10 KB maximum for the Free plan). If you exceed this limit, Algolia returns the error message `Record is too big`. Instead of upgrading your plan, make your records smaller before [sending them to Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data). Do this by: * Only sending the necessary information by [removing unused attributes](#remove-unused-attributes) * [Splitting long documents into smaller parts](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/indexing-long-documents). > [!NOTE] > > If you're using the Algolia Crawler and the [record size exceeds the limit](/doc/tools/crawler/troubleshooting/indexing-issues#records-exceed-the-maximum-for-your-algolia-plan), > use the [`helpers.splitContentIntoRecords()`](/doc/tools/crawler/apis/configuration/actions#param-helpers-split-content-into-records) helper to split the page into smaller chunks. ## Remove unused attributes You might not need to index every attribute from your data sources. For example, if you're developing a website to display tweets on specific topics, Twitter may send you a lot of data, but only some will help when searching. For example, an excerpt of the Twitter API response might look like this: ```json JSON icon=braces theme={"system"} [ { "text": "Good morning #LaraconEU ! Swing by the Algolia booth for some vintage video games, awesome stickers, and lightning-fast search 😎", "truncated": false, "in_reply_to_user_id": null, "in_reply_to_status_id": null, "pictures": true, "pictures_urls": [ "https://pbs.twimg.com/media/Dl1UftbX4AAF1Fj.jpg", "https://pbs.twimg.com/media/Dl1Um6OXsAAa1JS.jpg" ], "source": "Twitter for Web", "in_reply_to_screen_name": null, "in_reply_to_status_id_str": null, "retweeted": true, "retweet_count": 10, "like_count": 24, "place": null, "geolocated": false } ] ``` You can reduce this record's size by: * Only indexing attributes that help build your search experience * Reusing an attribute for different purposes. For example, `retweeted=false` is the same as `retweet_count=0` * Adding attributes that rely on others. For example, if you want to use the `pictures` attribute to display a gallery (in the `pictures_url` array), only include it when the `pictures_urls` array isn't empty. ### After removing attributes Choose only the information you need for searching, displaying, and ranking. Leave everything else out. For example, after transforming the previous Twitter data, your record might look like this: ```json JSON icon=braces theme={"system"} [ { "text": "Good morning #LaraconEU ! Swing by the Algolia booth for some vintage video games, awesome stickers, and lightning-fast search 😎", "pictures_urls": [ "https://pbs.twimg.com/media/Dl1UftbX4AAF1Fj.jpg", "https://pbs.twimg.com/media/Dl1Um6OXsAAa1JS.jpg" ], "retweet_count": 10, "like_count": 24 } ] ``` Selecting only the necessary data reduces the number of attributes and record size without hurting search quality. # Data cleaning Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/data-sanitization Learn about data cleaning and handling the data that Algolia returns. **Algolia accepts all your data "as is" without changing anything.** Algolia also returns all 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.) unmodified, including HTML tags and their properties. In other words, Algolia doesn't clean your data. ## How to clean your data You can clean data: * **Before you send your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) for indexing.** Make sure your data is safe. This helps protect your app and users from possible security risks like [cross-site scripting (XSS) attacks](https://wikipedia.org/wiki/Cross-site_scripting). * **When showing results.** By default, Algolia includes all attributes in the response, even if you don't show them. To exclude attributes from the response, set them as [`unretrievableAttributes`](/doc/api-reference/api-parameters/unretrievableAttributes). * **As users type into search.** Any HTML or code they may enter in the search box exposes you to an XSS attack because Algolia sends the query back in the response. The data cleaning process is similar wherever it's applied: 1. **Identify sensitive data.** Determine which parts of your records could potentially be harmful if misused. This includes any user-generated content or records that include special characters or scripts. 2. **Determine how to clean potentially harmful data.** This could involve [preventing code injection](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) by detecting and removing scripts, and replacing special HTML characters such as `<` and `>` with their [entity name or number](https://www.w3schools.com/html/html_entities.asp). ## How Algolia helps **Algolia ignores HTML tags during search.** For example, your records might contain the HTML tag `strong`: ```json JSON icon=braces theme={"system"} { "description": "She is amazingly powerful, deeply visionary." } ``` But, because Algolia ignores HTML tags during search, searching for the word "strong" wouldn't return this record. **Algolia removes some characters from the response:** * [Control characters](https://wikipedia.org/wiki/Control_character) (U+0000 to U+001F) * [Delete character](https://wikipedia.org/wiki/Delete_character) (U+007F). For more guidance, see [Security best practices](/doc/guides/security/security-best-practices). # Prepare your index Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/prepare-data-in-depth Learn how to prepare your data for Algolia, how to fetch it from your data source, and how to structure your index. Before sending anything to Algolia, consider what information you want to make searchable. For an ecommerce store, it's products. For a music store, it might be records and artists. For a real estate company, it might be houses and locations. Be selective about what goes in the record and only gather the information that helps you build a search experience. For more information, see these blog articles: * [12 ways to improve your search index](https://www.algolia.com/blog/engineering/12-ways-to-improve-your-search-index) * [Building an open source index for all](https://www.algolia.com/blog/engineering/building-an-open-source-index-for-all) ## Fetch your data Algolia doesn't directly search your data source. Instead, you send it to Algolia's servers where it's stored in a format that's optimal for fast search. The data may come from any format: relational databases, CSV, XML, or HTML files, or Excel spreadsheets. You need to write code to fetch, transform, and [send your data to Algolia](#send-data-to-algolia) as JSON objects. You might also want to refine the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) by changing their contents or adding new information. For example, you have a custom PHP blog with a MySQL database and want to make your blog posts searchable. You can create a script that fetches the posts from your database, transforms the data, and rearranges it into records. Then, use the [PHP API client](/doc/libraries/sdk) to send the objects to Algolia and keep the data up to date when you add, update, or delete a post. ## Send data to Algolia Once you have prepared your records, send them to 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.) on Algolia's servers using one of the API clients, a no-code connector, Crawler, or dashboard. The records can then be searched. For more information, see: * [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) * [Send and update your data](/doc/guides/sending-and-managing-data/send-and-update-your-data) ## Algolia index An index is where the data used by Algolia is stored. It's the equivalent of a table in a database but optimized for search and discovery operations. Once you've sent your data to Algolia, you can decide how to structure your indices. This includes how many indices you need and how to configure each one. You can put all your records into a single index or spread them across a few smaller indices. How you structure your indices depends on how you want to search and display your records. ### Structuring indices An index is a collection of records. When you perform a search, you examine an Algolia index's records. Flattened data is best for searching. This applies at the index level too. It's a good approach to create several indices and map them to your tables, where each index represents a different kind of entity. For example, you may want to separate movies from actors and create an index for each. However, this might not serve the purposes of your search. What if you want your users to search for both movies and actors simultaneously and for them to appear in the same results? In that case, a single index works better. The main reason for this is relevance. Algolia searches one index at a time: it doesn't perform cross-index searches. Searching two indices produces two sets of results, each with a dedicated relevance configuration. Algolia doesn't merge these results, and trying to do this yourself would break the relevance (since combining Algolia's results after a search requires understanding and re-implementing Algolia's ranking algorithm). You would be undoing all the work that Algolia does for you. It doesn't mean there are no reasons for [multiple indices](#when-to-use-multiple-indices) but splitting data per entity isn't one of them. ### Handling record hierarchy Simplifying and restructuring your records doesn't mean losing hierarchy or relationships. For example, if you want your users to search for movies and see them organized by director, store this relationship in your index. You can organize your data in any way you want and keep it straightforward without losing complexity. The following record represents a director and all the movies they directed (one or several records). However, it doesn't help users search for individual movies. While you wouldn't want to repeat data in a traditional database, this is perfectly okay in your Algolia index. ```json JSON icon=braces theme={"system"} { "director": "Hayao Miyazaki", "movies": [ { "title": "Spirited Away", "score": 201.6 }, { "title": "My Neighbor Totoro", "score": 196 }, { "title": "Princess Mononoke", "score": 195.4 } ] } ``` Now take a look at that same information in three flatter, less hierarchical records: ```json JSON icon=braces theme={"system"} [ { "title": "Spirited Away", "score": 201.6, "director": "Hayao Miyazaki" }, { "title": "My Neighbor Totoro", "score": 196, "director": "Hayao Miyazaki" }, { "title": "Princess Mononoke", "score": 195.4, "director": "Hayao Miyazaki" } ] ``` This format lets users find movies more easily. Additionally, if you wanted to show a limited number of movies per director, like the most popular ones, you could use Algolia's [`distinct`](/doc/api-reference/api-parameters/distinct) feature on `director` along with custom ranking on `score`. This way, searching for "miyazaki" would only return Hayao Miyazaki's most popular movie. Flattening data adds more records to your index. If you have 10,000 directors with an average of ten movies each, this results in an index with 100,000 records. It may sound like a lot, but it's not. Besides what your [plan](https://www.algolia.com/pricing/) allows, Algolia doesn't limit the number of records, only [disk size](https://support.algolia.com/hc/en-us/articles/4406981897617-Is-there-a-size-limit-for-my-index-records). For more information, see: * [Results deduplication with distinct](/doc/guides/managing-results/refine-results/grouping) * [Handle data relationships](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/handling-data-relationships) ### When to use multiple indices For your UI, you need to determine [whether to use one or more indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/one-or-more-indices). For example, if you want your search experience to display movies and actors separately, it's better to use different indices. Use separate indices when: * Showcasing popular queries with Algolia's [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) feature. You need two indices: one for your content and one for common queries. * You want to let users switch between rankings, such as ascending or descending popularity. While you can't dynamically change the ranking of an index, you can use [replica indices](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas) with the same data and different ranking strategies. # Algolia records Source: https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record Learn about Algolia records: attributes, types, limits, formatting, nesting, and more. Algolia uses [JSON](https://www.json.org/) to model records. Your records can have attributes with the following types: | Type | Example | | - | - | | String | `"title": "Breaking Bad"` | | Integer | `"like_count": 978` | | Float | `"avg_rating": 1.23456` | | Boolean | `"featured": true` | | Object | `"lead_role": { "name": "Walter White", "portrayed_by": "Bryan Cranston" }` | | Array | `"episodes": ["Crazy Handful of Nothin'", "Gray Matter"]` | For example, this record includes different data types and the `_tags` reserved attribute: ```json JSON icon=braces theme={"system"} [ { "objectID": 42, "title": "Breaking Bad", "episodes": ["Crazy Handful of Nothin'", "Gray Matter"], "like_count": 978, "avg_rating": 1.23456, "air_date": 1356846157, "featured": true, "lead_role": { "name": "Walter White", "portrayed_by": "Bryan Cranston" }, "_tags": ["tv series", "drugs"] } ] ``` > [!NOTE] > > Although integer and boolean values are treated as strings by > [searchable attributes](/doc/guides/managing-results/must-do/searchable-attributes), > they're interpreted as numbers for [numerical facets](/doc/api-reference/api-parameters/numericFilters). > For example, > Algolia treats `like_count` as a number. ## Unique record identifiers The `objectID` attribute is a unique identifier for each record. **Set object IDs yourself**, based on *your* data. If you don't set object IDs, Algolia assigns them automatically: you can check their values by browsing 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.). When you retrieve records, object IDs are in string format, even if you set them as integers. If you want to use integers in your app, convert object IDs to integers after retrieving the records but make sure that *all* your object IDs contain integer values. Since `objectID` uniquely identifies your objects, the following applies: * **You can search for it** by declaring it as a [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes). * **You can't highlight or snippet it**. If you declare `objectID` in [`attributesToHighlight`](/doc/api-reference/api-parameters/attributesToHighlight) or [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet), Algolia ignores it. * **You can't exclude it from results**. If you declare `objectID` in [`unretrievableAttributes`](/doc/api-reference/api-parameters/unretrievableAttributes) or omit it from [`attributesToRetrieve`](/doc/api-reference/api-parameters/attributesToRetrieve), Algolia still returns it. For this reason, object IDs mustn't contain any sensitive data. * **You can use it as a facet filter, but you can't facet it**. If you declare `objectID` in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting), Algolia ignores it. [Faceting on a unique identifier](/doc/guides/managing-results/refine-results/faceting#faceting-on-object-id) makes little sense since every facet count would equal one. ### Keep object IDs stable Derive each `objectID` from the unique identifier of the record's source, such as a database primary key or a product SKU. Don't derive it from the record's content, for example by hashing the attributes you send. An object ID must identify the same item for as long as that item exists: * **Updates and deletions target an object ID.** If the ID changes when the content changes, an update creates a second record instead of replacing the first, and the original stays in the index until you delete it. * **[Click and conversion events](/doc/guides/sending-events) reference an object ID.** If the ID changes, the record's event history splits across two IDs, and features that build on that history, such as Dynamic Re-Ranking, Recommend, and Personalization, lose the earlier signals. When one source item becomes several Algolia records, for example when you split a long document into sections or flatten a product's variants, build each object ID from the source identifier plus a deterministic suffix: ```json JSON icon=braces theme={"system"} [ { "objectID": "sku-4815-section-1", "sku": "4815", "section": 1 }, { "objectID": "sku-4815-section-2", "sku": "4815", "section": 2 } ] ``` Use a suffix that stays the same across reindexing, such as a variant identifier or a section anchor. Don't use a value that shifts when the source changes, such as an array position or an incrementing counter. ### Acceptable characters for object IDs `objectID` strings can: * Contain any character. * Be of any length as long as the record fits within your [plan's size limit](https://support.algolia.com/hc/en-us/articles/4406981897617-Is-there-a-size-limit-for-my-index-records-). ## Dates Date attributes must be formatted as [Unix timestamps](https://www.unixtimestamp.com) (for example, `1435735848`). To convert dates to Unix timestamps, use an appropriate function for your programming language, for example: * Android: [`Date()`](https://developer.android.com/reference/java/util/Date#Date\(\)) * Go: [`time.Unix`](https://pkg.go.dev/time#Unix) * Java and Scala (from the `java.time` package): [`getTime()`](https://docs.oracle.com/javase/7/docs/api/java/util/Date.html#getTime%28%29) * JavaScript: [`getTime()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime) * Kotlin: [`kotlinx-datetime`](https://github.com/Kotlin/kotlinx-datetime#converting-instant-to-and-from-unix-time) * C#/.Net: [`ToUnixTimeSeconds`](https://learn.microsoft.com/en-us/dotnet/api/system.datetimeoffset.tounixtimeseconds) * PHP: [`strtotime`](https://www.php.net/manual/en/function.strtotime.php) * Python: [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime-objects) * Ruby: [`date.to_time`](https://ruby-doc.org/stdlib-3.1.0/libdoc/date/rdoc/Date.html#method-i-to_time) * Swift: [`NSDate(withTimeIntervalSince1970:)`](https://developer.apple.com/documentation/foundation/nstimeintervalsince1970) > [!NOTE] > > Depending on the language, > the output might require conversion from milliseconds to seconds. ## Reserved attribute names The following attribute names are reserved by Algolia. * `_geoloc` * `_highlightResult` * `_rankingInfo` * `_snippetResult` * `_tags` * `distinctSeqId` > [!NOTE] > > The `_collections` attribute is reserved only if you're using InstantSearch to implement a [collections page](/doc/guides/solutions/ecommerce/browse/tutorials/collections#build-a-collections-page-with-instantsearch). > If you're not using InstantSearch, `_collections` behaves like any other attribute. ### In your records In a record, you can use the `_tags` or `_geoloc` attribute names but they have an [imposed schema](/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-attributes#filter-by-tags). Other attribute names are schema-agnostic. Algolia doesn't search reserved attribute names unless you explicitly include them in [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes). ### In the search response Algolia returns `_highlightResult`, `_snippetResult`, `_rankingInfo`, and `_distinctSeqID` in the search response. They're reserved Algolia attributes tied to specific features. To avoid conflicts, don't specify these attribute names in your records. # Send and update your data Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data Choose how to send data to Algolia based on where your data is stored and how you want to send updates. Algolia doesn't query your database. It searches a copy of your data, stored as records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in 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.). After you've [structured your data](/doc/guides/sending-and-managing-data/prepare-your-data) into records, choose how to send them to Algolia. > [card] [Algolia Academy](https://academy.algolia.com/training/0199aaf1-800d-7fcf-aa9e-82636cfcf669/overview) > > Learn more about: Pushing Data to Algolia ## Choose how to send your data Start with two questions. Where is your data stored? How to send it to Algolia? ### Index from a platform If your data is in one of these platforms, use an [integration](/doc/integration): Shopify, Adobe Commerce, BigCommerce, commercetools, Salesforce B2C Commerce, or Zendesk. Each integration copies your catalog to an index and sends the changes. Shopify and BigCommerce also provide a search UI and can send click and conversion events. ### Let Algolia get your data If Algolia can get your data directly, use a [connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors). Examples are a hosted JSON or CSV file, MySQL, Elasticsearch, BigQuery, Supabase, or an Algolia index. Algolia keeps the schedule, the retries, and the transformations. Configure them in the Algolia dashboard without deploying code. ### Index a website If your content is only web pages, and you don't have an API or database export, use the [Crawler](/doc/tools/crawler/getting-started/overview). The Crawler extracts content from your pages and turns it into records. ### Send records from your app If your app controls which records to send and when, use code. If your records need an Algolia-managed [transformation](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code), use the [Push to Algolia connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push). If your records don't need a transformation, use an [API client](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api) or a [web framework integration](/doc/framework-integration). To generate the ingestion code for an existing app, use the [Algolia Wizard](/doc/tools/algolia-wizard). It writes and runs an ingestion script for the data your app already reads. ### Test with a local file To test Algolia with sample data, [upload a file in the Algolia dashboard](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-from-the-dashboard) or use the [Algolia CLI](/doc/tools/cli/get-started). For a source that changes, use one of the other methods. ## Update your data After the first import, send the changes from your source to your index. * [Incremental updates](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/incremental-updates) * [Data synchronization strategies](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies) * [Wait for indexing tasks](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/index-operations-are-asynchronous) * [Handle concurrency with versioning](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/handling-concurrency-with-versioning) # Connectors Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors Index your data without writing code. Connectors copy data from a source, such as a MySQL database or a hosted JSON file, into an Algolia index. You configure them in the [Algolia dashboard](https://dashboard.algolia.com/connectors) or with the [Ingestion API](/doc/rest-api/ingestion). ## Why use connectors A connector replaces the ingestion pipeline you would otherwise write and operate yourself: * **Algolia hosts the ingestion service.** The schedule, the retries, and the run history all run in Algolia. * **Transformations run in Algolia.** Use no-code transformations or JavaScript to change your records. Preview the result in the Algolia dashboard. No separate service between your source and your index. * **Credentials stay in Algolia.** Store the credentials for a source once, then reuse them across tasks. * **Failures are visible without extra tooling.** The [**Connector Debugger**](https://dashboard.algolia.com/connectors/debugger) shows the status of each run, and Algolia [emails you about repeated failures](#task-status-and-alerts). * **Anyone on your team can make changes.** The schedule, the indexing strategy, and the transformations are dashboard fields, so a colleague can adjust them without a deployment. ## Available connectors * [**Algolia Index**](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/post-indexing-transformations): reindex records that are already in an Algolia index. * [**BigCommerce**](/doc/integration/bigcommerce/get-started/installation): index a BigCommerce store. * [**commercetools**](/doc/integration/commercetools/get-started/installation): index a commercetools product catalog. * [**CSV**](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/csv): index a hosted CSV file. * [**Elasticsearch**](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/elasticsearch): index an Elasticsearch cluster. * [**Google BigQuery**](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/bigquery): index a BigQuery table. * [**JSON**](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/json): index a hosted JSON file. * [**MySQL**](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/mysql): index a MySQL database. * [**Push to Algolia**](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push): send records from your app through an Algolia-managed transformation. * [**Supabase**](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/supabase): index Supabase tables or views. * [**Zendesk Knowledge Base**](/doc/integration/zendesk/indexing): index Zendesk help center articles. ## How connectors work Connectors have these parts: * **Sources** define where your data comes from, such as a JSON file on your server, or a MySQL database. * **Destinations** define where your data goes, such as an Algolia index or the Insights API. * **Tasks** define when and how a sync runs, for example, on a regular schedule. * **[Transformations](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code)** change your source data before it reaches the destination. They're optional, and you add them to a destination. ## Authentication Connectors authenticate with your sources and destinations. Each source supports different authentication methods, such as basic authentication or a Google service account. Algolia indices require an API key with the following [ACL](/doc/guides/security/api-keys#access-control-list-acl): * `addObject` * `deleteIndex` * `editSettings` Manage your connector credentials in the [**Authentications**](https://dashboard.algolia.com/connectors/authentications) tab of the Algolia dashboard. ## Allow Algolia IP addresses If you restrict access to your **source**, allow these Algolia IP addresses: * `104.196.103.173` * `35.234.69.129` ## Task status and alerts If a task fails more than five times in a row, Algolia deactivates it and sets its status to `critical` in the **Connector Debugger**. Algolia also sends an email to every address associated with your Algolia application. ## See also * [Send and update your data](/doc/guides/sending-and-managing-data/send-and-update-your-data) * [Connectors limits](/doc/guides/scaling/algolia-service-limits#connectors-limits) * [Transformation and fetch limits](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits) # Google BigQuery connector Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/bigquery Use the Google BigQuery connector to index data from a BigQuery table. Use the Google BigQuery connector to index records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) from a BigQuery table without writing an indexing script. In this quickstart, you import a sample product dataset into BigQuery, create a source for the BigQuery table, use an Algolia 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.) destination, and create an on-demand synchronization task. ## 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. * **A Google Cloud account and project**. Select or create the project where you want to store the sample BigQuery table. * **The BigQuery API enabled for your Google Cloud project**. If the BigQuery API isn't enabled, enable it from the [BigQuery API page](https://console.cloud.google.com/apis/library/bigquery.googleapis.com). * **Permission to create and load BigQuery datasets and tables, create a Google service account and key, and grant IAM roles**. If your organization restricts service account keys or IAM changes, ask your Google Cloud administrator to complete those steps. ## Set up the Google BigQuery connector Create a source, destination, transformation, and synchronization task for the sample BigQuery table. The source identifies the BigQuery table to read. The destination identifies the Algolia index. The transformation enriches the records. The task determines when the connector runs. **1. Select your project in Cloud Hub** Open [Cloud Hub](https://console.cloud.google.com/cloud-hub/home) and select the Google Cloud project where you want to create the BigQuery dataset. Make a note of the project's **ID**. You use this value when configuring the [connector source](#source). Select the project itself, not an App Hub app or an app-enabled folder. You don't need to configure an App Hub app for this quickstart. **2. Set the quickstart variables** In the Google Cloud console's top toolbar, select the **Activate Cloud Shell** (`>_`) icon. Set the variables: ```sh Cloud Shell icon=square-terminal theme={"system"} PROJECT_ID="$GOOGLE_CLOUD_PROJECT" DATASET_ID="algolia_connector_demo" TABLE_ID="apparel" LOCATION="US" SERVICE_ACCOUNT_ID="algolia-bigquery-reader" SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_ID}@${PROJECT_ID}.iam.gserviceaccount.com" ``` > [!NOTE] > > Paste these commands exactly as shown. > Cloud Shell sets `GOOGLE_CLOUD_PROJECT` to the project you selected in Cloud Hub. > Change `LOCATION` to use another [BigQuery location](https://cloud.google.com/bigquery/docs/locations). **3. Create the BigQuery dataset** Create a dataset for the sample table: ```sh Cloud Shell icon=square-terminal theme={"system"} bq --location="$LOCATION" mk \ --dataset \ "$PROJECT_ID:$DATASET_ID" ``` **4. Download the sample data** Download the newline-delimited JSON sample data: ```sh Cloud Shell icon=square-terminal theme={"system"} curl --fail --silent --show-error --location \ --output apparel.ndjson \ 'https://raw.githubusercontent.com/algolia/quickstarts/main/sample-data/apparel.ndjson' ``` **5. Load the sample data into BigQuery** > [!WARNING] > > The `--replace` option overwrites any existing table named `apparel` > in the selected dataset. > Change `TABLE_ID` if you need to preserve an existing table. ```sh Cloud Shell icon=square-terminal theme={"system"} bq --location="$LOCATION" load \ --replace \ --autodetect \ --source_format=NEWLINE_DELIMITED_JSON \ "$PROJECT_ID:$DATASET_ID.$TABLE_ID" \ apparel.ndjson ``` For more information, see [Loading JSON data from a local file](https://cloud.google.com/bigquery/docs/batch-loading-data#loading_json_data_from_a_local_file). **6. Verify the BigQuery import** Count the imported rows and unique object IDs: ```sh Cloud Shell icon=square-terminal theme={"system"} bq --location="$LOCATION" query \ --use_legacy_sql=false \ "SELECT COUNT(*) AS records, COUNT(DISTINCT objectID) AS unique_object_ids FROM \`${PROJECT_ID}.${DATASET_ID}.${TABLE_ID}\`" ``` Confirm that `records` and `unique_object_ids` have the same value. **7. Create the connector service account** Create a dedicated service account for the Algolia connector: ```sh Cloud Shell icon=square-terminal theme={"system"} gcloud iam service-accounts create "$SERVICE_ACCOUNT_ID" \ --display-name="Algolia BigQuery reader" \ --project="$PROJECT_ID" ``` **8. Grant the BigQuery permissions** Grant the service account permission to run BigQuery jobs in the project: ```sh Cloud Shell icon=square-terminal theme={"system"} gcloud projects add-iam-policy-binding "$PROJECT_ID" \ --member="serviceAccount:$SERVICE_ACCOUNT_EMAIL" \ --role="roles/bigquery.jobUser" ``` Grant the service account read access to the quickstart dataset: ```sh Cloud Shell icon=square-terminal theme={"system"} bq --location="$LOCATION" query \ --use_legacy_sql=false \ "GRANT \`roles/bigquery.dataViewer\` ON SCHEMA \`${PROJECT_ID}.${DATASET_ID}\` TO \"serviceAccount:${SERVICE_ACCOUNT_EMAIL}\"" ``` **9. Create and download the service account key** Create a JSON key: ```sh Cloud Shell icon=square-terminal theme={"system"} gcloud iam service-accounts keys create \ algolia-bigquery-key.json \ --iam-account="$SERVICE_ACCOUNT_EMAIL" \ --project="$PROJECT_ID" ``` Download the key from Cloud Shell: ```sh Cloud Shell icon=square-terminal theme={"system"} cloudshell download algolia-bigquery-key.json ``` Your browser downloads `algolia-bigquery-key.json`. You upload this file when you configure the connector source. If your organization prevents service account key creation, ask your Google Cloud administrator whether they can create an approved key for this dedicated service account. **10. Choose the Google BigQuery connector** Go to the Algolia dashboard and select your Algolia application. Open the [**Connectors**](https://dashboard.algolia.com/connectors) page. Find **Google BigQuery**, then select **Connect**. **11. Configure the Google service account authentication** Under **Google Service Account**, select **Search or create an authentication**, then **Create a new Google Service Account authentication**. Upload `algolia-bigquery-key.json` as the **Service Account File**. In **Name**, enter `BigQuery quickstart service account`, then select **Create authentication**. **12. Configure the BigQuery source** Under **Select your BigQuery table**, enter or select the following BigQuery details: | Field | Value | | - | - | | **Project ID** | Your project's `ID` value in Cloud Hub | | **Dataset ID** | `algolia_connector_demo` | | **Table name** | `apparel` | | **Unique property identifier** | `objectID` | Leave the custom SQL statement empty. Without a custom SQL statement, the connector reads all rows and columns from the selected table. In **Connector name**, enter `Quickstart products BigQuery source`, then select **Create source**. **13. Delete the service account file** After you have created the source, delete the key file from Cloud Shell: ```sh Cloud Shell icon=square-terminal theme={"system"} rm -f algolia-bigquery-key.json ``` Delete the downloaded copy from your computer after uploading it to Algolia. Deleting these files doesn't revoke the key used by the connector. **14. Transform the records** This transformation adds a `price_range` attribute to each record. After the connector indexes your records, you can display `price_range` or configure it as a facet. Select **Transform using the code editor** and replace the placeholder **Transformation code** with this function: ```js JavaScript icon=code expandable theme={"system"} async function transform(record, helper) { const price = Number(record.price); if (!Number.isFinite(price)) { return record; } if (price < 25) { record.price_range = "Under $25"; } else if (price < 50) { record.price_range = "$25 to $49"; } else if (price < 100) { record.price_range = "$50 to $99"; } else { record.price_range = "$100 and up"; } return record; } ``` Select **Save**. **15. Choose the destination index** Under **Configure your destination**, select **Create a new destination**. Under **Search**, enter `quickstart-products` as the index name. > [!WARNING] > > If you enter the name of an existing index, > the connector overwrites the records and settings in that index. **16. Create the destination** Under **Index credentials**, select **Create one for me**. To use an existing API key, choose one with the `addObject`, `deleteIndex`, and `editSettings` [ACLs](/doc/guides/security/api-keys#access-control-list-acl). In **Name**, enter `BigQuery quickstart products destination`, then select **Create destination**. **17. Create and run an on-demand synchronization task** Under **Configure your task**, select **On demand**. Select **Full reindexing**. Select **Create task**, then **Run**. Wait for the task to finish. **18. Verify the indexed records** Open the [`quickstart-products` index in the Algolia dashboard](https://dashboard.algolia.com/explorer/browse/quickstart-products). The index contains the imported product records, with attributes such as `title`, `description`, `product_type`, `price`, `price_range`, and `showcase_image`. > [!NOTE] > > You can use this index as the data source for > [Build your first search experience](/doc/guides/get-started/quickstart). > > Before you build the UI, > [configure `product_type` as an attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) > in the `quickstart-products` index. ## Index your own BigQuery data Authenticate the connector with a dedicated Google service account. Don't reuse a service account that can update or delete your production BigQuery data. ### Authentication Grant the service account these roles: | Resource | Role | | - | - | | Google Cloud project used to run queries | BigQuery Job User (`roles/bigquery.jobUser`) | | BigQuery dataset you want to index | BigQuery Data Viewer (`roles/bigquery.dataViewer`) | If your organization uses a custom IAM role instead, include these permissions: ```txt BigQuery permissions theme={"system"} bigquery.datasets.get bigquery.datasets.getIamPolicy bigquery.jobs.create bigquery.models.export bigquery.models.getData bigquery.models.getMetadata bigquery.models.list bigquery.routines.get bigquery.routines.list bigquery.tables.createSnapshot bigquery.tables.export bigquery.tables.get bigquery.tables.getData bigquery.tables.getIamPolicy bigquery.tables.list resourcemanager.projects.get ``` ### Cloud Storage data If the connector service account reads the table's Cloud Storage objects directly, grant it the Storage Object Viewer role (`roles/storage.objectViewer`) on each source bucket. If your organization uses a custom IAM role instead, include these permissions: ```txt Cloud Storage permissions theme={"system"} storage.folders.get storage.objects.get storage.objects.list ``` ### JSON key Create a Google service account JSON key and upload the key file when you [configure the BigQuery source](#source). > [!WARNING] > > A service account JSON key is a long-lived credential. > Store it securely, don't add it to source control, > and delete local copies after uploading it to Algolia. ### Customize the data you index with SQL By default, the connector imports all rows and columns from the selected table. In the BigQuery source's **Custom SQL statement** field, enter a GoogleSQL statement to filter rows, combine columns, or calculate attributes. Use `%s` as the placeholder for the selected table. For example: ```sql SQL icon="database" theme={"system"} SELECT * FROM %s WHERE status = 'Available' ``` The query result must include the column selected as the source's unique identifier. Its value must be present, unique, and stable for every returned row. An existing `objectID` column is suitable. If your table uses a composite key, return its components as a single column. For example: ```sql SQL icon="database" theme={"system"} SELECT TO_JSON_STRING( STRUCT( account_id AS account_id, product_id AS product_id ) ) AS objectID, title, description, price FROM %s ``` > [!NOTE] > > You can instead assign `objectID` in an Algolia transformation if the BigQuery source provides a stable unique identifier. ### Transformations Use transformations to add computed attributes or change values before the connector indexes your records. To use a transformed attribute for search, faceting, or ranking, update the relevant [index settings](/doc/api-reference/settings-api-parameters), such as `searchableAttributes`, `attributesForFaceting`, or `customRanking`. > [!NOTE] > > Test transformations with repeated task runs. > Transformations that read from and write to the same attribute, > or extract a value and then delete the source attribute, > can produce different results when the task runs again. ### Synchronization schedule After you create a task, you can [edit it in the Algolia dashboard](https://dashboard.algolia.com/connectors/tasks) to choose when the connector runs: * **On demand**. Run the connector manually. * **Scheduled**. Select a predefined schedule or enter a custom [cron expression](https://crontab.guru/). ### Indexing strategy You can choose how the connector updates your index. For information about full reindexing, full record updates, and partial record updates, see [Data synchronization strategies](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies). > [!WARNING] > > To stay within your BigQuery quota, > don't schedule a Google BigQuery connector task to run more than once per day. ## Limitations This connector is subject to the following limitations: * [Connectors limits](/doc/guides/scaling/algolia-service-limits#connectors-limits) * [Transformation limits](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits) ## See also * [Connector overview](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors) * [JSON connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/json) * [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) * [Transform your data with code](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code) # CSV connector Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/csv Use the CSV connector to index data from a hosted CSV file. Use the CSV connector to index records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) from a hosted CSV file without writing an indexing script. In this quickstart, you create a source for a public product dataset, use an Algolia 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.) destination, and an on-demand synchronization task. ## Before you begin If you don't have an Algolia account, [create one](https://www.algolia.com/users/sign_up) for free. ## Index a sample dataset with the CSV connector Create a source, destination, transformation, and synchronization task for the sample product dataset. * The source tells the connector where to fetch the CSV file. * The destination tells the connector which Algolia index to write to. * The transformation changes records before the connector indexes them. * The task controls when the connector runs. **1. Choose the CSV connector** Go to the Algolia dashboard and select your Algolia application. Open the [**Connectors**](https://dashboard.algolia.com/connectors) page. Find **CSV**, then select **Connect**. **2. Configure the CSV source** Under **Configure your data source**, select **Create a new source**. Under **Is your data source protected?**, select **None**. In **URL**, enter: ```txt theme={"system"} https://raw.githubusercontent.com/algolia/quickstarts/main/sample-data/apparel.csv ``` For **HTTP Method**, select **GET**. **3. Configure column types** In **Unique column identifier**, enter `objectID`. Under **Column type mapping**, add these columns and select the corresponding type: | Column name | Type | | - | - | | `price` | **integer** | | `taxable` | **boolean** | | `units_sold` | **integer** | | `weight` | **float** | **4. Create the CSV source** In **Connector name**, enter `Quickstart products CSV source`, then select **Create source**. **5. Transform the records** This transformation adds a `price_range` attribute to each record. After the connector indexes your records, you can display `price_range` or configure it as a facet. Select **Transform using the code editor** and replace the placeholder **Transformation code** with this function: ```js JavaScript icon=code expandable theme={"system"} async function transform(record, helper) { const price = Number(record.price); if (!Number.isFinite(price)) { return record; } if (price < 25) { record.price_range = "Under $25"; } else if (price < 50) { record.price_range = "$25 to $49"; } else if (price < 100) { record.price_range = "$50 to $99"; } else { record.price_range = "$100 and up"; } return record; } ``` Select **Save**. **6. Choose the destination index** Under **Configure your destination**, select **Create a new destination**. Under **Search**, enter `quickstart-products` as the index name. > [!WARNING] > > If you enter the name of an existing index, the connector overwrites the records and settings in that index. **7. Create the destination** Under **Index credentials**, select **Create one for me**. To use an existing API key, choose one with the `addObject`, `deleteIndex`, and `editSettings` [ACLs](/doc/guides/security/api-keys#access-control-list-acl). In **Name**, enter `CSV quickstart products destination`, then select **Create destination**. **8. Create an on-demand synchronization task** Under **Configure your task**, select **On demand** and **Full reindexing**. Select **Create task**, then **Run** and wait for the task to finish. **9. Verify the indexed records** When the task completes, open the [`quickstart-products` index in the Algolia dashboard](https://dashboard.algolia.com/explorer/browse/quickstart-products). The index contains product records with attributes such as `title`, `description`, `product_type`, `price`, `price_range`, and `showcase_image`. > [!NOTE] > > You can use this index as the data source for > [Build your first search experience](/doc/guides/get-started/quickstart). > > Before you build the UI, > [configure `product_type` as an attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) > in the `quickstart-products` index. ## Index your own CSV data To index your own records, update the connector source URL to point to a CSV file you host. After you update the source, [run the connector task](https://dashboard.algolia.com/connectors/tasks) again to index your records. ### Source file format The first row of your CSV file must contain the column names. Each remaining row represents one record. For example: ```csv CSV icon=braces theme={"system"} id,title,description,product_type,price product-1,Product name,"A short, searchable description",Product type,49.99 ``` Each record needs a unique `objectID` attribute. In the source configuration, use **Unique property identifier** to set this attribute. Choose a column whose values are unique for every row (`id` in this example). Enclose fields that contain commas, double quotation marks, or line breaks in double quotation marks. For more information, see [Common Format and MIME Type for Comma-Separated Values (CSV) Files ](https://www.rfc-editor.org/rfc/rfc4180.html#section-2). ### Source file location Update the connector source URL to point to your hosted file. The source URL can use `http`, `https`, `ftp`, `ftps`, or `sftp`. For `http` or `https` sources, choose the request method your server expects: * **GET** if your server returns the file from a standard request. * **POST** if your server requires a POST request. ### Source authentication Select the authentication method for your file: * **None** if the file is public. * **Basic Auth** if the file requires a username and password. ### Transformations Use transformations to add computed attributes or change values before the connector indexes your records. To use a transformed attribute for search, faceting, or ranking, update the relevant [index settings](/doc/api-reference/settings-api-parameters), such as `searchableAttributes`, `attributesForFaceting`, or `customRanking`. > [!NOTE] > > Test transformations with repeated task runs. > Transformations that read from and write to the same attribute, > or extract a value and then delete the source attribute, > can produce different results when the task runs again. ### Synchronization schedule After you create a task, you can [edit it in the Algolia dashboard](https://dashboard.algolia.com/connectors/tasks) to choose when the connector runs: * **On demand**. Run the connector manually. * **Scheduled**. Select a predefined schedule or enter a custom [cron expression](https://crontab.guru/). ### Indexing strategy You can choose how the connector updates your index. For information about full reindexing, full record updates, and partial record updates, see [Data synchronization strategies](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies). ## Limitations This connector is subject to the following limitations: * [Connectors limits](/doc/guides/scaling/algolia-service-limits#connectors-limits) * [Transformation limits](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits) ## See also * [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) * [Transform your data with code](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code) # Elasticsearch connector Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/elasticsearch Use the Elasticsearch connector to sync data from an Elasticsearch index to an Algolia index. Use the Elasticsearch connector to index records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) from Elasticsearch 7 or later without writing an indexing script. In this quickstart, you connect an existing Elasticsearch index, use an Algolia 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.) destination, transform the records, and create an on-demand synchronization task. ## 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. * **An Elasticsearch 7 or later deployment containing the sample data in an index named `apparel`**. Download the [Algolia apparel sample data](https://raw.githubusercontent.com/algolia/quickstarts/main/sample-data/apparel.ndjson) and add it to the index: * For Kibana, follow Elastic's [file upload instructions](https://www.elastic.co/docs/manage-data/ingest/upload-data-files). * For other ingestion methods, see [Ingest: Bring your data to Elastic](https://www.elastic.co/docs/manage-data/ingest). ## Set up the Elasticsearch connector Create a source, destination, transformation, and synchronization task for the sample Elasticsearch index. The source identifies the Elasticsearch deployment. The destination identifies the Algolia index. The transformation enriches the records. The task identifies the source Elasticsearch index and determines when the connector runs. **1. Choose the Elasticsearch connector** Go to the Algolia dashboard and select your Algolia application. Open the [**Connectors**](https://dashboard.algolia.com/connectors) page. Find **Elasticsearch**, then select **Connect**. **2. Configure the Elasticsearch source** Under **Configure your data source**, select **Create a new source**. Enter the following details: | Field | Value | | - | - | | **Server Endpoint** | The Elasticsearch endpoint URL | | **Authentication method** | **Api Key/Secret** | | **API Key ID** | The ID for an Elasticsearch API key that can access the `apparel` index | | **API Key Secret** | The secret for that API key | In **Connector name**, enter `Quickstart products Elasticsearch source`, then select **Create source**. **3. Transform the records** This transformation adds a `price_range` attribute to each record. After the connector indexes your records, you can display `price_range` or configure it as a facet. Select **Transform using the code editor** and replace the placeholder **Transformation code** with this function: ```js JavaScript icon=code expandable theme={"system"} async function transform(record, helper) { const price = Number(record.price); if (!Number.isFinite(price)) { return record; } if (price < 25) { record.price_range = "Under $25"; } else if (price < 50) { record.price_range = "$25 to $49"; } else if (price < 100) { record.price_range = "$50 to $99"; } else { record.price_range = "$100 and up"; } return record; } ``` Select **Save**. **4. Choose the destination Algolia index** Under **Configure your destination**, select **Create a new destination**. Under **Search**, enter `quickstart-products` as the index name. > [!WARNING] > > If you enter the name of an existing index, > the connector overwrites the records and settings in that index. **5. Create the destination** Under **Index credentials**, select **Create one for me**. To use an existing API key, choose one with the `addObject`, `deleteIndex`, and `editSettings` [ACL permissions](/doc/guides/security/api-keys#access-control-list-acl). In **Name**, enter `Elasticsearch quickstart products destination`, then select **Create destination**. **6. Create and run an on-demand synchronization task** Under **Configure your task**, select **On demand**. Select **Full reindexing**. Under **Which table do you want to sync?**, select `apparel` as the source Elasticsearch index and select the `objectID` field as the **ObjectID** unique identifier. Select **Create task**, then **Run** and wait for the task to finish. **7. Verify the indexed records** Open the [`quickstart-products` index in the Algolia dashboard](https://dashboard.algolia.com/explorer/browse/quickstart-products). The index contains the imported product records, with attributes such as `title`, `description`, `product_type`, `price`, `price_range`, and `showcase_image`. > [!NOTE] > > You can use this index as the data source for > [Build your first search experience](/doc/guides/get-started/quickstart). > > Before you build the UI, > [configure `product_type` as an attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) > in the `quickstart-products` index. ## Index your own Elasticsearch data To index your own data, configure the connector with your Elasticsearch server, choose an authentication method and destination, and create a synchronization task for the Elasticsearch indices you want to import. ### Configure the connector for your data Use a populated Elasticsearch index that the Algolia connector can reach. If you restrict inbound access to your Elasticsearch server, allow the [Algolia connector IP addresses](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors#allow-algolia-ip-addresses). Follow the quickstart steps to create the [source](#source), [destination](#destination), and [task](#task). Replace the sample endpoint, credentials, source index, and destination index with your own values. Review or remove the sample transformation so that it matches your record structure. When you configure the task, select the field to use as the **ObjectID**. The field must be present in every source document, and its value must be unique and stable. ### Elasticsearch authentication To create an API key and secret, use [Kibana](https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys) or the [Elasticsearch API](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-create-api-key). Elasticsearch API keys include an ID and secret. The Algolia connector requires these values separately. If Elasticsearch gives you only an encoded credential, decode it locally to recover the ID and secret. For more information, see [Elasticsearch API keys](https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys). When you [configure the Elasticsearch source](#source), choose one of these authentication methods: * **None**: the connector reads your data without authentication. * **Api key/Secret**: enter the decoded API key ID and API key secret. * **Username/Password**: enter the username and password the connector should use. For **Api key/Secret** or **Username/Password** authentication, grant read-only access to the Elasticsearch indices you want to import. Use the following privileges: * **Cluster**: `monitor` * **Indices**: `read`, `view_index_metadata`, and `monitor` Scope the index privileges to the relevant index names or patterns. For **Username/Password** authentication, choose a user whose roles grant the same access. > [!NOTE] > > The connector reads from Elasticsearch and writes to the Algolia destination. > It doesn't need permission to create Elasticsearch indices or documents. ### Transformations Use transformations to add computed attributes or change values before the connector indexes your records. To use a transformed attribute for search, faceting, or ranking, update the relevant [index settings](/doc/api-reference/settings-api-parameters), such as `searchableAttributes`, `attributesForFaceting`, or `customRanking`. > [!NOTE] > > Test transformations with repeated task runs. > Transformations that read from and write to the same attribute, > or extract a value and then delete the source attribute, > can produce different results when the task runs again. ### Synchronization schedule After you create a task, you can [edit it in the Algolia dashboard](https://dashboard.algolia.com/connectors/tasks) to choose when the connector runs: * **On demand**. Run the connector manually. * **Scheduled**. Select a predefined schedule or enter a custom [cron expression](https://crontab.guru/). ### Indexing strategy You can choose how the connector updates your index. For information about full reindexing, full record updates, and partial record updates, see [Data synchronization strategies](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies). > [!WARNING] > > Schedule full reindexing tasks only as often as necessary. > Each run reads all documents from the selected Elasticsearch indices > and rewrites the destination, > which increases the workload on Elasticsearch and the number of Algolia indexing operations. ## Limitations This connector is subject to the following limitations: * [Connectors limits](/doc/guides/scaling/algolia-service-limits#connectors-limits) * [Transformation limits](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits) ## See also * [Connector overview](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors) * [JSON connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/json) * [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) * [Transform your data with code](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code) # JSON connector Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/json Index your data from a hosted JSON file. Use the JSON connector to index records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) from a hosted JSON file without writing an indexing script. In this quickstart, you create a source for a public product dataset, an Algolia 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.) destination, and an on-demand synchronization task. ## Before you begin If you don't have an Algolia account, [create one](https://www.algolia.com/users/sign_up) for free. ## Set up the JSON connector Create a source, destination, transformation, and synchronization task for the sample product dataset. * The source tells the connector where to fetch the JSON file. * The destination tells the connector which Algolia index to write to. * The transformation changes records before the connector indexes them. * The task controls when the connector runs. **1. Choose the JSON connector** Connect the JSON source for the hosted sample product dataset. 1. Go to the Algolia dashboard and select your Algolia application. 2. Go to the [**Connectors**](https://dashboard.algolia.com/connectors) page in the Algolia dashboard. 3. Select the JSON connector and click **Connect**. **2. Configure the source URL** 1. Select **None** as your authentication method under **Is your data source protected?**. 2. In **URL**, enter `https://dashboard.algolia.com/api/1/sample_datasets?type=apparel`. 3. Select **GET** as the **HTTP Method**. **3. Create the JSON source** 1. In **Unique property identifier**, enter `objectID`. 2. In **Connector name**, enter `Quickstart products JSON source`. 3. Select **Create source**. **4. Transform the records** This transformation adds a `price_range` attribute to each record. After the connector indexes your records, you can display `price_range` or configure it as a facet. Select **Transform using the code editor** and replace the placeholder **Transformation code** with this function: ```js JavaScript icon=code expandable theme={"system"} async function transform(record, helper) { const price = Number(record.price); if (!Number.isFinite(price)) { return record; } if (price < 25) { record.price_range = "Under $25"; } else if (price < 50) { record.price_range = "$25 to $49"; } else if (price < 100) { record.price_range = "$50 to $99"; } else { record.price_range = "$100 and up"; } return record; } ``` Select **Save**. **5. Create the destination index** Choose the Algolia index where the connector stores the product records. 1. Under **Connect your data to Algolia Search**, enter `quickstart-products`. > [!WARNING] > > If you enter the name of an existing index, the connector overwrites the records and settings in that index. 2. Under **Index credentials**, select **Create one for me**. To use an existing key, choose one with the `addObject`, `deleteIndex`, and `editSettings` [ACLs](/doc/guides/security/api-keys#access-control-list-acl). 3. In **Name**, enter `Quickstart products destination`. 4. Select **Create destination**. **6. Create an on-demand synchronization task** Select **On demand** and **Full reindexing**. **7. Run the task** Select **Create task**, then **Run** and wait for the task to finish. **8. Verify the indexed records** When the task completes, open the [`quickstart-products` index in the Algolia dashboard](https://dashboard.algolia.com/explorer/browse/quickstart-products). The index contains product records with attributes such as `title`, `description`, `product_type`, `price`, `price_range`, and `showcase_image`. > [!NOTE] > > You can use this index as the data source for > [Build your first search experience](/doc/guides/get-started/quickstart). > > Before you build the UI, > [configure `product_type` as an attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) > in the `quickstart-products` index. ## Index your own JSON data To index your own records, update the connector source URL to point to a JSON file you host. After you update the source, [run the connector task](https://dashboard.algolia.com/connectors/tasks) again to index your records. ### Source file format Your JSON file must contain an array of JSON objects. For example: ```json JSON icon=braces theme={"system"} [ { "id": "product-1", "title": "Product name", "description": "Product description", "product_type": "Product type", "price": 49.99 } ] ``` Each object must include a property with a value that's unique across all records (`id` in this example). In the source configuration, set **Unique property identifier** to that property so the connector can use it as the Algolia `objectID`. ### Source file location Update the connector source URL to point to your hosted file. The source URL can use `http`, `https`, `ftp`, `ftps`, or `sftp`. For `http` or `https` sources, choose the request method your server expects: * **GET** if your server returns the file from a standard request. * **POST** if your server requires a POST request. ### Source authentication Select the authentication method for your file: * **None** if the file is public. * **Basic Auth** if the file requires a username and password. ### Transformations Use transformations to add computed attributes or change values before the connector indexes your records. To use a transformed attribute for search, faceting, or ranking, update the relevant [index settings](/doc/api-reference/settings-api-parameters), such as `searchableAttributes`, `attributesForFaceting`, or `customRanking`. > [!NOTE] > > Test transformations with repeated task runs. > Transformations that read from and write to the same attribute, > or extract a value and then delete the source attribute, > can produce different results when the task runs again. ### Synchronization schedule After you create a task, you can [edit it in the Algolia dashboard](https://dashboard.algolia.com/connectors/tasks) to choose when the connector runs: * **On demand**. Run the connector manually. * **Scheduled**. Select a predefined schedule or enter a custom [cron expression](https://crontab.guru/). ### Indexing strategy You can choose how the connector updates your index. For information about full reindexing, full record updates, and partial record updates, see [Data synchronization strategies](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies). ## Limitations This connector is subject to the following limitations: * [Connectors limits](/doc/guides/scaling/algolia-service-limits#connectors-limits) * [Transformation limits](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits) ## See also * [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) * [Transform your data with code](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code) # MySQL connector Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/mysql Use the MySQL connector to index data from a MySQL database. Use the MySQL connector to index records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) from a MySQL table without writing an indexing script. In this quickstart, you import a sample product dataset into MySQL, create a source for this database table, use an Algolia 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.) destination, and create an on-demand synchronization task. ## 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. * **A MySQL host that Algolia can reach**. Use a hosted MySQL server or configure an [SSH tunnel](https://dev.mysql.com/doc/mysql-shell/8.0/en/mysql-shell-connection-ssh.html) to your server. A server available only at `127.0.0.1` or `localhost` isn't reachable from the hosted Algolia connector. * **Your MySQL host's port number**. The default is `3306` but your provider may use a different port. * **A MySQL database**. Create or choose a database for the sample data and note its name. For more information, see [Creating and selecting a database](https://dev.mysql.com/doc/refman/8.4/en/creating-database.html). * **A MySQL username and password**. The user must have `CREATE`, `DROP`, `INSERT`, and `SELECT` access to the target database. For more information, see [GRANT Statement](https://dev.mysql.com/doc/refman/8.4/en/grant.html). ## Set up the MySQL connector Create a source, destination, transformation, and synchronization task for the sample product dataset. * The source tells the connector which MySQL database to read. * The destination tells the connector which Algolia index to write to. * The transformation converts MySQL JSON values and enriches the records. * The task controls which table to index and when the connector runs. **1. Download the sample SQL file** ```sh Command line icon=square-terminal theme={"system"} curl --fail --silent --show-error --location \ --output apparel.sql \ https://raw.githubusercontent.com/algolia/quickstarts/main/sample-data/apparel.sql ``` **2. Import the sample data into MySQL** > [!WARNING] > > This script replaces any existing `apparel` table in your database. Run the script: ```sh Command line icon=square-terminal theme={"system"} mysql --host=MYSQL_HOST \ --port=MYSQL_PORT \ --user=MYSQL_USER \ --password \ --database=MYSQL_DATABASE \ < apparel.sql ``` Replace `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_USER` and `MYSQL_DATABASE` with your MySQL connection details. When prompted, enter the password for `MYSQL_USER`. For more information, see [Executing SQL statements from a text file](https://dev.mysql.com/doc/refman/8.4/en/mysql-batch-commands.html). **3. Verify the import** Run: ```sh Command line icon=square-terminal theme={"system"} mysql --host=MYSQL_HOST \ --port=MYSQL_PORT \ --user=MYSQL_USER \ --password \ --database=MYSQL_DATABASE \ --execute="SELECT COUNT(*) AS records FROM apparel;" ``` When prompted, enter the password for `MYSQL_USER`. The SQL query should return `1000` records. **4. Choose the MySQL connector** Go to the Algolia dashboard and select your Algolia application. Open the [**Connectors**](https://dashboard.algolia.com/connectors) page. Find **MySQL**, then select **Connect**. **5. Configure the MySQL source** Under **Configure your data source**, select **Create a new source**. Enter the connection details for the database table that contains the sample data: | Field | Value | | - | - | | **Host** | `MYSQL_HOST` | | **Port** | `MYSQL_PORT`, usually `3306` | | **Database** | `MYSQL_DATABASE` | | **User** | `MYSQL_USER` | | **Password** | `MYSQL_USER_PASSWORD` | Under **SSH Tunnel Method**, select **No Tunnel** if Algolia can connect directly to your database. Otherwise, configure the authentication for your SSH tunnel. **6. Create the MySQL source** In **Connector name**, enter `Quickstart products MySQL source`, then select **Create source**. **7. Transform the records** This transformation adds a `price_range` attribute to each record. After the connector indexes your records, you can display `price_range` or configure it as a facet. Select **Transform using the code editor** and replace the placeholder **Transformation code** with this function: ```js JavaScript icon=code expandable theme={"system"} async function transform(record, helper) { const price = Number(record.price); if (!Number.isFinite(price)) { return record; } if (price < 25) { record.price_range = "Under $25"; } else if (price < 50) { record.price_range = "$25 to $49"; } else if (price < 100) { record.price_range = "$50 to $99"; } else { record.price_range = "$100 and up"; } return record; } ``` Select **Save**. **8. Choose the destination index** Under **Configure your destination**, select **Create a new destination**. Under **Search**, enter `quickstart-products` as the index name. > [!WARNING] > > If you enter the name of an existing index, the connector overwrites the records and settings in that index. **9. Create the destination** Under **Index credentials**, select **Create one for me**. To use an existing API key, choose one with the `addObject`, `deleteIndex`, and `editSettings` [ACLs](/doc/guides/security/api-keys#access-control-list-acl). In **Name**, enter `MySQL quickstart products destination`, then select **Create destination**. **10. Create and run an on-demand synchronization task** Under **Configure your task**, select **On demand**. Select the `apparel` table and keep all its columns selected. Confirm that the connector maps the table's `objectID` primary key to each record's Algolia `objectID`. Select **Create task**, then **Run** and wait for the task to finish. **11. Verify the indexed records** When the task completes, open the [`quickstart-products` index in the Algolia dashboard](https://dashboard.algolia.com/explorer/browse/quickstart-products). The index contains 1,000 product records with attributes such as `title`, `description`, `product_type`, `price`, `price_range`, and `showcase_image`. > [!NOTE] > > You can use this index as the data source for > [Build your first search experience](/doc/guides/get-started/quickstart). > > Before you build the UI, > [configure `product_type` as an attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) > in the `quickstart-products` index. ## Index your own MySQL data Use a dedicated read-only MySQL account with access only to the tables you want to index, and restrict network access to Algolia's connector IP addresses. ### Set up a dedicated MySQL connector user Connect to MySQL with an account that can create users and grant privileges. Run the following statements in the MySQL client or your hosting provider's SQL console. Create a dedicated connector user and grant it `SELECT` access to the table you want to index. For example: ```sql SQL icon="database" theme={"system"} CREATE USER 'algolia_connector'@'%' IDENTIFIED BY 'MYSQL_USER_PASSWORD'; GRANT SELECT ON MYSQL_DATABASE.TABLE_NAME TO 'algolia_connector'@'%'; ``` Replace `MYSQL_USER_PASSWORD` and `MYSQL_DATABASE.TABLE_NAME` with your connector user's password, database, and table name. If the user already exists, run only the [`GRANT` statement](https://dev.mysql.com/doc/refman/8.4/en/grant.html). ### Allow the Algolia connector to reach your MySQL server The `%` host permits remote connections. > [!WARNING] > > For production databases, > restrict inbound connections to [Algolia's connector IP addresses](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors#allow-algolia-ip-addresses) or use an SSH tunnel. > instead of allowing connections from all IP addresses. ### Configure the connector for your data 1. Follow the steps to [configure the MySQL source](#source) and create a [synchronization task](#task): replace the sample connection details and `apparel` table with your own database details and tables. 2. Use the dedicated connector user you created. 3. Review the [transformation](#transform) and [destination index](#destination) to make sure they're appropriate for your data. ### Secure the connection Configure connection security (**SSL modes**) in your [MySQL source](https://dashboard.algolia.com/connectors/sources). SSL encrypts the connection to your MySQL server and can verify the server's identity. An SSH tunnel lets the connector reach a database that isn't directly accessible from Algolia. Depending on your setup, you can use SSL, an SSH tunnel, or both. #### Configure SSL Choose an SSL mode that matches your MySQL server's TLS configuration: * **Preferred.** The connector uses SSL if the MySQL server supports it. Otherwise, it connects without SSL. * **Required** requires an encrypted connection but doesn't verify the server certificate. * **Verify CA** requires encryption and verifies the server certificate against a trusted certificate authority. Add the CA certificate supplied by your database provider to **CA certificate**. * **Verify Identity** verifies that the certificate identifies the host. Add the provider's **CA certificate**. Leave **Client certificate** and **Client key** empty unless your MySQL server requires mutual TLS authentication. If it does, add the client certificate supplied by your database provider to **Client certificate** and its corresponding private key to **Client key**. #### Connect through an SSH tunnel If Algolia can't connect directly to your MySQL database, connect through an SSH host. Select an authentication method: > [tab] SSH Key Authentication > > To authenticate with an RSA private key: > > 1. Generate an RSA key pair in PEM format: > > ```sh Command line icon=square-terminal theme={"system"} > ssh-keygen -t rsa -m PEM -f algolia_mysql_rsa > ``` > > 2. Add `algolia_mysql_rsa.pub` to the SSH user's authorized keys on the bastion host. > > 3. In the MySQL source configuration, select **SSH Key Authentication** under **SSH Tunnel Method**. > > 4. Enter the **SSH Tunnel Jump Server Host**, SSH port, and **SSH Login Username**. > > 5. Add the contents of `algolia_mysql_rsa` to **SSH Private Key**. > [tab] Password Authentication > > To authenticate with a password: > > 1. In the MySQL source configuration, select **Password Authentication** under **SSH Tunnel Method**. > 2. Enter the **SSH Tunnel Jump Server Host**, SSH port, and **SSH Login Username**. > 3. Enter the SSH user's password. > > Use the credentials for the SSH host, not the MySQL username and password. ### MySQL data types The connector maps MySQL values to JSON-compatible Algolia attributes. In particular: * `DECIMAL` values become numbers. * `TINYINT(1)` values become booleans by default. * Native MySQL `JSON` values become serialized JSON strings. * `TEXT` and `LONGTEXT` columns remain strings, including columns that contain JSON-encoded data. For JSON-encoded strings, use a transformation to convert them into arrays or objects before indexing. ### Transformations Use transformations to add computed attributes or change values before the connector indexes your records. To use a transformed attribute for search, faceting, or ranking, update the relevant [index settings](/doc/api-reference/settings-api-parameters), such as `searchableAttributes`, `attributesForFaceting`, or `customRanking`. > [!NOTE] > > Test transformations with repeated task runs. > Transformations that read from and write to the same attribute, > or extract a value and then delete the source attribute, > can produce different results when the task runs again. ### Synchronization schedule After you create a task, you can [edit it in the Algolia dashboard](https://dashboard.algolia.com/connectors/tasks) to choose when the connector runs: * **On demand**. Run the connector manually. * **Scheduled**. Select a predefined schedule or enter a custom [cron expression](https://crontab.guru/). ### Indexing strategy You can choose how the connector updates your index. For information about full reindexing, full record updates, and partial record updates, see [Data synchronization strategies](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies). ## Limitations This connector is subject to the following limitations: * [Connectors limits](/doc/guides/scaling/algolia-service-limits#connectors-limits) * [Transformation limits](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits) ## See also * [Connector overview](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors) * [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) * [Transform your data with code](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code) # Push to Algolia connector Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push Push records from your app through an Algolia-managed transformation to an index. Use the Push to Algolia connector when your app controls which records to index and when to send them. Your app sends records to a connector task with an API client. Algolia transforms the records and writes them to the destination index. If Algolia can retrieve your data directly, use another [connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors). For example, use the [JSON connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/json) if your records are available as a hosted JSON file and you want Algolia to fetch them on demand or on a schedule without an indexing script. If your records don't need an Algolia-managed transformation, [send them directly with an API client](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/sending-records-in-batches). ## Quickstart Create a transformation, destination, and task for a public product dataset, then run a Node.js script that pushes the records through the connector. The script uses `pushTask` to identify the connector task explicitly by its task ID. ### Before you begin Make sure you have: * [An Algolia account](https://dashboard.algolia.com/users/sign_up). Create one for free if you don't already have one. * Node.js 22 or later and npm. * Your Algolia [Write API key](https://dashboard.algolia.com/account/api-keys). * Your Algolia application's [analytics region](https://dashboard.algolia.com/account/infrastructure/analytics): `us` for the United States or `eu` for Europe. ### Set up the Push to Algolia connector **1. Open the Push to Algolia connector** Go to the Algolia dashboard 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 **Data sources**, then open the [**Connectors**](https://dashboard.algolia.com/connectors) page. **2. Connect the source** Select **Push to Algolia**, then select **Connect**. **3. Transform the records** This transformation adds a `price_range` attribute to each record. After the connector indexes your records, you can display `price_range` or configure it as a facet. Select **Transform using the code editor** and replace the placeholder **Transformation code** with this function: ```js JavaScript icon=code expandable theme={"system"} async function transform(record, helper) { const price = Number(record.price); if (!Number.isFinite(price)) { return record; } if (price < 25) { record.price_range = "Under $25"; } else if (price < 50) { record.price_range = "$25 to $49"; } else if (price < 100) { record.price_range = "$50 to $99"; } else { record.price_range = "$100 and up"; } return record; } ``` Select **Save**. **4. Select index and credentials** Select **Create a new destination** to choose 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.) for the connector to store records. Under **Connect your data to Algolia Search**, enter `quickstart-products` and, under **Index credentials**, select **Create one for me**. > [!WARNING] > > Use a new index for this quickstart to avoid adding new records to it. **5. Create the destination** In **Name**, enter `Quickstart push destination` and select **Create destination**. **6. Create the task** Select **Create task**. The dashboard opens **API Client & Code snippets** with a generated `pushTask` example. Copy the `taskID` value from the example, then select **Close**. Continue with the next step instead of copying the generated records or code. The next steps use environment variables to keep your API key out of the source code. **7. Create a Node.js project** In a terminal, create a project and install the Algolia API client and `dotenv`: ```sh Command line icon=square-terminal theme={"system"} mkdir algolia-push-quickstart cd algolia-push-quickstart npm init -y npm install algoliasearch@5 dotenv ``` **8. Add your Algolia credentials** Create a `.env` file in the project directory: ```dotenv .env icon=lock-keyhole theme={"system"} ALGOLIA_APPLICATION_ID= ALGOLIA_API_KEY= ALGOLIA_APPLICATION_REGION= ALGOLIA_TASK_ID= ``` Set the variables to your Algolia application ID, write API key, analytics region, and task ID. To keep your Write API key secret, create a `.gitignore` file in the project directory: ```text .gitignore icon=file-cog theme={"system"} .env node_modules/ ``` **9. Push the sample records** Create a `push.mjs` file with this code: ```js push.mjs icon=code expandable theme={"system"} import "dotenv/config"; import { algoliasearch } from "algoliasearch"; const datasetUrl = "https://dashboard.algolia.com/api/1/sample_datasets?type=apparel"; async function main() { const { ALGOLIA_APPLICATION_ID: appId, ALGOLIA_API_KEY: apiKey, ALGOLIA_APPLICATION_REGION: region, ALGOLIA_TASK_ID: taskID, } = process.env; if (!appId || !apiKey || !region || !taskID) { throw new Error( "Set ALGOLIA_APPLICATION_ID, ALGOLIA_API_KEY, " + "ALGOLIA_APPLICATION_REGION, and ALGOLIA_TASK_ID in your .env file.", ); } if (region !== "us" && region !== "eu") { throw new Error( "ALGOLIA_APPLICATION_REGION must be either 'us' or 'eu'.", ); } const client = algoliasearch(appId, apiKey).initIngestion({ region }); // Confirm that the task belongs to this application and region. await client.getTask({ taskID }); const response = await fetch(datasetUrl); if (!response.ok) { throw new Error( `Couldn't fetch the sample dataset: ` + `${response.status} ${response.statusText}`, ); } const products = await response.json(); if (!Array.isArray(products)) { throw new Error("The sample dataset didn't return an array of records."); } const result = await client.pushTask({ taskID, pushTaskPayload: { action: "addObject", records: products, }, watch: true, }); console.log(`Submitted ${products.length} records.`); console.log(`Run ID: ${result.runID}`); } main().catch((error) => { const message = error instanceof Error ? error.message : String(error); console.error(`Push failed: ${message}`); process.exitCode = 1; }); ``` Run the script: ```sh Command line icon=square-terminal theme={"system"} node push.mjs ``` Copy the **Run ID** printed by the script. **10. Verify the indexing operation** Open the [Connector Debugger](https://dashboard.algolia.com/connectors/debugger) and search for the run ID printed by `push.mjs`. Confirm that the operation completed successfully. Open the [`quickstart-products` index](https://dashboard.algolia.com/explorer/browse/quickstart-products) in the Algolia dashboard. Confirm that it contains product records with attributes such as `title`, `description`, `product_type`, `price`, `price_range`, and `showcase_image`. > [!NOTE] > > You can use this index as the data source for > [Build your first search experience](/doc/guides/get-started/quickstart). > > Before you build the UI, > [configure `product_type` as an attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) > in the `quickstart-products` index. ## Use the connector with your app In production, replace the quickstart data request in `push.mjs` with code that selects records from your app or data pipeline. For example, supply records from a processing job or a source for which Algolia doesn't provide another connector. ### Prepare your records Send only the records required by the indexing operation. For example, your app can send a changed record after updating its source data instead of exporting and synchronizing the full dataset. Ensure each record has a [stable, unique `objectID`](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#keep-object-ids-stable). If your source uses a different unique property, map it before pushing the records: ```js JavaScript icon=code theme={"system"} const records = sourceRecords.map((record) => ({ ...record, objectID: String(record.id), })); ``` Update your transformation to match your record structure. If you want to make a transformed attribute searchable, use it for faceting, or use it for ranking, update the corresponding index setting: [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes), [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting), or [`customRanking`](/doc/api-reference/api-parameters/customRanking). ### Choose an API client method Choose a method based on how you want to identify the connector task and update the index. | Use case | Method | | - | - | | Target a specific connector task by its task ID | [`pushTask`](/doc/libraries/sdk/methods/ingestion/push-task) | | Push one request by index name when one task matches | [`push`](/doc/libraries/sdk/methods/ingestion/push) | | Push large record sets in batches, with control over the action and batch size | [`chunkedPush`](/doc/libraries/sdk/methods/ingestion/chunked-push) | | Add or replace complete records by adapting an existing Search API implementation | [`saveObjectsWithTransformation`](/doc/libraries/sdk/methods/search/save-objects-with-transformation) | | Add or update selected attributes by adapting an existing Search API implementation | [`partialUpdateObjectsWithTransformation`](/doc/libraries/sdk/methods/search/partial-update-objects-with-transformation) | | Replace every record in an index by adapting an existing Search API implementation | [`replaceAllObjectsWithTransformation`](/doc/libraries/sdk/methods/search/replace-all-objects-with-transformation) | * `push`, `chunkedPush`, and the `WithTransformation` helper methods select a user-created Push to Algolia task by index name. They support one user-created Push to Algolia connector, including when the index also uses [Collections](/doc/guides/solutions/ecommerce/browse/tutorials/collections). * If multiple destinations link to the index or multiple user-created Push to Algolia connectors target it, use `pushTask` and specify the task ID. To send records through the Collections pipeline, use its task ID with `pushTask`. ### Push with the Ingestion API Use the Ingestion API client to choose the indexing action or identify a connector task by ID. #### Push by task ID Use `pushTask` to select a connector task by its task ID. ```cs C# theme={"system"} var response = await client.PushTaskAsync( "6c02aeb1-775e-418e-870b-1faccd4b2c0f", new PushTaskPayload { Action = Enum.Parse("AddObject"), Records = new List { new PushTaskRecords { ObjectID = "o", AdditionalProperties = new Dictionary { { "key", "bar" }, { "foo", "1" }, }, }, new PushTaskRecords { ObjectID = "k", AdditionalProperties = new Dictionary { { "key", "baz" }, { "foo", "2" }, }, }, }, } ); ``` ```dart Dart theme={"system"} final response = await client.pushTask( taskID: "6c02aeb1-775e-418e-870b-1faccd4b2c0f", pushTaskPayload: PushTaskPayload( action: Action.fromJson("addObject"), records: [ PushTaskRecords( objectID: "o", additionalProperties: { 'key': "bar", 'foo': "1", }, ), PushTaskRecords( objectID: "k", additionalProperties: { 'key': "baz", 'foo': "2", }, ), ], ), ); ``` ```go Go theme={"system"} response, err := client.PushTask(client.NewApiPushTaskRequest( "6c02aeb1-775e-418e-870b-1faccd4b2c0f", ingestion.NewEmptyPushTaskPayload().SetAction(ingestion.Action("addObject")).SetRecords( []ingestion.PushTaskRecords{ *ingestion.NewEmptyPushTaskRecords().SetAdditionalProperty("key", "bar").SetAdditionalProperty("foo", "1").SetObjectID("o"), *ingestion.NewEmptyPushTaskRecords().SetAdditionalProperty("key", "baz").SetAdditionalProperty("foo", "2").SetObjectID("k"), }), )) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} WatchResponse response = client.pushTask( "6c02aeb1-775e-418e-870b-1faccd4b2c0f", new PushTaskPayload() .setAction(Action.ADD_OBJECT) .setRecords( Arrays.asList( new PushTaskRecords().setAdditionalProperty("key", "bar").setAdditionalProperty("foo", "1").setObjectID("o"), new PushTaskRecords().setAdditionalProperty("key", "baz").setAdditionalProperty("foo", "2").setObjectID("k") ) ) ); ``` ```js JavaScript theme={"system"} const response = await client.pushTask({ taskID: '6c02aeb1-775e-418e-870b-1faccd4b2c0f', pushTaskPayload: { action: 'addObject', records: [ { key: 'bar', foo: '1', objectID: 'o' }, { key: 'baz', foo: '2', objectID: 'k' }, ], }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.pushTask( taskID = "6c02aeb1-775e-418e-870b-1faccd4b2c0f", pushTaskPayload = PushTaskPayload( action = Action.entries.first { it.value == "addObject" }, records = listOf( PushTaskRecords( objectID = "o", additionalProperties = mapOf("key" to JsonPrimitive("bar"), "foo" to JsonPrimitive("1")), ), PushTaskRecords( objectID = "k", additionalProperties = mapOf("key" to JsonPrimitive("baz"), "foo" to JsonPrimitive("2")), ), ), ), ) ``` ```php PHP theme={"system"} $response = $client->pushTask( '6c02aeb1-775e-418e-870b-1faccd4b2c0f', ['action' => 'addObject', 'records' => [ ['key' => 'bar', 'foo' => '1', 'objectID' => 'o', ], ['key' => 'baz', 'foo' => '2', 'objectID' => 'k', ], ], ], ); ``` ```python Python theme={"system"} response = client.push_task( task_id="6c02aeb1-775e-418e-870b-1faccd4b2c0f", push_task_payload={ "action": "addObject", "records": [ { "key": "bar", "foo": "1", "objectID": "o", }, { "key": "baz", "foo": "2", "objectID": "k", }, ], }, ) ``` ```ruby Ruby theme={"system"} response = client.push_task( "6c02aeb1-775e-418e-870b-1faccd4b2c0f", Algolia::Ingestion::PushTaskPayload.new( action: "addObject", records: [ Algolia::Ingestion::PushTaskRecords.new(key: "bar", foo: "1", algolia_object_id: "o"), Algolia::Ingestion::PushTaskRecords.new(key: "baz", foo: "2", algolia_object_id: "k") ] ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.pushTask( taskID = "6c02aeb1-775e-418e-870b-1faccd4b2c0f", pushTaskPayload = PushTaskPayload( action = Action.withName("addObject"), records = Seq( PushTaskRecords( objectID = "o", additionalProperties = Some(List(JField("key", JString("bar")), JField("foo", JString("1")))) ), PushTaskRecords( objectID = "k", additionalProperties = Some(List(JField("key", JString("baz")), JField("foo", JString("2")))) ) ) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.pushTask( taskID: "6c02aeb1-775e-418e-870b-1faccd4b2c0f", pushTaskPayload: PushTaskPayload( action: IngestionAction.addObject, records: [ PushTaskRecords(from: [ "objectID": AnyCodable("o"), "key": AnyCodable("bar"), "foo": AnyCodable("1"), ]), PushTaskRecords(from: [ "objectID": AnyCodable("k"), "key": AnyCodable("baz"), "foo": AnyCodable("2"), ]), ] ) ) ``` #### Push by index name Use `push` when one task targets the specified index. The request returns an error if no task or more than one task matches the index name. ```cs C# theme={"system"} var response = await client.PushAsync( "INDEX_NAME", new PushTaskPayload { Action = Enum.Parse("AddObject"), Records = new List { new PushTaskRecords { ObjectID = "o", AdditionalProperties = new Dictionary { { "key", "bar" }, { "foo", "1" }, }, }, new PushTaskRecords { ObjectID = "k", AdditionalProperties = new Dictionary { { "key", "baz" }, { "foo", "2" }, }, }, }, } ); ``` ```dart Dart theme={"system"} final response = await client.push( indexName: "INDEX_NAME", pushTaskPayload: PushTaskPayload( action: Action.fromJson("addObject"), records: [ PushTaskRecords( objectID: "o", additionalProperties: { 'key': "bar", 'foo': "1", }, ), PushTaskRecords( objectID: "k", additionalProperties: { 'key': "baz", 'foo': "2", }, ), ], ), ); ``` ```go Go theme={"system"} response, err := client.Push(client.NewApiPushRequest( "INDEX_NAME", ingestion.NewEmptyPushTaskPayload().SetAction(ingestion.Action("addObject")).SetRecords( []ingestion.PushTaskRecords{ *ingestion.NewEmptyPushTaskRecords().SetAdditionalProperty("key", "bar").SetAdditionalProperty("foo", "1").SetObjectID("o"), *ingestion.NewEmptyPushTaskRecords().SetAdditionalProperty("key", "baz").SetAdditionalProperty("foo", "2").SetObjectID("k"), }), )) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} WatchResponse response = client.push( "INDEX_NAME", new PushTaskPayload() .setAction(Action.ADD_OBJECT) .setRecords( Arrays.asList( new PushTaskRecords().setAdditionalProperty("key", "bar").setAdditionalProperty("foo", "1").setObjectID("o"), new PushTaskRecords().setAdditionalProperty("key", "baz").setAdditionalProperty("foo", "2").setObjectID("k") ) ) ); ``` ```js JavaScript theme={"system"} const response = await client.push({ indexName: 'foo', pushTaskPayload: { action: 'addObject', records: [ { key: 'bar', foo: '1', objectID: 'o' }, { key: 'baz', foo: '2', objectID: 'k' }, ], }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.push( indexName = "INDEX_NAME", pushTaskPayload = PushTaskPayload( action = Action.entries.first { it.value == "addObject" }, records = listOf( PushTaskRecords( objectID = "o", additionalProperties = mapOf("key" to JsonPrimitive("bar"), "foo" to JsonPrimitive("1")), ), PushTaskRecords( objectID = "k", additionalProperties = mapOf("key" to JsonPrimitive("baz"), "foo" to JsonPrimitive("2")), ), ), ), ) ``` ```php PHP theme={"system"} $response = $client->push( 'INDEX_NAME', ['action' => 'addObject', 'records' => [ ['key' => 'bar', 'foo' => '1', 'objectID' => 'o', ], ['key' => 'baz', 'foo' => '2', 'objectID' => 'k', ], ], ], ); ``` ```python Python theme={"system"} response = client.push( index_name="INDEX_NAME", push_task_payload={ "action": "addObject", "records": [ { "key": "bar", "foo": "1", "objectID": "o", }, { "key": "baz", "foo": "2", "objectID": "k", }, ], }, ) ``` ```ruby Ruby theme={"system"} response = client.push( "INDEX_NAME", Algolia::Ingestion::PushTaskPayload.new( action: "addObject", records: [ Algolia::Ingestion::PushTaskRecords.new(key: "bar", foo: "1", algolia_object_id: "o"), Algolia::Ingestion::PushTaskRecords.new(key: "baz", foo: "2", algolia_object_id: "k") ] ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.push( indexName = "INDEX_NAME", pushTaskPayload = PushTaskPayload( action = Action.withName("addObject"), records = Seq( PushTaskRecords( objectID = "o", additionalProperties = Some(List(JField("key", JString("bar")), JField("foo", JString("1")))) ), PushTaskRecords( objectID = "k", additionalProperties = Some(List(JField("key", JString("baz")), JField("foo", JString("2")))) ) ) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.push( indexName: "INDEX_NAME", pushTaskPayload: PushTaskPayload( action: IngestionAction.addObject, records: [ PushTaskRecords(from: [ "objectID": AnyCodable("o"), "key": AnyCodable("bar"), "foo": AnyCodable("1"), ]), PushTaskRecords(from: [ "objectID": AnyCodable("k"), "key": AnyCodable("baz"), "foo": AnyCodable("2"), ]), ] ) ) ``` #### Push records in batches Use `chunkedPush` to split a large record set into batches and send each batch with `push`. The default batch size is 1,000 records. Use it directly when you need more control over the indexing action or batch size. ### Search API `WithTransformation` methods Use the Search API client helpers to adapt an existing implementation that calls `saveObjects`, `partialUpdateObjects`, or `replaceAllObjects`. The helpers route records through the Push to Algolia connector and automatically split them into batches. They're subject to the [connector limits](/doc/guides/scaling/algolia-service-limits#connectors-limits). To run the examples, [install the latest API client](/doc/libraries/sdk/install). #### Set up transformation options Before calling a `WithTransformation` method, configure `transformationOptions` with your Algolia application's analytics region. The API client throws an error if you omit `transformationOptions`. ```cs C# theme={"system"} namespace Algolia; using System; using System.Collections.Generic; using Algolia.Search.Clients; using Algolia.Search.Http; using Algolia.Search.Models.Search; class SetUpTransformationOptions { async Task Main(string[] args) { // Set TransformationOptions with your transformation region to use the `WithTransformation` helper methods. // Replace "us" with "eu" if your Algolia application uses the Europe analytics region. var client = SearchClient.WithTransformation( "ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY", new TransformationOptions("us") ); // Save records, transforming them through the Push connector try { var result = await client.SaveObjectsWithTransformationAsync( "INDEX_NAME", new List { new Dictionary { { "objectID", "1" }, { "name", "Adam" } }, new Dictionary { { "objectID", "2" }, { "name", "Benoit" } }, }, true ); } catch (Exception e) { Console.WriteLine(e.Message); } } } ``` ```dart Dart theme={"system"} import 'package:algolia_client_search/algolia_client_search.dart'; void main() async { // Set transformationOptions with your transformation region to use the `WithTransformation` helper methods. // Replace 'us' with 'eu' if your Algolia application uses the Europe analytics region. final client = SearchClient( appId: 'ALGOLIA_APPLICATION_ID', apiKey: 'ALGOLIA_API_KEY', transformationOptions: TransformationOptions(region: 'us'), ); try { // Save records, transforming them through the Push connector await client.saveObjectsWithTransformation( indexName: "INDEX_NAME", objects: [ { 'objectID': "1", 'name': "Adam", }, { 'objectID': "2", 'name': "Benoit", }, ], waitForTasks: true, ); print('Done!'); } catch (e) { print('Error: ${e.toString()}'); } } ``` ```go Go theme={"system"} package main import ( "fmt" "github.com/algolia/algoliasearch-client-go/v4/algolia/search" ) func setUpTransformationOptions() { // Set transformationOptions with your transformation region to use the `WithTransformation` helper methods. // Replace "us" with "eu" if your Algolia application uses the Europe analytics region. client, err := search.NewClient( "ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY", search.WithTransformationOptions(search.TransformationOptions{Region: "us"}), ) if err != nil { // The client can fail to initialize if you pass an invalid parameter. panic(err) } // Save records, transforming them through the Push connector result, err := client.SaveObjectsWithTransformation( "INDEX_NAME", []map[string]any{{"objectID": "1", "name": "Adam"}, {"objectID": "2", "name": "Benoit"}}, search.WithWaitForTasks(true)) if err != nil { panic(err) } fmt.Printf("Done! Uploaded records in %d batches.", len(result)) } ``` ```java Java theme={"system"} package com.algolia; import com.algolia.api.SearchClient; import com.algolia.config.*; import com.algolia.model.search.*; import java.util.Arrays; import java.util.HashMap; public class setUpTransformationOptions { public static void main(String[] args) throws Exception { // Set `transformationOptions` with your transformation region to use the `WithTransformation` // helper methods. // Replace "us" with "eu" if your Algolia application uses the Europe analytics region. SearchClient client = SearchClient.withTransformation("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY", new TransformationOptions("us")); // Save records, transforming them through the Push connector client.saveObjectsWithTransformation( "INDEX_NAME", Arrays.asList( new HashMap() { { put("objectID", "1"); put("name", "Adam"); } }, new HashMap() { { put("objectID", "2"); put("name", "Benoit"); } } ), true ); client.close(); } } ``` ```js JavaScript theme={"system"} import { algoliasearch } from 'algoliasearch'; // Set `transformationOptions` with your transformation region to use the `*WithTransformation` helper methods. // Replace 'us' with 'eu' if your Algolia application uses the Europe analytics region. const client = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_API_KEY', { transformationOptions: { region: 'us' }, }); async function run() { // Save records, transforming them through the Push connector const response = await client.saveObjectsWithTransformation({ indexName: 'INDEX_NAME', objects: [ { objectID: '1', name: 'Adam' }, { objectID: '2', name: 'Benoit' }, ], waitForTasks: true, }); console.log(response); } run().catch((err) => console.error(err)); ``` ```kotlin Kotlin theme={"system"} package org.example import com.algolia.client.api.SearchClient import com.algolia.client.configuration.* import com.algolia.client.extensions.* import com.algolia.client.transport.* import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject suspend fun main() { // Set `transformationOptions` with your transformation region to use the `WithTransformation` // helper methods. // Replace "us" with "eu" if your Algolia application uses the Europe analytics region. val client = SearchClient.withTransformation( appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY", transformationOptions = TransformationOptions(region = "us"), ) try { // Save records, transforming them through the Push connector client.saveObjectsWithTransformation( indexName = "INDEX_NAME", objects = listOf( buildJsonObject { put("objectID", JsonPrimitive("1")) put("name", JsonPrimitive("Adam")) }, buildJsonObject { put("objectID", JsonPrimitive("2")) put("name", JsonPrimitive("Benoit")) }, ), waitForTasks = true, ) } catch (e: Exception) { println(e.message) } } ``` ```php PHP theme={"system"} setTransformationOptions(new TransformationOptions('us')); $client = SearchClient::createWithConfig($config); // Save records, transforming them through the Push connector $client->saveObjectsWithTransformation( 'INDEX_NAME', [ ['objectID' => '1', 'name' => 'Adam', ], ['objectID' => '2', 'name' => 'Benoit', ], ], true, ); echo 'Done!'; ``` ```python Python theme={"system"} from algoliasearch.search.client import SearchClientSync from algoliasearch.search.config import SearchConfig, TransformationOptions # Set `transformation_options` with your transformation region to use the `*_with_transformation` helper methods. # Replace "us" with "eu" if your Algolia application uses the Europe analytics region. _config = SearchConfig( "ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY", transformation_options=TransformationOptions(region="us"), ) _client = SearchClientSync.create_with_config(_config) # Save records, transforming them through the Push connector _client.save_objects_with_transformation( index_name="INDEX_NAME", objects=[ { "objectID": "1", "name": "Adam", }, { "objectID": "2", "name": "Benoit", }, ], wait_for_tasks=True, ) ``` ```ruby Ruby theme={"system"} require "algolia" # Set transformation options with your transformation region to use the `*_with_transformation` helper methods. # Replace 'us' with 'eu' if your Algolia application uses the Europe analytics region. client = Algolia::SearchClient.with_transformation( "ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY", Algolia::TransformationOptions.new("us") ) # Save records, transforming them through the Push connector client.save_objects_with_transformation( "INDEX_NAME", [{objectID: "1", name: "Adam"}, {objectID: "2", name: "Benoit"}], true ) puts("Done!") ``` ```scala Scala theme={"system"} import scala.concurrent.duration.Duration import scala.concurrent.{Await, ExecutionContextExecutor} import algoliasearch.api.SearchClient import algoliasearch.config.* import algoliasearch.extension.SearchClientExtensions import org.json4s.* object SetUpTransformationOptions { def main(args: Array[String]): Unit = { implicit val ec: ExecutionContextExecutor = scala.concurrent.ExecutionContext.global // Set transformationOptions with your transformation region to use the `WithTransformation` helper methods. // Replace "us" with "eu" if your Algolia application uses the Europe analytics region. val client = SearchClient.withTransformation( appId = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_API_KEY", transformationOptions = TransformationOptions("us") ) // Save records, transforming them through the Push connector try { Await.result( client.saveObjectsWithTransformation( indexName = "INDEX_NAME", objects = Seq( JObject(List(JField("objectID", JString("1")), JField("name", JString("Adam")))), JObject(List(JField("objectID", JString("2")), JField("name", JString("Benoit")))) ), waitForTasks = true ), Duration(100, "sec") ) } catch { case e: Exception => println(e) } } } ``` ```swift Swift theme={"system"} import Foundation #if os(Linux) // For linux interop import FoundationNetworking #endif import AlgoliaCore import AlgoliaSearch func setUpTransformationOptions() async throws { // Set transformationOptions with your transformation region to use the `WithTransformation` helper methods. // Replace `.us` with `.eu` if your Algolia application uses the Europe analytics region. let configuration = try SearchClientConfiguration( appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_API_KEY", transformationOptions: TransformationOptions(region: .us) ) let client = SearchClient(configuration: configuration) do { // Save records, transforming them through the Push connector try await client.saveObjectsWithTransformation( indexName: "INDEX_NAME", objects: [["objectID": "1", "name": "Adam"], ["objectID": "2", "name": "Benoit"]], waitForTasks: true ) } catch { print(error.localizedDescription) } } ``` `transformationOptions` creates a dedicated ingestion transporter that starts from the Ingestion API defaults: 25-second timeouts, hosts derived from the region, and no compression. It only overrides the options you set and doesn't inherit the Search API client's configuration. For more information, see [Transformation options and the ingestion transporter](/doc/libraries/sdk/customize#transformation-options-and-the-ingestion-transporter). > [!NOTE] > > `transformationOptions` replaces the deprecated `setTransformationRegion` method in [Java](/doc/libraries/sdk/upgrade/java), > [C#](/doc/libraries/sdk/upgrade/csharp), > and [PHP](/doc/libraries/sdk/upgrade/php), > the `set_transformation_region` method in [Python](/doc/libraries/sdk/upgrade/python), > and the `transformation` option in [JavaScript](/doc/libraries/sdk/upgrade/javascript). #### Add or replace records with a transformation To add or replace complete records with a transformation, call `saveObjectsWithTransformation`. ```cs C# theme={"system"} var response = await client.SaveObjectsWithTransformationAsync( "INDEX_NAME", new List { new Dictionary { { "objectID", "1" }, { "name", "Adam" } }, new Dictionary { { "objectID", "2" }, { "name", "Benoit" } }, }, true ); ``` ```dart Dart theme={"system"} final response = await client.saveObjectsWithTransformation( indexName: "INDEX_NAME", objects: [ { 'objectID': "1", 'name': "Adam", }, { 'objectID': "2", 'name': "Benoit", }, ], waitForTasks: true, ); ``` ```go Go theme={"system"} response, err := client.SaveObjectsWithTransformation( "INDEX_NAME", []map[string]any{{"objectID": "1", "name": "Adam"}, {"objectID": "2", "name": "Benoit"}}, search.WithWaitForTasks(true)) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} List response = client.saveObjectsWithTransformation( "INDEX_NAME", Arrays.asList( new HashMap() { { put("objectID", "1"); put("name", "Adam"); } }, new HashMap() { { put("objectID", "2"); put("name", "Benoit"); } } ), true ); ``` ```js JavaScript theme={"system"} const response = await client.saveObjectsWithTransformation({ indexName: 'cts_e2e_saveObjectsWithTransformation_javascript', objects: [ { objectID: '1', name: 'Adam' }, { objectID: '2', name: 'Benoit' }, ], waitForTasks: true, }); ``` ```kotlin Kotlin theme={"system"} var response = client.saveObjectsWithTransformation( indexName = "INDEX_NAME", objects = listOf( buildJsonObject { put("objectID", JsonPrimitive("1")) put("name", JsonPrimitive("Adam")) }, buildJsonObject { put("objectID", JsonPrimitive("2")) put("name", JsonPrimitive("Benoit")) }, ), waitForTasks = true, ) ``` ```php PHP theme={"system"} $response = $client->saveObjectsWithTransformation( 'INDEX_NAME', [ ['objectID' => '1', 'name' => 'Adam', ], ['objectID' => '2', 'name' => 'Benoit', ], ], true, ); ``` ```python Python theme={"system"} response = client.save_objects_with_transformation( index_name="INDEX_NAME", objects=[ { "objectID": "1", "name": "Adam", }, { "objectID": "2", "name": "Benoit", }, ], wait_for_tasks=True, ) ``` ```ruby Ruby theme={"system"} response = client.save_objects_with_transformation( "INDEX_NAME", [{objectID: "1", name: "Adam"}, {objectID: "2", name: "Benoit"}], true ) ``` ```scala Scala theme={"system"} val response = Await.result( client.saveObjectsWithTransformation( indexName = "INDEX_NAME", objects = Seq( JObject(List(JField("objectID", JString("1")), JField("name", JString("Adam")))), JObject(List(JField("objectID", JString("2")), JField("name", JString("Benoit")))) ), waitForTasks = true ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.saveObjectsWithTransformation( indexName: "INDEX_NAME", objects: [["objectID": "1", "name": "Adam"], ["objectID": "2", "name": "Benoit"]], waitForTasks: true ) ``` #### Add or update record attributes with a transformation To add or update selected attributes with a transformation, call [`partialUpdateObjectsWithTransformation`](/doc/libraries/sdk/methods/search/partial-update-objects-with-transformation). ```cs C# theme={"system"} var response = await client.PartialUpdateObjectsWithTransformationAsync( "INDEX_NAME", new List { new Dictionary { { "objectID", "1" }, { "name", "Adam" } }, new Dictionary { { "objectID", "2" }, { "name", "Benoit" } }, }, true, true ); ``` ```dart Dart theme={"system"} final response = await client.partialUpdateObjectsWithTransformation( indexName: "INDEX_NAME", objects: [ { 'objectID': "1", 'name': "Adam", }, { 'objectID': "2", 'name': "Benoit", }, ], createIfNotExists: true, waitForTasks: true, ); ``` ```go Go theme={"system"} response, err := client.PartialUpdateObjectsWithTransformation( "INDEX_NAME", []map[string]any{ {"objectID": "1", "name": "Adam"}, {"objectID": "2", "name": "Benoit"}, }, search.WithCreateIfNotExists(true), search.WithWaitForTasks(true), ) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} List response = client.partialUpdateObjectsWithTransformation( "INDEX_NAME", Arrays.asList( new HashMap() { { put("objectID", "1"); put("name", "Adam"); } }, new HashMap() { { put("objectID", "2"); put("name", "Benoit"); } } ), true, true ); ``` ```js JavaScript theme={"system"} const response = await client.partialUpdateObjectsWithTransformation({ indexName: 'cts_e2e_partialUpdateObjectsWithTransformation_javascript', objects: [ { objectID: '1', name: 'Adam' }, { objectID: '2', name: 'Benoit' }, ], createIfNotExists: true, waitForTasks: true, }); ``` ```kotlin Kotlin theme={"system"} var response = client.partialUpdateObjectsWithTransformation( indexName = "INDEX_NAME", objects = listOf( buildJsonObject { put("objectID", JsonPrimitive("1")) put("name", JsonPrimitive("Adam")) }, buildJsonObject { put("objectID", JsonPrimitive("2")) put("name", JsonPrimitive("Benoit")) }, ), createIfNotExists = true, waitForTasks = true, ) ``` ```php PHP theme={"system"} $response = $client->partialUpdateObjectsWithTransformation( 'INDEX_NAME', [ ['objectID' => '1', 'name' => 'Adam', ], ['objectID' => '2', 'name' => 'Benoit', ], ], true, true, ); ``` ```python Python theme={"system"} response = client.partial_update_objects_with_transformation( index_name="INDEX_NAME", objects=[ { "objectID": "1", "name": "Adam", }, { "objectID": "2", "name": "Benoit", }, ], create_if_not_exists=True, wait_for_tasks=True, ) ``` ```ruby Ruby theme={"system"} response = client.partial_update_objects_with_transformation( "INDEX_NAME", [{objectID: "1", name: "Adam"}, {objectID: "2", name: "Benoit"}], true, true ) ``` ```scala Scala theme={"system"} val response = Await.result( client.partialUpdateObjectsWithTransformation( indexName = "INDEX_NAME", objects = Seq( JObject(List(JField("objectID", JString("1")), JField("name", JString("Adam")))), JObject(List(JField("objectID", JString("2")), JField("name", JString("Benoit")))) ), createIfNotExists = true, waitForTasks = true ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.partialUpdateObjectsWithTransformation( indexName: "INDEX_NAME", objects: [["objectID": "1", "name": "Adam"], ["objectID": "2", "name": "Benoit"]], createIfNotExists: true, waitForTasks: true ) ``` #### Replace all records with a transformation To replace all records with a transformation, call `replaceAllObjectsWithTransformation`. ```cs C# theme={"system"} var response = await client.ReplaceAllObjectsWithTransformationAsync( "INDEX_NAME", new List { new Dictionary { { "objectID", "1" }, { "name", "Adam" } }, new Dictionary { { "objectID", "2" }, { "name", "Benoit" } }, new Dictionary { { "objectID", "3" }, { "name", "Cyril" } }, new Dictionary { { "objectID", "4" }, { "name", "David" } }, new Dictionary { { "objectID", "5" }, { "name", "Eva" } }, new Dictionary { { "objectID", "6" }, { "name", "Fiona" } }, new Dictionary { { "objectID", "7" }, { "name", "Gael" } }, new Dictionary { { "objectID", "8" }, { "name", "Hugo" } }, new Dictionary { { "objectID", "9" }, { "name", "Igor" } }, new Dictionary { { "objectID", "10" }, { "name", "Julia" } }, }, 3 ); ``` ```dart Dart theme={"system"} final response = await client.replaceAllObjectsWithTransformation( indexName: "INDEX_NAME", objects: [ { 'objectID': "1", 'name': "Adam", }, { 'objectID': "2", 'name': "Benoit", }, { 'objectID': "3", 'name': "Cyril", }, { 'objectID': "4", 'name': "David", }, { 'objectID': "5", 'name': "Eva", }, { 'objectID': "6", 'name': "Fiona", }, { 'objectID': "7", 'name': "Gael", }, { 'objectID': "8", 'name': "Hugo", }, { 'objectID': "9", 'name': "Igor", }, { 'objectID': "10", 'name': "Julia", }, ], batchSize: 3, ); ``` ```go Go theme={"system"} response, err := client.ReplaceAllObjectsWithTransformation( "INDEX_NAME", []map[string]any{ {"objectID": "1", "name": "Adam"}, {"objectID": "2", "name": "Benoit"}, {"objectID": "3", "name": "Cyril"}, {"objectID": "4", "name": "David"}, {"objectID": "5", "name": "Eva"}, {"objectID": "6", "name": "Fiona"}, {"objectID": "7", "name": "Gael"}, {"objectID": "8", "name": "Hugo"}, {"objectID": "9", "name": "Igor"}, {"objectID": "10", "name": "Julia"}, }, search.WithBatchSize(3), ) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} ReplaceAllObjectsWithTransformationResponse response = client.replaceAllObjectsWithTransformation( "INDEX_NAME", Arrays.asList( new HashMap() { { put("objectID", "1"); put("name", "Adam"); } }, new HashMap() { { put("objectID", "2"); put("name", "Benoit"); } }, new HashMap() { { put("objectID", "3"); put("name", "Cyril"); } }, new HashMap() { { put("objectID", "4"); put("name", "David"); } }, new HashMap() { { put("objectID", "5"); put("name", "Eva"); } }, new HashMap() { { put("objectID", "6"); put("name", "Fiona"); } }, new HashMap() { { put("objectID", "7"); put("name", "Gael"); } }, new HashMap() { { put("objectID", "8"); put("name", "Hugo"); } }, new HashMap() { { put("objectID", "9"); put("name", "Igor"); } }, new HashMap() { { put("objectID", "10"); put("name", "Julia"); } } ), 3 ); ``` ```js JavaScript theme={"system"} const response = await client.replaceAllObjectsWithTransformation({ indexName: 'cts_e2e_replace_all_objects_with_transformation_javascript', objects: [ { objectID: '1', name: 'Adam' }, { objectID: '2', name: 'Benoit' }, { objectID: '3', name: 'Cyril' }, { objectID: '4', name: 'David' }, { objectID: '5', name: 'Eva' }, { objectID: '6', name: 'Fiona' }, { objectID: '7', name: 'Gael' }, { objectID: '8', name: 'Hugo' }, { objectID: '9', name: 'Igor' }, { objectID: '10', name: 'Julia' }, ], batchSize: 3, }); ``` ```kotlin Kotlin theme={"system"} var response = client.replaceAllObjectsWithTransformation( indexName = "INDEX_NAME", objects = listOf( buildJsonObject { put("objectID", JsonPrimitive("1")) put("name", JsonPrimitive("Adam")) }, buildJsonObject { put("objectID", JsonPrimitive("2")) put("name", JsonPrimitive("Benoit")) }, buildJsonObject { put("objectID", JsonPrimitive("3")) put("name", JsonPrimitive("Cyril")) }, buildJsonObject { put("objectID", JsonPrimitive("4")) put("name", JsonPrimitive("David")) }, buildJsonObject { put("objectID", JsonPrimitive("5")) put("name", JsonPrimitive("Eva")) }, buildJsonObject { put("objectID", JsonPrimitive("6")) put("name", JsonPrimitive("Fiona")) }, buildJsonObject { put("objectID", JsonPrimitive("7")) put("name", JsonPrimitive("Gael")) }, buildJsonObject { put("objectID", JsonPrimitive("8")) put("name", JsonPrimitive("Hugo")) }, buildJsonObject { put("objectID", JsonPrimitive("9")) put("name", JsonPrimitive("Igor")) }, buildJsonObject { put("objectID", JsonPrimitive("10")) put("name", JsonPrimitive("Julia")) }, ), batchSize = 3, ) ``` ```php PHP theme={"system"} $response = $client->replaceAllObjectsWithTransformation( 'INDEX_NAME', [ ['objectID' => '1', 'name' => 'Adam', ], ['objectID' => '2', 'name' => 'Benoit', ], ['objectID' => '3', 'name' => 'Cyril', ], ['objectID' => '4', 'name' => 'David', ], ['objectID' => '5', 'name' => 'Eva', ], ['objectID' => '6', 'name' => 'Fiona', ], ['objectID' => '7', 'name' => 'Gael', ], ['objectID' => '8', 'name' => 'Hugo', ], ['objectID' => '9', 'name' => 'Igor', ], ['objectID' => '10', 'name' => 'Julia', ], ], 3, ); ``` ```python Python theme={"system"} response = client.replace_all_objects_with_transformation( index_name="INDEX_NAME", objects=[ { "objectID": "1", "name": "Adam", }, { "objectID": "2", "name": "Benoit", }, { "objectID": "3", "name": "Cyril", }, { "objectID": "4", "name": "David", }, { "objectID": "5", "name": "Eva", }, { "objectID": "6", "name": "Fiona", }, { "objectID": "7", "name": "Gael", }, { "objectID": "8", "name": "Hugo", }, { "objectID": "9", "name": "Igor", }, { "objectID": "10", "name": "Julia", }, ], batch_size=3, ) ``` ```ruby Ruby theme={"system"} response = client.replace_all_objects_with_transformation( "INDEX_NAME", [ {objectID: "1", name: "Adam"}, {objectID: "2", name: "Benoit"}, {objectID: "3", name: "Cyril"}, {objectID: "4", name: "David"}, {objectID: "5", name: "Eva"}, {objectID: "6", name: "Fiona"}, {objectID: "7", name: "Gael"}, {objectID: "8", name: "Hugo"}, {objectID: "9", name: "Igor"}, {objectID: "10", name: "Julia"} ], 3 ) ``` ```scala Scala theme={"system"} val response = Await.result( client.replaceAllObjectsWithTransformation( indexName = "INDEX_NAME", objects = Seq( JObject(List(JField("objectID", JString("1")), JField("name", JString("Adam")))), JObject(List(JField("objectID", JString("2")), JField("name", JString("Benoit")))), JObject(List(JField("objectID", JString("3")), JField("name", JString("Cyril")))), JObject(List(JField("objectID", JString("4")), JField("name", JString("David")))), JObject(List(JField("objectID", JString("5")), JField("name", JString("Eva")))), JObject(List(JField("objectID", JString("6")), JField("name", JString("Fiona")))), JObject(List(JField("objectID", JString("7")), JField("name", JString("Gael")))), JObject(List(JField("objectID", JString("8")), JField("name", JString("Hugo")))), JObject(List(JField("objectID", JString("9")), JField("name", JString("Igor")))), JObject(List(JField("objectID", JString("10")), JField("name", JString("Julia")))) ), batchSize = 3 ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.replaceAllObjectsWithTransformation( indexName: "INDEX_NAME", objects: [ ["objectID": "1", "name": "Adam"], ["objectID": "2", "name": "Benoit"], ["objectID": "3", "name": "Cyril"], ["objectID": "4", "name": "David"], ["objectID": "5", "name": "Eva"], ["objectID": "6", "name": "Fiona"], ["objectID": "7", "name": "Gael"], ["objectID": "8", "name": "Hugo"], ["objectID": "9", "name": "Igor"], ["objectID": "10", "name": "Julia"], ], batchSize: 3 ) ``` ### Supported indexing actions The Push to Algolia connector supports all values of the [`action`](/doc/rest-api/search/batch) property for batch indexing operations. For `deleteObject`, `delete`, and `clear` actions, the connector skips the transformation and uses [traditional indexing](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/sending-records-in-batches). ## Connector Debugger To debug push operations: * Check incoming events in the [Connector Debugger](https://dashboard.algolia.com/connectors/debugger). The `runID` returned by a successful `push` or `pushTask` request identifies the indexing operation. * To wait for a `push` or `pushTask` operation and receive its result in the response, add the [`watch`](/doc/rest-api/ingestion/push-task) parameter. The response reports successful and failed operations. ## Limitations This connector is subject to the following limitations: * [Connectors limits](/doc/guides/scaling/algolia-service-limits#connectors-limits) * [Transformation limits](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits) ## See also * [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) * [Transform your data with code](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code) # Supabase connector Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/supabase Use the Supabase connector to index data from a Supabase table or view. Use the Supabase connector to index records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) from a Supabase table or view without writing an indexing script. In this quickstart, you create a Supabase source, transform the records, create an Algolia destination, and configure a synchronization task. [How to integrate Algolia with your Supabase project](https://www.youtube.com/embed/sLr6-K7_Av8) ## 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. * **A Supabase project with the sample data in a `public.apparel` table**. Download the [Algolia apparel sample data](https://raw.githubusercontent.com/algolia/quickstarts/main/sample-data/apparel.csv) and follow Supabase's [data import instructions](https://supabase.com/docs/guides/database/import-data). Use `objectID` as the table's primary key. ## Set up the Supabase connector Create a source, transformation, destination, and synchronization task. **1. Choose the Supabase connector** Go to the Algolia dashboard and select your Algolia application. Open the [**Connectors**](https://dashboard.algolia.com/connectors) page. Find [**Supabase** and select **Connect**](https://dashboard.algolia.com/connectors/supabase). Under **Quick setup**, select **Connect Supabase**. **2. Configure the Supabase source** For **Select a project**, choose the project containing the `public.apparel` table. The connector copies **Host**, **Port**, **Session Pooler Port**, **Database Name**, **Username**, and **Password** values from your Supabase project. For **SSL modes**, select **Prefer**. In **Connector name**, enter `Quickstart products Supabase source`, then select **Create source**. **3. Transform the records** This transformation adds a `price_range` attribute to each record. After the connector indexes your records, you can display `price_range` or configure it as a facet. Select **Transform using the code editor** and replace the placeholder **Transformation code** with this function: ```js JavaScript icon=code expandable theme={"system"} async function transform(record, helper) { const price = Number(record.price); if (!Number.isFinite(price)) { return record; } if (price < 25) { record.price_range = "Under $25"; } else if (price < 50) { record.price_range = "$25 to $49"; } else if (price < 100) { record.price_range = "$50 to $99"; } else { record.price_range = "$100 and up"; } return record; } ``` Select **Save**. **4. Choose the destination index** Under **Configure your destination**, select **Create a new destination**. Under **Search**, enter `quickstart-products` as the index name. > [!WARNING] > > If you use an existing index, > running the connector overwrites the records and settings in that index. **5. Create the destination** Under **Index credentials**, select **Create one for me**. To use an existing API key, choose one with the `addObject`, `deleteIndex`, and `editSettings` [access control list permissions](/doc/guides/security/api-keys#access-control-list-acl). In **Name**, enter `Supabase quickstart products destination`, then select **Create destination**. **6. Create and run an on-demand synchronization task** Under **Configure your task**, select **On demand** and clear **Scheduled**. Select **Full reindexing**. Select `apparel` as the source table. Select all columns. For **ObjectID**, select `objectID`. Select **Create task**, then **Run**, and wait for the task to finish. **7. Verify the indexed records** Open the [`quickstart-products` index in the Algolia dashboard](https://dashboard.algolia.com/explorer/browse/quickstart-products). Check that the index contains the imported product records, with attributes such as `title`, `description`, `product_type`, `price`, `price_range`, and `showcase_image`. > [!NOTE] > > You can use this index as the data source for > [Build your first search experience](/doc/guides/get-started/quickstart). > > Before you build the UI, > [configure `product_type` as an attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) > in the `quickstart-products` index. ## Index your own Supabase data To index your own data: * Use a populated Supabase table or view that the connector credentials can read. * Follow the quickstart steps to create the [source](#source), [destination](#destination), and [task](#task). Replace the sample project, table, credentials, and destination index with your own values. * Review or remove the sample transformation so that it matches your record structure. When you configure the task, select the columns you want to include in your Algolia records. * Select the column to use as the Algolia `objectID`. This column must be present in every row, and its value must be unique and stable. ### Supabase database credentials The quickstart uses the database settings imported from your Supabase project. For your own data, create a dedicated Postgres login role for the connector. Grant the role permission to: * Connect to the database. * Use the schema that contains the source data. * Run `SELECT` queries on each table or view the connector needs to read. If Row Level Security (RLS) is enabled, add a `SELECT` policy that lets the connector role read the rows you want to index. For more information, see [Postgres roles](https://supabase.com/docs/guides/database/postgres/roles) and [Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security). ### Configure the connector username Supabase's session pooler details show the username for the default `postgres` role. When you configure the source, replace `postgres` with the dedicated connector role name, but keep the project reference after the period. For example, for a connector role named `algolia_connector`, replace `postgres.PROJECT_REFERENCE` with `algolia_connector.PROJECT_REFERENCE`. ### Combine data with a Postgres view An Algolia record can combine data from several Supabase tables. For example, a product record might combine product details with review ratings and recent order data. If your source data spans multiple tables, create a Postgres view that joins and aggregates the required data. Then, select the view as the source when you configure the synchronization [task](#task). Make sure each row in the view includes a unique, stable column that you can use as the Algolia `objectID`. This example creates a view that combines data from the `apparel` and `reviews` tables. ```sql SQL icon="database" theme={"system"} create view apparel_search with (security_invoker = true) as select p."objectID", p.title, p.description, p.price, avg(r.rating) as average_rating, count(distinct r.id) as review_count from apparel as p left join reviews as r on r.product_id = p."objectID" group by p."objectID", p.title, p.description, p.price; ``` Create a separate synchronization task for each table or view you want to index. For more information, see [Prepare your data in Supabase](https://supabase.com/blog/algolia-connector-for-supabase#1-prepare-your-data-in-supabase). ### Transformations Use transformations to add computed attributes or change values before the connector indexes your records. To use a transformed attribute for search, faceting, or ranking, update the relevant [index settings](/doc/api-reference/settings-api-parameters), such as `searchableAttributes`, `attributesForFaceting`, or `customRanking`. > [!NOTE] > > Test transformations with repeated task runs. > Transformations that read from and write to the same attribute, > or extract a value and then delete the source attribute, > can produce different results when the task runs again. ### Synchronization schedule After you create a task, you can [edit it in the Algolia dashboard](https://dashboard.algolia.com/connectors/tasks) to choose when the connector runs: * **On demand**. Run the connector manually. * **Scheduled**. Select a predefined schedule or enter a custom [cron expression](https://crontab.guru/). ### Indexing strategy You can choose how the connector updates your index. For information about full reindexing, full record updates, and partial record updates, see [Data synchronization strategies](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies). ## Limitations This connector is subject to the following limitations: * [Connectors limits](/doc/guides/scaling/algolia-service-limits#connectors-limits) * [Transformation limits](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits) ## See also * [Connector overview](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors) * [Prepare your records for indexing](/doc/guides/sending-and-managing-data/prepare-your-data) * [Transform your data with code](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code) # AI-assisted transformations Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/ai-assisted-transformations How AI can help build no-code transformations. Instead of using the form-based [no-code transformation builder](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-without-code), describe the transformation you need in plain English, and AI will generate the configuration for you. This is useful for complex conditions or computed values. ![Screenshot of the AI transformation interface with a text box prompting the user to describe their desired change in plain English](https://mintcdn.com/algolia/BaxS3Doxn613Z9Q0/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/ncdt_ai_hero.png?fit=max\&auto=format\&n=BaxS3Doxn613Z9Q0\&q=85\&s=6315d8f8ee4fce159b24566ecd0c59da) ## When to use AI-Assisted transformations This feature is for users who need to adjust data without writing code. It helps when you want to: * **Add new attributes.** For example, add a discount percentage field from the full price and the sale price. * **Remove fields** that are no longer relevant. * **Filter records.** For example, to exclude test products or filter by status. * **Compute new values** from existing attributes. For example, create a ranking signal by combining popularity and newness. ### Key benefits * **Simplifies complex logic.** You don't need to build nested "if/then" rules manually: AI handles it. * **Faster iteration.** Quickly test and refine transformations using natural language. * **You stay in control.** AI fills in the form, but you can review and adjust it before applying. * **Preview before applying.** See how your data will look before committing changes. ## Use AI to build a transformation 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 **Data sources**. 3. Open the [**Connectors**](https://dashboard.algolia.com/connectors) page and select a connector. 4. Configure your data source by selecting the appropriate options. 5. Click **Create source** 6. Select **Create a new transformation** and then select **Transform without code**. 7. Click **AI Assist** 8. Describe your transformation by typing it in plain English. For example: "If price is greater than 100, add the label 'expensive'." ![Screenshot of the 'Delete Attribute: inventory' step with a text input and options to add conditions or groups.](https://mintcdn.com/algolia/BaxS3Doxn613Z9Q0/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/ncdt_ai_english_request.png?fit=max\&auto=format\&n=BaxS3Doxn613Z9Q0\&q=85\&s=6bce3a671290d5f6ad1223fcb29f7c35) 9. **Review the suggestion.** AI generates a transformation configuration based on your prompt. You'll see a preview showing how your data will change before you apply it. Refine the transformation either by adjusting your prompt or by editing the form fields. ![Screenshot showing AI-generated transformation preview with editable field](https://mintcdn.com/algolia/BaxS3Doxn613Z9Q0/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/ncdt_ai_preview.png?fit=max\&auto=format\&n=BaxS3Doxn613Z9Q0\&q=85\&s=ed2007586e9eed2d1a3754ca61d094c7) 10. Name the transformation and save it. 11. Complete the remaining connector workflow: select a destination, create the task that will schedule the job (and its indexing strategy), and then click **Run**. The connector then runs the transformation as part of the workflow. ![Screenshot of a task configuration interface with 'On demand' and 'Partial record updates' selected.](https://mintcdn.com/algolia/BaxS3Doxn613Z9Q0/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/ncdt_ai_finish.png?fit=max\&auto=format\&n=BaxS3Doxn613Z9Q0\&q=85\&s=604c5c1eaa476c30d3b28d0288c07bac) ## Limitations The AI assistant has a few constraints to keep in mind when using it with No Code data transformations. * **Limited transformation types.** Only these transformation types are supported: *Add Attribute*, *Remove Attribute*, *Filter Records*, and *Compute New Value*. It doesn't cover advanced operations like joins, type conversions, or hierarchical restructuring. * **Accuracy of suggestions.** The perfect transformation may not always be generated. Review and, if needed, refine the suggestion before applying it. This "human in the loop" step is essential. * **Prompt constraints.** Prompts must be concise and specific. Long or ambiguous descriptions may lead to incomplete or incorrect transformations. * **Performance limits.** Transformations are still bound by Algolia's [service limits](/doc/guides/scaling/algolia-service-limits) (for example, record size, memory, and execution timeout). AI can't bypass these constraints. * **Security and data handling.** Only the record schema and your written prompt are sent to the AI model, not full record data. This means if you include sensitive data in a prompt, it *could* be processed, so avoid adding personally identifiable information or confidential values. * **Over-reliance.** Use AI to help, not replace your judgment. Confirm that the previewed changes are correct, as incorrect logic could affect search relevance. * **Language coverage.** Prompts are optimized for English. Support for other languages is limited. # Enrich your records with Fetch Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/enrich-your-records-with-fetch Enrich your records with Fetch Helper Functions. Fetch lets you enrich your Algolia data directly within a code transformation. Using a JavaScript function within your transformation, you can retrieve information from third-party APIs to enhance the quality and relevance of your search results. Using Fetch affects your record ingestion rate. To avoid slowing down your indexing process, ensure you use third-party APIs that are fast and reliable. Use Fetch for operations that can be completed quickly. This feature isn't available on every plan. Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. ## Create a code transformation 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 **Data sources**. 3. Open the [**Connectors**](https://dashboard.algolia.com/connectors) page. 4. Select a connector and click **Connect** to start creating your task. 5. Create a new source or select an existing one. 6. To create a transformation function, click **Create transformation**. ## Retrieve and store secrets > [!NOTE] > > To call an external API, you typically need an API key or other credential type (such as basic access authentication or OAuth 2.0). > You can store these credentials as a secret and reference them in your transformation code. 1. Retrieve the secret from the third-party API. 2. In [your code transformation](#create-a-code-transformation), click **Secret**. 3. Paste in the third-party API key, give it name, then click **Add**. You can now add the secret to your transformation code. ```js JavaScript theme={"system"} const mySecretValue = helper.secrets.get("mySecretName"); ``` ## How to use fetch helper functions 1. Click **Helper Functions**. 2. Select **Fetch / API** 3. Select a helper and click **Add**. You can either use third-party APIs directly or use a generic Fetch function. ### Third-party APIs | Helper | Description | | - | - | | OpenAI - Text Generation | Generate text based on a prompt. You need an [OpenAI](https://openai.com/api/) API key to use this function. For more information, see the [OpenAI text generation documentation](https://platform.openai.com/docs/guides/text-generation). | | OpenAI - Vision | Generate a description or tags for an image. You need an OpenAI API key to use this function. For more information, see the [OpenAI vision documentation](https://platform.openai.com/docs/guides/vision). | | Convert currency | Convert a currency to another. You need a [CurrencyAPI](https://currencyapi.net/) API key to use this function. For more information, see the [CurrencyAPI documentation](https://currencyapi.net/documentation) . | | Translate text | Translate text from one language to another. You need a [DeepL](https://www.deepl.com/en/products/api) API key to use this function. For more information, see the [DeepL documentation](https://currencyapi.net/documentation). | ### Generic function This function lets you connect to any service with an API. | Helper type | Description | | - | - | | Fetch with API key (in header) | Connect to any service with an API key in the header. | | Fetch with API key (in URL) | Connect to any service with an API key in the query. | | Fetch with Basic Auth | Connect to any service with [basic access authentication](https://wikipedia.org/wiki/Basic_access_authentication). | > [!TIP] > > There's a [timeout and response size limit](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits) on all transformations that include Fetch requests. # Import data from the dashboard Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-from-the-dashboard Use a no-code connector or a dashboard file upload to index your data in Algolia. The Algolia dashboard has two methods to index records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) without code: * A **connector** reads your data from a source you host, then updates 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.) on a schedule or on demand. Use a connector for data that changes. * A **file upload** sends one file from your computer to your index. Use a file upload for sample data. ## Index your data with connectors You point a connector at your data and choose the index it keeps up to date. Algolia manages the ingestion pipeline, so you don't have to write your own. You can also apply [transformations](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-without-code) to your records before indexing. For files, use one of these connectors: * The [CSV connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/csv) indexes a hosted CSV file. * The [JSON connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/json) indexes a hosted JSON file. For more information, see [Connectors](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors). ## Upload a file for a one-time import Use a file upload to index a local file—you don't need to host it. Keep your files smaller than 100 MB. Larger files can make your browser unresponsive. To index them, use a connector or the [API](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api). ### Import file formats You can import JSON, CSV, and TSV files. ```json JSON theme={"system"} [ { "firstname": "Jimmie", "lastname": "Barninger" }, { "firstname": "Warren", "lastname": "Speach" } ] ``` ```csv CSV theme={"system"} firstname,lastname Jimmie,Barninger Warren,Speach ``` ```tsv TSV theme={"system"} "firstname" "lastname" "Jimmie" "Barninger" "Warren" "Speach" ``` ### Upload the file An upload adds records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) to the index you're viewing, and never deletes records. Every record in your index has a unique [object ID](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#unique-record-identifiers). If a record in your file has the same object ID as a record in your index, the upload replaces it. If a record in your file doesn't have an object ID, Algolia generates one from the record's content. 1. Click **Add records > Upload file**. On an empty index, click **Upload records > Upload file**. 2. Drop the file into the **Upload records** box, or select the file on your file system. Then click **Upload**. When Algolia indexes your data, the dashboard shows a success notification. ### Create a new index To upload your file to a new index, create the index first: 1. In the Algolia dashboard, go to the [**Indices** tab](https://dashboard.algolia.com/explorer/browse). Click **New > Index**. 2. Enter a name for your index. Click **Create**. # Importing with the API Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api How to index your data to Algolia using an API client. Algolia doesn't directly search your data sources. Instead, you upload the parts of your data that are relevant for search to Algolia. Algolia stores this data in 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.): a data structure optimized for fast search. * For more information about preparing your data for indexing, see [Format and structure your data](/doc/guides/sending-and-managing-data/prepare-your-data). * For alternatives to the API for pushing data, see [Send your data to Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data). ## Required credentials To send data to Algolia, you need an Application ID and a valid API key (with [`addObjects`](/doc/guides/security/api-keys#access-control-list-acl) permission). You can find them in the **API Keys** section of [Algolia's dashboard](https://dashboard.algolia.com/account/api-keys/all). * **Application ID**. Your Application ID is what Algolia uses to identify your app, where all your indices live. * **API key**. [API keys](/doc/guides/security/api-keys) control access to Algolia's API and determine what you're allowed to do, such as searching an index, or adding new records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). For better security, create specific API keys with minimal [permissions](/doc/guides/security/api-keys#access-control-list-acl) for indexing tasks, and use them only in server-side code. Keep your indexing API keys secret. **Only use the [Admin API key](/doc/guides/security/api-keys#admin-api-key) to create other API keys**. Don't use the Admin API key in your apps. ## Fetch your data Before sending anything to Algolia, **you need to retrieve your data**. You can do this in several ways, depending on the nature of your app. ### From a database ```cs C# theme={"system"} IEnumerable FetchDataFromDataBase() { // Fetch data from your database } var actors = FetchDataFromDataBase(); ``` ```go Go theme={"system"} func fetchDataFromDatabase() []Actor { // Fetch data from your database } func main() { records := fetchDataFromDatabase() } ``` ```java Java theme={"system"} public List fetchDataFromDatabase() { // Fetch data from your database } List actors = fetchDataFromDataBase(); ``` ```js JavaScript theme={"system"} const fetchDataFromDatabase = () => { // Fetch data from your database. }; const records = fetchDataFromDatabase(); ``` ```kt Kotlin theme={"system"} fun fetchFromDatabase(): List { // Fetch data from your database } val actors: List = fetchFromDatabase() ``` ```php PHP theme={"system"} function fetchDataFromDatabase() { // Fetch data from your database } $records = fetchDataFromDatabase(); ``` ```python Python theme={"system"} def fetch_data_from_database(): data = {} # Fetch data from your database return data records = fetch_data_from_database() ``` ```ruby Ruby theme={"system"} def fetch_data_from_database # Fetch data from your database end records = fetch_data_from_database ``` ```scala Scala theme={"system"} def fetchDataFromDatabase(): Iterable[Actor] = { // Fetch data from your database } val actors = fetchDataFromDatabase() ``` ```swift Swift theme={"system"} func fetchDataFromDataBase() -> [[String: Any]] { // Fetch data from your database } let records = fetchDataFromDataBase() ``` ### From a file You can use this [actors dataset](https://github.com/algolia/datasets/blob/master/movies/actors.json) to test this out. ```cs C# theme={"system"} using System.Collections.Generic; using System.IO; using Algolia.Search.Clients; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; public class Actor { public string Name { get; set; } public string ObjectID { get; set; } public int Rating { get; set; } public string ImagePath { get; set; } public string AlternativePath { get; set; } } public class AlgoliaActorsIndexer { private SearchClient client; private SearchIndex index; public AlgoliaActorsIndexer(string appId, string apiKey, string indexName) { client = new SearchClient(appId, apiKey); index = client.InitIndex(indexName); } public void IndexActorsFromFile(string filePath) { var settings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() } }; IEnumerable actors = JsonConvert.DeserializeObject>(File.ReadAllText(filePath), settings); index.SaveObjects(actors); } } // Usage public class Program { public static void Main(string[] args) { AlgoliaActorsIndexer indexer = new AlgoliaActorsIndexer("undefined", "undefined", "actors"); indexer.IndexActorsFromFile("actors.json"); } } ``` ```go Go theme={"system"} package main import ( "encoding/json" "io/ioutil" ) type Actor struct { Name string `json:"name"` Rating int `json:"rating"` ImagePath string `json:"image_path"` AlternativeName string `json:"alternative_name"` ObjectID string `json:"objectID"` } func main() { var actors []Actor data, _ := ioutil.ReadFile("actors.json") _ = json.Unmarshal(data, &actors) } ``` ```java Java theme={"system"} import java.io.FileInputStream; import java.io.InputStream; import com.fasterxml.jackson.databind.ObjectMapper; public class Actor { // Getters/Setters omitted private String name; private String objectID; private int rating; private String imagePath; private String alternativePath; } ObjectMapper objectMapper = Defaults.getObjectMapper(); InputStream input = new FileInputStream("actors.json"); Actor[] actors = objectMapper.readValue(input, Actor[].class); ``` ```js JavaScript theme={"system"} const records = require("./actors.json"); ``` ```kt Kotlin theme={"system"} @Serializable data class Actor( val name: String, val rating: Int, val imagePath: String, val alternativePath: String, override val objectID: ObjectID ) : Indexable val string = File("actors.json").readText() val actors: List = Json.plain.parse(Actor.serializer().list, string) ``` ```php PHP theme={"system"} $records = json_decode(file_get_contents('actors.json'), true); ``` ```python Python theme={"system"} import json with open('actors.json') as f: records = json.load(f) ``` ```ruby Ruby theme={"system"} require 'json' file = File.read('actors.json') records = JSON.parse(file) ``` ```scala Scala theme={"system"} import org.json4s._ import org.json4s.native.JsonMethods._ case class Actor(name: String, rating: Int, image_path: String, alternative_path: Option[String], objectID: String) object Main { def main(args: Array[String]): Unit = { val json = parse(new FileInputStream("actors.json")).extract[Seq[Actor]] } } ``` ```swift Swift theme={"system"} let filePath = Bundle.main.path(forResource: "actors", ofType: "json")! let contentData = FileManager.default.contents(atPath: filePath)! let records = try! JSONSerialization.jsonObject(with: contentData, options: []) as! [[String: Any]] ``` ### From the source code directly Only use this method for exploration purposes or if you have a small amount of data. ```cs C# theme={"system"} using System.Collections.Generic; public class Actor { public string Name { get; set; } } public class Program { public static void Main() { IEnumerable records = new List { new Actor { Name = "Tom Cruise" }, new Actor { Name = "Scarlett Johansson" } }; } } ``` ```go Go theme={"system"} actors := []Actor{ {Name: "Tom Cruise"}, {Name: "Scarlett Johansson"}, } ``` ```java Java theme={"system"} public class Person { private String name; public Person() {} public String getName() { return name; } public Person setName(String name) { this.name = name; return this; } } ArrayList persons = new ArrayList() {{ add(new Person().setName("Tom Cruise")); add(new Person().setName("Scarlett Johansson")); }}; ``` ```js JavaScript theme={"system"} const records = [{ name: "Tom Cruise" }, { name: "Scarlett Johansson" }]; ``` ```kt Kotlin theme={"system"} val records = listOf(json { "name" to "Tom Cruise" }, json { "name" to "Scarlett Johansson" }) ``` ```php PHP theme={"system"} $records = [ ['name' => 'Tom Cruise'], ['name' => 'Scarlett Johansson'] ]; ``` ```python Python theme={"system"} records = [ {'name': 'Tom Cruise'}, {'name': 'Scarlett Johansson'} ] ``` ```ruby Ruby theme={"system"} records = [ { name: 'Tom Cruise' }, { name: 'Scarlett Johansson' } ] ``` ```scala Scala theme={"system"} case class Person(name: String) val records = Seq( Person("Tom Cruise"), Person("Scarlett Johansson"), ) ``` ```swift Swift theme={"system"} let records: [[String: Any]] = [ ["name": "Tom Cruise"], ["name": "Scarlett Johansson"], ] ``` ## Send the data to Algolia Once the records are ready, you can push them to Algolia using the [`saveObjects`](/doc/libraries/sdk/v1/methods/save-objects) method. ```cs C# theme={"system"} List response = await client.SaveObjectsAsync( "INDEX_NAME", new List { new Dictionary { { "objectID", "1" }, { "name", "Adam" } }, new Dictionary { { "objectID", "2" }, { "name", "Benoit" } }, } ); ``` ```go Go theme={"system"} response, err := client.SaveObjects( "INDEX_NAME", []map[string]any{{"objectID": "1", "name": "Adam"}, {"objectID": "2", "name": "Benoit"}}) if err != nil { // handle the eventual error panic(err) } // response is a slice of search.BatchResponse ``` ```java Java theme={"system"} List response = client.saveObjects( "INDEX_NAME", Arrays.asList( new HashMap() { { put("objectID", "1"); put("name", "Adam"); } }, new HashMap() { { put("objectID", "2"); put("name", "Benoit"); } } ) ); ``` ```js JavaScript theme={"system"} const response: BatchResponse[] = await client.saveObjects({ indexName: 'cts_e2e_saveObjects_javascript', objects: [ { objectID: '1', name: 'Adam' }, { objectID: '2', name: 'Benoit' }, ], }); ``` ```kotlin Kotlin theme={"system"} var response: List = client.saveObjects( indexName = "INDEX_NAME", objects = listOf( buildJsonObject { put("objectID", JsonPrimitive("1")) put("name", JsonPrimitive("Adam")) }, buildJsonObject { put("objectID", JsonPrimitive("2")) put("name", JsonPrimitive("Benoit")) }, ), ) ``` ```php PHP theme={"system"} /** @var array $response */ $response = $client->saveObjects( 'INDEX_NAME', [ ['objectID' => '1', 'name' => 'Adam', ], ['objectID' => '2', 'name' => 'Benoit', ], ], ); ``` ```python Python theme={"system"} response: list[BatchResponse] = client.save_objects( index_name="INDEX_NAME", objects=[ { "objectID": "1", "name": "Adam", }, { "objectID": "2", "name": "Benoit", }, ], ) ``` ```ruby Ruby theme={"system"} response = client.save_objects( "INDEX_NAME", [{objectID: "1", name: "Adam"}, {objectID: "2", name: "Benoit"}] ) # response is an array of BatchResponse objects ``` ```scala Scala theme={"system"} val response: Seq[BatchResponse] = Await.result( client.saveObjects( indexName = "INDEX_NAME", objects = Seq( JObject(List(JField("objectID", JString("1")), JField("name", JString("Adam")))), JObject(List(JField("objectID", JString("2")), JField("name", JString("Benoit")))) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response: [BatchResponse] = try await client.saveObjects( indexName: "INDEX_NAME", objects: [["objectID": "1", "name": "Adam"], ["objectID": "2", "name": "Benoit"]] ) ``` ### Send your data in batches For better performance, send several records at once instead of one by one. If you have many records to index, [send them in batches](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/sending-records-in-batches). ## Next steps After indexing your data, you can [configure relevance settings](/doc/guides/managing-results/relevance-overview). # Incremental updates Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/incremental-updates How to keep your data in sync with Algolia. After your initial upload, keep 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.) up to date with incremental updates. Track additions, changes, and deletions in your data source, then apply them to the corresponding records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in Algolia. ## Track each record with a unique identifier Incremental updates rely on the `objectID` attribute to match your source data to the right record. Derive each record's `objectID` from the source item's unique identifier and keep it stable. Changing the `objectID` can create a second record instead of updating the existing one. For more information, see [Unique record identifiers](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#unique-record-identifiers). ## Choose how to send updates Use a [connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors) to schedule updates from a supported source, such as a database or a hosted JSON or CSV file. To send updates from your own code, use the API methods in the following sections. If you want Algolia to transform records before indexing them, use [Push to Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push) instead of calling the Search API directly. ## Add records When you add data to your source, use the [`saveObjects`](/doc/libraries/sdk/methods/search/save-objects) method to send the corresponding records to Algolia. To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install). ```cs C# theme={"system"} List response = await client.SaveObjectsAsync( "INDEX_NAME", new List { new Dictionary { { "objectID", "1" }, { "name", "Adam" } }, new Dictionary { { "objectID", "2" }, { "name", "Benoit" } }, } ); ``` ```go Go theme={"system"} response, err := client.SaveObjects( "INDEX_NAME", []map[string]any{{"objectID": "1", "name": "Adam"}, {"objectID": "2", "name": "Benoit"}}) if err != nil { // handle the eventual error panic(err) } // response is a slice of search.BatchResponse ``` ```java Java theme={"system"} List response = client.saveObjects( "INDEX_NAME", Arrays.asList( new HashMap() { { put("objectID", "1"); put("name", "Adam"); } }, new HashMap() { { put("objectID", "2"); put("name", "Benoit"); } } ) ); ``` ```js JavaScript theme={"system"} const response: BatchResponse[] = await client.saveObjects({ indexName: 'cts_e2e_saveObjects_javascript', objects: [ { objectID: '1', name: 'Adam' }, { objectID: '2', name: 'Benoit' }, ], }); ``` ```kotlin Kotlin theme={"system"} var response: List = client.saveObjects( indexName = "INDEX_NAME", objects = listOf( buildJsonObject { put("objectID", JsonPrimitive("1")) put("name", JsonPrimitive("Adam")) }, buildJsonObject { put("objectID", JsonPrimitive("2")) put("name", JsonPrimitive("Benoit")) }, ), ) ``` ```php PHP theme={"system"} /** @var array $response */ $response = $client->saveObjects( 'INDEX_NAME', [ ['objectID' => '1', 'name' => 'Adam', ], ['objectID' => '2', 'name' => 'Benoit', ], ], ); ``` ```python Python theme={"system"} response: list[BatchResponse] = client.save_objects( index_name="INDEX_NAME", objects=[ { "objectID": "1", "name": "Adam", }, { "objectID": "2", "name": "Benoit", }, ], ) ``` ```ruby Ruby theme={"system"} response = client.save_objects( "INDEX_NAME", [{objectID: "1", name: "Adam"}, {objectID: "2", name: "Benoit"}] ) # response is an array of BatchResponse objects ``` ```scala Scala theme={"system"} val response: Seq[BatchResponse] = Await.result( client.saveObjects( indexName = "INDEX_NAME", objects = Seq( JObject(List(JField("objectID", JString("1")), JField("name", JString("Adam")))), JObject(List(JField("objectID", JString("2")), JField("name", JString("Benoit")))) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response: [BatchResponse] = try await client.saveObjects( indexName: "INDEX_NAME", objects: [["objectID": "1", "name": "Adam"], ["objectID": "2", "name": "Benoit"]] ) ``` ## Update records You can update records in two ways: * Replace the entire record * Update specific attributes ### Replace old records To replace an existing record, use `saveObjects` with its `objectID` and the complete updated record. Algolia replaces the entire record, removing any existing attributes you omit. Use full record updates when your system can send the complete record, even if you don't know which attributes changed. ```cs C# theme={"system"} List response = await client.SaveObjectsAsync( "INDEX_NAME", new List { new Dictionary { { "objectID", "1" }, { "name", "Adam" } }, new Dictionary { { "objectID", "2" }, { "name", "Benoit" } }, } ); ``` ```go Go theme={"system"} response, err := client.SaveObjects( "INDEX_NAME", []map[string]any{{"objectID": "1", "name": "Adam"}, {"objectID": "2", "name": "Benoit"}}) if err != nil { // handle the eventual error panic(err) } // response is a slice of search.BatchResponse ``` ```java Java theme={"system"} List response = client.saveObjects( "INDEX_NAME", Arrays.asList( new HashMap() { { put("objectID", "1"); put("name", "Adam"); } }, new HashMap() { { put("objectID", "2"); put("name", "Benoit"); } } ) ); ``` ```js JavaScript theme={"system"} const response: BatchResponse[] = await client.saveObjects({ indexName: 'cts_e2e_saveObjects_javascript', objects: [ { objectID: '1', name: 'Adam' }, { objectID: '2', name: 'Benoit' }, ], }); ``` ```kotlin Kotlin theme={"system"} var response: List = client.saveObjects( indexName = "INDEX_NAME", objects = listOf( buildJsonObject { put("objectID", JsonPrimitive("1")) put("name", JsonPrimitive("Adam")) }, buildJsonObject { put("objectID", JsonPrimitive("2")) put("name", JsonPrimitive("Benoit")) }, ), ) ``` ```php PHP theme={"system"} /** @var array $response */ $response = $client->saveObjects( 'INDEX_NAME', [ ['objectID' => '1', 'name' => 'Adam', ], ['objectID' => '2', 'name' => 'Benoit', ], ], ); ``` ```python Python theme={"system"} response: list[BatchResponse] = client.save_objects( index_name="INDEX_NAME", objects=[ { "objectID": "1", "name": "Adam", }, { "objectID": "2", "name": "Benoit", }, ], ) ``` ```ruby Ruby theme={"system"} response = client.save_objects( "INDEX_NAME", [{objectID: "1", name: "Adam"}, {objectID: "2", name: "Benoit"}] ) # response is an array of BatchResponse objects ``` ```scala Scala theme={"system"} val response: Seq[BatchResponse] = Await.result( client.saveObjects( indexName = "INDEX_NAME", objects = Seq( JObject(List(JField("objectID", JString("1")), JField("name", JString("Adam")))), JObject(List(JField("objectID", JString("2")), JField("name", JString("Benoit")))) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response: [BatchResponse] = try await client.saveObjects( indexName: "INDEX_NAME", objects: [["objectID": "1", "name": "Adam"], ["objectID": "2", "name": "Benoit"]] ) ``` ### Update a subset of the record Partial updates let you change specific attributes without sending the complete record. This is useful when different systems manage different attributes of the same record. Use the [`partialUpdateObjects`](/doc/libraries/sdk/methods/search/partial-update-objects) method and pass each record's `objectID` with the attributes to update. Other attributes in the record remain unchanged. If an attribute you update contains a JSON object, include every value you want to keep in that object. Algolia replaces the object rather than merging its contents. The following example sets `createIfNotExists` to `true`, so Algolia creates a record if the specified `objectID` doesn't exist. ```cs C# theme={"system"} List response = await client.PartialUpdateObjectsAsync( "INDEX_NAME", new List { new Dictionary { { "objectID", "1" }, { "name", "Adam" } }, new Dictionary { { "objectID", "2" }, { "name", "Benoit" } }, }, true ); ``` ```go Go theme={"system"} response, err := client.PartialUpdateObjects( "INDEX_NAME", []map[string]any{{"objectID": "1", "name": "Adam"}, {"objectID": "2", "name": "Benoit"}}, search.WithCreateIfNotExists(true)) if err != nil { // handle the eventual error panic(err) } // response is a slice of search.BatchResponse ``` ```java Java theme={"system"} List response = client.partialUpdateObjects( "INDEX_NAME", Arrays.asList( new HashMap() { { put("objectID", "1"); put("name", "Adam"); } }, new HashMap() { { put("objectID", "2"); put("name", "Benoit"); } } ), true ); ``` ```js JavaScript theme={"system"} const response: BatchResponse[] = await client.partialUpdateObjects({ indexName: 'cts_e2e_partialUpdateObjects_javascript', objects: [ { objectID: '1', name: 'Adam' }, { objectID: '2', name: 'Benoit' }, ], createIfNotExists: true, }); ``` ```kotlin Kotlin theme={"system"} var response: List = client.partialUpdateObjects( indexName = "INDEX_NAME", objects = listOf( buildJsonObject { put("objectID", JsonPrimitive("1")) put("name", JsonPrimitive("Adam")) }, buildJsonObject { put("objectID", JsonPrimitive("2")) put("name", JsonPrimitive("Benoit")) }, ), createIfNotExists = true, ) ``` ```php PHP theme={"system"} /** @var array $response */ $response = $client->partialUpdateObjects( 'INDEX_NAME', [ ['objectID' => '1', 'name' => 'Adam', ], ['objectID' => '2', 'name' => 'Benoit', ], ], true, ); ``` ```python Python theme={"system"} response: list[BatchResponse] = client.partial_update_objects( index_name="INDEX_NAME", objects=[ { "objectID": "1", "name": "Adam", }, { "objectID": "2", "name": "Benoit", }, ], create_if_not_exists=True, ) ``` ```ruby Ruby theme={"system"} response = client.partial_update_objects( "INDEX_NAME", [{objectID: "1", name: "Adam"}, {objectID: "2", name: "Benoit"}], true ) # response is an array of BatchResponse objects ``` ```scala Scala theme={"system"} val response: Seq[BatchResponse] = Await.result( client.partialUpdateObjects( indexName = "INDEX_NAME", objects = Seq( JObject(List(JField("objectID", JString("1")), JField("name", JString("Adam")))), JObject(List(JField("objectID", JString("2")), JField("name", JString("Benoit")))) ), createIfNotExists = true ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response: [BatchResponse] = try await client.partialUpdateObjects( indexName: "INDEX_NAME", objects: [["objectID": "1", "name": "Adam"], ["objectID": "2", "name": "Benoit"]], createIfNotExists: true ) ``` ## Delete records Whenever you delete data in your data source, you can delete the corresponding record from Algolia with [`deleteObjects`](/doc/libraries/sdk/methods/search/delete-objects). ```cs C# theme={"system"} List response = await client.DeleteObjectsAsync( "INDEX_NAME", new List { "1", "2" } ); ``` ```go Go theme={"system"} response, err := client.DeleteObjects( "INDEX_NAME", []string{"1", "2"}) if err != nil { // handle the eventual error panic(err) } // response is a slice of search.BatchResponse ``` ```java Java theme={"system"} List response = client.deleteObjects("INDEX_NAME", Arrays.asList("1", "2")); ``` ```js JavaScript theme={"system"} const response: BatchResponse[] = await client.deleteObjects({ indexName: 'INDEX_NAME', objectIDs: ['1', '2'], }); ``` ```kotlin Kotlin theme={"system"} var response: List = client.deleteObjects(indexName = "INDEX_NAME", objectIDs = listOf("1", "2")) ``` ```php PHP theme={"system"} /** @var array $response */ $response = $client->deleteObjects( 'INDEX_NAME', [ '1', '2', ], ); ``` ```python Python theme={"system"} response: list[BatchResponse] = client.delete_objects( index_name="INDEX_NAME", object_ids=[ "1", "2", ], ) ``` ```ruby Ruby theme={"system"} response = client.delete_objects("INDEX_NAME", ["1", "2"]) # response is an array of BatchResponse objects ``` ```scala Scala theme={"system"} val response: Seq[BatchResponse] = Await.result( client.deleteObjects( indexName = "INDEX_NAME", objectIDs = Seq("1", "2") ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response: [BatchResponse] = try await client.deleteObjects( indexName: "INDEX_NAME", objectIDs: ["1", "2"] ) ``` ### Delete by query To delete records that match an attribute value, use the [`deleteBy`](/doc/libraries/sdk/methods/search/delete-by) method. Specify the condition in the request's [`filters`](/doc/rest-api/search/delete-by#body-filters) parameter. For example, `brand:brandName` deletes all records whose `brand` attribute is `brandName`. To filter on the `brand` string attribute, add it to your index's [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting). ```cs C# theme={"system"} var response = await client.DeleteByAsync( "INDEX_NAME", new DeleteByParams { Filters = "brand:brandName" } ); ``` ```dart Dart theme={"system"} final response = await client.deleteBy( indexName: "INDEX_NAME", deleteByParams: DeleteByParams( filters: "brand:brandName", ), ); ``` ```go Go theme={"system"} response, err := client.DeleteBy(client.NewApiDeleteByRequest( "INDEX_NAME", search.NewEmptyDeleteByParams().SetFilters("brand:brandName"))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.deleteBy("INDEX_NAME", new DeleteByParams().setFilters("brand:brandName")); ``` ```js JavaScript theme={"system"} const response = await client.deleteBy({ indexName: 'theIndexName', deleteByParams: { filters: 'brand:brandName' } }); ``` ```kotlin Kotlin theme={"system"} var response = client.deleteBy( indexName = "INDEX_NAME", deleteByParams = DeleteByParams(filters = "brand:brandName"), ) ``` ```php PHP theme={"system"} $response = $client->deleteBy( 'INDEX_NAME', ['filters' => 'brand:brandName', ], ); ``` ```python Python theme={"system"} response = client.delete_by( index_name="INDEX_NAME", delete_by_params={ "filters": "brand:brandName", }, ) ``` ```ruby Ruby theme={"system"} response = client.delete_by("INDEX_NAME", Algolia::Search::DeleteByParams.new(filters: "brand:brandName")) ``` ```scala Scala theme={"system"} val response = Await.result( client.deleteBy( indexName = "INDEX_NAME", deleteByParams = DeleteByParams( filters = Some("brand:brandName") ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.deleteBy( indexName: "INDEX_NAME", deleteByParams: DeleteByParams(filters: "brand:brandName") ) ``` > [!NOTE] > > `deleteBy` is resource-intensive. > If you know the records' object IDs, use `deleteObjects` for better performance. ## Manage individual records in the dashboard For one-off changes, you can add, edit, or delete single records in the Algolia dashboard without writing code. 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. Do one of the following: * To add records, select **Add records > Add manually**. Paste your records in JSON format, then click **Save**. * To edit a record, find it in the **Browse** tab, click **Edit record**, change the JSON, then click **Edit**. * To delete a record, find it in the **Browse** tab and click **Delete record**, then click **Delete**. To import a file instead, see [Import from the dashboard](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-from-the-dashboard). # Post-indexing transformations Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/post-indexing-transformations Apply post-indexing transformations to your data already in an Algolia index with the Algolia Index connector. Post-indexing [transformations](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code) can help you optimize your data: * **Data cleaning.** Correct inconsistencies or errors discovered after indexing, rename attributes, or change data types. * **A/B testing.** Create variations of attributes for testing different ranking strategies. * **Index optimizations.** Bucket, compute new values, convert units of measure. To apply post-indexing transformations, use the Algolia Index connector. > [!NOTE] > > The Algolia Index connector transforms the records in your index, not your source data. > It can't repair a transformation that wrote incorrect values, because those values are its input. ## Get started 1. Go to the Algolia dashboard 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 **Data sources**. 3. On the [**Connectors**](https://dashboard.algolia.com/connectors/) page, select **Algolia Index** connector, and click **Connect**. 4. Choose the index to transform. 5. Configure your [transformation](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code): create a new transformation or reuse an existing one. 6. Create the task. You can select two types of tasks: * **On demand**. You can manually trigger the task from the [**Task**](https://dashboard.algolia.com/connectors/tasks) page in the dashboard, or using the [API](/doc/rest-api/ingestion/run-task). * **Scheduled**. You can select a schedule from the list, or enter a custom schedule with a [cron expression](https://en.wikipedia.org/wiki/Cron). ### Costs The Algolia Index connector browses 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.) and re-applies the changes. Depending on the indexing strategy you choose, this **adds read and write operations**. These operations might lead to costs, depending on your plan. To control how often your connector task runs, change the schedule in the task configuration. ## Performance considerations If you don't need [an Algolia-managed transformation step](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code), [send records directly to Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/sending-records-in-batches) instead. The Algolia Index connector adds overhead to your ingestion process. ### Filter the source index If your transformation doesn't need to run over the whole index, set the `filters` property on the Algolia Index connector source to restrict the records the connector browses. Filtering at the source reduces extraction time and the number of read operations on your index and the overall run duration. Use the same syntax as the [Search API `filters` parameter](/doc/api-reference/api-parameters/filters). Filtered attributes must be declared in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) on the source index. ## Constraints * Index size: no limit on the index size. * Duration: each run can't take longer than 60 minutes. ## How to select the right indexing strategy Data drift happens when you have different sources of truth for your data. To avoid **data drift**, select your indexing strategy when you use the Algolia Index connector. The connector reads records from an Algolia index and applies transformations. Make sure this workflow doesn't conflict with your primary ingestion method, such as the API or another connector. ### Key limitation for real-time updates and full record updates Be careful when using the Algolia Index connector with ingestion methods that use the full record updates action with real-time updates. Full record updates replace the entire record. If a real-time update runs after the connector reads data from your index but before it completes the transformations, **any transformation applied by the connector is lost**. That's because a real-time update will overwrite the entire record, including the transformed fields. The following Algolia ecommerce integrations use the full record updates by default: * [Shopify](/doc/integration/shopify/getting-started/quick-start) * [Magento](/doc/integration/magento-2/getting-started/quick-start) * [Salesforce](/doc/integration/salesforce-commerce-cloud-b2c/getting-started/introduction) * [BigCommerce](/doc/integration/bigcommerce/get-started/installation) * [commercetools](/doc/integration/commercetools/get-started/installation) ### Other connectors and API clients using partial updates Connectors and API clients using partial updates work with the Algolia Index connector. Partial updates ensure that only the specified attributes are modified, preserving other attributes and avoiding conflicts. To avoid data loss, select your indexing strategy when using the Algolia Index connector and prefer partial updates whenever possible. * [**Partial record updates (recommended)**](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies#partial-record-updates) allow creating, updating, and deleting records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) during the connector run. However, concurrent field updates will be overwritten. * [**Full record updates**](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies#full-record-updates) replace individual records, overriding changes made during the connector run. Avoid this strategy. * [**Full reindexing**](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies#full-reindexing) replaces your index, overriding any changes made during the connector run. Avoid this indexing strategy. ### Example scenario of conflicts The following scenario illustrates a potential issue when two connectors try to update the same index: 1. API client: * Ingests product data, including `price`, `category`, and `inventory`. * Uses "Partial Update" action. * Runs on a cron job every hour. 2. Algolia Index connector: * Reads records from the Algolia index and adds a new field, `discounted_price`, based on the `price`. * Uses "Partial Update" action. ### Potential issue An issue can arise if the Algolia Index connector runs for a long time. For example, if the API client updates a product's price at 10 AM, the Algolia Index connector starts at 10:07 AM, and finishes after 11 AM, the calculated `discounted_price` attribute will be outdated. At 11 AM, the API client already updates the price again, which means the `discounted_price` attribute will be based on an outdated `price` attribute. ### Recommendation To avoid such issues, ensure that the Algolia Index connector never runs concurrently with any other indexing operation. You can also [programmatically trigger your connector](/doc/libraries/sdk/methods/ingestion/run-task) after your other indexing operations. # Sending records in batches Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/sending-records-in-batches Send records to Algolia in batches to reduce network calls and speed up indexing. Send indexing operations in batches to reduce network calls and speed up indexing. Each batch groups multiple records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in a single API request. For example, sending one million records individually requires one million indexing requests. Sending them in batches of 1,000 reduces this to 1,000 requests. Algolia accepts up to 1 GB per batch, but for better indexing performance, keep each batch below 10 MB. The number of records per batch depends on their average size. Two things to keep in mind: * **Batching records doesn't reduce your operations count.** [Algolia counts indexing operations per record](https://support.algolia.com/hc/en-us/articles/18138875086865), not per method call, so from a pricing perspective, batching records is the same as indexing records individually. * **Be careful when approaching your plan's [record limit](https://www.algolia.com/pricing/).** If you're close to the record limit, batch operations may fail. The error message "You have exceeded your Record quota" means the engine doesn't know if the batch operation updates or adds records. If this happens, upgrade to a plan with a higher record limit or reduce your batch size. ## Manage records with the API To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install). When using the [`saveObjects`](/doc/libraries/sdk/methods/search/save-objects) method, the API clients automatically chunk your records into batches of 1,000. With this approach, you would make 1,000 indexing requests instead of a million. Depending on your records' sizes and your network speed, you could create bigger or smaller chunks. ```cs C# theme={"system"} List response = await client.SaveObjectsAsync( "INDEX_NAME", new List { new Dictionary { { "objectID", "1" }, { "name", "Adam" } }, new Dictionary { { "objectID", "2" }, { "name", "Benoit" } }, } ); ``` ```go Go theme={"system"} response, err := client.SaveObjects( "INDEX_NAME", []map[string]any{{"objectID": "1", "name": "Adam"}, {"objectID": "2", "name": "Benoit"}}) if err != nil { // handle the eventual error panic(err) } // response is a slice of search.BatchResponse ``` ```java Java theme={"system"} List response = client.saveObjects( "INDEX_NAME", Arrays.asList( new HashMap() { { put("objectID", "1"); put("name", "Adam"); } }, new HashMap() { { put("objectID", "2"); put("name", "Benoit"); } } ) ); ``` ```js JavaScript theme={"system"} const response: BatchResponse[] = await client.saveObjects({ indexName: 'cts_e2e_saveObjects_javascript', objects: [ { objectID: '1', name: 'Adam' }, { objectID: '2', name: 'Benoit' }, ], }); ``` ```kotlin Kotlin theme={"system"} var response: List = client.saveObjects( indexName = "INDEX_NAME", objects = listOf( buildJsonObject { put("objectID", JsonPrimitive("1")) put("name", JsonPrimitive("Adam")) }, buildJsonObject { put("objectID", JsonPrimitive("2")) put("name", JsonPrimitive("Benoit")) }, ), ) ``` ```php PHP theme={"system"} /** @var array $response */ $response = $client->saveObjects( 'INDEX_NAME', [ ['objectID' => '1', 'name' => 'Adam', ], ['objectID' => '2', 'name' => 'Benoit', ], ], ); ``` ```python Python theme={"system"} response: list[BatchResponse] = client.save_objects( index_name="INDEX_NAME", objects=[ { "objectID": "1", "name": "Adam", }, { "objectID": "2", "name": "Benoit", }, ], ) ``` ```ruby Ruby theme={"system"} response = client.save_objects( "INDEX_NAME", [{objectID: "1", name: "Adam"}, {objectID: "2", name: "Benoit"}] ) # response is an array of BatchResponse objects ``` ```scala Scala theme={"system"} val response: Seq[BatchResponse] = Await.result( client.saveObjects( indexName = "INDEX_NAME", objects = Seq( JObject(List(JField("objectID", JString("1")), JField("name", JString("Adam")))), JObject(List(JField("objectID", JString("2")), JField("name", JString("Benoit")))) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response: [BatchResponse] = try await client.saveObjects( indexName: "INDEX_NAME", objects: [["objectID": "1", "name": "Adam"], ["objectID": "2", "name": "Benoit"]] ) ``` If you use an Algolia-managed transformation to process records, use [`saveObjectsWithTransformation`](/doc/libraries/sdk/methods/search/save-objects-with-transformation) instead of `saveObjects`. Like `saveObjects`, it uploads records in batches, but sends each batch through the configured [Push to Algolia connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors/push). For more control over the batch size or indexing action, call [`chunkedPush`](/doc/libraries/sdk/methods/ingestion/chunked-push) on the Ingestion client directly. For more information, see: * [Large indexing jobs](/doc/guides/scaling/scaling-to-larger-datasets) * [Import with the API](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api) ## Manage records in the dashboard * To add, edit, or delete individual records without code, see [Manage individual records in the dashboard](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/incremental-updates#manage-individual-records-in-the-dashboard). * To import a file, see [Import from the dashboard](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-from-the-dashboard). # Transform your data with code Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-with-code Transform your data with JavaScript before writing to your Algolia index. You can add transformations to your connector's destination to manipulate data before it's written to your Algolia indices. To use transformations, create a task that specifies the data source and the destination (usually an Algolia 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.)). Add transformations during task creation or apply them directly to existing destinations. To learn more about connectors, read the [Connectors overview](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors). Code transformations are small JavaScript functions. > [!NOTE] > > You can only add one transformation to a destination. ## Get started with code transformations 1. Go to the Algolia dashboard 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 **Data sources**. 3. Open the [**Connectors**](https://dashboard.algolia.com/connectors) page. 4. Select a connector and click **Connect** to start creating your task. 5. Configure your data source: create a new source or select an existing one. 6. To create your transformation function, click **Create transformation**. When writing a transformation, you can: * Use the code editor, which includes autocompletion and syntax highlighting. For more information, see [Write your transformation](#write-your-transformation). * Use predefined [helper functions](#helper-functions) for common Algolia-specific tasks. To use a helper function, click **Helper functions** and select from the list. 7. Configure your destination: create a new destination or select an existing one. 8. Configure when your task should run and specify the operation type for writing your Algolia records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). 9. Confirm whether you want to run your task immediately. To see your transformation in action, click **Run** and inspect your index records after the task finishes. ## Write your transformation The signature of the transformation function is as follows. You can't change its names or its arguments: ```js JavaScript icon=code theme={"system"} async function transform(record, helper) { // Your transformation code here } ``` The transformation function returns a record or an array of records and receives two arguments: * `record`. The record to transform. It's an editable JavaScript object representing a data source record, like a JSON array item or a CSV row. * `helper`. A helper object that provides access to utility methods and contextual information to help you build your transformation. The term **helper** can refer to two related but distinct concepts: * **Helper object methods.** Methods available on the `helper` object passed to the transformation function. These give access to secrets and metadata. * **Helper functions.** Pre-built utilities that help you change or enrich your records during transformation. ### Helper object methods The `helper` object includes utility methods for accessing secrets and metadata: * `helper.secrets.get(secretName)`: retrieve a secret value from the vault. * `helper.getMetadata().appID`: retrieve the Algolia application ID (A unique alphanumeric string that identifies an Algolia application.) for the running task. * `helper.getMetadata().indexName`: retrieve the target index name for the record. * `helper.getMetadata().taskID`: retrieve the ID of the running task. * `helper.getMetadata().actionType`: retrieve the action type for this record. For more information about possible values, see the [API reference documentation](/doc/libraries/sdk/v1/methods/batch#param-action). ## Helper functions Use these pre-built functions to change or enrich your records: | Helper function | Description | | - | - | | Reshape | Add or remove record attributes. | | Filters | Exclude records based on conditions. | | Convert | Change attribute data types, trim or normalize values, and convert to ISO formats. | | Compute | Create new attributes, such as a calculated discount price. | | Ranking | Optimize [custom ranking](/doc/guides/managing-results/must-do/custom-ranking) by prioritizing recent activity and adjusting attribute importance. | | Query Categorization | Convert a flat list into a hierarchical category structure. | | Fetch/API | [Enrich records with data from external APIs.](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/enrich-your-records-with-fetch) | ## Preview transformation results When you create a transformation, you can preview the function on the right side of the code editor. ![Preview your transformation running on a sample record](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/connectors/transform_sample.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=1f6440649cf727dfa2d5e6c92473333e) The preview includes the following information: * **Input Record**. A sample record, taken from the data source before applying the transformation. * **Transformation Preview**. Click **Try it out** to see the results of your transformation on the sample record. > [!NOTE] > > Not all connectors support fetching sample records. ## Debug transformations Use the [**Connector Debugger**](https://dashboard.algolia.com/connectors/debugger) for insights into the status of your tasks, including transformations. ![Observe the status of previous executions of your transformations](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/connectors/transform_debug.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=e944c76f45a3a7aacc07f445cda4bb56) ## Error handling If your transformation function throws an error, it may fail the task depending on the task type and setup. Take care to avoid errors when writing transformation functions. Use `try` and `catch` blocks to handle errors gracefully. ```js JavaScript icon=code theme={"system"} async function transform(record, helper) { try { // Your transformation code here. } catch (error) { // Return the original record to avoid breaking the task, // or return undefined to delete and ignore the record. return record; } } ``` Also make allowances for when your transformation function doesn't work as expected. For example, use [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) to access missing properties. ```js JavaScript icon=code theme={"system"} async function transform(record, helper) { // Accessing a property that may not exist. const value = record?.property?.nestedProperty; // Your transformation code here. } ``` ### Broken transformations and consecutive failures Algolia stops your transformation if it encounters several consecutive failures within a short period. The transformation restarts if its code changes or after a specific time. To avoid this, write transformation functions to [avoid errors](#error-handling) and [preview the transformation](#preview-transformation-results) before running it. ## Performance considerations Adding a transformation adds an overhead to the ingestion process. Try to keep the JavaScript transformation as simple as possible. ## What transformations can't do Transformations process one record at a time. They can return zero, one, or more records. Transformations have two limits: * **External data.** To add data from a different system to a record, a transformation can call a third-party API. The request must complete within the [fetch timeout and response-size limits](/doc/guides/scaling/algolia-service-limits#data-transformation-and-fetch-limits). For more information, see [Enrich your records with fetch](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/enrich-your-records-with-fetch). * **Operations across records.** Some operations need access to more than one record, for example, deduplicating records or calculating an attribute from your full index. Transformations process each record independently and can't do these operations. ## Indexing strategy impact Data transformations change your records before indexing. By default, the entire record is returned after transformation. However, the correct approach depends on the [indexing strategy](/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies) you choose. The following example adds a new property to the record and returns it. This is the default behavior. ```js JavaScript icon=code theme={"system"} async function transform(record, helper) { record.newProperty = "foo"; return record; } ``` If your data has several sources such as API clients, connectors, or ecommerce integrations, choose an indexing strategy that avoids data conflicts. Sometimes, returning the whole record can lead to data loss or other unwanted behavior. The following sections describe various synchronization strategies and how they influence output. ### Full reindexing and full record updates Since these methods completely replace an existing record, your transformation must return the entire modified record, including any additions, deletions, or changes. Change an existing or a new record: ```js JavaScript icon=code theme={"system"} async function transform(record, helper) { // Add a property. record.newProperty = "foo"; // Remove a property. delete record.existingProperty; // Modify a property. record.existingProperty2 = "a new value"; // Return the full modified record. return record; } ``` Delete record: ```js JavaScript icon=code theme={"system"} async function transform(record, helper) { return undefined; } ``` Every record your transformation returns needs its own `objectID`. Build it from the source record's identifier plus a deterministic suffix so the same input always produces the same object ID. For more information, see [Keep object IDs stable](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/what-is-in-a-record#keep-object-ids-stable). Create new records: ```js JavaScript icon=code theme={"system"} async function transform(record, helper) { // Create a new record with an object ID derived from the source record newRecord = {}; newRecord.objectID = `${record.objectID}-summary`; // Create a new object from the current record. Change the objectID! copyRecord = { ...record }; copyRecord.objectID = `${record.objectID}-copy`; // Return the initial, copy, and new record return [record, newRecord, copyRecord]; } ``` ### Partial record updates This method only updates specific attributes Your transformation should only return the object ID and the modified or added attributes. Existing attributes not included in the returned object remain unchanged. You can't delete records with partial updates. Change an existing record: ```js JavaScript icon=code theme={"system"} async function transform(record, helper) { // We only keep the objectID let partialRecord = { objectID: record.objectID }; // We only add or modify what we need. partialRecord.newProperty = "foo"; partialRecord.existingProperty = "new value"; return partialRecord; } ``` Add new records: ```js JavaScript icon=code theme={"system"} async function transform(record, helper) { // Create a brand new record with an object ID derived from the source record newRecord = {}; newRecord.objectID = `${record.objectID}-summary`; // Return the new record. The original record will be untouched. return [newRecord]; } ``` # Transform your data without code Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/transform-your-data-without-code Transform your data before writing to your Algolia index. You can add *transformations* to your connector's destination to manipulate data before it's written to your Algolia indices. To use transformations, create a task that specifies the data source *and* the destination (usually an Algolia 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.)). Add transformations during task creation or apply them directly to existing destinations. To learn more about connectors, read the [Connectors](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors) overview. ## Get started with no-code transformations 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 **Data sources**. 3. Open the [**Connectors**](https://dashboard.algolia.com/connectors) page. 4. Select a connector and click **Connect** to start creating your task. 5. Configure your data source: create a new source or select an existing one. 6. To create your transformation, click **Create a new transformation** and select **Transform without code**. To learn more about the available transformation steps, see [Create your transformation](#create-your-transformation). 7. Configure your destination: create a new destination or select an existing one. 8. Configure when your task should run and specify the operation type for writing your Algolia records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). 9. Confirm whether you want to run your task immediately. To see your transformation in action, click **Run** and inspect your index records after the task finishes. ## Create your transformation A no-code transformation consists of one or more steps. Each step can include a condition, so it only applies to records that meet specific criteria. If any of the steps fail, the entire transformation will be marked as failed. ### Available step types | Step type | Description | | - | - | | [Add record attribute](#add-attribute) | Adds a new attribute to a record. The value can be a fixed value or calculated from other attributes in the record. | | [Remove record attribute](#delete-attribute) | Removes an attribute from a record. | | [Remove records](#remove-records) | Removes records based on a condition. For example, you can remove records with a price below a certain threshold. | #### Add attribute Use the **Add Attribute** step to create a new attribute in your records, or to update the value of an existing one: ![Form with fields for assigning or creating attributes, building conditions to select a subset of records, or for computing attributes](https://mintcdn.com/algolia/BaxS3Doxn613Z9Q0/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/nocode-transform-add-step.png?fit=max\&auto=format\&n=BaxS3Doxn613Z9Q0\&q=85\&s=4d5e2f5a5c648723ec05b74138f215e2) 1. On the **Transformation** page, select **Create Attribute**. 2. Enter the name of the attribute you want to add or update. If the attribute doesn't exist yet, its added to each matching record. If the attribute already exists, its value is replaced. If you enter an existing attribute, the values are updated. Use *dot* notation for nested attributes, such as `prices.discount_pct`. 3. Optional: [Add a condition](#conditions) to select the records for adding or updating attributes. 4. Specify the value for the new or updated attribute: * To assign the same value to each record matching the conditions, enter a value, for example, `true`, `10`, or `new`. * To assign a computed value based on other attributes, enter a formula, for example, `price * discount_pct`. > [!NOTE] > > **Computed values** are based on other record attributes. > For example, to calculate the discounted price of a product, use the formula: > `price * (1 - discount_pct)`, > where `price` and `discount_pct` are attributes in the record. > > For a list of available functions you can use to compute values, > see [Function reference](#function-reference). ### Delete attribute Use the **Delete Attribute** step to delete an attribute from your records. This is useful for cleaning up data or removing fields you don't want to send to your Algolia index. ![Form with fields for selecting an attribute to delete and for building conditions to select a subset of records](https://mintcdn.com/algolia/BaxS3Doxn613Z9Q0/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/nocode-transform-remove-step.png?fit=max\&auto=format\&n=BaxS3Doxn613Z9Q0\&q=85\&s=c10cc4650d39c46bf15fb387c58d78f9) 1. Select **Delete Attribute** as the step type. 2. Enter the name of the attribute you want to delete. For nested attributes, use dot notation, for example, `prices.discount_pct`. Only that nested field will be deleted, not the entire attribute. 3. Optional: [Add a condition](#conditions) to determine from which records the attribute is removed. ### Remove records Use the **Remove Records** step to remove records from your index based on specific conditions. This is useful for excluding records that don't meet your criteria before sending them to your Algolia index. ![Form with fields for building conditions to select a subset of records to delete](https://mintcdn.com/algolia/BaxS3Doxn613Z9Q0/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/nocode-transform-remove-records-step.png?fit=max\&auto=format\&n=BaxS3Doxn613Z9Q0\&q=85\&s=0a8df14b5b9d9b85b62a319ce8361d97) 1. Choose **Remove Records** as the step type. 2. [Add a condition](#conditions) to determine which records to remove. ## Conditions Conditions let you control when a transformation step runs for a particular record: * **Add Attribute:** Only add or modify an attribute to records that meet the conditions. * **Delete Attribute:** Only delete attributes from records that meet the conditions. * **Remove Records:** Only remove records that meet the conditions. ### Parts of conditions Conditions consist of the following elements: * **Field.** The attribute to check, for example, `category` or `price`. * **Operator.** The comparison to perform, for example, `equals`, `does not equal`, or `greater than`. * **Attribute type.** The data type of the field: String, Number, or Boolean. This specifies *how* to compare the field to the value. * **Value.** The value to compare the field against, for example, `"food"`, `200`, or `true`. ### Example: a basic condition * Field: `category` * Operator: `Equals` * Type: `String` * Value: `Food` This condition selects records where the `category` attribute has the value `Food`. ![Screenshot of a condition builder with 'category' set to 'Equal' to 'Food', showing the resulting condition '(category = 'Food')'.](https://mintcdn.com/algolia/BaxS3Doxn613Z9Q0/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/nocode-transform-condition2.png?fit=max\&auto=format\&n=BaxS3Doxn613Z9Q0\&q=85\&s=5ee1008e574b6adaec9ce30d101897ab) ### Multiple conditionals You can combine multiple conditionals using **AND** and **OR** logic. For example: * `category equals "food" AND price < 200` For more complex logic, you can **group** conditionals. This is similar to using parentheses in a formula. * `category equals "food" AND (price < 200 OR discount == true)` ![Screenshot of condition builder with three grouped conditions: 'category' equals 'Food', 'price' less than '200', and 'discount' equals 'True'.](https://mintcdn.com/algolia/BaxS3Doxn613Z9Q0/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/nocode-transform-condition3.png?fit=max\&auto=format\&n=BaxS3Doxn613Z9Q0\&q=85\&s=fba0a5d186719384ae69d49b8aef5a34) > [!NOTE] > > Grouping conditions changes the order in which conditions are evaluated, > allowing for complex logic. ### Supported attribute types | Type | Description | | - | - | | String | A string of text, for example: `food` | | Number | A number, for example: `200` | | Boolean | A boolean value: true or false | > [!NOTE] > > Array and object types aren't supported in no-code transformations. ## Preview your transformation At any point during setup, you can preview your transformation. The preview feature lets you apply your transformation to a sample of your data, so you can compare the results before and after the changes. If you data source doesn't support previewing, you can still preview your transformation by copying a sample record into the text area available in preview. > [!NOTE] > > Not all data sources support previewing. ## Limitations * You can only add one transformation to a destination. * Support for data sampling in preview is limited to these data sources: * Algolia index * CSV * JSON * Edits to a transformation don't support sampling. * In a given task, you can use either no-code or code transformations, not both. * Transforming complex records structures (arrays and objects) isn't supported. ## Function reference The following table lists available functions for [computed values](#add-attribute). For more information, see the [Expr](https://github.com/expr-lang/expr/blob/master/docs/language-definition.md) documentation. | Function | Description | Example usage | | - | - | - | | `mean([x, y, ...)]` | Returns the mean of the numbers. | `mean([1, 2, 3])` → `2` | | `median([x,y, ...])` | Returns the median of the numbers. | `median([1, 2, 3])` → `2` | | `round(x)` | Rounds `x` to the nearest integer. | `round(2.5)` → `3` | | `max(a, b)` | Returns the largest of two values | `max(1, 5)` → `5` | | `min(a, b)` | Returns the smallest of two values. | `min(1, 5, 3)` → `1` | | `len(x)` | Returns the length of a string. | `len("abc")` → `3` | | `lower(x)` | Converts a string to lowercase. | `lower("HELLO")` → `"hello"` | | `upper(x)` | Converts a string to uppercase. | `upper("hello")` → `"HELLO"` | | `replace(s, old, new)` | Replace all occurrences of `old` with `new` in `s`. | `replace("foo", "o", "a")` → `"faa"` | | `trim(str[,chars])` | Trim whitespace from start and end of string. Optionally trim `chars`. | `trim(" foo ")` → `"foo"` | | `split(s, sep)` | Splits string `s` into an array using separator `sep`. | `split("a,b,c", ",")` → `["a","b","c"]` | | `join(arr, sep)` | Joins elements of array `arr` into a string, separated by `sep`. | `join(["a","b"], ",")` → `"a,b"` | | `uniq([x,y,...]`) | Removes duplicate values from an array. | `uniq([1, 2, 2, 3])` → `[1, 2, 3]` | | `now()` | Returns the current time. | `now()` → `"2024-06-07T12:34:56Z"` | # Handling concurrency with versioning Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/handling-concurrency-with-versioning Learn how to use versioning and optimistic locking in your indexing pipeline to guarantee data consistency, even in concurrent environments. If you have multiple indexing processes running, one process could try to update a record at the same time as another. If this happens, your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) might turn out differently than expected. If you have an ecommerce website, and your backend runs two processes to index its changes to Algolia with the [`partialUpdateObjects`](/doc/libraries/sdk/v1/methods/partial-update-objects) method. One process sends an update to set the stock of a product from `10` to `20`, because you just received ten new items in a shipment. Simultaneously, another process sets the stock for this product from `10` to `9` because of an order that just came in. Depending on which job wins the race condition, the stock is now either `9` or `20`, even though the correct stock is `19`. ## Versioning One way to handle concurrency is the [`IncrementSet`](/doc/libraries/sdk/v1/methods/partial-update-objects#update-only-if-the-value-is-greater-than-a-numeric-attribute) [operation](/doc/libraries/sdk/v1/methods/partial-update-objects#built-in-operations). It lets you update a record only if you provide a higher numerical value than already exists in the record, or a value higher than 0 if the record doesn't exist. A great use-case for this is adding a Unix timestamp to your records. With [`IncrementSet`](/doc/libraries/sdk/v1/methods/partial-update-objects#update-only-if-the-value-is-greater-than-a-numeric-attribute), Algolia only updates your record if the timestamp you sent is more recent than the existing one. If you update in the meantime, you prevent Algolia from overriding your record: **only the latest update is taken into account**. ## Optimistic locking Another way to handle concurrency is with the [`IncrementFrom`](/doc/libraries/sdk/v1/methods/partial-update-objects#update-only-if-the-value-matches-a-numeric-attribute) [operation](/doc/libraries/sdk/v1/methods/partial-update-objects#built-in-operations). It lets you update a record only if you provide the current version of that record, or 0 if the record doesn't exist. A typical use-case is when you perform a read-modify-write sequence: 1. Read a record 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.). 2. Make the changes you want to make. 3. Provide the [`IncrementFrom`](/doc/libraries/sdk/v1/methods/partial-update-objects#update-only-if-the-value-matches-a-numeric-attribute) operation with the old value of your versioning attribute. With this approach, Algolia only updates your record if the value from your [`IncrementFrom`](/doc/libraries/sdk/v1/methods/partial-update-objects#update-only-if-the-value-matches-a-numeric-attribute) operation matches the current value currently in the record, and increments it. **If another process incremented this value in the meantime, Algolia ignores the update.** > [!NOTE] > > Algolia doesn't provide feedback when it rejects an update because of versioning or optimistic locking. > You have to wait for the indexing operation to complete, > then fetch the record to compare it to the desired result. > An ignored operation won't necessarily show up as published with the [`waitTask`](/doc/libraries/sdk/v1/methods/wait-task) API method. # Wait for indexing tasks Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/index-operations-are-asynchronous Indexing operations are asynchronous. Learn when to wait for index and app tasks to finish, and how to do it with the API clients. **All indexing operations run asynchronously.** Algolia queues each operation and returns a `taskID`. A successful response means Algolia accepted the request, not that the change is live. These operations include adding, updating, or deleting records (A record is a searchable object in an Algolia index. Each record consists of named attributes.), and changing 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.) settings, rules, synonyms, or dictionary entries. Most tasks finish in milliseconds or seconds. The time depends on server load and the number of queued tasks. Until a task finishes, searches can return the data from before the change. ## When to wait Wait for a task if later requests need its change. Wait in these cases: * **Managing indices**: wait for an index operation to finish if the next operation depends on its result. For example, wait for an index copy to finish before changing the copy's settings. * **Testing**: wait for the task to finish before you test a search on the updated index. * **User feedback**: wait for the task to finish before you show a success message or reload data that depends on the change. Otherwise, the user can see the old data and assume the change failed. If later operations don't need the change, continue without waiting. This prevents unnecessary delays. For atomic reindexing, use [`replaceAllObjects`](/doc/libraries/sdk/methods/search/replace-all-objects). It fills a temporary index, then replaces the original without interrupting searches. The method waits for each step, so you don't need to call `waitForTask`. ## Wait for index tasks Use the method that matches what the operation changes. Operations that change an index return an index task ID. These include: * Adding, updating, and deleting records, including batch operations, deleting by query, and clearing an index * Changing settings, rules, and synonyms * Copying, moving, and deleting an index Pass the index name and the returned `taskID` to [`waitForTask`](/doc/libraries/sdk/methods/search/wait-for-task). Some methods, such as `saveObjects`, return a task ID for each batch. If later requests depend on all the changes, wait for all returned tasks. `waitForTask` polls until the task status is `published`. If it reaches the retry limit first, it returns an error. The task can still complete after polling stops. You can set the retry limit when you call `waitForTask`. ```cs C# theme={"system"} var response = await client.WaitForTaskAsync("INDEX_NAME", 123L); ``` ```go Go theme={"system"} response, err := client.WaitForTask( "INDEX_NAME", 123) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} GetTaskResponse response = client.waitForTask("INDEX_NAME", 123L); ``` ```js JavaScript theme={"system"} const response = await client.waitForTask({ indexName: 'wait-task-javascript', taskID: 123 }); ``` ```kotlin Kotlin theme={"system"} var response = client.waitForTask(indexName = "INDEX_NAME", taskID = 123L) ``` ```php PHP theme={"system"} $response = $client->waitForTask( 'INDEX_NAME', 123, ); ``` ```python Python theme={"system"} response = client.wait_for_task( index_name="INDEX_NAME", task_id=123, ) ``` ```ruby Ruby theme={"system"} response = client.wait_for_task("INDEX_NAME", 123) ``` ```scala Scala theme={"system"} val response = Await.result( client.waitForTask( indexName = "INDEX_NAME", taskID = 123L ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.waitForTask(indexName: "INDEX_NAME", taskID: Int64(123)) ``` ## Wait for app-level tasks Dictionaries apply to all indices in your Algolia application. Operations that change dictionary entries or dictionary settings return app-level task IDs. Pass the returned `taskID` to [`waitForAppTask`](/doc/libraries/sdk/methods/search/wait-for-app-task). App-level tasks aren't tied to an index, so don't pass an index name. ```cs C# theme={"system"} var response = await client.WaitForAppTaskAsync(123L); ``` ```go Go theme={"system"} response, err := client.WaitForAppTask( 123) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} GetTaskResponse response = client.waitForAppTask(123L); ``` ```js JavaScript theme={"system"} const response = await client.waitForAppTask({ taskID: 123 }); ``` ```kotlin Kotlin theme={"system"} var response = client.waitForAppTask(taskID = 123L) ``` ```php PHP theme={"system"} $response = $client->waitForAppTask( 123, ); ``` ```python Python theme={"system"} response = client.wait_for_app_task( task_id=123, ) ``` ```ruby Ruby theme={"system"} response = client.wait_for_app_task(123) ``` ```scala Scala theme={"system"} val response = Await.result( client.waitForAppTask( taskID = 123L ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.waitForAppTask(taskID: Int64(123)) ``` ## Other task IDs * Connectors use different identifiers: a task ID identifies the connector's configuration, and a run ID identifies one execution of it. Check a run's status in the Algolia dashboard or with the Ingestion API [`getRun`](/doc/rest-api/ingestion/get-run) operation. Don't pass either to `waitForTask` or `waitForAppTask`. * API key operations don't return a task ID. To wait for them, use [`waitForApiKey`](/doc/libraries/sdk/methods/search/wait-for-api-key). # Data synchronization strategies Source: https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/in-depth/the-different-synchronization-strategies Learn about the different ways to reindex your data into Algolia whenever your content changes. Once you've indexed your data, **you need to keep it up to date as the data changes**. Most of the time, you need to reindex in the following scenarios: * You've changed 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.) structure. * Your source has changed since the last update. * You need to resolve "data drift" which happens when you have different sources of data, making it difficult to track changes. You can update your index in one of several ways: * **Full reindexing**: replace your entire index with a new set of records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). * **Full record updates**: replace an entire record with a new one. * **Partial record updates**: update only a subset of attributes within a record. The appropriate method depends on your use case. > [card] [Algolia Academy](https://academy.algolia.com/training/0199aaf1-7fd2-7264-adce-b4f3dc073b6d/overview) > > Learn more about: Data Sync: Keeping Your Index Fresh ## Choose an update frequency How often you update depends on **how often your content changes and how fast you need it to be searchable**. Take the example of an ecommerce store: * Update price changes and product availability in real time. * Send the number of sales, used for custom ranking, periodically in batches. Aim for a balance between getting fresh information into the search experience as fast as possible and [reducing the number of operations](https://support.algolia.com/hc/en-us/articles/18138875086865), which affects your indexing performance and, potentially, your Algolia bill. ## Full reindexing When performing a full reindex, **you're removing all existing records and replacing them with a fresh set.** This is ideal when your data changes a lot or when there's no way for you to track it. For example, if you have a content site that constantly changes, such as technical documentation, [split long pages into multiple records](/doc/guides/sending-and-managing-data/prepare-your-data/how-to/indexing-long-documents) to improve the relevance of your search results. This makes tracking changes difficult, because adding a single sentence in the middle of a page can shift the entire record structure. Also, depending on the structure of the site and how you update it, it might be impossible to know what exact pages were impacted. **It's a typical scenario where full reindexing is necessary.** You can fully reindex your data with API clients or from the Connectors page in the Algolia dashboard: * **API client**: use the [`replaceAllObjects`](/doc/libraries/sdk/v1/methods/replace-all-objects) method. This ensures there's no downtime during the reindex, and your search is always available. * **[Connectors](https://dashboard.algolia.com/connectors/tasks)**: edit the task and choose **Full reindexing** (or choose that option during setup). > [!WARNING] > > A full reindex [can significantly increase your indexing operations count](https://support.algolia.com/hc/en-us/articles/18138875086865). > It's also slower than incremental updates, because you're reindexing every single record. > This is a powerful way of keeping your data up to date, but **be mindful of the impact on your operations count and indexing pipeline.** ## Full record updates If you can track changes to your data and are able to identify affected records, then you can update records individually. For example, suppose you're running a forum with a search to look for members. Your records have a one to one relationship with member entities in your data. In this scenario, you can update each record whenever the entity changes. Whenever you add, update, or delete a member, you could also perform an incremental update to your Algolia index. You can update individual records with an API client or from the Connectors page in the Algolia dashboard: * **API client**: use the [`saveObjects`](/doc/libraries/sdk/v1/methods/save-objects) method and specify the `objectID` of the record to update. * **[Connectors](https://dashboard.algolia.com/connectors/tasks)**: edit the task and choose **Full record updates** (or choose that option during setup). ## Partial record updates Full record updates work when you're able to specify the full data to put in the record. Yet, there are cases when you only know the part of the data to update, and not the rest of the data. In those situations, you can perform partial record updates with only the data to change. Consider an online store with independent systems for content management and user reviews. When a product's description changes, your CMS doesn't know how many reviews the product has. Conversely, whenever a user posts a review, the API of your third-party service can only provide you with this data. In this case, you would probably want to centralize updates and perform partial record updates based on the data you receive. You can update individual attributes in records with an API client or from the Connectors page in the Algolia dashboard: * **API client**: use the [`partialUpdateObjects`](/doc/libraries/sdk/v1/methods/partial-update-objects) method. * **[Connectors](https://dashboard.algolia.com/connectors/tasks)**: edit the task and choose **Partial record updates** (or choose that option during setup). ## See also To learn more, see also [Incremental updates](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/incremental-updates) # NeuralSearch A/B testing Source: https://www.algolia.com/doc/guides/ai-relevance/neuralsearch/ab-testing Measure the impact of NeuralSearch by running controlled experiments before rolling out to all traffic. Before you enable NeuralSearch for all search traffic, run an A/B test to measure how it affects your search metrics. NeuralSearch uses Algolia's standard A/B testing features. You can create tests with the A/B testing API or from the Algolia dashboard. ## Choose a test type You can run one of these test types: | Test type | Control | Variant | When to use | | - | - | - | - | | Keyword vs NeuralSearch | Keyword search | NeuralSearch | Compare keyword search with NeuralSearch | | Adaptive Intent test | NeuralSearch | NeuralSearch with Adaptive Intent | Compare NeuralSearch to NeuralSearch with [Adaptive Intent](/doc/guides/ai-relevance/neuralsearch/adaptive-intent) | ### Compare keyword search with NeuralSearch Your index must have NeuralSearch enabled before you can launch an A/B test. NeuralSearch can be in one of these states: * **Inactive**: NeuralSearch isn't configured for the index. * **Preview**: NeuralSearch is configured for testing but doesn't serve *all* search traffic. * **Active**: NeuralSearch is enabled for the index's search traffic. To change the NeuralSearch state for an index, set [`neuralSearchMode`](/doc/guides/ai-relevance/neuralsearch/get-started#parameters) appropriately. This A/B test evaluates whether NeuralSearch improves your existing search experience. * The control uses the index's default keyword search. * The variant enables NeuralSearch. ```sh Command line expandable icon=square-terminal theme={"system"} curl --request POST \ --url https://analytics.algolia.com/3/abtests \ --header 'content-type: application/json' \ --header 'x-algolia-api-key: {ALGOLIA_ANALYTICS_API_KEY}' \ --header 'x-algolia-application-id: {ALGOLIA_APPLICATION_ID}' \ --data '{ "name": "Keyword vs NeuralSearch", "variants": [ { "index": "{INDEX_NAME}", "trafficPercentage": 50, "description": "Keyword search (control)" }, { "index": "{INDEX_NAME}", "trafficPercentage": 50, "customSearchParameters": { "enableNeuralSearch": true }, "description": "NeuralSearch (variant)" } ], "metrics": [ { "name": "conversionRate" } ], "endAt": "2026-12-31T00:00:00.000Z" }' ``` ### Compare NeuralSearch to NeuralSearch with Adaptive Intent This A/B test measures whether [Adaptive Intent](/doc/guides/ai-relevance/neuralsearch/adaptive-intent) improves NeuralSearch results. * The control uses NeuralSearch without Adaptive Intent. * The variant enables Adaptive Intent. ```sh Command line expandable icon=square-terminal theme={"system"} curl --request POST \ --url https://analytics.algolia.com/3/abtests \ --header 'content-type: application/json' \ --header 'x-algolia-api-key: {ALGOLIA_ANALYTICS_API_KEY}' \ --header 'x-algolia-application-id: {ALGOLIA_APPLICATION_ID}' \ --data '{ "name": "NeuralSearch vs NeuralSearch with Adaptive Intent", "variants": [ { "index": "{INDEX_NAME}", "trafficPercentage": 50, "description": "NeuralSearch (control)" }, { "index": "{INDEX_NAME}", "trafficPercentage": 50, "customSearchParameters": { "adiConfig": { "enabled": true } }, "description": "NeuralSearch with Adaptive Intent (variant)" } ], "metrics": [ { "name": "conversionRate" } ], "endAt": "2026-12-31T00:00:00.000Z" }' ``` ## Start tests from the Algolia dashboard To start NeuralSearch A/B tests from the Algolia dashboard: 1. Go to the [Algolia dashboard](https://dashboard.algolia.com/) and select your Algolia application. 2. Select your index. 3. Click the **NeuralSearch** tab. 4. Click **Launch an A/B test**. 5. Select the test type: **Keyword vs NeuralSearch** or **Adaptive Intent test**. 6. Set the traffic split and test duration. 7. Click **Create test**. The Algolia dashboard pre-fills the required `customSearchParameters` for each test type, so you don't need to configure the API parameters manually. ## Test metrics Algolia tracks these metrics automatically for every NeuralSearch A/B test: * Conversion rate * Click-through rate * Add-to-cart rate * No-results rate * Average click position For more information, see [Click and conversion events](/doc/guides/sending-events/concepts/event-types). ## Test duration Run tests long enough to reach statistical significance: * Tests should run for at least 14 days to account for weekly traffic patterns. * Aim for a minimum increase of 3% on conversion rate. * Indices with more traffic usually reach statistical significance faster. ## Stopping a test Stop tests from the [Algolia dashboard](/doc/guides/ab-testing) or with the [A/B Testing API](/doc/rest-api/abtesting-v3/stop-ab-test). After you stop a test, traffic returns to the index's active configuration automatically. # Adaptive Intent Source: https://www.algolia.com/doc/guides/ai-relevance/neuralsearch/adaptive-intent Improve NeuralSearch results for high-traffic queries using your engagement data. **Adaptive Intent** improves NeuralSearch results on high-volume, high-value queries: the searches that drive the most engagement on your site. Without Adaptive Intent, NeuralSearch relies on general semantic similarity. Terms specific to *your* data, such as brand names, niche terminology, or regional product names, might not match the records users expect. Adaptive Intent solves this by learning from the records your users actually click and convert on. ## How Adaptive Intent works For each query with enough engagement, Adaptive Intent builds a **bag**. The bag combines records associated with clicks and conversions for that query. The bag takes the form of a vector, weighted by engagement signal strength: * Conversions count 10× more than clicks, so records with stronger engagement have more influence * The bag refreshes regularly as new engagement data becomes available. * At query time, NeuralSearch uses the bag instead of the vector generated from the query text. This helps search results reflect user intent, not just semantic similarity. Queries without enough engagement data continue to use NeuralSearch as usual. ### Where Adaptive Intent helps most Adaptive Intent addresses problems that standard NeuralSearch struggles with: * **Brand names and niche terminology.** Generic language models might not recognize terms specific to your data: sporting goods brands, regional product names, specialty vocabulary. Adaptive Intent learns these from your data without manual work. * **Ambiguous queries.** Short or vague queries such as "bottle" or "hydro" can match unintended records when relying on generic semantic similarity. Adaptive Intent resolves ambiguity from users' past behavior. * **Conversion misalignment.** Semantically relevant results aren't always the ones that convert. Adaptive Intent bridges this gap by learning which results users engage with for each query. ## Before you begin To use Adaptive Intent, your index must: * Use NeuralSearch * Send click and conversion events to Algolia Adaptive Intent activates automatically for queries that meet a minimum engagement threshold. At least 60 engagement points (1 point for each click and 10 points for each conversion) across at least three records. Queries below this threshold fall back to standard NeuralSearch with no degradation. > [!NOTE] > > If you're using Dynamic Re-Ranking or Algolia A/B testing, > you're probably already sending the required events. ## Enable Adaptive Intent from the dashboard 1. Go to the Algolia dashboard and select your Algolia application and index. 2. Click the **NeuralSearch** tab. 3. Open **Settings**. 4. Turn on **Adaptive Intent**. Adaptive Intent trains on your engagement data and activates for qualifying queries automatically. ## Enable Adaptive Intent with the API Adaptive Intent is controlled through the `adiConfig` extension in the Search API. To enable it on a per-query basis or within an A/B test: ```sh Command line expandable icon=square-terminal theme={"system"} curl --request POST \ --url https://{ALGOLIA_APPLICATION_ID}.algolia.net/1/indexes/{INDEX_NAME}/query \ --header 'content-type: application/json' \ --header 'x-algolia-api-key: {ALGOLIA_ANALYTICS_API_KEY}' \ --header 'x-algolia-application-id: {ALGOLIA_APPLICATION_ID}' \ --data '{ "query": "your search query", "adiConfig": { "enabled": true } }' ``` ## Monitor and manage Adaptive Intent Use the Algolia dashboard to check which queries use Adaptive Intent and compare their results with other retrieval strategies. ### Review trained queries in Query Explorer To see the impact of Adaptive Intent on individual queries, go to the **NeuralSearch** tab, click **Compare**, and open the **Query Explorer**. For each trained query, the Query Explorer shows: * **Number of documents.** How many records are in the training set for that query. * **Similarity score** across training documents. A high score may indicate overfitting, while a lower score suggests a more diverse training set. * **Training status:** whether Adaptive Intent is active for that query. Turn Adaptive Intent on or off for any query with the toggle in the Query Explorer. When you turn off Adaptive Intent for a query, it uses standard NeuralSearch vector retrieval. ### Compare retrieval strategies To see how Adaptive Intent affects a specific query's results: 1. Go to the **Compare** tab in the NeuralSearch section of the Algolia dashboard. 2. Enter a query that has Adaptive Intent training. 3. Use the toggles on each side to switch between keyword search, vector search, and NeuralSearch with Adaptive Intent. This lets you compare all three retrieval strategies side by side for the same query. ## Run an A/B test with Adaptive Intent To measure Adaptive Intent's impact before you enable it for all traffic, run an A/B test. ### Launch an Adaptive Intent test Before starting the test, check that **Adaptive Intent** in your NeuralSearch settings is set to **Disabled**. This ensures the control variant doesn't use Adaptive Intent. ```sh Command line expandable icon=square-terminal theme={"system"} curl --request POST \ --url https://analytics.algolia.com/2/abtests \ --header 'content-type: application/json' \ --header 'x-algolia-api-key: {ALGOLIA_ANALYTICS_API_KEY}' \ --header 'x-algolia-application-id: {ALGOLIA_APPLICATION_ID}' \ --data '{ "name": "NeuralSearch vs NeuralSearch with Adaptive Intent", "variants": [ { "index": "{INDEX_NAME}", "trafficPercentage": 50, "description": "NeuralSearch (control)" }, { "index": "{INDEX_NAME}", "trafficPercentage": 50, "customSearchParameters": { "adiConfig": { "enabled": true } }, "description": "NeuralSearch with Adaptive Intent (variant)" } ], "endAt": "2026-12-31T00:00:00.000Z" }' ``` For guidance on test duration and metrics, see [NeuralSearch A/B testing](/doc/guides/ai-relevance/neuralsearch/ab-testing). ## Limitations * Adaptive Intent requires click and conversion events. Queries with insufficient engagement fall back to standard NeuralSearch. * You can't manually edit bags, such as by adding or removing records or adjusting weights. # Get started with NeuralSearch Source: https://www.algolia.com/doc/guides/ai-relevance/neuralsearch/get-started Activate NeuralSearch to combine keyword and semantic search on your Algolia index. NeuralSearch adds vector search to your keyword search, then merges and ranks the results. This means your index returns results that match concepts and intent, not just exact keywords. > [card] [Algolia Academy](https://academy.algolia.com/pages/contentcatalog/training/0199aad2-7326-7367-a843-33f2a39a0206/overview) > > Learn more about: NeuralSearch: Fundamentals ## Before you begin This feature isn't available on every plan. Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. To get started, [your index must contain records](/doc/guides/sending-and-managing-data/send-and-update-your-data). Click and conversion events aren't required. If you have them, NeuralSearch uses them to choose which record attributes to use for vectors. Without events, NeuralSearch selects attributes automatically from your record structure. ## Activate NeuralSearch 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 index. 4. Click the **NeuralSearch** tab. 5. Click **Configure NeuralSearch**. 6. Select the source index for events. 7. Click **Train NeuralSearch**. * **If your index has events**, NeuralSearch analyzes the relationships between queries, events, and records to select and weight the attributes that produce the most relevant vector representations. * **If your index doesn't have events**, NeuralSearch automatically selects attributes from your record structure. For example, attributes such as `title`, `description`, `color`, or `category`. Once you have events, you can retrain NeuralSearch to use them. > [!NOTE] > > Building the NeuralSearch index can take longer for larger datasets. ## Preview your results When training is complete, NeuralSearch enters **Preview** mode automatically. In preview mode you can: * Compare NeuralSearch and keyword results using the **Compare** tab * Launch an A/B test to measure impact on real traffic * Apply NeuralSearch to live traffic ### Compare results Use the Compare tab to evaluate NeuralSearch result quality. Go to the **NeuralSearch** tab in your index and click **Compare**. The Compare tab shows: * **Top queries:** queries from your analytics that you can click to run immediately in compare mode * **No-result queries:** queries that don't return any results in keyword search, so you can see NeuralSearch's impact * **Training coverage:** which queries NeuralSearch has trained on Compare mode shows keyword and NeuralSearch results side by side. Turn vector results on and off to isolate the impact of semantic search. The graph between the two columns shows which results have been re-ranked or moved as a result of vector search: lines crossing indicate records that shifted position. ### Inspect vector results You can also inspect individual results in the **Index** tab. 1. Run a query and look for results with a blue **Vector** badge: these were retrieved by vector search. 2. Click a result, then click the compass icon to open its details. 3. Look for **Vector similarity:** a score between 0 and 1. Closer to 1 means a stronger semantic match. ## Understand search result changes NeuralSearch expands what your index can match: | Capability | Keyword search | NeuralSearch | | - | - | - | | Retrieval | Keyword matches | Keyword matches and semantic matches | | Results | Exact or similar keyword matches | Relevant results even when the query doesn't match keywords | | Intent understanding | Limited | Infers intent from the query | For example, a query for "something to keep coffee hot" can return "insulated travel mug", even with no keyword overlap. ## How NeuralSearch ranks results NeuralSearch ranks results in three stages: 1. Retrieval 2. Tie-breaking 3. Merge results ### Retrieve results NeuralSearch retrieves keyword results and vector results independently. Vector results are initially ordered by similarity: how close the record's vector is to the query's vector. ### Apply tie-breaking Tie-breaking is applied independently to each result list. The keyword list goes through Algolia's full [tie-breaking algorithm](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria), including textual relevance steps like typo tolerance, attribute ordering, and proximity. The vector list uses a subset of tie-breaking steps, but only those that apply to non-textual relevance: * Geographical distance * Optional filters * Custom ranking Text-based tie-breaking steps don't apply to vector results. ### Merge results NeuralSearch merges the ranked vector and keyword lists, then normalizes their scores to a common scale so they can be combined. The [presets](#configure-presets) determine final result order. ## Configure presets Presets control how keyword and semantic results are blended. NeuralSearch uses the `default` preset unless you configure otherwise. | Preset | Behavior | | - | - | | `conservative` | Semantic search activates only for queries that don't return any keyword results. This preset minimizes changes to existing keyword results and is a good starting point. | | `expanded_reach` | Semantic search activates when keyword result counts are low. | | `append_only` | Semantic results are appended after all keyword results. | | `default` | Semantic search runs on every query and blends with keyword results, with keyword results taking priority. | | `custom` | Lets you configure `semanticBlendWeight`, `minHitsForSemantic`, and `enableNeuralSearchSortBy`. | Configure presets with the [`semanticSearch/settings` API endpoint](#configure-neuralsearch-with-the-semantic-settings-endpoint). ## Semantic Precision NeuralSearch retrieves up to 100 vector results per query by default. Semantic Precision lets you control the trade-off between precision and recall by filtering those results based on similarity. Configure Semantic Precision in your NeuralSearch settings. Choose from these options: | Setting | Behavior | | - | - | | **More Recall** (default) | Allows a broader set of results, including broader semantic matches. | | **Balanced** | A midpoint between recall and precision. | | **More Precision** | Filters out results with lower similarity scores, keeping only the closest semantic matches. | To configure Semantic Precision with the API, see [`semanticSearch/settings`](#configure-neuralsearch-with-the-semantic-settings-endpoint). ## Configure NeuralSearch with the semantic settings endpoint The semantic settings endpoint gives you programmatic control over NeuralSearch configuration for an index. Use it to set presets, adjust blending behavior, configure Semantic Precision filtering, and configure positional ranking. Changes take effect immediately. No reindexing is required unless you change `neuralExpression` or `vectorModelId`. ```txt Base URL theme={"system"} https://{ALGOLIA_APPLICATION_ID}.algolia.net/1/indexes/{INDEX_NAME}/semanticSearch/settings ``` ### Retrieve settings To inspect the current NeuralSearch configuration for an index, send a `GET` request to the `semanticSearch/settings` endpoint: ```sh Command line icon=square-terminal theme={"system"} curl --request GET \ --url https://{ALGOLIA_APPLICATION_ID}.algolia.net/1/indexes/{INDEX_NAME}/semanticSearch/settings \ --header 'x-algolia-api-key: {ALGOLIA_ADMIN_API_KEY}' \ --header 'x-algolia-application-id: {ALGOLIA_APPLICATION_ID}' ``` ### Update settings To update the NeuralSearch configuration for an index, send a `PUT` request to the `semanticSearch/settings` endpoint. This example sets the NeuralSearch preset to `custom` and configures Semantic Precision. ```sh Command line expandable icon=square-terminal theme={"system"} curl --request PUT \ --url https://{ALGOLIA_APPLICATION_ID}.algolia.net/1/indexes/{INDEX_NAME}/semanticSearch/settings \ --header 'content-type: application/json' \ --header 'x-algolia-api-key: {ALGOLIA_ADMIN_API_KEY}' \ --header 'x-algolia-application-id: {ALGOLIA_APPLICATION_ID}' \ --data '{ "neuralSearchPreset": "custom", "semanticBlendWeight": 0.5, "dynamicThreshold": { "enabled": true, "aggression": 0.3, "lowerLimit": 0.64, "upperLimit": 0.85 } }' ``` ### Parameters | Parameter | Type | Description | | - | - | - | | `neuralSearchMode` | `string` | `"active"`, `"preview"`, or `"inactive"`. Controls whether NeuralSearch is on for live traffic. | | `neuralSearchPreset` | `string` | One of `"default"`, `"conservative"`, `"expanded_reach"`, `"append_only"`, or `"custom"`. For more information, see [Configure presets](#configure-presets). | | `semanticBlendWeight` | `float` | From `0.0001` to `0.999`. Relative weight given to semantic results. `custom` preset only. | | `minHitsForSemantic` | `integer` | From `0` to `20`. Minimum keyword results before semantic results are included. `custom` preset only. | | `enableNeuralSearchSortBy` | `boolean` | When `true`, index ranking and sorting settings apply to the full NeuralSearch result set. `custom` preset only. | | `dynamicThreshold` | `object` | Semantic Precision filter configuration. See [Semantic Precision](#semantic-precision). | | `usePositionalSemanticRanking` | `boolean` | When `true`, vector results use Algolia's standard tie-breaking algorithm. See [Positional semantic ranking](#positional-semantic-ranking). | ### Positional semantic ranking By default, vector results are ranked by how closely they match the meaning of the query. Algolia's ranking formula doesn't apply to them. Setting `usePositionalSemanticRanking: true` in the semantic settings endpoint changes this behavior. With positional ranking enabled: * Geographical distance is factored into semantic result ranking * Optional filters apply to vector results * Custom ranking attributes affect the order of semantic results ## See also * **Measure the impact.** Run an [A/B test](/doc/guides/ai-relevance/neuralsearch/ab-testing) to compare NeuralSearch against keyword search on real traffic before you enable it for all traffic. * **Improve results for your top queries.** After you have enough engagement data, enable [Adaptive Intent](/doc/guides/ai-relevance/neuralsearch/adaptive-intent) to continuously learn from clicks and conversions. * **Configure NeuralSearch with the API.** Use [`semanticSearch/settings`](#configure-neuralsearch-with-the-semantic-settings-endpoint) for programmatic control over presets, blending, and ranking behavior. * **Understand what's not supported.** Review [Limitations](/doc/guides/ai-relevance/neuralsearch/limitations) before building on top of NeuralSearch. # NeuralSearch limits and unsupported features Source: https://www.algolia.com/doc/guides/ai-relevance/neuralsearch/limitations Features, configurations, and index sizes that NeuralSearch doesn't support. NeuralSearch uses different infrastructure than Algolia's keyword search. Because of this, some features work differently or aren't supported. Review these limits before you turn on NeuralSearch or use it in production. ## Virtual replicas NeuralSearch doesn't support [virtual replicas](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas). You use virtual replicas for relevant sorting. To use NeuralSearch with sorted results, use a standard replica instead of a virtual replica. If you want custom ranking criteria to affect vector results, enable `usePositionalSemanticRanking`. For more information, see [Configure NeuralSearch with the semantic settings endpoint](/doc/guides/ai-relevance/neuralsearch/get-started#configure-neuralsearch-with-the-semantic-settings-endpoint). ## Natural language processing (NLP) features NLP features apply only to the keyword search part of NeuralSearch. Semantic (vector) retrieval doesn't use these features: * **[Synonyms](/doc/guides/managing-results/optimize-search-results/adding-synonyms)** configured in your index expand keyword queries. They don't change how semantic retrieval represents vectors. * **Decompounding** applies only to keyword matching. Algolia uses it for German and other languages with compound words. * **Plurals and word forms.** Stemming and plural normalization apply to keyword matching. The semantic model handles word variation through embeddings. * **Algolia Rules** only affect keyword retrieval. If you need precise NLP behavior, keyword results in NeuralSearch work the same way as keyword-only search. Semantic results supplement keyword results rather than replacing them. ## Case sensitivity and query normalization Semantic (vector) retrieval doesn't normalize queries. Differences in case, spelling, and special characters can change semantic results or rankings. For example, `table`, `Table`, and `TABLE` might produce different results. The same applies to `canapé` and `canape`. NeuralSearch merges keyword and semantic results. These differences can change the final ranking. The keyword part of the query still applies your index's normalization settings. This behavior only affects the semantic part of the results. ## Sort by filter or geographic distance NeuralSearch supports sort-by replicas for sorting results by a fixed ranking order. By default, these sorting settings don't apply filter-based ranking or geographic distance to vector results. To apply geographic distance and filter-based ranking to vector results, enable `usePositionalSemanticRanking`. For more information, see [Configure NeuralSearch with the semantic settings endpoint](/doc/guides/ai-relevance/neuralsearch/get-started#positional-semantic-ranking). ## Index size limits NeuralSearch indexes follow the same [size limits](/doc/guides/scaling/algolia-service-limits#application-record-and-index-limits) as other Algolia indexes: * Maximum index size: 100 GB * Maximum record size: 100 KB Indexes that exceed these limits aren't supported by either keyword search or NeuralSearch. ## Other considerations * **Log IP forwarding.** NeuralSearch indices don't show user IP addresses from `X-Forwarded-For` in search logs. # Manage model training preferences Source: https://www.algolia.com/doc/guides/ai-relevance/neuralsearch/model-training Learn how Algolia uses data to improve AI models and how you can manage your organization's participation. NeuralSearch's AI models improve by learning from anonymized, combined usage patterns across connected apps. This shared learning helps NeuralSearch better interpret language, understand intent, and surface more relevant results over time. You don't need to build or maintain custom training pipelines. By default, all applications in your organization are automatically included in model training. Application owners can change this setting at any time in the dashboard. Opting out excludes that application's data from future training cycles. Search continues to function normally, but it won't receive improvements from shared model updates. You can opt back in at any time. To manage your organization's training preference: **1. Go to Organization Settings** 1. Go to the [Algolia dashboard](https://dashboard.algolia.com). 2. On the left sidebar, select **Settings > Organization Settings**. 3. In the **General** section, click **Model Training**. **2. Review your applications** A table lists all applications in your organization. Filter the table and review the details to decide which applications should participate. **3. Update participation** You can manage preferences individually or in bulk: * **Toggle switch**. Turn training on or off for an application. * **Checkboxes**. Select multiple applications to apply bulk actions. * **Select all**. Apply changes to every eligible application at once. * To confirm your selection, select **Opt In Selected** or **Opt Out Selected**. **4. Save changes** To apply your changes, click **Save Settings**. To undo any changes before saving, click **Discard**. ## What happens after opting out * Future model training runs won't use data from excluded applications. Previously trained models remain unchanged. * You'll continue to receive NeuralSearch feature updates, but not incremental improvements from shared model training. * You can opt back in at any time to receive the latest shared model improvements. ## See also To learn more about how Algolia trains language models, including data usage, privacy safeguards, and retention policies, see: * [AI Trust Center](https://www.algolia.com/trust-center) * [How Algolia trains language models](https://www.algolia.com/blog/ai/language-model-training) # Dynamic Facets Source: https://www.algolia.com/doc/guides/algolia-ai/dynamic-facets Dynamic Facets helps users find relevant results by automatically reordering facets and facet values based on past filtering patterns. Instead of a static list, users see the most relevant facets first for their query. ![Comparison showing how Dynamic Facets reorders facets and facet values based on usage](https://mintcdn.com/algolia/lfjpuQC-sr_BgWwS/images/guides/algolia-ai/dynamic-facets/comparison.png?fit=max\&auto=format\&n=lfjpuQC-sr_BgWwS\&q=85\&s=1cebd61a35f61d3d7611b501aea3bac8) ## How dynamic facets work Dynamic Facets uses [query aggregation data](/doc/guides/search-analytics/concepts/query-aggregation) to reorder facets and facet values based on filtering patterns in past searches. Dynamic Facets: * Recomputes facet and facet value orderings daily and whenever you update the feature configuration. * Uses the last 30 days of search data. ## Get started To use Dynamic Facets in your UI, read facet and facet value ordering from the `renderingContent.facetOrdering` property in the search response. You can use this ordering with InstantSearch or in a custom UI. ### With InstantSearch Use the [`DynamicWidgets`](/doc/api-reference/widgets/dynamic-widgets/js) component to render refinement lists in the order returned by Dynamic Facets. > [!NOTE] > > To apply facet value ordering automatically from the search response, you need **InstantSearch.js 4.72.0 or later** or **React InstantSearch v6 or later**. ### With a custom UI To use Dynamic Facets without InstantSearch: 1. Read the facet ordering from the `renderingContent.facetOrdering` property in the search response. 2. Combine it with the `facets` property to build the final facet order in your UI. 3. Render only facets that are present in the response so you don't show outdated attributes. For example: ```json JSON icon=braces expandable wrap theme={"system"} { "renderingContent": { "facetOrdering": { "facets": { "order": ["brand", "categories", "color"] }, "values": { "brand": { "order": ["Nike", "Adidas", "Puma"], "sortRemainingBy": "count" }, "categories": { "order": ["Shoes", "Jacket", "Jogging"], "sortRemainingBy": "count" }, "color": { "order": ["Red", "Green", "Blue"], "sortRemainingBy": "count" } } } } } ``` ## Settings Use these settings to configure how Dynamic Facets orders facets and facet values. ### Coverage You can use another index as the source of events. For example, if a replica doesn't receive events, you can reorder its facets and facet values based on events from its primary index. ### Browsing facets If you [use Algolia for navigation](/doc/guides/solutions/ecommerce/filtering-and-navigation), you can select a browsing facet for empty queries. This means that instead of reordering facets based on a specific query, Dynamic Facets can reorder them for a single filter used for browsing, such as `categories:"Shoes"`. ### Where to apply Choose where Dynamic Facets applies. You can A/B test these options independently. * **All**: apply to search queries and category pages. * **Queries only**: apply only to search queries. * **Categories only**: apply only to category browsing. ### Pinned facets Pin up to 10 facets to keep them at the top of the list. Dynamic Facets orders all other facets automatically. Use pinned facets for globally relevant facets that should always stay visible, for example, for commercial or policy reasons. * Only facets already present in your `Facet display` index setting can be pinned. * Pinning applies to **all queries**. To override it for a specific query, use a [rule](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/merchandising-facets) that replaces the full `facetOrdering`. * Only facets can be pinned, not individual facet values. * If a pinned facet is also part of the dynamically reordered set, Dynamic Facets removes it from that set and places it in its pinned position. ### Preserve value order for selected facets Use this option for facets such as `size` or `date`, where facet values follow a logical order. The facet can still move within the list, but its values keep the order inherited from [`Facet display`](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/js). ## Interaction with `Facet display` index setting If your index has facets listed in the `Facet display` index setting, Dynamic Facets merges its output with that setting at search time instead of replacing it. ### Facet ordering `Facet display` acts as a list of allowed facets. Dynamic Facets reorders only facets listed there. If Dynamic Facets generates a facet that isn't in the index setting, that facet is removed. Facets present in the index setting but not in the Dynamic Facets output are appended at the end in the same order as the index setting. This guarantees that: * No unwanted facets appear in the response. * All configured facets are always present. If no facets are listed in the `Facet display` index setting, the Dynamic Facets output is used as-is. ### Facet value ordering For each facet in the final list: * If the facet is in the **Preserve value order** list, Dynamic Facets ignores its own value reordering and uses the index setting values as-is, including any pinned values, hidden values, and the `sortRemainingBy` property. * If the facet *isn't* in the **Preserve value order** list, Dynamic Facets uses its own value ordering (up to 20 values per facet) and sets `sortRemainingBy` to `count`. * If a facet doesn't have any Dynamic Facets values or index setting values, it's included without value ordering. The default frontend behavior applies. ## Interaction with other features * **Rules and merchandising.** If a [rule](/doc/guides/managing-results/rules/rules-overview) provides a `renderingContent` query parameter, it takes full precedence. Dynamic Facets is bypassed for that query. * **Personalization.** Dynamic Facets and personalization don't work together. If personalization is enabled for a signed-in user, the personalized order overrides Dynamic Facets. ## Limitations * Only the top **100,000 distinct queries** are considered. * Only queries between **3 and 100 characters** with at least **100 searches** are considered for facet reordering. * A maximum of **20 facets** and **20 facet values** per facet are reordered per query. * Remaining items follow the `sortRemainingBy` behavior in `renderingContent`. `count` is the default. * Deleted facets can still appear in Dynamic Facets output because the ordering is based on historical data. Check that a facet exists in the `facets` list before you display it. # AI Synonyms Source: https://www.algolia.com/doc/guides/algolia-ai/dynamic-synonym-suggestions Use AI to automate finding the best synonyms for your search. This feature isn't available on every plan. Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. Synonyms are words that share a similar meaning. Adding synonyms to your search helps your users find relevant results even if they use different words to express the same concept. For example, by adding a synonym for trousers and pants, a search for either term returns identical results. > [card] [Algolia Academy](https://academy.algolia.com/pages/contentcatalog/training/0199aad2-7308-7171-9b90-a01f65efa135/overview) > > Learn more about: AI Synonyms ## Get started To activate AI Synonyms for one of your indices: 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 (An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.). 4. Open the [**AI Synonyms**](https://dashboard.algolia.com/ai-synonyms) page and select **Activate for this index**. ## Send a user token with your searches To find suggestions, Algolia determines [how often users rewrite](#user-query-rewrites) a search term. To make that more accurate, send user tokens with your search requests. For more information, see [Distinguish users for analytics](/doc/guides/search-analytics/guides/usertoken). ## Review synonyms The **Suggested Synonyms** tab shows new suggestions. To show all suggestions, turn off the **Show only new synonyms** option. As you examine a suggested synonym, you'll see metrics to help you select the better option: the original term or the suggestion. These metrics include: * Conversion rate (CVR) * Click-through rate (CTR) * Result count * Confidence score > [!TIP] > > If you're [sending click and conversion events](/doc/guides/sending-events), > you can compare click-through and conversion rates for both alternatives. #### What the confidence score means Each suggestion includes a **confidence score** between 0 and 100. It helps you find the synonym suggestions with the highest probability of improving your search results. Two machine learning models determine the score: * **A semantic similarity model** which measures how closely related the terms are. * **A confidence scoring model** that converts this semantic similarity into a probability that the suggested synonym will enhance search relevance. For example: * A score of **90** means there's a 90% chance the suggested synonym will improve your search results. * A score of **10** means it's unlikely to help. ## Accept or decline suggestions To accept a suggestion, click **Add to configuration** (`✓`). If you don't want to add this suggestion to all replicas of this index, turn off the **Forward to replicas** option at the top. You can edit accepted suggestions in the **Accepted suggestions** tab. To decline a suggestion, click **Decline this suggestion** (`x`). You can edit and accept declined suggestions in the **Declined suggestions** tab. > [!TIP] > > After accepting a suggestion, only edit it in the dashboard, not with the API. ## Change synonym types By default, the AI suggests adding regular two-way synonyms. To change to another [synonym type](/doc/guides/managing-results/optimize-search-results/adding-synonyms), hover over the suggestion and click the **Edit** button. Then, click **Select Type** (`⇔`) and select a synonym type. ![Screenshot of the edit suggestions menu with options for changing synonym types](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/algolia-ai/synonyms/types.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=c5da9fed89b6e79e703620a79fc8a411) ## Edit suggestions To edit a suggestion, hover over it and click the **Edit** button. Then, you can edit both synonyms or add more. ![Screenshot of the edit suggestions menu showing the synonym to edit](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/algolia-ai/synonyms/edit.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=9d21f8862ce55c82dc915b9ae271a202) ## Alternative suggestions Sometimes, there are several suggestions for the same search query. To see the alternative suggestions, click **More suggestions**. ![Screenshot of alternative synonym suggestions, including 'refresh ⇔ update' and 'query suggestions refresh ⇔ query suggestions update'.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/algolia-ai/synonyms/alternative-terms.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=52a8489bec8182b1e7bc2b2b5dfb845b) ## How AI Synonyms work AI Synonyms provides a list of synonyms that may improve your search experience by considering how users rewrite their queries and the historical performance of different searches. ### User query rewrites Algolia analyzes user sessions to detect when people change their search query. If a user searches for a word and then changes the query to use a different word, that implies a potential synonym. To qualify as a synonym suggestion: * Users must search for one word and then change the query to another word (within about a minute). * The two words must seldom appear together in the same query, suggesting they're potential synonyms rather than companions. For example, since **"sneakers"** and **"trainers"** are often synonyms, they're unlikely to appear in the same query. However, **"sneakers"** and **"laces"** may frequently co-occur and, hence, are companions. For each index, the evaluation considers analytics data from the last 30 days. > [!NOTE] > > For multi-word terms, Algolia examines the frequency of these terms across different search queries to confirm they're potential synonyms and not part of a set phrase. #### Rewrite count Each synonym suggestion includes a **rewrite count**, which reflects how often users switched from one word to another during the preceding week. The more frequent this behavior is among your users, the higher the count. ### Search performance analysis Algolia uses your analytics data to see how well each search term performs. It only considers terms that users have typed into the search box. Low-performing terms are terms that lead to low engagement (few clicks, low conversions) or poor result quality (few or zero results). For each of these low-performing terms, Algolia suggests synonyms that are linguistically similar and likely to improve search outcomes. For example, if users frequently search for "denim trousers" but that term leads to few clicks, "jeans" might be a synonym suggestion. ## See also * [Synonyms](/doc/guides/managing-results/optimize-search-results/adding-synonyms) # Optimize search settings with AI Assist Source: https://www.algolia.com/doc/guides/algolia-ai/optimize-search-settings Use AI Assist in the Algolia dashboard to review an index's relevance settings and apply recommended configuration changes. This is a **beta feature** according to [Algolia's Terms of Service ("Beta Services")](https://www.algolia.com/policies/terms/). AI Assist reviews index settings that affect [relevance](/doc/guides/managing-results/relevance-overview) and recommends changes. Use AI Assist before you launch a search experience, after you change your record structure, or when search quality declines. AI Assist tailors its recommendations to your [Algolia plan](https://www.algolia.com/pricing) and only suggests features included in your plan. You can review each suggested change before you apply it. ## Before you begin Make sure you have: * An Algolia index with records. * Permission to [update index settings](/doc/guides/security/api-keys#rights-and-restrictions) (the `editSettings` ACL). ### Plan how to test and apply changes Before applying recommendations, consider how the index is used: * If this is a production index, test changes on a test index or with [A/B testing](/doc/guides/ab-testing/what-is-ab-testing) before applying them broadly. * If the index is part of an active A/B test, wait until the test ends before changing a setting that the test is evaluating. * If the index has replicas, decide whether each change should apply to the primary index, its replicas, or both. > [!WARNING] > > If you must change a setting during an active A/B test, keep that setting consistent across all variants. > Otherwise, the change can affect the comparison. ## Review and apply index setting recommendations 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 the index you want to review, then open the **Configuration** tab. 4. To review settings, select **Optimize Search** for any setting or, on the **Configuration** tab, select **AI Assist**, then enter `Optimize my search configuration`. 5. Answer the questions about your industry, search use case, and goals. AI Assist uses your answers to tailor its recommendations. 6. Review the **What's working well** and **Needs optimization** sections. For each proposed change, review the explanation. ![Screenshot of AI Assist listing ranking, custom ranking, and typo tolerance, and a recommended change to searchable attributes.](https://mintcdn.com/algolia/Yijri_uyhLzXO0zB/images/guides/algolia-ai/ai-assist/assist-recommendations.png?fit=max\&auto=format\&n=Yijri_uyhLzXO0zB\&q=85\&s=a8edb285e98985fbcbdbf54e44a9bee6) 7. Select the checkbox for each recommendation you want to use, then select **Apply**. 8. Wait for AI Assist to confirm that the changes are applied. > [!NOTE] > > If your Algolia plan includes Activity Logs, review the applied changes in the > [logs](https://dashboard.algolia.com/account/audit-logs). ## Supported settings AI Assist can suggest changes for these index settings: | Setting | What AI Assist reviews | | - | - | | [Searchable attributes](/doc/guides/managing-results/must-do/searchable-attributes) | Which record attributes Algolia searches and in what order. | | [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking) | The attributes Algolia uses to break ties between records that otherwise rank the same. | | [Ranking](/doc/api-reference/api-parameters/ranking) and [sorting](/doc/guides/managing-results/refine-results/sorting) | How the index ranks records and supports sort orders. | | [Typo tolerance](/doc/guides/managing-results/optimize-search-results/typo-tolerance) | How Algolia handles spelling mistakes. | # Query Categorization Source: https://www.algolia.com/doc/guides/algolia-ai/query-categorization Predict the categories to which a search query belongs with AI. This feature isn't available on every plan. Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. The Query Categorization feature uses an AI model to predict the appropriate category for a search query. It predicts the most likely category for a query based on past search and click and conversion data. For example, the query "banana" might be categorized under "Food > Fruits." Query Categorization also groups similar queries into categories. For example, both "blue jeans" and "denim" could be classified in the "Clothing > Pants" category. "Denim" could also belong to the "Clothing" category. > [card] [Algolia Academy](https://academy.algolia.com/training/0199aad2-7392-70b9-8ec7-d87b8e202cab/overview) > > Learn more about: Query Categorization: Tailor Results ## Features **Query Categorization** gives you access to these features: * **Dashboard control** lets you set up and check AI model predictions from the Algolia dashboard. * **Automatic filtering and boosting** improves the relevance of results without writing code. * **Category predictions:** use the Search API to generate real-time category predictions for user queries. Use these predictions to customize their search and discovery experience. ## Set up Query Categorization To get started with Query Categorization: 1. **[Send click and conversion data](#send-click-and-conversion-events)** to Algolia: this helps the AI model learn user behavior. 2. **[Create a facet hierarchy](#create-a-facet-hierarchy)** to define the categories and subcategories your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) belong to. This lets the AI model classify them. After setup, the [AI model](#the-ai-model) analyzes popular queries and categorizes them. It also predicts categories for new queries. ### Send click and conversion events To train the AI model, you must [send click or conversion events](/doc/guides/sending-events) to Algolia. The model uses this data to predict the categories for new queries. For a query to be eligible for Query Categorization model training, it must: * Be longer than three characters * Have returned at least 10 records * Have received events for at least three different records. The model is re-trained every 24 hours, using data from the last 90 days. ### Create a facet hierarchy To enable Query Categorization, use the [dashboard](https://dashboard.algolia.com/query-categorization/) to define the facets. These are the categories and subcategories the AI model uses. These facets should accurately represent your data hierarchy, up to five levels deep. Once you've entered your facets, click **Save** to start the model-building process. Depending on the number of categories and traffic, this can take several minutes to half an hour. #### Example facet hierarchy of nested categories Assuming a record structure like this: ```json JSON icon=braces theme={"system"} { "name": "banana", "description": "...", "price": 3.45, "hierarchicalCategories": { "lvl0": "Food", "lvl1": "Fruits" } } ``` Set `hierarchicalCategories.lvl0` as the first level used by the model and `hierarchicalCategories.lvl1` as the second level. #### Example facet hierarchy of flat categories Assuming a record structure like this: ```json JSON icon=braces theme={"system"} { "name": "banana", "description": "...", "price": 3.45, "group": "Food", "section": "Fruits" } ``` Set `group` as the first level used by the model and `section` as the second level. Your records might belong to several categories at the same time. If you use arrays to represent each level of depth, the model expects shared prefixes. For example, `Food` is the first level facet value and `Food > Fruits` is the second level. #### Unsupported hierarchical facet formats The model doesn't support records structured with only one attribute for all depth levels. For example: ```json JSON icon=braces theme={"system"} { "name": "banana", "description": "...", "price": 3.45, "categories": ["Food", "Food > Fruits"] } ``` ### Manage categories After model training, view the generated categories tree. Find it in the **Categories Tree View** tab in the [dashboard](https://dashboard.algolia.com/query-categorization/). This lets you review and adjust the structure as needed. #### Exclude categories From the **Categories Tree View** tab, you can also exclude non-categories like "Black Friday" or "On sale" to increase the model's accuracy. ### Manage events source You can use different data sources for your events. For example, use data from a production 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.) to improve predictions for a test index that hasn't had any user interactions. To use a different data source: 1. Go to the **Categories Setting** tab in the [dashboard](https://dashboard.algolia.com/query-categorization/). 2. In **Events source index**, select the new data source (it must be a [replica](/doc/guides/managing-results/refine-results/sorting/in-depth/replicas) or a copy of the existing source index). 3. Click **Save** to regenerate the category tree and make predictions using events from the new data source. ## The AI model After the facet hierarchy has been saved, the AI model performs these tasks: 1. Build a "categories tree" based on your data. 2. Identify top categories for popular queries. 3. Predict categories for new queries. ### Model output The AI model assigns a confidence level and a type to each prediction: * **Confidence level:** `very low`, `low`, `high`, `very high`, or `certain`. * **Type:** * `narrow`: the query matches a specific category. * `broad`: the query matches a category with subcategories. * `ambiguous`: the query matches several unrelated categories. * `none`: the model can't determine a category. ## Use predictions at query time Use Query Categorization predictions to enhance search results in your frontend at query time to: * **Expand results:** if results are limited, expand them. Add more items from the same category. * **Refine broad search terms:** if a query is classified as `broad`, suggest different categories. This helps users narrow their search. * **Customize search**: offer a tailored search experience by providing a specific layout for some categories. > [!NOTE] > > Query Categorization populates your search results with the predicted categories for the search query. > [Algolia normalizes](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp#normalization) the query used for prediction, not the raw query. ### Turn on Query Categorization at query time To retrieve Query Categorization results at query time, activate the option from the dashboard or in query parameters. * **In the [dashboard](https://dashboard.algolia.com/query-categorization/)**, enable the **Categories with Search API** toggle in the **Categories Settings** tab. * **In query parameters** as a JSON object or a [URL encoded string](https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding). For example, `extensions%3D%7B%22queryCategorization%22%3A%7B%22enableCategoriesRetrieval%22%3Atrue%7D%7D` Find Query Categorization parameters in the `extensions` field: ```jsonc JSON icon=braces theme={"system"} { // Other standard query parameters "extensions": { "queryCategorization": { "enableCategoriesRetrieval": true // Other options to control automatic filtering and boosting are available } } } ``` #### Search response format The [search response](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response) shows predictions in the attribute `extensions.queryCategorization`. ```jsonc JSON icon=braces theme={"system"} { // Regular search answer (like hits) "extensions": { "queryCategorization": { "normalizedQuery": "banana", "count": 2, "type": "narrow", "categories": [ { "bin": "very high", "hierarchyPath": [ { "facetName": "category.lvl0", "facetValue": "Food", "depth": 0 }, { "facetName": "category.lvl1", "facetValue": "Fruits", "depth": 1 } ] } ] } } } ``` Sometimes, `extensions.queryCategorization` is empty if the Query Categorization model can't categorize a query. ## How to override predictions You can override the AI model's predictions from the [dashboard's](https://dashboard.algolia.com/query-categorization/) **Predictions Explorer** tab. * To change the override or replace the predicted categories, click the edit (pencil) icon. * To revert an override to the predicted categories, click the lightning icon. * To remove a prediction, click the trash icon. ![Screenshot of a query card for 'BOOKMARKS' showing the predicted category 'Office Product' with 'Filtered' and 'CERTAIN' labels.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/algolia-ai/query-categorization/query-card.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=994fe5614db87fa826eacaf2e3b4f221) Algolia displays changes in the predictions list. To confirm them, click **Save changes** at the bottom of the page. > [!NOTE] > > Changing the index classification in **Categories Settings** deletes any override affected by this change. > For example, removing the second facet level from the index classification deletes overrides like `Food > Fruits`, > reverting the query to automatic predictions. ## Automatic filtering and boosting Automatic filtering and boosting applies filters for user queries based on Query Categorization predictions. * **Automatic filtering** [filters out](/doc/guides/managing-results/refine-results/filtering/in-depth/filters-and-facetfilters) items that don't match the predicted category. * **Automatic boosting** uses an [optional filter](/doc/guides/managing-results/rules/merchandising-and-promoting/in-depth/optional-filters) to boost items that match the predicted category. This moves matching items to the top of search results. Based on [confidence levels](#model-output), the Query Categorization model decides how to apply predictions. It can apply them as filters, as boosts, or not at all. ### Use automatic filtering and boosting To use this feature, click **Enable Automatic Filtering & Boosting**. You'll find this option in the **Automatic filtering & boosting Settings** tab in the [dashboard](https://dashboard.algolia.com/query-categorization/). By default, only automatic boosting is activated. To enable automatic filtering, see [Configure automatic filtering and boosting](#configure-automatic-filtering-and-boosting). Once activated, Algolia automatically adds boosts to your search parameters at query time. This doesn't require frontend changes. #### Exclude queries From the **Automatic filtering & boosting Settings** tab, you can also exclude queries that shouldn't be automatically filtered or boosted. Anything specified here overrides your index's configuration. ### Configure automatic filtering and boosting Adjust the impact of automatic filtering and boosting by modifying two parameters in the **Automatic filtering & boosting Settings** tab: * The minimum expected confidence level for filtering * The minimum expected confidence level for boosting These parameters let you configure when to apply filters or boosts based on the predictions' [confidence levels](#model-output). The feature: * Boosts predictions with a confidence level equal to or above the boosting level, but below the filtering level. * Filters predictions with a confidence level equal to or above the filtering level. For instance, say the boosting confidence level is `high` and the filtering confidence level is `certain`. Algolia then boosts `high` and `very high` predictions, and filters on `certain` predictions. > [!TIP] > > The confidence level for boosting must always be lower than the level for filtering. Turn off filtering or boosting with the corresponding options. ### Override automatic filtering and boosting at query time You can override the default configuration for automatic filtering and boosting with query parameters: ```jsonc JSON icon=braces theme={"system"} { // Other query parameters ... "extensions": { "queryCategorization": { "enableAutoFiltering": true } } } ``` To let users remove filters applied by automatic filtering and boosting, you must turn off automatic filters and boosts on the search query. Target the query at your index. Do this when users clear the automatic filter. [Create an InstantSearch widget to handle this behavior](/doc/guides/building-search-ui/going-further/automatic-filtering/js). ### Detect the impact of automatic filtering and boosting at query time When automatic filtering and boosting is active for a query, the `extensions.queryCategorization.autofiltering` section has the following content: ```jsonc JSON icon=braces theme={"system"} { // ... Regular search response (including hits) ... "extensions": { "queryCategorization": { "normalizedQuery": "banana", "count": 14870, "type": "narrow", "categories": [ { "bin": "certain", "hierarchyPath": [ { "facetName": "categories.lvl0", "facetValue": "Food", "depth": 0 }, { "facetName": "categories.lvl1", "facetValue": "Food > Fruits", "depth": 1 } ] } ], "autofiltering": { "enabled": true, "maxDepth": 5, "facetFilters": [ [ "categories.lvl0:Food" ], [ "categories.lvl1:Food > Fruits" ], ], "optionalFilters": [] } } } } ``` You can activate automatic filtering and boosting without it having an impact. Other fields don't appear in your [search response](#search-response-format). ### Preview automatic filtering and boosting You can preview automatic filtering and boosting for any index. Find this preview in the **Automatic filtering & boosting Preview** tab of the [**Query categorization** section in the dashboard](https://dashboard.algolia.com/query-categorization/). If you have category predictions for the selected index, you can preview results for any query with predicted categories. This shows how automatic filtering and boosting affects results, without activating it on your production traffic. ![Previewing automatic filtering and boosting on your indices with the automatic filtering and boosting Preview](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/algolia-ai/query-categorization/afb-simulator.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=90e62f157ee84d5f620bd9f5c71f2ccf) > [!NOTE] > > The automatic filtering and boosting preview shows how promotion [rules](/doc/guides/managing-results/rules/rules-overview) and [Dynamic Re-Ranking](/doc/guides/algolia-ai/re-ranking) affect results. > You can turn these features off in the preview with the **Rules** and **Dynamic Re-Ranking** options. ### A/B test automatic filtering and boosting You can use A/B testing to test automatic filtering and boosting on an index. This helps you measure the effect on your search accurately. To do this, open the [**Query Categorization**](https://dashboard.algolia.com/query-categorization/) page in the Algolia dashboard. Then, select the **Automatic filtering & boosting Settings** tab and click **Launch an A/B test**. ## Analytics grouped by categories After setting up the Query Categorization model, you can view queries grouped by their predicted categories. Find this view in the dashboard's **Grouped Searches** tab, under **Observe > Analytics**. This view doesn't include browsing queries: "empty" queries generated when a user clicks a filter or other UI element. Compare categories or click them to inspect their queries. Algolia automatically flags queries with low click-through or conversion rates. It labels them "underperforming" within each category. For example, Algolia flags `blue jeans` and `denim` as belonging to the same category (`pants`). Grouped analytics displays the performance of the category `pants`. This aggregates data for `blue jeans`, `denim`, and other queries in the `pants` category. You can then compare the performance of the two. For example, the `pants` category's click-through rate is 10%, but the click-through rate for `blue jeans` is only 4%. Algolia identifies `blue jeans` as underperforming. You can improve the performance of the query by, for example, adding a synonym or a Rule. > [!NOTE] > > With grouped analytics, you can combine your search analytics to uncover new insights. > This helps you optimize your search and discovery experience. > It simplifies search analysis. > It also helps manage the [long tail](https://wikipedia.org/wiki/Long_tail) of search queries. ## See also To learn more, see these blog articles: * [Query understanding](https://www.algolia.com/blog/product/query-understanding-101) * [12 ways to improve your search (including Query Categorization)](https://www.algolia.com/blog/engineering/12-ways-to-improve-your-search-index) # Dynamic Re-Ranking Source: https://www.algolia.com/doc/guides/algolia-ai/re-ranking Dynamic Re-Ranking adjusts ranking based on changing user behavior. This feature isn't available on every plan. Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. The relevance of your search results can change over time, sometimes quickly. Consider a drugstore. Before the COVID-19 pandemic, users who searched for "mask" could have been looking to buy facial masks for skincare. During the pandemic, the same query could refer to surgical masks. To respond to changing user expectations, you might have to adjust your results with [Rules](/doc/guides/managing-results/rules/rules-overview) or [`optionalFilters`](/doc/api-reference/api-parameters/optionalFilters). This can become time-consuming because trends come and go. A better, more scalable approach is to use the Dynamic Re-Ranking feature. > [card] [Algolia Academy](https://academy.algolia.com/training/0199aad2-73cf-7742-b538-7873f1db558e/overview) > > Learn more about: Dynamic Re-Ranking: Boosting Conversions ## What is Dynamic Re-Ranking? Dynamic Re-Ranking is an Algolia feature that uses AI to detect trends in user behavior. It learns from click and conversion events for a given 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 can promote records that are becoming more popular. Dynamic Re-Ranking only reorders records that pass textual relevance and your filters. For example, if users increasingly choose surgical masks when searching for "mask", Dynamic Re-Ranking can move surgical masks higher in future results. This can improve relevance and increase conversion rate (CVR) (The percentage of searches with `clickAnalytics` set to `true` that result in at least one conversion event.) without requiring rules or manual 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.) changes. ## How Dynamic Re-Ranking works Learn when Dynamic Re-Ranking can reorder results, how ranking works, and how it interacts with Personalization. ### Effect on relevance Dynamic Re-Ranking runs after Algolia has built the result set using textual relevance], filters, and custom ranking. However, there are some constraints on the feature: * Dynamic Re-Ranking only reorders records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) that are already eligible to appear in the results (it doesn't add new records). * Most Rules consequences (such as boosting or burying categories) run *before* Dynamic Re-Ranking. * Pin and hide consequences run *after* Dynamic Re-Ranking, so pinned records stay in place and hidden records don't appear. Dynamic Re-Ranking may also change ordering when you use [geographical filtering or sorting](/doc/guides/managing-results/refine-results/geolocation#geographical-filtering-and-sorting), depending on how you configure [the Geo ranking criterion](/doc/guides/managing-results/refine-results/geolocation/how-to/geo-ranking-info). In a typical search, Algolia applies these ranking steps in this order: 1. [Textual ranking criteria](/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria) 2. [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking) 3. [Personalization](/doc/guides/personalization/classic-personalization/what-is-personalization) (if you've enabled it and Algolia has enough information to personalize results for a particular user) 4. Rules ([boosting](/doc/guides/managing-results/must-do/custom-ranking/how-to/boost-or-penalize-some-records) and [burying](https://support.algolia.com/hc/en-us/articles/4406981935761#bury-categories)) or [Automatic Boosting from Query Categorization](/doc/guides/algolia-ai/query-categorization#automatic-filtering-and-boosting) 5. Dynamic Re-Ranking 6. Rules ([pinning](https://support.algolia.com/hc/en-us/articles/4406981935761#pin-items) and [hiding](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/how-to-hide-hits) specific items). > [!NOTE] > > By default, > Algolia doesn't apply Dynamic Re-Ranking to empty searches but you can > [enable re-ranking for empty searches filtered on a particular facet value](#enable-re-ranking-on-browsing-pages-browsing-facet). ### Allow Dynamic Re-Ranking when Personalization applies By default, Dynamic Re-Ranking doesn't run if [Personalization](/doc/guides/personalization/classic-personalization/what-is-personalization) reorders results on the first page for that user. To enable Dynamic Re-Ranking even when Personalization affects the first page of results, set [`allowReRankingOnPersonalization`](/doc/api-reference/api-parameters/allowReRankingOnPersonalization) to `true` at query time. ```json JSON icon=braces theme={"system"} { "query": "mask", "enablePersonalization": true, "personalizationImpact": 80, "enableReRanking": true, "allowReRankingOnPersonalization": true, "distinct": 0 } ``` > [!NOTE] > > Dynamic Re-Ranking doesn't apply when [`distinct`](/doc/api-reference/api-parameters/distinct) > 1, > even if `allowReRankingOnPersonalization` is `true`. > > Set `distinct` to `0` or `1` to use Dynamic Re-Ranking with Personalization. ## Dynamic Re-Ranking preparation Before you enable Dynamic Re-Ranking, make sure you send the right events and meet the data requirements. ### Send the required click and conversion events To use Dynamic Re-Ranking, you must send [click and conversion events](/doc/guides/sending-events). Dynamic Re-Ranking uses this data to detect trends and adjust ranking accordingly. Dynamic Re-Ranking may work with only click events (not conversion events) but performs better if you send *both* click and conversion events. Dynamic Re-Ranking refreshes the [ordering](#ordering-settings) for each query every 24 hours by default. It uses a sliding 30-day window of recent analytics data. To promote a record for a given query, Dynamic Re-Ranking needs at least 20 clicks or two conversions for that record during the 30-day window. Events must contain a [`queryID`](/doc/libraries/sdk/v1/methods/send-events#param-query-id) property that corresponds to a valid search. For each query and record (`objectID`), Dynamic Re-Ranking counts at most one click and one conversion per [`userToken`](#security). If the `userToken` in the event differs from the `userToken` in the search request, Dynamic Re-Ranking uses the search `userToken`. Events must include an `objectID` property for a record in the specified index. This `objectID` needs to belong to one of the returned records of the search with the specified `queryID`. For example: 1. On the first day of the month, a search returns record `X`. 2. Your app sends an event for record `X` in the production index with the corresponding `queryID`. 3. Dynamic Re-Ranking uses record `X` in its next ordering computation. 4. On the fifth day of the month, your app deletes record `X` from the production index. 5. Dynamic Re-Ranking stops using record `X` in future ordering computations. > [!WARNING] > > Dynamic Re-Ranking might not be suitable for indices with frequent record deletions, > such as those used by a second-hand car dealer where each car's record is deleted upon sale. > Instead, use [Automatic boosting from Query Categorization](/doc/guides/algolia-ai/query-categorization#automatic-filtering-and-boosting). #### Security To prevent malicious users from manipulating the ranking of your records, Dynamic Re-Ranking counts at most one click and one conversion per [`userToken`](/doc/api-reference/api-parameters/userToken) for a given user, query, and record. For a given [user-query-record combination](/doc/guides/sending-events/concepts/event-types#events-requirements-for-dynamic-re-ranking), Dynamic Re-Ranking counts at most one click and one conversion per `userToken`. For example, if the same user (same `userToken`) clicks on the same record 100 times after the same query, it counts as one click. If the same user clicks on the same record after different queries, they count as separate events. > [!TIP] > > To prevent users from crafting several user tokens, > use [secured API keys](/doc/libraries/sdk/v1/methods/generate-secured-api-key). > With secured API keys, you can include the `userToken` as an embedded parameter when you generate the key on your backend. > This gives each user a unique key, but they can't change its `userToken`. ### Validate with the simulator Simulate re-ranking for any 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.) from the [**Dynamic Re-Ranking** page in the dashboard](https://dashboard.algolia.com/reranking/). Once you've [sent enough click and conversion events](#send-the-required-click-and-conversion-events), try out any query and see how Dynamic Re-Ranking affects the results (without activating re-ranking on your live index). ![Simulating Dynamic Re-Ranking on your indices with the Re-Ranking Simulator](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/algolia-ai/re-ranking/reranking-simulator.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=ebec6cbde3cec4af32f6133817a9b1f0) In the simulator, each record has an attractiveness score per query. This score reflects the number of events the record received in the last 30 days compared with other records and it explains why re-ranking boosts one record over another. With time decay enabled, recent events matter more, so a record with fewer recent events can outrank a record with more historical events. Turn off the rules in the simulator with the **Rules** toggle to assess the benefits of Dynamic Re-Ranking. Consider removing these rules if you're satisfied with the records that Dynamic Re-Ranking promotes. > [!NOTE] > > If you use rules to [pin top records for popular queries](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/promote-hits), > Dynamic Re-Ranking has less impact because pinned records stay in place. ## Dynamic Re-Ranking settings Configure the following settings from the [**Dynamic Re-Ranking** page in the dashboard](https://dashboard.algolia.com/reranking/). ### General settings General settings control the goal, event source, and refresh frequency. They also control whether to combine events across grouped records. > [accordion] Goal > > This feature isn't available on every plan. > Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. > > The **Re-Ranking Revenue Goal** option is part of the Dynamic Re-Ranking for Revenue feature. > > By default, Dynamic Re-Ranking reorders your search results to maximize the number of conversions on your index (CVR). > > To optimize for total revenue instead, > send [Purchased object IDs after search events](/doc/libraries/sdk/v1/methods/purchased-object-ids-after-search) with `price`, `quantity`, and `currency` for each purchased item. > Dynamic Re-Ranking optimizes revenue for the currency that appears most often in an index's events and ignores events in other currencies. > [accordion] Events source index > > By default, Dynamic Re-Ranking uses the [events](#send-the-required-click-and-conversion-events) sent to the re-ranked index. > You can use another index as the source of the events by changing the **Events source index** option. > The indices must be replicas of each other. > > Use this parameter to experiment with your Dynamic Re-Ranking configuration without affecting production search results: > > * Create a replica of your production index > * Enable Dynamic Re-Ranking on the replica > * Set the event source index to your production index to use its events to generate the replica's re-ranking, which would otherwise be empty because the replica doesn't have any events > * Test changes to your Dynamic Re-Ranking configuration in the simulator on the replica > * Start an A/B test by comparing your production index with the replica > [accordion] Refresh every hour > > This feature isn't available on every plan. > Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. > > Refreshing every hour is an Advanced Dynamic Re-Ranking feature. > > By default, Dynamic Re-Ranking refreshes every 24 hours. > When you enable this option, Dynamic Re-Ranking refreshes every hour instead. > > **Refreshing every hour helps you respond to shorter-lived search trends.** > [accordion] Combine events across grouped records > > To combine click and conversion events across related records, such as product variants: > > 1. Set [`distinct`](/doc/api-reference/api-parameters/distinct) to `1`. > 2. Configure [`attributeForDistinct`](/doc/api-reference/api-parameters/attributeForDistinct) to define the groups. > 3. On the Dynamic Re-Ranking page, enable **Aggregate events across grouped records**. ### Coverage settings Coverage settings determine which queries Dynamic Re-Ranking can affect and the number of results it can reorder. > [accordion] Multi-Signal Ranking > > This feature isn't available on every plan. > Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. > > By default, Dynamic Re-Ranking uses your events to re-rank top queries. > > The Multi-Signal Ranking feature improves the ordering of less popular queries: > it doesn't affect the ordering of your top queries. > > For less popular queries, Algolia trains an AI model on your events. > The model considers each record's textual relevance to the query and the record's content. > Algolia retrains the model every day, and training can take up to an hour. > After training finishes, Dynamic Re-Ranking applies the updated ordering to those queries in search results (as it does for any re-ranked query). > > To train the model, choose up to 20 record attributes. > Choose attributes that influence the "attractiveness" of records for users. > Think about what influences user choices when they browse your site. > For example, `brand`, `price`, and `category.id` are good candidates but `SKU`, `imageURL`, or `margin` aren't. > > The attributes must be booleans, numbers, strings, or lists. > Objects aren't supported. > You can mark each attribute as: > > * `date` for numbers expressed as a [Unix timestamp](https://www.unixtimestamp.com/). > * `category` for string or number values shared across your records. > Good examples are `brand` and `color`. > Don't use `category` for unique fields like `productDescription` or `title`. > * `unclassified` for anything else. > > To see which attributes affected ranking the most, download a CSV from the dashboard. > The closer the value is to 0, the less impact the attribute has on re-ranking. > Negative values show that the attribute lowers ranking. > For example, the attribute `isOutOfStock` can be negative because out of stock items aren't desirable. > Multi-Signal Ranking learns to demote attributes with a negative impact. > [accordion] Enable re-ranking on browsing pages (Browsing Facet) > > By default, Algolia doesn't dynamically re-rank empty queries. > Setting a **Browsing Facet** re-ranks empty queries that include facet filters > (either with the [`facetFilters`](/doc/api-reference/api-parameters/facetFilters) or [`filters`](/doc/api-reference/api-parameters/filters) parameter) > for that attribute. > > For example, if you use Algolia to populate your website's category pages by performing an empty query with a `categoryPageID` facet, set `categoryPageID` as a browsing facet. > Re-ranking reorders records in each `categoryPageID` based on their popularity. > > If you set the root of a nested faceting attribute as a browsing facet, Dynamic Re-Ranking reorders all its sub-attributes. > For example, if you have an attribute named `hierarchicalCategories` with several nested levels (like `hierarchicalCategories.level0` and `hierarchicalCategories.level1`), using only `hierarchicalCategories` as a browsing facet re-ranks empty queries filtered on `hierarchicalCategories.level0` and `hierarchicalCategories.level1` as well. > > > [!NOTE] > > > > Set up to five browsing facets if your [plan](https://www.algolia.com/pricing) includes Advanced Dynamic Re-Ranking. > > Otherwise, you can use only one browsing facet. > [accordion] Enable re-ranking on empty query by default > > This feature isn't available on every plan. > Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. > > This is an Advanced Dynamic Re-Ranking feature. > > By default, Algolia doesn't dynamically re-rank empty queries. > When you enable this option, Dynamic Re-Ranking always re-ranks the empty query and promotes the most popular records. > Dynamic Re-Ranking uses events to sort re-ranked items only when the query is empty and no filters apply. > > If you set up [browsing facets](/doc/guides/algolia-ai/re-ranking#enable-re-ranking-on-browsing-pages-browsing-facet), > Dynamic Re-Ranking uses the empty-query fallback only when the search request doesn't include any browsing facets. > [accordion] Re-rank more results per query (Re-ranked Hits) > > You can increase the number of records at the top of search results from the default of 20 to 100. > > > [!NOTE] > > > > Dynamic Re-Ranking may promote fewer results than this value if Dynamic Re-Ranking doesn't gather significant traffic on enough records for some queries, or if filters remove records that were about to be re-ranked. ### Ordering settings Ordering settings control how Dynamic Re-Ranking weighs events and shares signals across similar queries. > [accordion] Give more weight to recent events (event freshness) > > To give more weight to recent events in the ranking computation, enable the **Event Freshness** option. > Otherwise, an event from four weeks ago affects re-ranking the same way as one from yesterday.” > **Enabling event freshness better handles seasonality and shorter search trends.** > [accordion] Group similar queries > > By default, each query is re-ranked independently. > **Group Similar Queries** helps to re-rank less frequent query variations. > > Dynamic Re-Ranking groups queries that contain the same words, in any order. > > Setting a language for grouping allows Dynamic Re-Ranking to ignore plurals, conjugations, and stop words during the grouping phase. > > Grouped queries share events and therefore use the same ordering. > [accordion] Turn off re-ranking for specific records (re-ranking filters) > > If you set a re-ranking filter, Dynamic Re-Ranking only promotes records matching the configured filters. > > For example, if you select `inStock = true`, Dynamic Re-Ranking only promotes items that are in stock. ## Enable Dynamic Re-Ranking for production Enable Re-Ranking on the [**Dynamic Re-Ranking** page in the dashboard](https://dashboard.algolia.com/reranking/). Choose the index you want to activate re-ranking for and select **Activate Re-Ranking**. ### A/B test Dynamic Re-Ranking Use [A/B testing](/doc/guides/ab-testing/what-is-ab-testing) to measure how Dynamic Re-Ranking affects search performance on your index. To create an A/B test from the [**Dynamic Re-Ranking** page in the dashboard](https://dashboard.algolia.com/reranking/), select **Launch an A/B test** in the top-right corner. This lets you choose the traffic split and duration to compare search performance with and without Dynamic Re-Ranking. ### Turn off Dynamic Re-Ranking for specific queries To turn Dynamic Re-Ranking off for a specific query, create a [rule](/doc/guides/managing-results/rules/rules-overview) with `{"enableReRanking": false}` as a consequence. # Enable Dynamic Re-Ranking on category pages Source: https://www.algolia.com/doc/guides/algolia-ai/re-ranking/tutorials/re-rank-category-pages Use Dynamic Re-Ranking to re-rank and promote popular products on category pages. You can use [Dynamic Re-Ranking](/doc/guides/algolia-ai/re-ranking) to detect trends in your users' behavior. Based on their search and browse interactions, Dynamic Re-Ranking boosts trending or popular results to improve relevance on category pages. Once you've configured your category pages and sent [click and conversion events](/doc/guides/sending-events/getting-started) to Algolia, Dynamic Re-Ranking uses this data to identify trends. You don't need to write any code to enable Dynamic Re-Ranking on category pages. This feature isn't available on every plan. Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. ## Before you begin Create [category pages](/doc/guides/solutions/ecommerce/browse/tutorials/category-pages) before enabling Dynamic Re-Ranking. ## Enable Dynamic Re-Ranking on category pages Set the following options in the [Dynamic Re-Ranking page in the dashboard](https://dashboard.algolia.com/reranking/): * **Events source index.** Select 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 receives your click and conversion events. You can also select an alternative source index. This is helpful if you have a staging index that replaces the production index every day for atomic reindexing. In this case, enable re-ranking on both the staging and production indices as source events, given that the production index already receives events. * **Browsing facets.** Set this to the category page identifier in your index, such as `collections`, `categories`, or `categoryPageId`. Check with your team to make sure the browsing 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.) is referencing the correct filter used in 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.) to generate the category page in the frontend UI. * **(Optional) Event freshness.** [Event freshness](/doc/guides/algolia-ai/re-ranking#give-more-weight-to-recent-events-event-freshness) gives more weight to recent events to account for seasonality and short-term trends. This setting is on by default, and in most cases, we recommend keeping it enabled. * **(Optional) Re-Ranking filter.** You can turn off re-ranking for specific records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) to prevent promoting popular items that are out of stock or no longer listed. To do this, select a filter value, for example `instock=true`. ## Turn on Dynamic Re-Ranking To turn the feature on, click **Activate Re-Ranking** from the [Dynamic Re-Ranking page in the dashboard](https://dashboard.algolia.com/reranking/). Dynamic Re-Ranking refreshes each query's ranking every 24 hours. It always takes the last 30 days into account, meaning it's based on a sliding window of the most recent analytics data. > [!NOTE] > > You can see re-ranked results only after Algolia has received enough search traffic and click and conversion events. > To be promoted for a given query, a record must receive **at least 20 clicks or two conversions within the 30-day window.** ## A/B test Dynamic Re-Ranking on category pages To ensure accurate measurement, launch your A/B test from the Dynamic Re-Ranking page in the dashboard: not from the dashboard's A/B test page. This applies the configuration as intended and limits results to category pages with enough traffic. ## See also * [Dynamic Re-Ranking's effect on relevance](/doc/guides/algolia-ai/re-ranking#effect-on-relevance). * [Send click and conversion events with InstantSearch.js](/doc/guides/building-search-ui/events/js). * [Choose how to send events](/doc/guides/sending-events/getting-started). * [Security best practice on sending click and conversion events](/doc/guides/algolia-ai/re-ranking#security). # Send click and conversion events with InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/events/js Unlock Algolia's most powerful features by sending click and conversion events from your InstantSearch app. ## What are events? Events are actions that users take on your app or website. They unlock powerful features, such as recommendations, personalization, smarter search results, and analytics that help you optimize your user experience. For more information, see [Choose how to send events](/doc/guides/sending-events/getting-started). ## How to send events InstantSearch widgets can automatically send certain events if you enable the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option when setting up your app. For a full guide to implementing events, see [Send events with InstantSearch](/doc/guides/sending-events/instantsearch/send-events). ## Default events With the `insights` middleware, your InstantSearch widgets send default events. To check the default events, go to the [**Events Debugger**](https://dashboard.algolia.com/events/debugger). For more information, see [Validate your events](/doc/guides/sending-events/guides/validate). ### Default click events for refinement widgets The following widgets send `click` events ("Filter Applied") when users select a refinement. [Custom widgets](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js) using the connectors send the same events. | Widget | Connector | | - | - | | [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js#param-send-event) | `connectHierarchicalMenu` | | [`menu`](/doc/api-reference/widgets/menu/js#param-send-event) | `connectMenu` | | [`menuSelect`](/doc/api-reference/widgets/menu-select/js#param-send-event) | `connectMenu` | | [`ratingMenu`](/doc/api-reference/widgets/rating-menu/js#param-send-event) | `connectRatingMenu` | | [`refinementList`](/doc/api-reference/widgets/refinement-list/js#param-send-event) | `connectRefinementList` | | [`toggleRefinement`](/doc/api-reference/widgets/toggle-refinement/js#param-send-event) | `connectToggleRefinement` | > [!NOTE] > > Numeric refinement widgets don't send `clicked` events as the `clickedFilters` event doesn't apply to numeric filters. > This is relevant for `connectNumericMenu`- and `connectRange`-based widgets. ### Default view events for results widgets The following widgets send `view` events ("Hits Viewed") for the visible items in the search results. [Custom widgets](/doc/guides/building-search-ui/widgets/create-your-own-widgets/js) using the connectors send the same events. | Widget | Connector | | - | - | | [`autocomplete`](/doc/api-reference/widgets/autocomplete/js#param-indices) | `connectAutocomplete` | | [`geoSearch`](/doc/api-reference/widgets/geo-search/js#param-send-event) | `connectGeoSearch` | | [`hits`](/doc/api-reference/widgets/hits/js#param-send-event) | `connectHits` | | [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js#param-send-event) | `connectInfiniteHits` | ### Default click events for results widgets The following widgets send `click` events ("Hit Clicked") when users click a search result. | Widget | Connector | | - | - | | [`hits`](/doc/api-reference/widgets/hits/js#param-send-event) | [`connectHits`](/doc/api-reference/widgets/hits/js#customize-the-ui-with-connecthits) | | [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js#param-send-event) | [`connectInfiniteHits`](/doc/api-reference/widgets/infinite-hits/js#customize-the-ui-with-connectinfinitehits) | > [!NOTE] > > Because of technical limitations, the default `click` events aren't sent when using `connectHits` or `connectInfiniteHits`. > If you're using connectors, make sure to set up `click` events on them. ```js JavaScript theme={"system"} function render(renderOptions, isFirstRender) { const { items, sendEvent, widgetParams } = renderOptions; widgetParams.container.innerHTML = `
    ${items .map( (item) => `
  • ${instantsearch.highlight({ attribute: 'name', hit: item })}
  • ` ) .join('')}
`; [...widgetParams.container.querySelectorAll('li')] .forEach((element, index) => { element.addEventListener('click', (event) => { sendEvent('click', hits[index], 'Hit Clicked'); }); }); } const customHits = connectHits(render); // or const customInfiniteHits = connectInfiniteHits(render); ``` # Get started with InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/getting-started/js Use InstantSearch.js to create a dynamic, filterable search UI. ## Before you begin This quickstart requires: * Basic JavaScript knowledge. * **[Node.js](https://nodejs.org/en/download)** version 16 or later with **npm** version 5.2 or later. ## Quickstart In this quickstart, you'll add an Algolia search interface to some starter code. The code: * Displays and formats a search box and results * Uses InstantSearch's pre-built UI components (widgets) to filter results > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/instantsearch/tree/master/examples/js/getting-started) > > Run and edit the InstantSearch.js quickstart example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/instantsearch/tree/master/examples/js/getting-started) > > Browse the source for the InstantSearch.js quickstart example on GitHub. **1. Add starter code** To generate some starter code, use the `create-instantsearch-app` tool. It provides 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.) data, all necessary code, and predefined credentials (application ID (A unique alphanumeric string that identifies an Algolia application.) and API key (An alphanumeric string that controls access to the Algolia APIs. It defines what actions are allowed, such as searching an index or adding new records.)). In a terminal, paste: ```sh Command line icon=square-terminal theme={"system"} npx create-instantsearch-app@latest ais-ecommerce-demo-app \ --template "InstantSearch.js" \ --app-id "B1G2GM9NG0" \ --api-key "aadef574be1f9252bb48d4ea09b5cfe5" \ --index-name demo_ecommerce \ --attributes-to-display name \ --attributes-for-faceting '' ``` This command creates a folder structure on your machine: ```txt theme={"system"} ais-ecommerce-demo-app/ ├── node_modules ├── src │ ├── app.css │ └── app.js ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── favicon.png ├── index.html ├── package.json └── README.md ``` **2. Add the search UI** 1. In `index.html`, remove the `header`, and replace `
` with: ```html HTML icon=code-xml theme={"system"}

InstantSearch.js e-commerce demo

``` 2. Open `src/app.css` and replace its content with: ```css CSS icon=paintbrush theme={"system"} body { font-family: sans-serif; padding: 1em; } .ais-SearchBox { margin: 1em 0; } ``` **3. Run the app** 1. In your terminal, type: ```sh Command line icon=square-terminal theme={"system"} cd ais-ecommerce-demo-app npm start ``` 2. Open your browser and go to [http://localhost:3000](https://localhost:3000). The app displays the search box, results, and pagination components. ![Search box with results and pagination in the quickstart app](https://mintcdn.com/algolia/1WCtS02LoYjihIhK/images/instantsearch/js/getting-started-1.png?fit=max\&auto=format\&n=1WCtS02LoYjihIhK\&q=85\&s=b3a130bf601d55e69f790d152024aad8) **4. Add filters and settings** 1. Open `index.html` and update it to: ```html HTML icon=code-xml theme={"system"}

InstantSearch.js e-commerce demo

Brands

``` 2. Open `src/app.js`, and add widgets to these placeholders: ```js JavaScript icon=code theme={"system"} // Before `search.start()` search.addWidgets([ instantsearch.widgets.clearRefinements({ container: "#clear-refinements", }), instantsearch.widgets.refinementList({ container: "#brand-list", attribute: "brand", }), instantsearch.widgets.configure({ hitsPerPage: 8, }), ]); ``` **5. Create a two-column layout** The widgets come with a [predefined style](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets) but you can customize it. 1. Open `src/app.css` and update it to the following to apply a two-column layout: ```css CSS icon=paintbrush theme={"system"} body { font-family: sans-serif; padding: 1em; } .ais-SearchBox { margin: 1em 0; } .ais-Pagination { margin-top: 1em; } .left-panel { float: left; margin-top: 1em; width: 250px; } .right-panel { margin-left: 260px; } ``` 1. In your browser, after a page refresh, the layout includes a **Brands** 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.). ![Search UI with a Brands filter in the quickstart app](https://mintcdn.com/algolia/1WCtS02LoYjihIhK/images/instantsearch/js/getting-started-2.png?fit=max\&auto=format\&n=1WCtS02LoYjihIhK\&q=85\&s=a945c7305924ba7b702f2960103be127) **6. Format search results** Open the `src/app.js` and replace the content of the `hits` widget with: ```js JavaScript icon=code theme={"system"} search.addWidgets([ instantsearch.widgets.hits({ container: "#hits", templates: { item: (hit, { html, components }) => html`
${hit.name}
${components.Highlight({ hit, attribute: "name" })}
${components.Highlight({ hit, attribute: "description" })}
$${hit.price}
`, }, }), ]); ``` **7. Refine the styling** 1. Open `src/app.css` and replace its content with: ```css CSS icon=paintbrush theme={"system"} body { font-family: sans-serif; padding: 1em; } .ais-SearchBox { margin: 1em 0; } .ais-Pagination { margin-top: 1em; } .left-panel { float: left; margin-top: 1em; width: 250px; } .right-panel { margin-left: 260px; } .ais-InstantSearch { max-width: 960px; margin: 0 auto; } .ais-Hits-list { display: grid; grid-template-columns: repeat(2, 1fr); grid-gap: 1em; } .ais-Hits-item img { margin-right: 1em; } .hit-name { margin-bottom: 0.5em; } .hit-description { color: #888; font-size: 14px; margin-bottom: 0.5em; } ``` In your browser, after a page refresh, you'll see product images. ![Search UI with product images in the quickstart app](https://mintcdn.com/algolia/1WCtS02LoYjihIhK/images/instantsearch/js/getting-started-3.png?fit=max\&auto=format\&n=1WCtS02LoYjihIhK\&q=85\&s=47192e749d189e8989f2904caa361c04) ## The widgets This quickstart uses several InstantSearch widgets: * [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) is the root InstantSearch.js component. You must wrap all other widgets inside this component. * [`searchBox`](/doc/api-reference/widgets/search-box/js) displays a search box for users to type queries into. * [`hits`](/doc/api-reference/widgets/hits/js) displays the results from Algolia, based on 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.). * [`pagination`](/doc/api-reference/widgets/pagination/js) adds navigation controls to browse through pages of results. * [`clearRefinements`](/doc/api-reference/widgets/clear-refinements/js) displays a button that clears the current refinements. * [`refinementList`](/doc/api-reference/widgets/refinement-list/js) displays a list of brands that users can use to filter the search. * [`configure`](/doc/api-reference/widgets/configure/js) passes [search parameters](/doc/api-reference/search-api-parameters). In this quickstart, it sets `hitsPerPage` to 8. ## Next steps Instead of the credentials from `create-instantsearch-app`, use your own Algolia [credentials](https://www.algolia.com/users/sign_up). You can send your own data or [a sample dataset](https://github.com/algolia/datasets/blob/master/ecommerce/records.json) to Algolia using the JavaScript API client that comes preinstalled with `create-instantsearch-app`. Then, configure the index with the JavaScript API client. For example, to configure the sample dataset, use the following code: ```js JavaScript icon="code" theme={"system"} index .setSettings({ searchableAttributes: [ "brand", "name", "categories", "unordered(description)", ], customRanking: ["desc(popularity)"], attributesForFaceting: ["searchable(brand)", "type", "categories", "price"], }) .then(() => { // done }); ``` ## See also * [What is InstantSearch?](/doc/guides/building-search-ui/what-is-instantsearch/js) * [Send and update your data](/doc/guides/sending-and-managing-data/send-and-update-your-data) * [InstantSearch widgets](/doc/api-reference/widgets/js) * [Customize an existing widget](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js) * [Send click and conversion events](/doc/guides/building-search-ui/events/js) # Access state outside the lifecycle Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/access-state-outside-lifecycle/js InstantSearch.js provides `renderState`, which you can use to render custom widgets or refine the search outside the InstantSearch.js lifecycle. Each InstantSearch widget owns its part of the search state. While this fits the majority of use cases, **sometimes you might want to read the state or refine the search outside of the InstantSearch lifecycle**. For example, you may want to let users refine on a specific category from a product page, and not just from a refinement list. InstantSearch.js lets you access the render state of each widget, which you can use to create custom widgets or refine the search outside the InstantSearch.js lifecycle. > [!NOTE] > > The `renderState` property is available starting in InstantSearch.js v4.9. ## Refining a search from the outside In InstantSearch, the [`refinementList`](/doc/api-reference/widgets/refinement-list/js) widget controls refinements for a given attribute. You can access its state from the `renderState` property. > [!NOTE] > > All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. > If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information. ```js JavaScript theme={"system"} const search = instantsearch({ indexName, searchClient, }); search.addWidgets([ // ... instantsearch.widgets.refinementList({ container: '#brand', attribute: 'brand', sortBy: ['isRefined'], }), ]); search.renderState[indexName].refinementList.brand; // returns an object containing the render state ``` The exposed state matches the render options exposed to the [`connectRefinementList`](/doc/api-reference/widgets/refinement-list/js#customize-the-ui-with-connectrefinementlist) render options. You can access the state of `brand` and use the [`refine`](/doc/api-reference/widgets/refinement-list/js#param-refine) method to programmatically set a refinement. ```js JavaScript theme={"system"} search.renderState[indexName].refinementList.brand.refine('Apple'); ``` This lets you refine on a brand from anywhere in your app, even outside of the InstantSearch lifecycle. For example, you might want to let users search in the related brand whenever they visit a product page. ```html HTML theme={"system"} ``` ```js JavaScript theme={"system"} document .querySelector('#apple-search-button') .addEventListener('click', () => { search.renderState[indexName].refinementList.brand.refine('Apple'); }); ``` ## Accessing the state of other widgets When refining on a brand, you might end up with no results at all. By default, the [`hits`](/doc/api-reference/widgets/hits/js) widget displays a "No results" message. You can customize it using the [`connectHits`](/doc/api-reference/widgets/hits/js#customize-the-ui-with-connecthits) connector along with the [`refinementList`](/doc/api-reference/widgets/refinement-list/js) render state. First, remove the default template for `empty` hits from the `hits` widget so it doesn't display anything when there are no hits. ```js JavaScript theme={"system"} instantsearch.widgets.hits({ container: '#hits', templates: { // ... empty: '' } }); ``` Second, add a button to reproduce the no results situation. ```html HTML theme={"system"} ``` ```js JavaScript theme={"system"} document .querySelector('#pear-search-button') .addEventListener('click', () => { search.renderState[indexName].refinementList.brand.refine('Pear'); }); ``` Then, you can add a dedicated custom widget to handle the no results situation. ```js JavaScript theme={"system"} const renderer = ({ items, widgetParams }) => { const container = document.querySelector(widgetParams.container); if (items.length > 0) { container.innerHTML = ''; return; } const brandState = search.renderState[indexName].refinementList.brand; const isPearRefined = brandState.items.filter((item) => item.label === 'Pear' && item.isRefined) .length > 0; if (!isPearRefined) { container.innerHTML = 'No results'; return; } container.innerHTML = `

No results for Pear

`; container.querySelector('button').addEventListener('click', () => { search.renderState[indexName].refinementList.brand.refine('Pear'); }); }; const emptyHits = instantsearch.connectors.connectHits(renderer); search.addWidgets([ emptyHits({ container: '#empty-hits', }) ]); ``` Even though you're using the `connectHits` connector, you can access the render state of the `brand` [`refinementList`](/doc/api-reference/widgets/refinement-list/js). This lets you check whether "Pear" is a selected facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) value and display a button to remove it. The `refine` method is a toggle function. When you call it with a value that's already refined, it removes it from the search state, and vice versa. > [!NOTE] > > If you're not familiar with customizing widgets with connectors, see the guide on [customizing the complete UI of widgets](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#customize-the-complete-ui-of-the-widgets). ## Using the panel widget The [`panel`](/doc/api-reference/widgets/panel/js) widget gives you easier access to the render state. This is the basic implementation of a [`panel`](/doc/api-reference/widgets/panel/js). ```js JavaScript theme={"system"} const categories = panel({ templates: { header: 'Categories', }, })(instantsearch.widgets.hierarchicalMenu); search.addWidgets([ categories({ container: '#categories', attributes: [ 'hierarchicalCategories.lvl0', 'hierarchicalCategories.lvl1', 'hierarchicalCategories.lvl2', 'hierarchicalCategories.lvl3', ], }), ]) ``` The `renderState` of [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js) for each attribute are available in the `collapsed` and `hidden` methods of widget. ```js JavaScript theme={"system"} const categories = panel({ templates: { header: 'Categories', }, collapsed(options) { // ... }, hidden(options) { // ... } })(instantsearch.widgets.hierarchicalMenu); ``` The provided `options` include the same state as you'd find under `search.renderState[indexName].hierarchicalMenu['hierarchicalCategories.lvl0']`. For example, when there are no facets to display, you can hide or collapse the [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js): ```js JavaScript theme={"system"} const categories = panel({ // ... hidden({ items }) { return items.length === 0; }, })(instantsearch.widgets.hierarchicalMenu); ``` You can see a [live demo on CodeSandbox](https://codesandbox.io/p/sandbox/github/algolia/doc-code-samples/tree/master/instantsearch.js/render-state). # Secured API keys in InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/api-keys-security/js Use secured API keys in your InstantSearch.js app to keep your data safe. ## Search-only API keys in web apps You can use the [search-only API key](/doc/guides/security/api-keys#search-only-api-key) in your frontend code. This key is safe to expose because it only allows search operations. If you need additional restrictions, such as limiting the access to specific indices, or applying rate limits, generate a **secured API key** on your backend. ## Secured API keys Secured API keys add restrictions and search parameters that your users can't change. You must generate them on your server, or anyone can remove the restrictions again. This guide shows how to use secured API keys in an InstantSearch app. For mode details, see [User-restricted access to data](/doc/guides/security/api-keys/how-to/user-restricted-access-to-data). The aim is to build an app that generates a secured API key with restrictions on the server, and then uses it on the client. See the full example on [GitHub](https://github.com/algolia/doc-code-samples/tree/master/vue-instantsearch/secured-api-keys). ### Server #### Generate a secured API key Generate the secured API key on your server. This example uses [Express](https://expressjs.com), but the same approach works with any server. To generate a key, you need the Algolia JavaScript client and a parent API key with the [search ACL](/doc/guides/security/api-keys#access-control-list-acl). In this example, the generated key only allows searching the `demo_ecommerce` 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.). You can also apply [additional restrictions](/doc/guides/security/api-keys/in-depth/api-key-restrictions), including [search parameters](/doc/guides/security/api-keys/in-depth/api-key-restrictions#query-parameter-restrictions). ```js JavaScript icon=code theme={"system"} const algoliasearch = require("algoliasearch"); const client = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY"); const securedApiKey = client.generateSecuredApiKey({ parentApiKey: "ALGOLIA_SEARCH_API_KEY", restrictions: { restrictIndices: "demo_ecommerce", }, }); ``` Send the secured API key to the client in one of these ways: * Return it from an endpoint, then fetch it before you create your InstantSearch app. * Inline it in the HTML, then read it from a global object in the client. ### Inline the API key If your build tool generates the HTML, add a placeholder value and replace it on the server before sending the page. > [!NOTE] > > If you control the HTML template, inline the value directly. ```html HTML icon=code-xml theme={"system"} ``` Replace the placeholder with the generated API key: ```js JavaScript icon=code theme={"system"} const path = require("path"); const fs = require("fs"); const util = require("util"); const express = require("express"); const app = express(); const readFileAsync = util.promisify(fs.readFile); app.get("/", async (_, res) => { const index = await readFileAsync( path.join(__dirname, "dist", "index.html"), "utf-8", ); const indexWithServerData = index.replace( "__SERVER_DATA__", JSON.stringify({ ALGOLIA_API_KEY: securedApiKey, }), ); res.send(indexWithServerData); }); ``` Start the server and read the key from `window.SERVER_DATA` in the client. The next section explains how to use this key in an InstantSearch app. ### Client #### Retrieve the API key Now that you have the API key on the global object, you can retrieve it from the client code and inject it into [`searchClient`](/doc/api-reference/widgets/instantsearch/js). Make sure you clean up the global object. Otherwise, this value will stay in memory. ```js JavaScript icon=code theme={"system"} const SERVER_DATA = window.SERVER_DATA; // clean up the global object delete window.SERVER_DATA; const searchClient = algoliasearch( "ALGOLIA_APPLICATION_ID", SERVER_DATA.ALGOLIA_API_KEY, ); const search = instantsearch({ indexName: "demo_ecommerce", searchClient, }); ``` That's it for the client. Your app can now only target the `demo_ecommerce` index. You can try to target a different one like `demo_media` but the API will return an error. * [Demo](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/secured-api-keys) * [Source code](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/secured-api-keys) ## Prevent cross-site scripting vulnerabilities Algolia's [highlighting feature](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/js) helps users understand how their query matches the results. It's a key visual cue. Algolia's [highlighting feature](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/vue) helps users understand how their query matches the results. It's a key visual cue. Algolia wraps matching words in HTML tags. By default, it uses tags like [`em`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em), which the browser renders as HTML. If your site includes user-generated content, this can expose your app to cross-site scripting (XSS) attacks. The same risk applies to the snippeting feature. To prevent this, InstantSearch restricts highlighting to a predefined set of HTML tags and escapes all others. If you set a custom tag in the Algolia dashboard, it's overridden and replaced by [`mark`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark). To use a different tag, such as [`strong`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong), set the `highlightedTagName` props in [`Highlight`](/doc/api-reference/widgets/highlight/js) or [`Snippet`](/doc/api-reference/widgets/snippet/js). > [!NOTE] > > All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. > If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js). ### Usage with template functions You can also provide the argument to the `Highlight` and `Snippet` components from [template functions](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#using-html-strings). ```js JavaScript theme={"system"} const search = instantsearch({ indexName: 'instant_search' searchClient, }); search.addWidgets([ instantsearch.widgets.hits({ container: '#hits', templates: { item(hit, { html, components }) { return html`

${components.Highlight({ hit, attribute: 'name' })}

${components.Snippet({ hit, attribute: 'description' })}

`; }, }, }) ]); ``` # Turn off automatic filtering in InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/automatic-filtering/js Build a widget to turn off automatic filtering and boosting with InstantSearch.js. Automatic Filtering and Boosting applies a category filter when users enter 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.). You can use [Query Categorization](/doc/guides/algolia-ai/query-categorization) to predict categories for your most popular queries. If you enable [Automatic Filtering and Boosting](/doc/guides/algolia-ai/query-categorization#automatic-filtering-and-boosting) the categories are automatically applied as filters. ## Control Automatic Filtering and Boosting in your UI If you want to have Automatic Filtering and Boosting on or off **for all searches**, you only need to enable the feature to filter the results automatically. If you want to let users turn Automatic Filtering and Boosting on or off, you can build an InstantSearch widget. This widget should inform users that the results are filtered. The widget should also let users remove any applied filters. ## Build a widget for Automatic Filtering and Boosting **1. Check the search response** The search response includes the following properties if [Automatic Filtering and Boosting is enabled](/doc/guides/algolia-ai/query-categorization#detect-the-impact-of-automatic-filtering-and-boosting-at-query-time): ```json JSON icon="braces" theme={"system"} { "extensions": { "queryCategorization": { "autofiltering": { "facetFilters": [], "optionalFilters": [] } } } } ``` If filters are applied to the query, they're listed in the `facetFilters` property. ```json JSON icon="braces" theme={"system"} { "facetFilters": [ "category.level0:Fresh products", "category.level1:Fresh vegetables", "category.level2:Tomatoes" ] } ``` **2. Let users remove applied filters** To remove filters applied with Automatic Filtering and Boosting, you need to [turn the feature off for the current query](/doc/guides/algolia-ai/query-categorization#override-automatic-filtering-and-boosting-at-query-time) using the `enableAutoFiltering` API parameter. ```json JSON icon="braces" theme={"system"} { "extensions": { "queryCategorization": { "enableAutoFiltering": false } } } ``` **3. Turn on automatic filtering and boosting for new queries** To keep Automatic Filtering and Boosting for other queries, check for a query change and then set `enableAutoFiltering` to `true`. ## Implementing the widget The following custom connector implements all steps to let users turn off Automatic Filtering and Boosting for a search query. > [!NOTE] > > The connector requires the [Algolia search helper](/doc/guides/building-search-ui/upgrade-guides/js#algolia-search-helper). ```JavaScript JavaScript theme={"system"} //connectAutoFiltering.js // util function to set the query parameter (step 2) function setAutoFiltering(value, helper) { return helper.setQueryParameter('extensions', { queryCategorization: { enableAutoFiltering: value, }, }); } export function connectAutoFiltering(renderFn, unmountFn = () => {}) { return function autoFiltering(widgetParams) { const connectorState = {}; return { $$type: 'algolia.beta.autoFiltering', init(initOptions) { const { instantSearchInstance } = initOptions; renderFn( { ...this.getWidgetRenderState(initOptions), instantSearchInstance, }, true ); }, render(renderOptions) { const { instantSearchInstance } = renderOptions; renderFn( { ...this.getWidgetRenderState(renderOptions), instantSearchInstance, }, false ); }, dispose() { unmountFn(); }, getWidgetSearchParameters(searchParameters) { return searchParameters; }, getRenderState(renderState, renderOptions) { return { ...renderState, autoFiltering: this.getWidgetRenderState(renderOptions), }; }, // this is where the logic happens getWidgetRenderState({ results, helper, state }) { if (!connectorState.cancelAutoFiltering) { // exposing a function to disable autofiltering connectorState.cancelAutoFiltering = () => { // Disable auto filtering for next search setAutoFiltering(false, helper); helper.search(); // storing in the state the disabled query connectorState.disabledQuery = helper.getQuery().query; }; } // empty results case if (!results) { return { appliedFilters: [], cancelAutoFiltering: () => {}, widgetParams, }; } // enabling back auto filtering if the query has changed (step 3) if ( // "state" stores the current query parameters state.extensions && state.extensions.queryCategorization.enableAutoFiltering === false && connectorState.disabledQuery && results.query !== connectorState.disabledQuery ) { setAutoFiltering(true, helper); if ( results.extensions && results.extensions.queryCategorization.normalizedQuery ) { // if the current query has predicted categories, we refine the search with autofiltering enabled helper.search(); } } // Retrieving the applied filters (step 1) const facetFilters = (results.extensions && results.extensions.queryCategorization.autofiltering && results.extensions.queryCategorization.autofiltering .facetFilters) || []; return { appliedFilters: facetFilters.map((facetFilter) => ({ name: facetFilter.split(':')[0], value: facetFilter.split(':')[1], })), cancelAutoFiltering: connectorState.cancelAutoFiltering, widgetParams, }; }, }; }; } ``` ```TypeScript TypeScript theme={"system"} // connectAutoFiltering.ts import type { Connector } from 'instantsearch.js'; export type AutoFilteringConnectorParams = {}; export type AutoFilteringRenderState = { appliedFilters: Array<{ name: string; value: string; }>; cancelAutoFiltering(): void; }; export type AutoFilteringWidgetDescription = { $$type: 'algolia.beta.autoFiltering'; renderState: AutoFilteringRenderState; indexRenderState: {}; indexUiState: {}; }; type AutoFilteringConnector = Connector< AutoFilteringWidgetDescription, AutoFilteringConnectorParams >; type ConnectorState = { disabledQuery?: string; cancelAutoFiltering?: AutoFilteringRenderState['cancelAutoFiltering']; }; // util function to set the query parameter (step 2) function setAutoFiltering(value, helper) { return helper.setQueryParameter('extensions', { queryCategorization: { enableAutoFiltering: value, }, }); } type WidgetRenderStateWithResultsExtensions = Parameters< ReturnType>['getWidgetRenderState'] >[0] & { state: { extensions?: { queryCategorization?: { enableAutoFiltering?: boolean; }; }; }; results: { extensions?: { queryCategorization?: { normalizedQuery?: string; autofiltering?: { facetFilters?: string[]; }; }; }; }; }; export const connectAutoFiltering: AutoFilteringConnector = ( renderFn, unmountFn = () => {} ) => { return function autoFiltering(widgetParams) { const connectorState: ConnectorState = {}; return { $$type: 'algolia.beta.autoFiltering', init(initOptions) { const { instantSearchInstance } = initOptions; renderFn( { ...this.getWidgetRenderState(initOptions), instantSearchInstance, }, true ); }, render(renderOptions) { const { instantSearchInstance } = renderOptions; renderFn( { ...this.getWidgetRenderState(renderOptions), instantSearchInstance, }, false ); }, dispose() { unmountFn(); }, getWidgetUiState(uiState) { return uiState; }, getWidgetSearchParameters(searchParameters) { return setAutoFiltering(true, searchParameters); }, getRenderState(renderState, renderOptions) { return { ...renderState, autoFiltering: this.getWidgetRenderState(renderOptions), }; }, // this is where the logic happens getWidgetRenderState({ results, helper, state, }: WidgetRenderStateWithResultsExtensions) { if (!connectorState.cancelAutoFiltering) { connectorState.cancelAutoFiltering = () => { // Disable auto filtering for next search setAutoFiltering(false, helper); helper.search(); // storing in the state the disabled query connectorState.disabledQuery = helper.getQuery().query; }; } // empty results case if (!results) { return { appliedFilters: [], cancelAutoFiltering: () => {}, widgetParams, }; } // enabling back auto filtering if the query has changed (step 3) if ( // "state" stores the current query parameters state.extensions?.queryCategorization?.enableAutoFiltering === false && connectorState.disabledQuery && results.query !== connectorState.disabledQuery ) { setAutoFiltering(true, helper); if (results.extensions.queryCategorization.normalizedQuery) { // if the current query has predicted categories, we refine the search with autofiltering enabled helper.search(); } } // Retrieving the applied filters (step 1) const facetFilters = results.extensions?.queryCategorization?.autofiltering ?.facetFilters || []; return { appliedFilters: facetFilters.map((facetFilter) => ({ name: facetFilter.split(':')[0], value: facetFilter.split(':')[1], })), cancelAutoFiltering: connectorState.cancelAutoFiltering, widgetParams, }; }, }; }; }; ``` Use this [InstantSearch connector](/doc/guides/building-search-ui/widgets/create-your-own-widgets/js#build-a-custom-connector) to create a widget. ```JavaScript JavaScript theme={"system"} // autofiltering.js import { connectAutoFiltering } from './connectAutoFiltering'; export function autoFiltering(widgetParams) { const { container: containerSelector } = widgetParams; if (!containerSelector) { console.error('The `container` option is required.'); } const container = document.querySelector(containerSelector); // the widget render function const renderer = (renderOptions, isFirstRender) => { const { appliedFilters, cancelAutoFiltering } = renderOptions; /** * Here we choose to display the applied filters in a banner with a button to disable autofiltering * We are displaying only the last filter of the hierarchy, but you can chose to display all of them if you want to * It is entirely up to you how you choose to render the filters */ container.innerHTML = appliedFilters.length ? ` Applied filter: ${appliedFilters.pop().value} × ` : ''; const button = container.querySelector('button'); if (button) { // attaching our cancel function to our button button.addEventListener('click', () => { cancelAutoFiltering(); }); } }; const makeWidget = connectAutoFiltering(renderer, () => { container.innerHTML = ''; }); return { ...makeWidget({}), $$widgetType: 'algolia.beta.autoFiltering', }; } ``` ```TypeScript TypeScript theme={"system"} // autofiltering.ts import { connectAutoFiltering } from './connectAutoFiltering'; import type { AutoFilteringRenderState } from './connectAutoFiltering'; type WidgetParams = { container: string; }; export function autoFiltering(widgetParams: WidgetParams) { const { container: containerSelector } = widgetParams; if (!containerSelector) { console.error('The `container` option is required.'); } const container = document.querySelector(containerSelector); // the widget render function const renderer = ( renderOptions: AutoFilteringRenderState, isFirstRender: boolean ) => { const { appliedFilters, cancelAutoFiltering } = renderOptions; /** * Here we choose to display the applied filters in a banner with a button to disable autofiltering * We are displaying only the last filter of the hierarchy, but you can chose to display all of them if you want to * It is entirely up to you how you choose to render the filters */ container.innerHTML = appliedFilters && appliedFilters.length ? ` Applied filter: ${appliedFilters.pop().value} × ` : ''; const button = container.querySelector('button'); if (button) { // attaching our cancel function to our button button.addEventListener('click', () => { cancelAutoFiltering(); }); } }; const makeWidget = connectAutoFiltering(renderer, () => { container.innerHTML = ''; }); return { ...makeWidget({}), $$widgetType: 'algolia.beta.autoFiltering', }; } ``` If you copy the code snippets into your implementation, check the widget render function to adapt to your design. You can add the widget to your UI: ```js JavaScript theme={"system"} import { autoFiltering } from './autoFiltering'; // ... const search = instantsearch({ indexName, searchClient, }); search.addWidgets([ // ... autoFiltering({ container: '#autofiltering-container', }), ]); ``` # Implement backend search with InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-instantsearch/js Implement server-side search with InstantSearch.js and Algolia's API clients. You may wish to query Algolia's servers from your backend instead of the frontend, while still being able to use InstantSearch widgets. Possible motivations could be for security restrictions, for SEO purposes or to enrich the Algolia data with data from your servers. Keep in mind though that [Algolia recommends frontend search](https://support.algolia.com/hc/en-us/articles/4406981933457-Searching-from-the-frontend-or-the-backend-What-do-you-recommend-) for performance and high availability reasons. ## How backend search works InstantSearch is the UI on top of a search client. Its state is managed by the [Algolia search helper](/doc/guides/building-search-ui/upgrade-guides/js#algolia-search-helper). However, you can use a different search client. ### Algolia search client Algolia's [search client](https://github.com/algolia/algoliasearch-client-javascript) queries Algolia's backend whenever users refine the search. The response from Algolia's search client is a JSON object that includes: * Results already formatted for display (full record content, embedded HTML, image URLs, and so on). * Highlighting and snippeting * Pagination For more information, see [Backend search with an API client](/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-search-with-an-api-client/js). ### Custom search client You can implement your own search client to query your backend which, in turn, uses the Algolia search client to query Algolia's backend. To create your own client, you must implement an interface that receives and returns formatted data that InstantSearch can understand. ## On the backend: create the necessary routes This guide assumes that you have an existing server running on [`http://localhost:3000`](http://localhost:3000) with the route `POST /search` that takes the default Algolia 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.) parameters as JSON. This backend could be using the [JavasScript API client](/doc/libraries/sdk/install) to query Algolia, on top of any other operations you want to perform. The `algoliasearch` package lets you query Algolia from your backend. Here's an example using [Express](https://expressjs.com/): ```js JavaScript icon="code" theme={"system"} // Instantiate an Algolia client const algoliasearch = require("algoliasearch"); const algoliaClient = algoliasearch( "ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY", ); // Add the search endpoint app.post("/search", async ({ body }, res) => { const { requests } = body; const results = await algoliaClient.search(requests); res.status(200).send(results); }); // Add the recommendations endpoint app.post("/getRecommendations", async ({ body }, res) => { const { requests } = body; const results = await algoliaClient.getRecommendations(requests); }); ``` Once your new route is ready, get back to the frontend and create a search client that can communicate with this server. ## On the frontend: call your new backend routes ### Searching for results The `search()` method runs whenever users search or refine results. Because the server accepts requests in the InstantSearch format, forward the search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.) from this method to your backend and return the response. ```js JavaScript icon="code" theme={"system"} const requestsCache = new Map(); const resultsCache = new Map(); function makeCacheKey(requests, target) { // You can go further here, like sorting individual values inside the requests. // This makse sure the key is always the same for the same requests. return target + JSON.stringify(requests); } const customSearchClient = { search(requests) { const key = makeCacheKey(requests, 'search'); if (resultsCache.has(key)) { return Promise.resolve(resultsCache.get(key)); } let request; if (requestsCache.has(key)) { request = requestsCache.get(key)!; } else { request = fetch('http://localhost:3000/search', { method: 'post', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ requests }), }).then(res => res.json()); } return request.then(results => { resultsCache.set(key, results); return results; }); }, getRecommendations(requests) { const key = makeCacheKey(requests, 'getRecommendations'); if (resultsCache.has(key)) { return resultsCache.get(key); } let request; if (requestsCache.has(key)) { request = requestsCache.get(key)!; } else { request = fetch('http://localhost:3000/getRecommendations', { method: 'post', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ requests }), }).then(res => res.json()); } return request.then(results => { resultsCache.set(key, results); return results; }); }, clearCache() { requestsCache.clear(); resultsCache.clear(); }, }; ``` This example uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to query the server. Check the [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#Browser_compatibility) requirements before using it in production. If you want to transform the data passed to your server, you can learn more about the parameters that the `search` and `getRecommendations` methods support: * [Search API parameters](/doc/api-reference/api-parameters) * [Recommendations API parameters](/doc/rest-api/recommend/get-recommendations) ### Search for facet values The same `search()` method is used by InstantSearch to fetch facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) values from refinement lists. > [!NOTE] > > You must set the option [`searchable`](/doc/api-reference/widgets/refinement-list/js#param-searchable) to `true` in your refinement list to make it searchable. ## Use the search client with InstantSearch Now, you need to tell InstantSearch to use the search client you created. This is possible with the `searchClient` option. This parameter turns off all Algolia requests coming from the frontend and proxies them to your backend and displays the UI. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "YourIndexName", searchClient: customSearchClient, }); search.start(); ``` ## Enrich data from the backend Now that InstantSearch is querying your backend before fetching results from Algolia, you could merge Algolia's data with yours (to offer your users more exhaustive results). A recurring problem with ecommerce websites using Algolia managing the remaining stock for each product. It's sometimes hard to keep track of the exact number of items. An approach made possible with a custom backend is to only store the item's availability on each Algolia record (`none`, `low`, `medium`, `high`) and to fetch the exact stock on your database. You need to make a few changes to your backend: ```js JavaScript icon=code theme={"system"} app.post('/search', async ({body}, res) => { const requests = body; const algoliaResults = await algoliaClient.search(requests); const results = { ...algoliaResults, results: algoliaResults.results.map(result => ({ ...result, hits: result.hits.map(async hit => ({ ...hit, // `getStock()` retrieves a product's exact stock from your database stock: await getStock(hit.productID), })), })), }; res.status(200).send(results); }); app.post('/getRecommendations', async ({body, res) => { /* The same logic as above can be applied here */ }); ``` You can now access the property `stock` on each hit with InstantSearch on the frontend. # Backend search with an API client Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-search-with-an-api-client/js Understand the search Algolia provides out of the box. To query your Algolia 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.), you need an Application ID and a valid API key. You can find them in the Algolia dashboard's [API Keys](https://dashboard.algolia.com/account/api-keys/) section. These credentials let you connect to Algolia and perform operations on your data. Use one of Algolia's [API clients](/doc/libraries/sdk) to guarantee end-to-end reliability. ## Basic queries To retrieve results from Algolia, use the [Search index](/doc/libraries/sdk/v1/methods/search) method. Querying Algolia involves: * Choosing the index to query against * Sending the query string * Adding optional parameters Along with the user's text, you can provide any number of [query parameters](/doc/api-reference/api-parameters) in your 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.). Some override an index's settings, such as disabling typo tolerance. Others enrich the search, such as filtering. Adding optional parameters can alter textual relevance behavior at query-time. For example, it's possible to define stop words or change plurals behavior only for certain queries. > [!NOTE] > > Algolia recommends querying directly from the user's browser, mobile device, or client to ensure optimal performance by reducing latency (and benefiting from offloading your servers). Here's a basic query example: ```js JavaScript icon="code" theme={"system"} index.search("query", { ignorePlurals: true, }); ``` ### Empty queries Sometimes, you might not need to pass in a textual query. It's possible to retrieve results from Algolia by specifying an index and other optional parameters (most likely, 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.)). In this way, it's possible to use Algolia beyond a standard "search results" experience. For example, one common use case is to use filtering capabilities to generate category landing pages. ### Browsing your index Algolia lets you fetch all records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in an index using the [`browse`](/doc/libraries/sdk/v1/methods/browse) method. If you need to retrieve the full contents of your index (for backup, SEO purposes or for running a script on it), use the [`browse`](/doc/libraries/sdk/v1/methods/browse) method instead of [`search`](/doc/libraries/sdk/v1/methods/search), as [`search`](/doc/libraries/sdk/v1/methods/search) is optimized for speed. Results are returned ranked by attributes and custom ranking. Additionally, [`browse`](/doc/libraries/sdk/v1/methods/browse) supports most search parameters, meaning you can easily apply filters to retrieve a subset of results. ### Multiple indices / multiple queries [Multiple indices](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/one-or-more-indices) are used when you have different kinds of data (movies + actors), or you need to display results with different rankings. You can do this by using the same query / response logic, one for each index or query. ## Response format If a query is successful, your search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.) will generate a JSON response in return. This response contains both the matching results (*hits*) as well as other information useful for building a full-fledged search experience (for example, facets, highlighting, hits count, paging, and more). For more information, see [Understand the API response](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/vue) ### Highlighting and snippeting Highlighting is an important tool to demonstrate to searchers *why* a result matched their query by providing different styling to all matched query words. By default, highlighting is enabled on all searchable attributes. Snippets only return a portion of the matched attribute: the matched words and some words around them. Unlike highlighting, snippeting must be proactively enabled for each attribute you wish to snippet, although you can set the value `*` to snippet all attributes. ### Pagination Pagination gives you full control over how you retrieve query results. You can implement standard paging or retrieve specific record subsets that ignore page boundaries (infinite scrolling). You can set pagination defaults at indexing time and override them at query time. ### Faceting Facets are used to create categories on a select group of attributes. For example, useful facets for an index of books might be author and genre. Additionally, Algolia calculates the count of records for each facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).). You can display facets and facet counts in the UI to let users filter results (for example, by author or genre). ### Search for facet values Closely tied to *faceting* is *searching* for facet values. With this powerful feature, you give your user the ability to navigate through maybe hundreds of different facet values. And because they can search, you are no longer concerned about creating too many values—they will find what they need. ## See also * [Faceting](/doc/guides/managing-results/refine-results/faceting) * [Paginate results](/doc/guides/building-search-ui/ui-and-ux-patterns/pagination/js) * [Highlighting](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/js) * [Search for facet values](/doc/libraries/sdk/v1/methods/search-for-facet-values) # Understand the API response Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js Learn more about the Algolia API response Algolia search responses look similar to: ```json JSON icon="braces" theme={"system"} { "hits": [ { "firstname": "Jimmie", "lastname": "Barninger", "objectID": "433", "_highlightResult": { "firstname": { "value": "Jimmie", "matchLevel": "partial" }, "lastname": { "value": "Barninger", "matchLevel": "none" }, "company": { "value": "California Paint & Wlpaper Str", "matchLevel": "partial" } } } ], "page": 0, "nbHits": 1, "nbPages": 1, "hitsPerPage": 20, "processingTimeMS": 1, "query": "jimmie paint" } ``` ## Hits The most important field in the search response is `hits`, an array of objects matching the active search state. Each hit contains the object's **retrievable attributes**, those attributes that have been configured to be returned in search responses. Two additional fields may be present if **highlighting** and **snippeting** have been enabled: * `_highlightResult`: an object keyed on the hit's **retrievable attributes**. Each key's value returns a highlighted version of the original attribute, as well as information describing the quality of the match. * `_snippetResult`: an object keyed on the hit's **retrievable attributes**. Each key's value returns a snippeted version of the original attribute, as well as information describing the quality of the match. If **highlighting** or **snippeting** is enabled, the highlighted and snippeted values of `firstname`, for example, will be available as `_highlightResult.firstname.value` and `_snippetResult.firstname.value`, respectively. These values can then be used, instead of their original values, to support highlighting and snippeting in a results interface. ## Pagination **A single response doesn't return all hits**—the hits are paginated, with the current pagination state reflected by a few response fields: * `nbHits`: the total number of hits matching the search state * `nbPages`: the total number of pages in the search response * `hitsPerPage`: the configurable number of hits presented per page * `page`: the current, zero-indexed page of responses In a search interface, these fields can be used to render appropriate pagination controls: * Increment `page` when a "Next" button is clicked, or a specific page when a number is clicked. * Don't show a "Next" button if `page` == `nbPages`. * Don't show a "Back" button if `page` == `0`. > [!NOTE] > > InstantSearch.js handles these cases with minimal configuration. ## Ranking information By default, the search response doesn't include ranking information to keep the response as small as possible. To include the ranking information, use the [`getRankingInfo`](/doc/api-reference/api-parameters/getRankingInfo) parameter with your search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.). For example: ```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 } } } ``` For more information, see [Search API response](/doc/rest-api/search/search-single-index#response-hits) # Backend search with InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/backend-search/js Set up search on your backend with InstantSearch.js. ## Frontend versus backend search Use frontend search because: * **It offers a better response speed**. Speed alone justifies the choice. Backend search requires two more trips between a user's browser and Algolia's servers. This decreases the effectiveness of Algolia's engine and infrastructure optimizations. * **UI libraries aid development**. Algolia's UI libraries ([InstantSearch](/doc/guides/building-search-ui/getting-started/js) and [Autocomplete](/doc/ui-libraries/autocomplete/introduction/getting-started)) help build a frontend solution so that you can focus on configuring relevance and creating the best possible search experience. * **It frees up your resources**. Algolia reduces the need for development and eliminates the requirement for additional server deployments and its associated infrastructure. ## Why backend search? * Server-side pre-processing. Perhaps the most important reason for a backend solution is to make complex calculations or format your data before displaying the results. * Real-time availability updates (like Airbnb) * In some situations, SEO is easier to manage. ## How to implement backend search You only need a few lines of code to send a query to Algolia and receive a response. The response contains everything needed to build your UI. You can perform backend search with either InstantSearch or one of the Algolia API clients. ### Backend InstantSearch [Backend InstantSearch](/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-instantsearch/js) offers a good compromise. You perform the search on your servers, carry out any required pre-processing, and then send your results to InstantSearch's widgets. This enables you to control the query and response but saves you the work of building a custom frontend. ### Backend search with an Algolia API client This option requires a server-side solution and custom frontend development. You manage all queries and responses, and you build the frontend UI. You need to parse the API response: a JSON object. The response includes: * Results already formatted for display (full record content, embedded HTML, or image URLs) * Highlighting and snippeting * Pagination For more information, see: * [Backend search with an API client](/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-search-with-an-api-client/js) * [Pagination](/doc/api-reference/api-parameters/page) * [Highlighting](/doc/api-reference/api-parameters/attributesToHighlight) * [Snippeting](/doc/api-reference/api-parameters/attributesToSnippet) # Conditional display in InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/conditional-display/js Learn how to display content conditionally in InstantSearch.js for empty queries, searches without results, and errors. This guide describes what to do when there are [no results](#handle-searches-without-results), when there's [no query](#handle-empty-queries), or when there are [errors](#handle-search-errors). If results haven't loaded, the search may still be in progress or the device may be offline. For more information, see [No hits or is the search still in progress?](/doc/guides/building-search-ui/ui-and-ux-patterns/pagination/js#no-hits-or-is-the-search-still-in-progress) If you want to feature content in your search results based on a set of conditions, you can use Algolia Rules to [Display advertisements or promotional banners](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/add-banners). > [!NOTE] > > To learn how to suppress InstantSearch's initial 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.), > see [Conditional requests](/doc/guides/building-search-ui/going-further/conditional-requests/js). ## Handle searches without results When a query doesn't return results, display a message and suggest changing the query or clearing filters. ### Display a message To display a fallback message when a query doesn't return results, use the [`templates.empty`](/doc/api-reference/widgets/hits/js#param-empty) option. > [!NOTE] > > All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. > If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information. ```js JavaScript theme={"system"} search.addWidgets([ instantsearch.widgets.hits({ container: 'hits', templates: { empty: ({ query }, { html }) => html`
No results have been found for ${ query }.
` } }) ]); ``` > [!NOTE] > > The preceding example also works with [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js). ### Let users reset filters and facets If users apply an overly specific 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.) configuration, they may not find any results. Account for this by letting them reset filters from the "no results" display so they can start another search. Do this with the [`clearRefinements`](/doc/api-reference/widgets/clear-refinements/js) widget. However, you can't use it inside the `empty` template, so must use [routing](/doc/guides/building-search-ui/going-further/routing-urls/js) instead. First, activate the URL sync mechanism: ```js JavaScript icon=code theme={"system"} const search = instantsearch({ routing: true }); ``` Routing makes your InstantSearch app aware of changes in the URL. By removing URL parameters, you can influence search parameters and reset filters and facets. ```js JavaScript icon=code theme={"system"} search.addWidgets([ instantsearch.widgets.hits({ container: 'hits', templates: { empty: ({ query }, { html }) => html`

No results have been found for ${query}}

Reset all filters
`, }, }), ]); ``` ## Handle empty queries By default, InstantSearch sends search requests even when the query is empty. To hide results until users enter a query, use the following code. This hides the results without preventing search requests. ### Using `transformItems` Using `transformItems` to display no results when the query is empty: ```js JavaScript icon=code theme={"system"} hits({ transformItems(items, { results }) { if (results.query === '') return []; return items; }, templates: { empty(results, { html }) { if (results.query === '') return null; return html`No results found for "${results.query}"`; }, } }); ``` ### Using a connector The following example uses the [`connectHits`](/doc/api-reference/widgets/hits/js#customize-the-ui-with-connecthits) connector. ```js JavaScript icon=code theme={"system"} const customHits = instantsearch.connectors.connectHits( (renderOptions, isFirstRender) => { const { results, widgetParams } = renderOptions; const { container } = widgetParams; container.innerHTML = results && results.query ? `
Searching for query "${results.query}".
` : `
No query
`; } ); search.addWidgets([ customHits({ container: document.querySelector('#hits') }) ]); ``` ## Handle search errors If an error occurs, you can display a specific piece of content to help users return to the standard state. ### Making an error-handling widget You can build a custom widget to handle errors. If you want to learn more about custom widgets, [check out the guide](/doc/guides/building-search-ui/widgets/create-your-own-widgets/js). ```js JavaScript icon=code theme={"system"} search.addWidgets([ { $$type: 'error-display', render({ status, error }) { if (status === 'error' && error) { console.error('Error', error); } } } ]); ``` You can improve this custom widget by writing a more meaningful error message, adapting it to your UI, figuring out if users are online, and logging the error in your monitoring system. # Conditional requests with InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/conditional-requests/js Learn how to make conditional requests with InstantSearch.js By default, InstantSearch sends an initial search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.) to Algolia's servers with an empty 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.). This connection helps speed up later requests. However, sometimes you don't want to perform more network calls than are necessary. For example, you may want to limit the number of search requests and reduce your overall Algolia usage. This guide helps you build a UI that prevents this initial request. ## How a search client works InstantSearch is the UI layer that sits on top of [Algolia's search client layer](https://github.com/algolia/algoliasearch-client-javascript), with a state managed by the [Algolia search helper](/doc/guides/building-search-ui/upgrade-guides/js#algolia-search-helper) layer. These three layers are interchangeable so that you can use the InstantSearch widgets with a different search client. The search client queries Algolia's servers whenever users refines a search. You can build a custom search client to add custom behavior. For example, to perform [backend searches with InstantSearch](/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-instantsearch/js). ## Implementing a proxy To prevent the initial empty query, you must wrap a custom search client around Algolia's, and pass it to [`instantsearch`](/doc/api-reference/widgets/instantsearch/js). The custom search client acts as a proxy for search requests: > [!NOTE] > > All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. > If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information. ```js JavaScript theme={"system"} const algoliaClient = algoliasearch( "ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY", ); const searchClient = { ...algoliaClient, search(requests) { return algoliaClient.search(requests); }, }; const search = instantsearch({ indexName: "instant_search", searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: "#searchbox", }), instantsearch.widgets.hits({ container: "#hits", }), ]); search.start(); ``` This proxy lets you add some logic before calling the `search` method. In this case, you only call it when performing a query. > [!NOTE] > > You can also use a proxy to only begin searching after a certain number of characters have been typed by adding a condition, for example, `query.length > 3`. ## Detecting empty search requests If you don't want to perform a search request when the query is empty (`""`), you need to detect it: ```js JavaScript icon=code theme={"system"} const searchClient = { ...algoliaClient, search(requests) { if (requests.every(({ params }) => !params.query)) { // Here we have to do something else } return algoliaClient.search(requests); }, }; ``` > [!NOTE] > > Sometimes, one user action results in multiple queries, for example, clicking on an [`refinementList`](/doc/api-reference/widgets/refinement-list/js) or using multiple [`index`](/doc/api-reference/widgets/index-widget/js) widgets. > Make sure that every query is empty before intercepting the function call. When an empty search is detected, you must return a [formatted response](/doc/libraries/sdk/v1/methods/search#response) as an array of objects of the same length as the `requests` array. Each object requires at least these properties: `hits`, `nbHits`, and `processingTimeMS`. ```js JavaScript icon=code theme={"system"} const searchClient = { ...algoliaClient, search(requests) { if (requests.every(({ params }) => !params.query)) { return Promise.resolve({ results: requests.map(() => ({ hits: [], nbHits: 0, nbPages: 0, page: 0, processingTimeMS: 0, hitsPerPage: 0, exhaustiveNbHits: false, query: "", params: "", })), }); } return algoliaClient.search(requests); }, }; ``` Now you can use the proxy with the [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) widget, like this: ```js JavaScript theme={"system"} const algoliaClient = algoliasearch( "ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY", ); const searchClient = { ...algoliaClient, search(requests) { if (requests.every(({ params }) => !params.query)) { return Promise.resolve({ results: requests.map(() => ({ hits: [], nbHits: 0, nbPages: 0, page: 0, processingTimeMS: 0, hitsPerPage: 0, exhaustiveNbHits: false, query: "", params: "", })), }); } return algoliaClient.search(requests); }, }; const search = instantsearch({ indexName: "instant_search", searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: "#searchbox", }), instantsearch.widgets.hits({ container: "#hits", }), ]); search.start(); ``` # Improve performance Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/improve-performance/js Learn how to optimize your InstantSearch.js app. Algolia is fast by default. But network speed and bandwidth can vary. This page lists best practices you can implement to adapt to your users' network conditions. ## Prepare the connection to Algolia When sending the first network request to a domain, a security handshake must happen, consisting of several round trips between the client and the Algolia server. If the handshake first happened when users typed their first keystroke, the speed of that first request would be slower. Use a [preconnect link](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preconnect) to carry out the handshake after loading the page but before any user interaction. To do this, add a link tag with your Algolia domain in the `head` of your page. ```html HTML icon=code-xml theme={"system"} ``` ## Add a loading indicator Consider a user accessing your app in a subway: 1. They type some characters 2. Nothing happens 3. They wait, but still, nothing happens However, you can enhance the user experience by displaying a loading indicator to indicate something is happening. To display a loading indicator in the `searchBox`, use the [`showLoadingIndicator`](/doc/api-reference/widgets/search-box/js#param-show-loading-indicator) option. The indicator will display slightly after the last query has been sent to Algolia. Change the duration of the delay with [`stalledSearchDelay`](/doc/api-reference/widgets/instantsearch/js#param-stalled-search-delay) (on the `instantSearch` widget). > [!NOTE] > > All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. > If, you're using a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js). For example: ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "instant_search", stalledSearchDelay: 200, // this is the default value for the delay searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: "#searchBox", placeholder: "Search for products", showLoadingIndicator: true, // this add the loading indicator }), ]); ``` ### Make your own loading indicator You can also use the loading indicator with other widgets. The following example shows how to make a custom component that writes `Loading...` when search stalls. If network conditions are optimal, users won't see this message. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "instant_search", searchClient, }); // The container to use const loadingContainer = document.querySelector("#loading"); search.addWidgets([ { render({ searchMetadata = {} }) { const { isSearchStalled } = searchMetadata; loadingContainer.innerHTML = isSearchStalled ? "Loading..." : ""; }, }, ]); ``` ## Debouncing Another way of improving the perception of performance is by preventing lag. InstantSearch generates one query per keystroke by default. This can lead to a lag in the worst network conditions because browsers can only make a limited number of parallel requests. By reducing the number of requests, you can prevent this lag. Debouncing limits the number of requests and avoids processing unnecessary ones by avoiding sending requests before a timeout. Implement debouncing at the `searchBox` level with the [`queryHook`](/doc/api-reference/widgets/search-box/js#param-query-hook) option. For example: ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "instant_search", searchClient, }); let timerId; let timeout = 0; search.addWidgets([ instantsearch.widgets.searchBox({ container: document.querySelector("#searchBox"), placeholder: "Search for products", queryHook(query, refine) { clearTimeout(timerId); timerId = setTimeout(() => refine(query), timeout); }, }), ]); ``` This function uses the option `queryHook` and is called on every keystroke. In the example, the code debounces the call to `refine`. When network speeds improve, restore search-as-you-type to offer users the best possible experience. You can use the [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API) to detect connection changes. > [!NOTE] > > The [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API) is unavailable on some browsers. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "instant_search", searchClient, }); let timerId; let timeout = 0; search.addWidgets([ instantsearch.widgets.searchBox({ container: document.querySelector("#searchBox"), placeholder: "Search for products", queryHook(query, refine) { clearTimeout(timerId); timerId = setTimeout(() => refine(query), timeout); }, }), ]); const connection = navigator.connection; if (connection) { connection.addEventListener("change", function updateTimeout() { timeout = ["slow-2g", "2g"].includes(connection?.effectiveType) ? 400 : 0; }); } ``` ### Select a debounce delay The optimal debouncing delay should match your audience's typing speed (typically 30 words per minute (WPM) on mobile devices and 40 WPM on desktop devices). If the delay is too short, users still see flashes of content. If the delay is too long, the time between key presses and results becomes too long. 200 ms is the preferred debounce delay. Delays of over 300 ms will start degrading the user experience. ## Optimize build size InstantSearch supports [dead code elimination through tree shaking](https://webpack.js.org/guides/tree-shaking/), but you must follow these rules for it to work: * Bundle your code using a module bundler that supports tree shaking with the `sideEffects` property in `package.json`, such as [Rollup](https://rollupjs.org/) or [webpack 4+](https://webpack.js.org). * Ensure you pick the ES module build of InstantSearch by targeting the `module` field in `package.json` ([`resolve.mainFields` option in webpack](https://webpack.js.org/configuration/resolve/#resolvemainfields), [`mainFields` option in `@rollup/plugin-node-resolve`](https://github.com/rollup/plugins/tree/master/packages/node-resolve#mainfields)). This is the default configuration in most popular bundlers: you only need to change something if you have a custom configuration. * Keep [Babel](https://babeljs.io) or other transpilers from transpiling ES6 modules to CommonJS modules. Tree shaking is much less optimal on CommonJS modules, so it's better to let your bundler handle modules itself. If you're using Babel, you can configure [`babel-preset-env`](https://babeljs.io/docs/en/babel-preset-env) not to process ES6 modules: ```js JavaScript icon=code theme={"system"} // babel.config.js module.exports = { presets: [ [ "env", { modules: false, }, ], ], }; ``` If you're using the [TypeScript compiler (`tsc`)](https://www.typescriptlang.org/docs/handbook/2/basic-types.html#tsc-the-typescript-compiler): ```jsonc JSON icon=braces theme={"system"} // tsconfig.json { "compilerOptions": { "module": "esnext", } } ``` Import only what you need. For this, use the multiple entry points from the ES module build: ```js JavaScript icon=code theme={"system"} // instantsearch() function without reference to the widgets or connectors import instantsearch from 'instantsearch.js/es'; // Import connectors individually import { connectSearchBox } from 'instantsearch.js/es/connectors'; // Import widgets individually import { searchBox } from 'instantsearch.js/es/widgets'; const search = instantsearch({ ... }); search.addWidgets([searchBox({ ... })]); search.addWidgets([connectSearchBox(() => { ... })({ ... })]) ``` ### Troubleshooting To check if tree shaking works, try to import InstantSearch into your project without using it. ```js JavaScript icon=code theme={"system"} import "instantsearch.js/es"; // Unused import ``` Build your app, then look for the unused code in your final bundle (for example, "InstantSearch"). If tree shaking works, you shouldn't find anything. ## Caching ### Caching by default (and how to turn it off) By default, Algolia caches the search results of the queries, storing them locally in the cache. This cache only persists during the current page session, and as soon as the page reloads, the cache clears. If users type a search (or part of it) that's already been entered, the results will be retrieved from the cache instead of requesting them from Algolia, making the app much faster. While it's a convenient feature, sometimes you may want to clear the cache and make a new request to Algolia. For instance, when changes are made to some records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) 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.), you should update your app's frontend to reflect that change (and avoid displaying stale results retrieved from the cache). The `refresh` function, available for custom connectors, lets you clear the cache and trigger a new search. The [`refresh()`](/doc/api-reference/widgets/instantsearch/js#param-refresh) method on the `instantsearch` instance lets you clear the cache and trigger a new search. ### When to discard the cache Consider discarding the cache when your app's data is updated by: * Your users (for example, in a dashboard). In this case, refresh the cache based on an app state, such as the last user modification. * Another process you don't manage (for example, a cron job that updates users inside Algolia). In this case, refresh your app's cache periodically. ### Refresh the cache triggered by a user action The following code triggers a refresh based on a user action (such as adding a new product or clicking a button). > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/) > > Run and edit the Improve performance example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/refresh-cache-user-action) > > Browse the source for the Improve performance example on GitHub. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "demo_ecommerce", searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: "#searchbox", }), ]); search.addWidgets([ instantsearch.widgets.hits({ container: "#hits", }), ]); document.querySelector("button").addEventListener("click", () => { search.refresh(); }); search.start(); ``` ### Refresh the cache periodically You can set an interval to determine how often the app clears the cache. Use this approach if you can't trigger cache clearance based on user actions. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "demo_ecommerce", searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: "#searchbox", }), instantsearch.widgets.hits({ container: "#hits", }), ]); setInterval(() => { search.refresh(); }, 5000); search.start(); ``` If you need to wait for an action from Algolia, use [`waitTask`](/doc/libraries/sdk/v1/methods/wait-task) to avoid refreshing the cache before the task completes. > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/refresh-cache-periodically) > > Run and edit the Improve performance example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/refresh-cache-periodically) > > Browse the source for the Improve performance example on GitHub. ### Disable the cache If you need the most current data and the performance impact of this isn't an issue, [turn off caching](/doc/libraries/sdk/caching#requests-and-responses). ```js JavaScript icon=code theme={"system"} import { liteClient as algoliasearch } from "algoliasearch/lite"; import { createNullCache } from "@algolia/cache-common"; const searchClient = algoliasearch( "ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY", { // Disable caching for completed requests responsesCache: createNullCache(), // Disable caching for in-flight requests requestsCache: createNullCache(), }, ); ``` ## Queries per second (QPS) Search operations aren't limited by a fixed "search quota". Instead, they're limited by your plan's [maximum QPS](https://support.algolia.com/hc/en-us/articles/4406975224721) and [operations limit](https://support.algolia.com/hc/en-us/articles/18138875086865). Every keystroke in InstantSearch using the `searchBox` counts as one operation. Then, depending on the widgets you add to your search interface, you may have more operations being counted on each keystroke. For example, if you have a search interface with a `searchBox`, a `hierarchicalMenu`, and a `refinementList`, then every keystroke triggers one operation. Upon each user change to a `menu` or `refinementList`, a new operation is executed. If you experience QPS limitations, consider implementing [a debounced `searchBox`](#debouncing). For more information, see: * [`searchBox`](/doc/api-reference/widgets/search-box/js) * [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js) * [`refinementList`](/doc/api-reference/widgets/refinement-list/js) # Integrate Google Analytics Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/integrate-google-analytics/js Get insights from user searches in your InstantSearch.js app. Although Algolia provides analytics tailored to your search implementation, you might want to send your search data to your existing analytics tools. To do this, implement some custom middleware: **1. Set up Google Analytics** See [Set up the Google tag with gtag.js](https://developers.google.com/tag-platform/gtagjs). **2. Create the middleware to send events** In the event listener, send events to Google Analytics. This example debounces the event for 3 seconds but adjust this to your needs. ```js JavaScript icon="code" theme={"system"} function googleAnalyticsMiddleware() { let timer; const sendEventDebounced = () => { clearTimeout(timer); timer = setTimeout(() => { gtag('event', 'page_view', { page_location: window.location.pathname + window.location.search, }); }, 3000); }); return { onStateChange() { sendEventDebounced(); }, subscribe() {}, unsubscribe() {}, }; } ``` **3. Inject the middleware into the InstantSearch lifecycle** ```js JavaScript theme={"system"} const search = instantsearch({ searchClient, indexName, }); search.use(googleAnalyticsMiddleware); ``` # Using InstantSearch.js on mobile Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/native/js InstantSearch.js doesn't support native platforms. Use the dedicated InstantSearch libraries for iOS and Android instead. InstantSearch.js doesn't support native platforms at the moment. # Optimize InstantSearch.js search requests Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/optimize-search-request-usage/js Find areas to optimize the number of search requests and reduce billing in InstantSearch.js. Each keystroke sent to an Algolia 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.) counts as one search request. To reduce the number of requests sent to Algolia, and potentially reduce billing, you can ["debounce searches"](/doc/guides/building-search-ui/going-further/improve-performance/js#debouncing). ## What influences search request usage Your usage depends on factors that influence the volume of search requests, such as: * **Where do you plan to implement Algolia?** If you implement both mobile and desktop apps, your search request usage will be more than if you just had a desktop app. * **Will you just capture searches or also capture other elements?** Expect more search request usage if you also use Algolia to capture other elements such as navigation on [category pages](/doc/guides/solutions/ecommerce/browse/tutorials/category-pages), discovery solutions, and other implementations like marketing landing pages and shared widgets. ## How to optimize search request usage You can make changes to both your **frontend and backend implementations** to ensure your search request usage is as intended. ### Optimize search requests on your frontend implementation To find out how to optimize the number of search requests, use the browser's developer tools console to inspect Algolia API calls. #### Check per keystroke Type in the search box and **see how many API calls are sent to Algolia at each keystroke**. * By default, there is one request for each keystroke and index. * If you use [federated search](/doc/guides/sending-and-managing-data/manage-indices-and-apps/manage-indices/concepts/one-or-more-indices), you have separate requests for each queried index. **Compare the number of search requests for three elements** - the API calls, the number of keystrokes, and the results. * If you have more requests than your displayed indices, they could be duplicating queries to the same index or querying indices that aren't displayed. * If you have a non-optimized multi-index search request, you are already successfully using the multi-query endpoint (federated search uses multiple indices). * If you don't group the requests into one network request, all requests are on separate lines and billed separately. * If you have an optimized multi-index search request and you have five different requests, the multi-query endpoint groups all five different requests to different indices. #### Check per facet click If your results page includes a facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).), check whether applying it triggers an additional search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.). There should only be one. #### Check on page load * **Clear your browser's cache and reload the page**. Were any queries sent to Algolia? If yes, check to see if any Algolia results are being displayed on the page by default. It could be that 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.) is sent to populate the page's contents, like on category pages. * If Algolia-delivered results aren't visible on the page, **this could be a redundant query**. You can use your discretion to remove the query. #### Check if you are using single or multi-query endpoint How you query Algolia is important and can make a big difference to your billing. Each line in your browser developer tool's **Network** tab is called a "network request". * **A network request that starts with "query"** means only one request was sent to one index. * **A network request starting with "queries"** means that two or more queries are grouped in a single network request, and it's either used to search through multiple indices or it's searching through one index but needs to make multiple queries. An example of a search through one index with multiple queries is when using [`dynamicWidgets`](/doc/api-reference/widgets/dynamic-widgets/js) which could [cause an extra search request](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/js#optimize-dynamic-widget-network-requests). **You need to optimize if you have a multi-index search implementation** and there are few calls to the multi-query endpoint. Algolia has [API clients](/doc/libraries/sdk/v1/methods/multiple-queries) to help you optimize the usage of multi-query endpoints, including [an API for InstantSearch](/doc/api-reference/widgets/index-widget/js) and for frontend libraries like [Magento](/doc/integration/magento-2/getting-started/upgrading?client=php#after-upgrading). #### Check for expected signs of usage * Check for linear growth in **the number of queries over time**. Study the graph in the **Monitoring** > **Usage** section in your dashboard. * Check for linear growth in Analytics for **searches in comparison to the number of users**. A high number of searches per user can mean: * You have a [backend implementation](#optimize-search-requests-on-your-backend-and-native-mobile-implementations). * You have a low number of users. * You have a bug that is sending too many queries per keystroke. * Your search is used more extensively than initially scoped. For example, after an initial scope to power your search, has Algolia now been implemented on the category pages or has it been extended to a new country? This could result in an increase in search requests. * Check for **abnormal spikes** in search requests. For example, is there an increase during holiday or sale seasons? * Compare **search queries and the average number of visitors**. This comparison depends on your use case and search experience. For example, if you use Algolia to browse and search, you will have more queries per user. And the number of as-you-type queries depends on how prominently the search box is displayed. #### Check if you are making extended use of Algolia Check if Algolia is **used across multiple domains** beyond the initial scope. #### Check Analytics on your dashboard to see the top searches * Check if a high number of searches are labeled as "empty search". This means that a majority of the usage is going towards browsing the content page. * Check the **Searches** tab to see if the filters on the "empty search" queries have a category-based 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.). This implies that Algolia is used extensively for filters and category pages. If this wasn't scoped for initially, it could explain a high search request usage. #### Check for bots crawling your website [Google bots](https://support.algolia.com/hc/en-us/articles/4406981900433) could cause an increase in search queries. ### Optimize search requests on your backend and native mobile implementations Depending on your implementation, you may need to use your internal tools to investigate. **Check the usage of each search endpoint** by looking at the incoming API operations on the Algolia dashboard's **Monitoring** > **Operations** > **API Calls** section If you are using a multi-index search and few calls are made to the multi-query endpoint, there is room for optimization. To start using multi-query endpoint, use Algolia's [API clients](/doc/libraries/sdk/v1/methods/multiple-queries) or frontend libraries like [InstantSearch for federated search](/doc/api-reference/widgets/index-widget/js) or [Magento 2](/doc/integration/magento-2/getting-started/upgrading?client=php#after-upgrading). # Routing and URL syncing in InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/routing-urls/js How to synchronize your URLs with InstantSearch.js. Synchronizing your UI with the browser URL is considered good practice. It lets your users take one of your results pages, copy the URL, and share it. It also improves the user experience by enabling the use of the back and next browser buttons to keep track of previous searches. InstantSearch provides the necessary API entries to let you synchronize the state of your search UI (your refined widgets and current 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.)) with any kind of storage. Use the [`routing`](/doc/api-reference/widgets/instantsearch/js#param-routing) option to synchronize UI state with the browser URL, so users can bookmark, share, and revisit a search. This guide covers enabling default routing, customizing URLs, and creating SEO-friendly URLs. > [!NOTE] > > Don't configure [`initialUiState`](/doc/api-reference/widgets/instantsearch/js#param-initial-ui-state) and `routing` together. > > * Use `initialUiState` to set the UI state when the search first loads, such as for a default query or filter. > * Use `routing` to keep the UI state synchronized with the URL, so users can bookmark or share a search. ## Default URLs Set `routing` to `true` to use the default routing configuration: a history router with simple state mapping. This stores routing-compatible UI state in URL query parameters. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ searchClient, indexName: "instant_search", routing: true, }); ``` Setting `routing` to `true` is equivalent to this configuration: ```js JavaScript icon=code theme={"system"} const search = instantsearch({ searchClient, indexName: "instant_search", routing: { router: instantsearch.routers.history(), stateMapping: instantsearch.stateMappings.simple(), }, }); ``` Assume the following search UI state: * **Query:** "galaxy" * **Menu:** * `categories`: "Cell Phones" * **Refinement List:** * `brand`: "Apple", "Samsung" * **Page:** 2 This produces the following URL: ```txt theme={"system"} https://example.org/?instant_search[query]=galaxy&instant_search[menu][categories]=Cell Phones&instant_search[refinementList][brand][0]=Apple&instant_search[refinementList][brand][0]=Samsung&instant_search[page]=2 ``` By default, routing includes state from routing-compatible widgets. With many widgets, this can create long URLs. To create shorter or more descriptive URLs, customize the URL routing. ## Customize URL routing You can customize which values appear and rename the URL parameters. The `stateMapping` option maps between InstantSearch's `uiState` and the `routeState` used by the router. Use it to rename parameters or omit values that you don't want to include in the URL. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ searchClient, indexName: "instant_search", routing: { stateMapping: { stateToRoute(uiState) { // ... }, routeToState(routeState) { // ... }, }, }, }); ``` InstantSearch stores widget state in [`uiState`](/doc/api-reference/widgets/ui-state/js). The following example maps that state to shorter URL parameters. The state contains information about the user's search, including the query, the 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.) selection, the page being viewed, and the widget hierarchy. `uiState` only stores modified widget values, not defaults. To persist this state in the URL, InstantSearch converts the `uiState` into an object called `routeState`: this `routeState` then becomes a URL. Conversely, when InstantSearch reads the URL and applies it to the search, it converts `routeState` into `uiState`. This logic lives in two functions: * `stateToRoute`: converts `uiState` to `routeState`. * `routeToState`: converts `routeState` to `uiState`. Assume the following search UI state: * **Query:** "galaxy" * **Menu:** * `categories`: "Cell Phones" * **Refinement List:** * `brand`: "Apple" and "Samsung" * **Page:** 2 This translates into the following `uiState`: ```json JSON icon="braces" theme={"system"} { "indexName": { "query": "galaxy", "menu": { "categories": "Cell Phones" }, "refinementList": { "brand": ["Apple", "Samsung"] }, "page": 2 } } ``` Implement `stateToRoute` to flatten this object into a URL, and `routeToState` to restore the URL into a UI state: ```js JavaScript icon=code expandable theme={"system"} const indexName = "instant_search"; const search = instantsearch({ searchClient, indexName, routing: { stateMapping: { stateToRoute(uiState) { const indexUiState = uiState[indexName]; return { q: indexUiState.query, categories: indexUiState.menu && indexUiState.menu.categories, brand: indexUiState.refinementList && indexUiState.refinementList.brand, page: indexUiState.page, }; }, routeToState(routeState) { return { [indexName]: { query: routeState.q, menu: { categories: routeState.categories, }, refinementList: { brand: routeState.brand, }, page: routeState.page, }, }; }, }, }, }); ``` ### Keep unrelated URL parameters If you enable InstantSearch routing, the URL includes only parameters from widgets. To keep other parameters unrelated to InstantSearch, add them when implementing `createURL`. For example, to keep all URL parameters that start with `utm_`, use the following code: ```js JavaScript icon=code expandable theme={"system"} history({ // ... other options parseURL({ qsModule, location }) { return qsModule.parse(location.search.slice(1)); }, createURL({ qsModule, location, routeState }) { const { origin, pathname, hash } = location; const queriesFromUrl = qsModule.parse(location.search.slice(1)); // Preserve unrelated URL parameters that start with "utm_". const utmQueries = Object.fromEntries( Object.entries(queriesFromUrl).filter( ([key]) => !Object.keys(routeState).includes(key) && // Add here a condition to keep the parameters you want, for example starting with "utm_" key.startsWith("utm_"), ), ); // Combine InstantSearch state with the preserved parameters. const queryString = qsModule.stringify( { ...routeState, ...utmQueries, }, { addQueryPrefix: true, arrayFormat: "repeat", }, ); return `${origin}${pathname}${queryString}${hash}`; }, }); ``` ## SEO-friendly URLs To create more descriptive URLs, move search state from query parameters into the URL path. This is a common pattern for ecommerce category and search pages. ```txt theme={"system"} https://example.org/search/Cell+Phones/?query=galaxy&page=2&brands=Apple&brands=Samsung ``` ### Store categories in the URL path This example stores the category in the path and the query, page, and brands as query parameters. ```js JavaScript icon=code expandable theme={"system"} // Convert a category name to a URL slug. // Replace spaces with "+" and encode other characters. function getCategorySlug(name) { return name.split(" ").map(encodeURIComponent).join("+"); } // Convert a category slug to a category name. // Replace "+" with spaces and decode other characters. function getCategoryName(slug) { return slug.split("+").map(decodeURIComponent).join(" "); } const search = instantsearch({ searchClient, indexName: "instant_search", routing: { router: instantsearch.routers.history({ windowTitle({ category, query }) { const queryTitle = query ? `Results for "${query}"` : "Search"; if (category) { return `${category} – ${queryTitle}`; } return queryTitle; }, createURL({ qsModule, routeState, location }) { const urlParts = location.href.match(/^(.*?)\/search/); const baseUrl = `${urlParts ? urlParts[1] : ""}/`; const categoryPath = routeState.category ? `${getCategorySlug(routeState.category)}/` : ""; const queryParameters = {}; if (routeState.query) { queryParameters.query = encodeURIComponent(routeState.query); } if (routeState.page !== 1) { queryParameters.page = routeState.page; } if (routeState.brands) { queryParameters.brands = routeState.brands.map(encodeURIComponent); } const queryString = qsModule.stringify(queryParameters, { addQueryPrefix: true, arrayFormat: "repeat", }); return `${baseUrl}search/${categoryPath}${queryString}`; }, parseURL({ qsModule, location }) { const pathnameMatches = location.pathname.match(/search\/(.*?)\/?$/); const category = getCategoryName( (pathnameMatches && pathnameMatches[1]) || "", ); const { query = "", page, brands = [], } = qsModule.parse(location.search.slice(1)); // `qs` does not return an array when there's a single value. const allBrands = Array.isArray(brands) ? brands : [brands].filter(Boolean); return { query: decodeURIComponent(query), page, brands: allBrands.map(decodeURIComponent), category, }; }, }), stateMapping: { stateToRoute(uiState) { const indexUiState = uiState["instant_search"] || {}; return { query: indexUiState.query, page: indexUiState.page, brands: indexUiState.refinementList && indexUiState.refinementList.brand, category: indexUiState.menu && indexUiState.menu.categories, }; }, routeToState(routeState) { return { instant_search: { query: routeState.query, page: routeState.page, menu: { categories: routeState.category, }, refinementList: { brand: routeState.brands, }, }, }; }, }, }, }); ``` This example uses [`instantsearch.routers.history`](/doc/api-reference/widgets/history-router/js) to configure the default history router. The router reads and writes URLs, while `stateMapping` maps `uiState` to `routeState` and back. When you configure the history router, you can customize these functions: * `windowTitle`: returns the browser window title for a `routeState`. * `createURL`: creates a URL from `routeState`. InstantSearch calls it when synchronizing the browser URL, rendering links in the `menu` widget, or when a connector calls `createURL`. * `parseURL`: creates `routeState` from the URL when users load or reload the page or use the browser's back or forward navigation. ### Make URLs more discoverable Shorter category URLs can be more readable and memorable. Use a mapping object to map category names to shorter URL slugs. Given the dataset in this guide, you can make some categories more discoverable: * "Cameras and camcorders" → `/Cameras` * "Car electronics and GPS" → `/Cars` When users open `https://example.org/search/Cameras`, InstantSearch selects the "Cameras and camcorders" category. Define mappings between category names and URL slugs: ```js JavaScript icon=code expandable theme={"system"} // Map URL slugs to category names. const encodedCategories = { Cameras: "Cameras and camcorders", Cars: "Car electronics and GPS", Phones: "Phones", TV: "TV and home theater", }; // Map category names to URL slugs. const decodedCategories = Object.keys(encodedCategories).reduce((acc, key) => { const newKey = encodedCategories[key]; const newValue = key; return { ...acc, [newKey]: newValue, }; }, {}); // Use the mappings when converting category names and slugs. function getCategorySlug(name) { const encodedName = decodedCategories[name] || name; return encodedName.split(" ").map(encodeURIComponent).join("+"); } function getCategoryName(slug) { const decodedSlug = encodedCategories[slug] || slug; return decodedSlug.split("+").map(decodeURIComponent).join(" "); } ``` You can build these dictionaries from your Algolia records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). With such a solution, you have full control over what categories are discoverable from the URL. ### About SEO For your search results to be part of a public search engine's results, you must be selective. Trying to index too many search results pages could be considered spam. To do that, create a [`robots.txt`](http://www.robotstxt.org/) and host it at `https://example.org/robots.txt`. Here's an example based on the URL scheme you created. ```txt robots.txt theme={"system"} User-agent: * Allow: /search/Audio/ Allow: /search/Phones/ Disallow: /search/ Allow: * ``` > [card] [Basic routing demo](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/routing-basic) > > Explore basic InstantSearch routing in CodeSandbox. > [card] [SEO-friendly routing demo](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/routing-seo-friendly) > > Explore SEO-friendly routing in CodeSandbox. ## Group facet values To group facet values such as "turquoise", "ocean", and "sky" under "blue", add the group at indexing time. Either add a separate grouping attribute or store both the individual value and group value in the same attribute. For example, with the following dataset: ```json JSON icon=braces theme={"system"} [ { "objectID": "1", "color": "turquoise" }, { "objectID": "2", "color": "ocean" }, { "objectID": "3", "color": "sky" } ] ``` To facet on a separate grouping attribute, add `colorGroup` to each record: ```json JSON icon=braces theme={"system"} [ { "objectID": "1", "color": "turquoise", "colorGroup": "blue" }, { "objectID": "2", "color": "ocean", "colorGroup": "blue" }, { "objectID": "3", "color": "sky", "colorGroup": "blue" } ] ``` To facet on both individual colors and their group, store both values in the `color` attribute: ```json JSON icon=braces theme={"system"} [ { "objectID": "1", "color": ["turquoise", "blue"] }, { "objectID": "2", "color": ["ocean", "blue"] }, { "objectID": "3", "color": ["sky", "blue"] } ] ``` # Dynamic rendering with InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/going-further/server-side-rendering/js InstantSearch.js doesn't support server-side rendering, but you can get the same benefits by implementing dynamic rendering. > [!WARNING] > > Dynamic rendering is now considered a workaround instead of a recommended approach and Rendertron, the tool mentioned in this guide, is deprecated. > You can use server-side rendering with [React InstantSearch](/doc/guides/building-search-ui/going-further/server-side-rendering/react) and [Vue InstantSearch](/doc/guides/building-search-ui/going-further/server-side-rendering/vue). InstantSearch.js doesn't support server-side rendering (SSR). However, it's possible to get some of the SSR benefits using another technique: [dynamic rendering](https://developers.google.com/search/docs/guides/dynamic-rendering). ## Dynamic rendering [Dynamic rendering](https://developers.google.com/search/docs/guides/dynamic-rendering) lets you switch between client-side rendered and pre-rendered content for specific user agents, typically search engine user agents. This means that whenever your server receives a request, it first looks at the user agent to determine whether it's coming from a search engine or a regular user. * If the request is coming from a search engine, the response is proxied through a headless browser that pre-renders it. * If the request is coming from a regular user, the response is rendered client-side. ### Build an InstantSearch app The easiest way to setup a basic InstantSearch app is using [`create-instantsearch-app`](https://github.com/algolia/create-instantsearch-app/). Install [create-instantsearch-app](https://github.com/algolia/create-instantsearch-app/) and create your app. ```bash Shell theme={"system"} npm install -g create-instantsearch-app@latest create-instantsearch-app my-app ``` > [!NOTE] > > Feel free to pick any InstantSearch flavor your like. > Pick the default values for the rest of the options. Run the build command and confirm it produced a `dist/` folder. ```bash Shell theme={"system"} cd my-app npm run build ``` Add a basic [Express.js](https://expressjs.com/) server that will serve static files. ```bash Shell theme={"system"} npm install --save express touch index.js ``` ```js JavaScript theme={"system"} // index.js const express = require('express'); const app = express(); app.use(express.static('dist')); const port = process.env.PORT || 3000; app.listen(port, err => { if (err) { console.error('failed to start server.'); return; } console.log(`server started at http://localhost:${port}/`); }); ``` Now run the server and visit [http://localhost:3000](http://localhost:3000) to make sure everything is working. ```bash Shell theme={"system"} node index.js ``` ### Set up and configure a dynamic renderer Google recommends using either [rendertron](https://github.com/GoogleChrome/rendertron) (free, self-hosted) or [prerender.io](https://prerender.io/) (fully hosted). This example uses [rendertron](https://github.com/GoogleChrome/rendertron). ```bash Shell theme={"system"} npm install -g rendertron PORT=3001 rendertron & ``` The pre-renderer is now listening on port `3001` and ready to render JavaScript generated content into HTML. ### Proxy your requests through Rendertron You need to send every request with the `Content-Type: text/html` header and the relevant user agent to the URL of Rendertron: [`http://localhost:3001/render`](http://localhost:3001/render). Depending on your stack, there are multiple ways you can achieve this kind of proxying, but if you're using an [Express.js](https://expressjs.com/) server, [rendertron-middleware](https://github.com/GoogleChrome/rendertron/blob/master/middleware/) can handle this proxying for you. ```bash Shell theme={"system"} npm install --save rendertron-middleware ``` ```js JavaScript theme={"system"} const rendertronMiddleware = require('rendertron-middleware'); /* ... */ app.use( rendertronMiddleware.makeMiddleware({ proxyUrl: `http://localhost:3001/render/`, // rendertron address with a /render/ suffix timeout: 60 * 1000, userAgentPattern: /googlebot|bingbot/i // by default }) ); app.use(express.static('dist')); /* ... */ ``` > [!NOTE] > > Feel free to adjust the `userAgentPattern` or use the [default values](https://github.com/GoogleChrome/rendertron/blob/4f396d4d522cd39ceaa0b33b203afd81371e28f5/middleware/src/middleware.ts#L25-L40). Close and restart your server, then run this curl command to confirm pre-rendering works. ```bash Shell theme={"system"} curl --user-agent "googlebot" http://localhost:3000/ ``` # How to install InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/installation/js Learn how to install InstantSearch.js with a package manager or use it directly from a content delivery network. Install InstantSearch.js with a packaging system, with a direct link in your web page, or with `create-instantsearch-app`. ## With a packaging system If you have a JavaScript build tool, you can install InstantSearch.js from npm: ```sh Command line icon=square-terminal theme={"system"} npm install algoliasearch instantsearch.js ``` Then in your module, you can load the main package: ```js JavaScript theme={"system"} // Import InstantSearch.js import { liteClient as algoliasearch } from "algoliasearch/lite"; import instantsearch from "instantsearch.js"; import { searchBox, hits } from "instantsearch.js/es/widgets"; const appID = "ALGOLIA_APPLICATION_ID"; const apiKey = "ALGOLIA_SEARCH_API_KEY"; const indexName = "INDEX_NAME"; const searchClient = algoliasearch(appID, apiKey); const search = instantsearch({ indexName, searchClient, }); search.addWidgets([ searchBox({ container: "#searchbox", }), hits({ container: "#hits", }), ]); search.start(); ``` [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) is the root InstantSearch.js component. All widgets must be wrapped within it > [!NOTE] > > In a production environment, [secure your API keys](/doc/guides/security/security-best-practices#secure-your-api-keys) as environment variables. ## Directly in your page This method uses the version of **InstantSearch.js** from the [jsDelivr](https://www.jsdelivr.com/) CDN: ```html HTML icon=code-xml theme={"system"} ``` ```html HTML icon=code=xml theme={"system"} ``` You then have access to the [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) function in the global scope (`window`). ```js JavaScript icon=code theme={"system"} const { liteClient: algoliasearch } = window["algoliasearch/lite"]; const appID = "ALGOLIA_APPLICATION_ID"; const apiKey = "ALGOLIA_SEARCH_API_KEY"; const indexName = "INDEX_NAME"; const searchClient = algoliasearch(appID, apiKey); const search = instantsearch({ indexName, searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: "#searchbox", }), instantsearch.widgets.hits({ container: "#hits", }), ]); search.start(); ``` [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) is the root InstantSearch.js component. All widgets must be wrapped within it. > [!NOTE] > > In a production environment, [secure your API keys](/doc/guides/security/security-best-practices#secure-your-api-keys) as environment variables. ### Load the styles You need to manually load the companion [CSS file](https://cdn.jsdelivr.net/npm/instantsearch.css@8.5.1/themes/reset-min.css) into your page: ```html HTML icon=code-xml theme={"system"} ``` Or you can load the [satellite theme](https://cdn.jsdelivr.net/npm/instantsearch.css@8.5.1/themes/satellite-min.css) widget styles into your page. ```html HTML icon=code-xml theme={"system"} ``` You can customize the themes using CSS variables. For a complete reference of customization options, see [Styling and theming](/doc/guides/building-search-ui/styling/js). ## With `create-instantsearch-app` Use [`create-instantsearch-app`](https://github.com/algolia/instantsearch/tree/master/packages/create-instantsearch-app#create-instantsearch-app) to create a working InstantSearch app. Similar to other interactive command-line apps, run it with `npm` or `yarn`: ```sh npm theme={"system"} npx create-instantsearch-app@latest 'getting-started' ``` ```sh yarn theme={"system"} yarn create instantsearch-app 'getting-started' ``` To launch the app, type inside your terminal: ```sh Command line icon=square-terminal theme={"system"} cd getting-started npm start # or yarn start ``` ## Send click and conversion events Capturing real-time user interactions as events gives you actionable insights with [click and conversion metrics](/doc/guides/search-analytics/concepts/metrics#click-through-rate), and they help you increase your customer engagement and conversions. Events are used to activate Algolia features and products like [NeuralSearch](/doc/guides/ai-relevance/neuralsearch/get-started), [Dynamic Re-Ranking](/doc/guides/algolia-ai/re-ranking), [Query Categorization](/doc/guides/algolia-ai/query-categorization), [Recommend](/doc/guides/algolia-recommend/overview), and [Personalization](/doc/guides/personalization/classic-personalization/what-is-personalization). To send click and conversion events when users interact with your search UI, set the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option to `true`. ## Browser support Algolia supports the last two versions of the major browsers: Chrome, Edge, Firefox, Safari. To support [Internet Explorer 11](https://wikipedia.org/wiki/Internet_Explorer_11) you need [polyfills](https://wikipedia.org/wiki/Polyfill_\(programming\)) for: `Array.prototype.find`, `Array.prototype.includes`, `Promise`, `Object.entries`, and `Object.assign`. > [!NOTE] > > The code samples in this documentation use a JavaScript syntax that isn't natively supported by older browsers like Internet Explorer 11. > If your site needs to support older browsers, use a tool like [Babel](https://babeljs.io/repl) to make your code work in the browsers you target. # Styling and theming Source: https://www.algolia.com/doc/guides/building-search-ui/styling/js Learn how to customize InstantSearch.js appearance using CSS variables and themes. InstantSearch.css provides a set of themes to style your search experiences with minimal effort. The themes are designed to be customizable through [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) (CSS custom properties), allowing you to match your brand identity while maintaining the core layout and features. > [!NOTE] > > CSS variables are currently only supported for [`chat`](/doc/api-reference/widgets/chat/js) and [`autocomplete`](/doc/api-reference/widgets/autocomplete/js) widgets. ## Loading themes InstantSearch.css includes two main themes: * **`satellite.css`** - A complete theme with modern styling * **`algolia.css`** - Algolia-branded theme Both themes include `reset.css`, a minimal reset theme that provides base styles. ### Use a CDN The themes are available on jsDelivr: ```html HTML icon=code-xml theme={"system"} ``` ### Using npm ```sh npm theme={"system"} npm install instantsearch.css ``` ```sh yarn theme={"system"} yarn add instantsearch.css ``` Then import it in your project: ```js JavaScript icon=code theme={"system"} // Satellite theme import 'instantsearch.css/themes/satellite.css'; // Or Algolia theme // import 'instantsearch.css/themes/algolia.css'; ``` ### Loading individual component styles If you only need specific components, you can import their styles individually to reduce bundle size. This is currently available for `chat` and `autocomplete` components: ```js JavaScript icon=code theme={"system"} // Import only chat component styles import 'instantsearch.css/components/chat.css'; // Or import the minified version // import 'instantsearch.css/components/chat-min.css'; ``` ```js JavaScript icon=code theme={"system"} // Import only autocomplete component styles import 'instantsearch.css/components/autocomplete.css'; // Or import the minified version // import 'instantsearch.css/components/autocomplete-min.css'; ``` > [!NOTE] > > Individual component styles include all CSS variables and theming capabilities of the full themes. > You can still customize them using the same CSS variable overrides. ## Development tools InstantSearch.css provides developer tools that help you customize CSS variables visually in your browser. The developer tools display an interactive panel where you can: * Modify CSS variables in real-time * Toggle between theme modes (Auto, Light, Dark) * Export your customized variables to clipboard ![InstantSearch.css developer tools](https://mintcdn.com/algolia/RxD-6-IbmmSXialK/images/build-search-ui/instantsearch-css-devtools.png?fit=max\&auto=format\&n=RxD-6-IbmmSXialK\&q=85\&s=93edffe6efa3dd5195c7bc81733c51a3) ### Use the developer tools Import the developer tools in your application: ```js JavaScript icon=code theme={"system"} import 'instantsearch.css/devtools/inject'; ``` The developer tools panel will appear in the bottom-left corner of your browser, allowing you to interactively customize colors, spacing, typography, and more. > [!NOTE] > > The developer tools are intended for development only. > Don't include them in your production build. ### Programmatic usage You can also create the developer tools programmatically with custom options: ```js JavaScript icon=code theme={"system"} import { createInstantSearchDevtools } from 'instantsearch.css/devtools'; createInstantSearchDevtools({ // Optional: custom container container: document.getElementById('devtools'), // Optional: custom styling style: { position: 'fixed', top: '1rem', right: '1rem', zIndex: '1000', }, }); ``` ## Customization example You can override any CSS variable to match your brand: ```css CSS icon=paintbrush theme={"system"} :root { /* Brand colors */ --ais-primary-color-rgb: 124, 58, 237; /* Spacing */ --ais-spacing-factor: 1.5; } ``` ## Dark mode The themes support dark mode in two ways: ### Using data-theme attribute ```html HTML icon=code-xml theme={"system"} ``` ### Using CSS class ```html HTML icon=code-xml theme={"system"} ``` Both approaches automatically adjust the following variables: ```css CSS icon=paintbrush theme={"system"} :root[data-theme='dark'], .dark { /* Text colors */ --ais-text-color-rgb: 255, 255, 255; --ais-primary-color-rgb: 110, 160, 255; --ais-muted-color-rgb: 190, 190, 190; --ais-button-text-color-rgb: 255, 255, 255; /* Border color */ --ais-border-color-rgb: 100, 100, 100; /* Background color */ --ais-background-color-rgb: 38, 38, 38; /* Shadow color */ --ais-shadow-color-rgb: 0, 0, 0; } ``` ### Custom dark mode colors You can override dark mode colors: ```css CSS icon=paintbrush theme={"system"} :root[data-theme='dark'], .dark { --ais-primary-color-rgb: 147, 197, 253; --ais-background-color-rgb: 15, 23, 42; } ``` ## Complete variable reference For a complete list of all available CSS variables with detailed descriptions, see the tables below: ### Text colors | Variable | Default | Description | | - | - | - | | `--ais-text-color-rgb` | `38, 38, 38` | RGB values for the base text color | | `--ais-text-color-alpha` | `1` | Alpha value for the base text color | | `--ais-primary-color-rgb` | `30, 89, 255` | RGB values for the accent color | | `--ais-primary-color-alpha` | `1` | Alpha value for the accent color | | `--ais-muted-color-rgb` | `82, 82, 82` | RGB values for muted/secondary text | | `--ais-muted-color-alpha` | `1` | Alpha value for muted/secondary text | | `--ais-button-text-color-rgb` | `255, 255, 255` | RGB values for button text | | `--ais-button-text-color-alpha` | `1` | Alpha value for button text | ### Border, background, and shadow colors | Variable | Default | Description | | - | - | - | | `--ais-border-color-rgb` | `150, 150, 150` | RGB values for borders | | `--ais-border-color-alpha` | `1` | Alpha value for borders | | `--ais-background-color-rgb` | `255, 255, 255` | RGB values for backgrounds | | `--ais-background-color-alpha` | `1` | Alpha value for backgrounds | | `--ais-shadow-color-rgb` | `23, 23, 23` | RGB values for shadow colors | ### Spacing | Variable | Default | Description | | - | - | - | | `--ais-base-unit` | `16` | Base unit value to calculate font sizes and spacing | | `--ais-spacing-factor` | `1` (tap: `1.2`) | Multiplier for spacing calculations | | `--ais-spacing` | calculated | Computed spacing value | ### Typography | Variable | Default | Description | | - | - | - | | `--ais-font-size` | `16px` | Base font size derived from `--ais-base-unit` | | `--ais-font-size-sm` | `14px` | Small font size derived from `--ais-font-size` | | `--ais-font-weight-medium` | `500` | Medium font weight | | `--ais-font-weight-semibold` | `600` | Semibold font weight | | `--ais-font-weight-bold` | `700` | Bold font weight | ### Icons | Variable | Default | Description | | - | - | - | | `--ais-icon-size` | `20px` | Icon size | | `--ais-icon-stroke-width` | `1.6` | Icon stroke width | ### Shadows | Variable | Default | Description | | - | - | - | | `--ais-shadow-sm` | `0px 0px 0px 1px rgba(..., 0.05), 0px 1px 3px 0px rgba(..., 0.25)` | Small shadow for subtle depth | | `--ais-shadow-md` | `0px 0px 0px 1px rgba(..., 0.05), 0px 4px 8px -2px rgba(..., 0.25)` | Medium shadow for cards | | `--ais-shadow-lg` | `0 0 0 1px rgba(..., 0.05), 0 6px 16px -4px rgba(..., 0.15)` | Large shadow for modals | ### Border radius | Variable | Default | Description | | - | - | - | | `--ais-border-radius-sm` | `4px` | Small border radius | | `--ais-border-radius-md` | `8px` | Medium border radius | | `--ais-border-radius-lg` | `16px` | Large border radius | | `--ais-border-radius-full` | `9999px` | Full border radius for pills | ### Transitions | Variable | Default | Description | | - | - | - | | `--ais-transition-duration` | `0.3s` | Duration for transitions | | `--ais-transition-timing-function` | `cubic-bezier(0.4, 0, 0.2, 1)` | Timing function for transitions | ### Z-index | Variable | Default | Description | | - | - | - | | `--ais-z-index-chat` | `9999` | Z-index for chat component | | `--ais-z-index-autocomplete` | `10000` | Z-index for autocomplete component | ### Component-specific #### Chat component | Variable | Default | Description | | - | - | - | | `--ais-chat-width` | `22.5rem` | Default width of chat panel | | `--ais-chat-height` | `70%` | Default height of chat panel | | `--ais-chat-maximized-width` | `70%` | Width when maximized | | `--ais-chat-maximized-height` | `100%` | Height when maximized | | `--ais-chat-margin` | `1.5rem` | Margin around chat panel | | `--ais-chat-carousel-item-width` | `calc(var(--ais-spacing) * 10)` | Width of carousel items | #### Autocomplete component | Variable | Default | Description | | - | - | - | | `--ais-autocomplete-search-input-height` | `44px` | Height of search input | | `--ais-autocomplete-panel-max-height` | `650px` | Maximum height of autocomplete panel | # Federated two-column autocomplete Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/examples/federated/js Build a two-column autocomplete with recent searches, Query Suggestions, and product previews. This example builds on the [`autocomplete`](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/js) widget guide. It creates a federated autocomplete experience with recent searches and Query Suggestions on the left, and product previews on the right. > [tab] Desktop > > ![Federated autocomplete with recent searches and Query Suggestions on the left and product previews on the right (desktop)](https://mintcdn.com/algolia/nyJ2KZzw6bfBNB-S/images/autocomplete/widget-federated-desktop.png?fit=max\&auto=format\&n=nyJ2KZzw6bfBNB-S\&q=85\&s=df2d4d6d04ddbc1bbe73f4209b961eb2) > [tab] Mobile > > ![Federated autocomplete in detached mode on a narrow screen (mobile)](https://mintcdn.com/algolia/nyJ2KZzw6bfBNB-S/images/autocomplete/widget-federated-mobile.png?fit=max\&auto=format\&n=nyJ2KZzw6bfBNB-S\&q=85\&s=66f7874dc3a81a2c863b22cbf05e7a33) > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/autocomplete) > > Run and edit the federated autocomplete example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/autocomplete) > > Browse the source code for the federated autocomplete example on GitHub. The example has three states. Each one arranges the same two columns differently: | Search state | Left column | Right column | | - | - | - | | **Empty query** | Recent searches and popular searches | Shortcuts | | **Results** | Suggestions and [a category](#add-a-category-suggestion) | Product (and article) previews | | **No results** | A "no results" message | Popular categories. To show popular categories, add a source that returns category facet values | The sections below build each piece, then combine them in a single `panel` template. The `panel` template arranges the sources into two columns, and each source brings its own header and item templates: ```js JavaScript icon=code expandable theme={"system"} import { liteClient as algoliasearch } from "algoliasearch/lite"; import instantsearch from "instantsearch.js"; import { autocomplete } from "instantsearch.js/es/widgets"; import "instantsearch.css/themes/satellite.css"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); const search = instantsearch({ indexName: "instant_search", searchClient, }); search.addWidgets([ autocomplete({ container: "#autocomplete", placeholder: "Search for products", // Left column, top: recent searches from local storage showRecent: { templates: { header: (_, { html }) => html`Recent searches`, }, }, // Left column, bottom: popular Query Suggestions showQuerySuggestions: { indexName: "instant_search_demo_query_suggestions", searchParameters: { hitsPerPage: 5 }, templates: { header: (_, { html }) => html`Suggestions`, }, }, // Right column: product previews with image, name, price, and highlight indices: [ { indexName: "instant_search", searchParameters: { hitsPerPage: 5 }, templates: { header: ({ items }, { html }) => items.length === 0 ? null : html`Products`, noResults: (_, { html }) => html`
No products found.
`, item: ({ item }, { html, components }) => html`
${item.name}

${components.Highlight({ hit: item, attribute: "name" })}

${item.brand} · $${item.price}

`, }, }, ], // Access built-in sources by their element names and index sources by index name. templates: { panel: ({ elements }, { html }) => html`
${elements.recent} ${elements.suggestions}
${elements["instant_search"]}
`, }, }), ]); search.start(); ``` Add CSS for the columns, product rows, and state-specific elements. The [example style sheet](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/autocomplete/src/app.css) includes classes for: * Two-column grids * Product rows * Shortcut cards * "See all" and no-results states ## Add a shortcuts panel You can show promotional cards or shortcuts when the query is empty. Define them as static data and render them in the `panel` template. When users type a query, show product previews instead: ```js JavaScript icon=code expandable theme={"system"} // Quick-access cards shown in the empty state. const QUICK_ACCESS = [ { title: "Spring sale", subtitle: "up to 60% off", image: "https://picsum.photos/seed/spring-sale/300/200", href: "#", }, { title: "New collection", subtitle: "spring / summer", image: "https://picsum.photos/seed/new-collection/300/200", href: "#", }, ]; templates: { panel: ({ elements, indices }, { html }) => { const products = indices.find( (index) => index.indexName === "instant_search", ); const isEmptyQuery = products?.results?.query === ""; return html`
${elements.recent} ${elements.suggestions}
${isEmptyQuery && QUICK_ACCESS.length > 0 ? html`` : elements["instant_search"]}
`; }, }, ``` > [!NOTE] > > For dynamic cards, return custom data from an [Algolia rule](/doc/guides/managing-results/rules/rules-overview). > In the `panel` template, read the data from `results.userData` for the products index instead of using a static array. ## Add a category suggestion To show a category suggestion in the left column, find the products index and read the top product's `categories` from `results.hits[0]` in the `panel` template. Guard against a missing or empty `categories` attribute, then render the category after the suggestions: ```js JavaScript icon=code theme={"system"} const products = indices.find( (index) => index.indexName === "instant_search", ); // Derive a category from the top product, if any. const categories = products?.results?.hits?.[0]?.categories ?? []; // In the left column, after the suggestions: html`${elements.suggestions} ${categories.length > 0 ? html`
${categories.join(" › ")}
` : null}`; ``` ## Show a "See all" link The index source doesn't have a `footer` template. To show a **See all** link with the total number of matching results, read the products index's `results.nbHits` in the `panel` template and render the link after the product previews: ```js JavaScript icon=code theme={"system"} const products = indices.find( (index) => index.indexName === "instant_search", ); const nbHits = products?.results?.nbHits ?? 0; // In the right column, after the product elements: html`${elements["instant_search"]} ${nbHits > 5 ? html` See all ${nbHits.toLocaleString()} results ` : null}`; ``` ## Show the no-results state When a result source doesn't have any items, the widget doesn't render its `item` template. Use the source's `noResults` template to show a message such as "No products found", as shown in the main example. ## Combine the empty and typing states The final `panel` template combines two states: * For an empty query, show recent and popular searches in the left column and shortcut cards in the right column. * After users enter a query, show Query Suggestions and a category in the left column and product previews in the right column. To switch between the states: * Remove the `header` template from `showQuerySuggestions`. * Render the suggestions heading in the `panel` template. Use "Popular searches" for an empty query and "Suggestions" for a non-empty query. * Show the heading only when the Query Suggestions source has results. For the assembled `panel` template, see [`src/app.js`](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/autocomplete/src/app.js). ## See also * [Autocomplete](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/js) guide for the widget basics. * [Rich text box with mentions](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/examples/mentions/js) for an example of a headless, connector-based pattern. # Build a mentions text box Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/examples/mentions/js Build a headless social media mentions text box with the `connectAutocomplete` connector. This example builds on the [`autocomplete`](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/js) widget guide. Use this guide to build a headless mentions text box with the `connectAutocomplete` connector. This widget is **experimental** and is subject to change in minor versions. ## Build a custom UI with the connector The widget renders its own input and panel. If you need full control over the markup, a custom input element, a different panel structure, or an inline type-ahead like `@` mentions, use the `connectAutocomplete` connector instead. It turns a render function into a widget, leaving all the markup to you. Your render function receives the current query, the matching hits per index, and a `refine` function to run a new search: ```js JavaScript icon=code expandable theme={"system"} import instantsearch from "instantsearch.js"; import { connectAutocomplete } from "instantsearch.js/es/connectors"; // `connectAutocomplete` builds a custom widget from a render function. const customAutocomplete = connectAutocomplete( (renderOptions, isFirstRender) => { const { indices, refine, widgetParams } = renderOptions; const { input, list } = widgetParams; // Connect the input once, so typing doesn't lose focus on re-render if (isFirstRender) { input.addEventListener("input", (event) => { refine(event.currentTarget.value); }); return; } // Render your own markup from the hits on every result const hits = indices[0]?.hits ?? []; list.innerHTML = hits .map((hit) => `
  • ${hit._highlightResult.name.value}
  • `) .join(""); }, ); search.addWidgets([ customAutocomplete({ input: document.querySelector("#autocomplete-input"), list: document.querySelector("#autocomplete-list"), }), ]); ``` Each entry in `indices` has the shape `{ indexName, indexId, hits, results, sendEvent }`. The connector searches the Algolia indices in your widget tree, the root index and any nested [`index`](/doc/api-reference/widgets/index-widget/js) widgets, so you don't pass index names to the connector. > [!NOTE] > > Because you render the markup, you're responsible for keyboard navigation, active-item state, and ARIA attributes. > If you need the same accessible combobox behavior without building it yourself, use the [`autocomplete`](/doc/api-reference/widgets/autocomplete/js) widget instead of the connector. ## Build a rich text box with mentions Autocomplete can do more than redirect to a search page. In a text box, it can help people find and insert usernames as they type. For example, the social media mentions feature lets users mention another user with the `@` character so they can complete the message with the right username. The text box provides type-ahead suggestions. The panel doesn't block typing. Users can keep typing and ignore the suggestions or select one to complete the message. The compose box doesn't process a query from a search input. Instead, it parses the content of a text box and detects when you're trying to mention someone. To replicate this, you need full control over the markup, so you use the `connectAutocomplete` connector rather than the widget. This example searches the public `autocomplete_twitter_accounts` index, whose records include a `name`, a `handle`, and an `image`. ![A text box that replicates a social media mentions experience: typing "@" opens a panel of matching accounts to complete the mention](https://mintcdn.com/algolia/nyJ2KZzw6bfBNB-S/images/autocomplete/widget-mentions.png?fit=max\&auto=format\&n=nyJ2KZzw6bfBNB-S\&q=85\&s=9180ac2e6656de0c2d8ee66905dcabaa) > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/autocomplete-mentions) > > Run and edit the mentions example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/autocomplete-mentions) > > Browse the source code for the mentions example on GitHub. ### Install dependencies Install InstantSearch.js, the Algolia API client, and [`textarea-caret`](https://www.npmjs.com/package/textarea-caret) (used later to position the panel at the caret): ```sh npm theme={"system"} npm install instantsearch.js algoliasearch textarea-caret ``` ```sh yarn theme={"system"} yarn add instantsearch.js algoliasearch textarea-caret ``` ### Render the text box Render a `textarea` for the message and an empty container for the suggestions panel. > [!NOTE] > > This example uses a `textarea` element instead of an `input`, which is better for free-form plain text spanning multiple lines. ```html HTML icon=code-xml theme={"system"}
    ``` ### Detect a mention If you pass the full text box value to `refine`, Algolia searches the entire message. You only want to search when the caret sits inside a mention, and you only want to send the mention itself, not the whole message. To do that, tokenize the text and find the word under the caret, then check whether it's a valid username: ```js JavaScript icon=code expandable theme={"system"} // The word under the caret, with its [start, end] range in the text. function getActiveToken(input, cursor) { const re = /\S+/g; let match; while ((match = re.exec(input))) { const [start, end] = [match.index, match.index + match[0].length]; if (start <= cursor && cursor <= end) { return { word: match[0], range: [start, end] }; } } return null; } // A mention is "@" followed by 1–15 word characters. const isMention = (word) => /^@\w{1,15}$/.test(word); ``` ### Position the panel When users mention someone, the panel should follow the caret instead of sitting at the bottom of the text box. Use `getCaretCoordinates(textarea, position)` from [`textarea-caret`](https://www.npmjs.com/package/textarea-caret) to get the caret's `top`, `left`, and `height` at a given offset. Use it to place the panel just below the `@` of the active mention. ### Search for accounts and render the panel Attach `connectAutocomplete` to the text box. On every input, find the active token. If it's a mention, call `refine` with the text after `@` and show the matching accounts. Otherwise, hide the panel. The connector gives you `indices[0].hits` and a `refine` function. You manage the remaining behavior, including the active token, panel state, and selection handling. This excerpt shows the connector wiring. The setup (search client and instance, element references) and the `renderHits`, `positionPanel`, and `hidePanel` helpers are in [the complete `src/app.js`](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/autocomplete-mentions/src/app.js). ```js JavaScript icon=code expandable theme={"system"} // Only search when the caret sits inside a mention. function onInput() { activeToken = getActiveToken(textarea.value, textarea.selectionEnd); if (activeToken && isMention(activeToken.word)) { refine(activeToken.word.slice(1)); } else { hidePanel(); } } const customAutocomplete = connectAutocomplete( (renderOptions, isFirstRender) => { refine = renderOptions.refine; if (isFirstRender) { textarea.addEventListener("input", onInput); textarea.addEventListener("click", onInput); textarea.addEventListener("keyup", onInput); return; } const hits = renderOptions.indices[0]?.hits ?? []; if (!activeToken || !isMention(activeToken.word) || hits.length === 0) { hidePanel(); return; } // `positionPanel` places the panel at the caret; `renderHits` builds the // suggestion rows with DOM APIs positionPanel(); panel.hidden = false; renderHits(hits); }, ); search.addWidgets([customAutocomplete({})]); search.start(); ``` When users type `@` followed by a name, highlighted results appear. ### Show a loading state On slow connections, the panel can appear empty while results load. The InstantSearch instance exposes a `status` (`"idle"`, `"loading"`, `"stalled"`, or `"error"`), and it re-renders your widget when the search stalls. Show an indicator when the search is stalled and the caret is inside a mention: ```js JavaScript icon=code theme={"system"} // Inside the connectAutocomplete render function, before rendering the hits: if (activeToken && isMention(activeToken.word) && search.status === "stalled") { positionPanel(); panel.hidden = false; const loading = document.createElement("li"); loading.className = "account-loading"; loading.textContent = "Searching…"; panel.replaceChildren(loading); return; } ``` ### Select an account The goal of the mention feature is to help users find an account and autocomplete its handle. For example, when users type a few letters after `@`, the panel opens with matching accounts. Selecting an account replaces the typed text with the account's handle, such as "@jwestlakedc", and closes the panel. When the user picks an account, replace the active token with the correct handle and move the caret after it. Use `mousedown` (with `preventDefault`) rather than `click` so the `textarea` keeps focus: ```js JavaScript icon=code theme={"system"} const [start, end] = activeToken.range; const replacement = `@${button.dataset.handle} `; textarea.value = textarea.value.slice(0, start) + replacement + textarea.value.slice(end); const caret = start + replacement.length; textarea.setSelectionRange(caret, caret); textarea.focus(); hidePanel(); ``` ### Navigate in the text box Typing isn't the only action in a text box. Users can also edit their text or move the caret to a different position. When the caret lands on a mention, the panel should open. When it leaves a mention, the panel should close. Listen for `click` and `keyup` in addition to `input`, and run the same active-token check again: ```js JavaScript icon=code theme={"system"} textarea.addEventListener("input", onInput); textarea.addEventListener("click", onInput); textarea.addEventListener("keyup", onInput); ``` ### Add styles Style the text box, the suggestions panel, and the account rows with your own CSS. For a complete style sheet, see [`src/app.css`](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/autocomplete-mentions/src/app.css). Users can move the caret through the text, and the panel updates when the caret enters or leaves a mention. ### Next steps This pattern also applies to collaborative editing (such as Google Docs), email composition (such as Gmail), and chat apps (such as Slack). To extend this pattern: * Reuse the same logic to add hashtags by changing the `isMention` function to detect hashtags and search a hashtag index instead. * Add [synonyms](/doc/guides/managing-results/optimize-search-results/adding-synonyms) or [Algolia Rules](/doc/guides/managing-results/rules/rules-overview) so people are found by their nicknames. * Render mentions and hashtags as interactive tokens with [`contenteditable`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable). ## See also * [`autocomplete`](/doc/api-reference/widgets/autocomplete/js) widget reference for the full list of options. * [Autocomplete](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/js) guide for the widget basics. * [Federated two-column autocomplete](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/examples/federated/js) for an example of a widget-based, multi-source panel. # Autocomplete Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/js Learn how to build an autocomplete search experience with InstantSearch.js. **Autocomplete is a ubiquitous part of most search experiences.** Search providers like Google, ecommerce sites like Amazon, and messaging apps like Slack all offer autocomplete experiences on mobile and desktop. ![Screenshot of a search box with 'laptop' entered, showing autocomplete suggestions and product listings for Apple MacBooks.](https://mintcdn.com/algolia/KSYHF7soFPXylOAb/images/autocomplete/autocomplete.jpg?fit=max\&auto=format\&n=KSYHF7soFPXylOAb\&q=85\&s=06b7c03aff8a11c10c79cdacf8f7f8a2) Algolia provides a full-featured solution to build autocomplete experiences with InstantSearch. The [`autocomplete`](/doc/api-reference/widgets/autocomplete/js) widget lets you build an accessible, as-you-type autocomplete user interface that you can integrate anywhere on your site or app. > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/instantsearch/tree/master/examples/js/query-suggestions) > > Run and edit the Autocomplete example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/instantsearch/tree/master/examples/js/query-suggestions) > > Browse the source for the Autocomplete example on GitHub. A common pattern in search is to implement a search box with an autocomplete as a first step of the search experience. **Adding an autocomplete to a search page lets you enhance the search experience and create a richer, more contextual search.** You can use the [`autocomplete`](/doc/api-reference/widgets/autocomplete/js) widget in an existing or a new InstantSearch.js implementation to create this kind of experience. ## Install InstantSearch.js The `autocomplete` widget is part of InstantSearch.js. Install InstantSearch.js and the Algolia API client from the [npm](https://www.npmjs.com/) registry: ```sh npm theme={"system"} npm install algoliasearch instantsearch.js instantsearch.css ``` ```sh yarn theme={"system"} yarn add algoliasearch instantsearch.js instantsearch.css ``` Import a theme to style the widget out of the box: ```js JavaScript icon=code theme={"system"} import "instantsearch.css/themes/satellite.css"; ``` ## Add a container The widget renders into a container element. Add one to your markup: ```html HTML icon=code-xml theme={"system"}
    ``` Provide a container (for example, a `div`), not an `input`. The widget generates a fully accessible search box for you. ## Add the widget to InstantSearch Unlike the standalone [Autocomplete](/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete) library, the `autocomplete` widget runs inside an InstantSearch instance. Create the instance, add the widget with [`addWidgets`](/doc/api-reference/widgets/instantsearch/js#param-add-widgets), then [`start`](/doc/api-reference/widgets/instantsearch/js#param-start) the search: ```js JavaScript icon=code expandable theme={"system"} import { liteClient as algoliasearch } from "algoliasearch/lite"; import instantsearch from "instantsearch.js"; import { autocomplete } from "instantsearch.js/es/widgets"; import "instantsearch.css/themes/satellite.css"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); const search = instantsearch({ indexName: "instant_search", searchClient, }); search.addWidgets([ autocomplete({ container: "#autocomplete", placeholder: "Search for products", }), ]); search.start(); ``` The widget is added to InstantSearch but it doesn't show results until you configure the indices to query. ## Display results from an index Use the [`indices`](/doc/api-reference/widgets/autocomplete/js#param-indices) option to define which indices to query and how to display their results. Each index can declare its own [`templates`](/doc/api-reference/widgets/autocomplete/js#templates) and a [`getURL`](/doc/api-reference/widgets/autocomplete/js#param-indices) function to navigate when a user selects an item: ```js JavaScript icon=code expandable theme={"system"} autocomplete({ container: "#autocomplete", placeholder: "Search for products", indices: [ { indexName: "instant_search", getURL: (item) => `/products/${item.objectID}`, searchParameters: { hitsPerPage: 5, }, templates: { header: ({ items }, { html }) => html`
    Products (${items.length})
    `, item: ({ item, onSelect }, { html, components }) => html`
    ${components.ReverseHighlight({ hit: item, attribute: "name" })}
    `, }, }, ], }); ``` You can query several indices at once by adding more objects to the `indices` array. To learn more, see [Including multiple result types](/doc/ui-libraries/autocomplete/guides/including-multiple-result-types). ## Customize result templates Each index renders with its own `header`, `item`, and `noResults` templates, and the widget accepts a top-level `panel` template for the overall layout. The [Display results from an index](#display-results-from-an-index) section covers `header` and `item`. ### Highlight the matched query Item templates receive a `components` object with `Highlight`, `ReverseHighlight`, `Snippet`, and `ReverseSnippet` helpers that emphasize the part of an attribute matching the user's query: ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", indices: [ { indexName: "instant_search", templates: { item: ({ item }, { html, components }) => html`
    ${components.Highlight({ hit: item, attribute: "name" })}
    `, }, }, ], }); ``` Use `Snippet` to highlight within a longer, truncated attribute, and the `Reverse` variants to emphasize the text that doesn't match. This is a common style for Query Suggestions. ### Show an empty state The `noResults` template renders when an index returns nothing. If any index defines it, the panel stays open even when every index is empty, so you can show a helpful message: ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", indices: [ { indexName: "instant_search", templates: { noResults: (_, { html }) => html`
    No products found.
    `, }, }, ], }); ``` ### Customize the panel layout The top-level `panel` template controls the layout of the whole panel, including the order and grouping of recent searches, query suggestions, and each index. It receives an `elements` object whose keys are `recent`, `suggestions`, and each index's `indexName`, so you can arrange sections into rows or columns—for example, suggestions on the left and results on the right: ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", templates: { panel: ({ elements }, { html }) => html`
    ${elements.suggestions}
    ${elements["instant_search"]}
    `, }, // ... }); ``` The template only sets the markup. Style the columns with your own CSS: ```css CSS icon=code theme={"system"} .aa-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 1rem; } ``` ## Filter results To narrow what an index returns, set Algolia [search parameters](/doc/api-reference/search-api-parameters) on that index with `searchParameters`. You can apply [`filters`](/doc/api-reference/api-parameters/filters), [`facetFilters`](/doc/api-reference/api-parameters/facetFilters), [`numericFilters`](/doc/api-reference/api-parameters/numericFilters), and other parameters per index: ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", indices: [ { indexName: "instant_search", searchParameters: { hitsPerPage: 5, filters: "categories:Phones", facetFilters: [["brand:Apple", "brand:Samsung"]], }, }, ], }); ``` The widget applies these parameters to every query for that index. For an interactive filter UI inside the autocomplete—such as selectable tags or facet values that users add and remove—use the standalone [Autocomplete](/doc/ui-libraries/autocomplete/guides/filtering-results) library, which provides the tags plugin and facet sources. ## Reshape and deduplicate results Use `transformItems` to adjust results before they're displayed—for example, to remove duplicates across indices or limit the number of items each index shows. The function receives an array with one entry per index, each shaped as `{ indexName, indexId, hits, results }`, and must return the same shape: ```js JavaScript icon=code expandable theme={"system"} autocomplete({ container: "#autocomplete", indices: [ { indexName: "instant_search" }, { indexName: "instant_search_demo_query_suggestions" }, ], transformItems(indices) { const seen = new Set(); return indices.map((index) => ({ ...index, hits: index.hits .filter((hit) => { if (seen.has(hit.objectID)) { return false; } seen.add(hit.objectID); return true; }) .slice(0, 5), })); }, }); ``` Unlike the standalone library's [reshape](/doc/ui-libraries/autocomplete/guides/reshaping-sources) API, the widget doesn't group or sort sources for you—deduplication and per-index limits are manual, as shown above. ## Add Query Suggestions and recent searches The widget can display query suggestions from a [Query Suggestions index](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) and recent searches stored in the browser's local storage. Enable them with [`showQuerySuggestions`](/doc/api-reference/widgets/autocomplete/js#param-show-query-suggestions) and [`showRecent`](/doc/api-reference/widgets/autocomplete/js#param-show-recent): ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", placeholder: "Search for products", showRecent: true, showQuerySuggestions: { indexName: "instant_search_demo_query_suggestions", getURL: (item) => `/search?q=${item.query}`, }, indices: [ { indexName: "instant_search", // ... }, ], }); ``` To customize suggestions and recent searches, set a custom storage key, or refine your main index when a user selects a suggestion, see [Build a Query Suggestions UI](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/query-suggestions/js). ## Send click and conversion events To send [click and conversion events](/doc/guides/sending-events/getting-started) when users interact with the autocomplete, enable [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) on the InstantSearch instance: ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "instant_search", searchClient, insights: true, }); ``` The widget then sends view and click events automatically when users see and select results. Conversion events, like `add-to-cart`, usually happen after the user leaves the autocomplete, on the destination page. Send those where the conversion occurs with the [Insights client](/doc/guides/sending-events/getting-started). The autocomplete item template doesn't expose a `sendEvent` function. ## Support keyboard navigation The widget renders an accessible combobox, so users can operate it entirely from the keyboard—no extra configuration: * **Up and Down arrows** move through the items. The highlight wraps around at the ends of the list. * **Enter** opens the highlighted item, or submits the typed query when nothing is highlighted. * **Escape** closes the panel, then clears the highlight. * **Tab** closes the panel and moves focus to the next element. What happens on **Enter** depends on how you handle navigation: * If the highlighted item's index defines [`getURL`](#display-results-from-an-index), the browser opens that URL. * Otherwise, when you set `getSearchPageURL`, submitting the query sends the user to your search results page. * To use client-side routing instead of a full page load, provide an `onSelect` function and handle navigation in your app. ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", // On Enter with no highlighted item, open the search results page. getSearchPageURL: ({ query }) => `/search?q=${query}`, indices: [ { indexName: "instant_search", // On Enter or click with a highlighted item, open its URL. getURL: (item) => `/products/${item.objectID}`, }, ], }); ``` The widget also sets the matching ARIA roles (`combobox`, `aria-expanded`, `aria-activedescendant`), so screen readers announce the active item as users move through the list. ## Enable detached (mobile) mode On small screens, the widget can switch to a full-screen overlay. Use the [`detachedMediaQuery`](/doc/api-reference/widgets/autocomplete/js#param-detached-media-query) option to control when this happens. By default, detached mode activates at `(max-width: 680px)`. ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", detachedMediaQuery: "(max-width: 1024px)", // ... }); ``` To turn off detached mode, set `detachedMediaQuery` to an empty string. ## Explore advanced examples * [Federated two-column autocomplete](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/examples/federated/js): a layout that combines recent searches, Query Suggestions, product previews, a shortcuts panel, and category suggestions. * [Rich text box with mentions](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/examples/mentions/js): a headless "@" mentions text box, built with the [`connectAutocomplete`](/doc/api-reference/widgets/autocomplete/js) connector, that reproduces the X compose experience. ## See also * [`autocomplete`](/doc/api-reference/widgets/autocomplete/js) widget reference for the full list of options. * [Build a Query Suggestions UI](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/query-suggestions/js). # Build a Query Suggestions UI Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/query-suggestions/js Use the Autocomplete widget to show Query Suggestions and recent searches. Use [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) to help users find popular searches as they type. Query Suggestions creates 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.) from popular searches. You can use this index to show search suggestions. After you configure the Query Suggestions index, use the [`autocomplete`](/doc/api-reference/widgets/autocomplete/js) widget to show suggestions. When users select a suggestion, the widget navigates to the suggestion URL or refines the search query, depending on your configuration. > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/instantsearch/tree/master/examples/js/query-suggestions) > > Run and edit the Query Suggestions UI example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/instantsearch/tree/master/examples/js/query-suggestions) > > Browse the source code for the Query Suggestions UI example on GitHub. ## Add Query Suggestions and recent searches The widget displays suggestions from a [Query Suggestions index](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) and recent searches stored in the browser's local storage. Enable them with [`showQuerySuggestions`](/doc/api-reference/widgets/autocomplete/js#param-show-query-suggestions) and [`showRecent`](/doc/api-reference/widgets/autocomplete/js#param-show-recent): ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", placeholder: "Search for products", showRecent: true, showQuerySuggestions: { indexName: "instant_search_demo_query_suggestions", getURL: (item) => `/search?q=${item.query}`, }, indices: [ { indexName: "instant_search", // ... }, ], }); ``` ## Refine results with suggestions To keep users on the same page, set up a multi-index search experience that uses the selected suggestion to search the main index. The [`autocomplete`](/doc/api-reference/widgets/autocomplete/js) widget targets the index that contains the suggestions. The other widgets target the main index that contains your records. For more information, see [Multi-index search](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/js). ```js JavaScript icon=code expandable theme={"system"} // ... const searchClient = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY"); const search = instantsearch({ indexName: "instant_search", searchClient, }); search.addWidgets([ autocomplete({ container: '#autocomplete', showQuerySuggestions: { indexName: 'instant_search_demo_query_suggestions', }, }), hits({ container: "#hits", templates: { // ... }, }), ]); search.start(); ``` ## Customize recent searches Pass an object to `showRecent` to customize how each recent search renders: ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", showRecent: { templates: { item: ({ item, onSelect }, { html }) => html`
    ${item.query}
    `, }, }, // ... }); ``` ## Customize suggestions Pass an object to `showQuerySuggestions` to customize each suggestion and apply [search parameters](/doc/api-reference/search-api-parameters) to the suggestions query: ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", showQuerySuggestions: { indexName: "instant_search_demo_query_suggestions", searchParameters: { hitsPerPage: 5 }, templates: { item: ({ item, onSelect }, { html, components }) => html`
    ${components.ReverseHighlight({ hit: item, attribute: "query" })}
    `, }, }, // ... }); ``` Use `ReverseHighlight` to emphasize the text that doesn't match the query. This is a common style for Query Suggestions. The widget doesn't have built-in category grouping for suggestions and there's no `categoryAttribute`. To show a category next to a suggestion, render the category in the suggestion's `item` template. ## Filter suggestions on category pages After you [configure category page filtering](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js#filter-suggestions-on-category-pages), set the `filters` search parameter for Query Suggestions. Use the current category page value in the filter. For example, to show suggestions with matching hits in the `Accessories` category of the `products` source index: ```js JavaScript icon=code theme={"system"} autocomplete({ container: "#autocomplete", showQuerySuggestions: { indexName: "products_query_suggestions", searchParameters: { filters: "products.browsing_facet_values:Accessories", }, }, // ... }); ``` Update the filter whenever the category page changes. Replace `products` with the name of your configured source index. ## See also * [Autocomplete](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/js) guide for the widget basics. # Facet display Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/js Learn how to configure and order facets in InstantSearch.js. Learn how to configure, order, hide, and display facets and facet values using the dashboard, API, and your choice of InstantSearch or a custom solution. ## Manage facet display from the Algolia dashboard Control the facets to display and their order by sending a [`renderingContent`](/doc/api-reference/api-parameters/renderingContent) parameter with your search query. Your UI must interpret this setting so your users can interact with the index. The main implementation steps are: 1. [Configure the attributes to use for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting) on your search index. 2. Configure your facet display. 3. [Build a UI capable of interpreting the `renderingContent` setting](#build-the-ui-with-instantsearch). For this step, use InstantSearch library widgets. You can create a UI with other tools but must write the interpretation logic yourself. > [accordion] Configure facet display from the dashboard > > 1. Select the **Search** product icon on your dashboard and then select your index. > > 2. Click the **Configuration** tab. > > 3. Under the **Filtering and Faceting** category, click **Facet display**. > > 4. Click **Add facet to display** to choose the facets you want to display in your UI. > > 5. Use the `=` icon next to each facet to drag them into the correct position. The order you set here determines how facets display in your UI. > > 6. For each facet, click the pen icon to configure how the engine should display the facet's values. You can: > > * Pin some values at the top of the list if you want to display them first. > * Hide some values from the list entirely. > * Choose how to display the remaining facet values: by count, alphabetically, or only display pinned values. > > 7. To remove a facet from the list and stop displaying it in your UI, click the trash icon. > > 8. Save your changes. > > If you want to only display the facets "brand", "size", and "color" in your UI, declare them as [attributes for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting) and add them to the list of facets to display. > > With those three facets added, you want to display the brands alphabetically. > However: > > * Since you have a specific partnership with the brand "Lacoste", you want the brand to always display on top. > * Sizes need to be in a specific order: S, M, L, XL. You may have sizes in different formats in your data, but for the sake of clarity, you only want to display those four. You pin them at the top in the correct order and choose not to display other values. > * For colors, you want to display them by count and not pin any specific value. > > As soon as you save the changes, the UI adapts to this new configuration. > > > [!NOTE] > > > > This approach lets you configure a static facet display. > > If you want a more dynamic approach that customizes the display of the facets and values for a specific query or category, see the [Merchandising facets](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/merchandising-facets). > [accordion] Configure facet display with the API > > To replicate the dashboard example, > use the API client's [`setSettings`](/doc/rest-api/search/set-settings) method with the following settings applied to your index: > > ```cs C# expandable theme={"system"} > var response = await client.SetSettingsAsync( > "INDEX_NAME", > new IndexSettings > { > RenderingContent = new RenderingContent > { > FacetOrdering = new FacetOrdering > { > Facets = new Facets > { > Order = new List { "brand", "size", "color" }, > }, > Values = new Dictionary > { > { > "brand", > new Value > { > Order = new List { "Lacoste" }, > SortRemainingBy = Enum.Parse("Alpha"), > } > }, > { > "size", > new Value > { > Order = new List { "S", "M", "L", "XL" }, > SortRemainingBy = Enum.Parse("Hidden"), > } > }, > { > "color", > new Value > { > SortRemainingBy = Enum.Parse("Count"), > } > }, > }, > }, > }, > } > ); > ``` > > ```dart Dart expandable theme={"system"} > final response = await client.setSettings( > indexName: "INDEX_NAME", > indexSettings: IndexSettings( > renderingContent: RenderingContent( > facetOrdering: FacetOrdering( > facets: Facets( > order: ["brand", "size", "color"], > ), > values: { > 'brand': Value( > order: ["Lacoste"], > sortRemainingBy: SortRemainingBy.fromJson("alpha"), > ), > 'size': Value( > order: ["S", "M", "L", "XL"], > sortRemainingBy: SortRemainingBy.fromJson("hidden"), > ), > 'color': Value( > sortRemainingBy: SortRemainingBy.fromJson("count"), > ), > }, > ), > ), > ), > ); > ``` > > ```go Go expandable theme={"system"} > response, err := client.SetSettings(client.NewApiSetSettingsRequest( > "INDEX_NAME", > search.NewEmptyIndexSettings().SetRenderingContent( > search.NewEmptyRenderingContent().SetFacetOrdering( > search.NewEmptyFacetOrdering().SetFacets( > search.NewEmptyFacets().SetOrder([]string{"brand", "size", "color"}), > ).SetValues(map[string]search.Value{ > "brand": *search.NewEmptyValue().SetOrder( > []string{"Lacoste"}, > ).SetSortRemainingBy(search.SortRemainingBy("alpha")), > "size": *search.NewEmptyValue().SetOrder( > []string{"S", "M", "L", "XL"}, > ).SetSortRemainingBy(search.SortRemainingBy("hidden")), > "color": *search.NewEmptyValue().SetSortRemainingBy(search.SortRemainingBy("count")), > }), > ), > ), > )) > if err != nil { > // handle the eventual error > panic(err) > } > ``` > > ```java Java expandable theme={"system"} > UpdatedAtResponse response = client.setSettings( > "INDEX_NAME", > new IndexSettings().setRenderingContent( > new RenderingContent().setFacetOrdering( > new FacetOrdering() > .setFacets(new Facets().setOrder(Arrays.asList("brand", "size", "color"))) > .setValues( > new HashMap() { > { > put("brand", new Value().setOrder(Arrays.asList("Lacoste")).setSortRemainingBy(SortRemainingBy.ALPHA)); > put("size", new Value().setOrder(Arrays.asList("S", "M", "L", "XL")).setSortRemainingBy(SortRemainingBy.HIDDEN)); > put("color", new Value().setSortRemainingBy(SortRemainingBy.COUNT)); > } > } > ) > ) > ) > ); > ``` > > ```js JavaScript expandable theme={"system"} > const response = await client.setSettings({ > indexName: "INDEX_NAME", > indexSettings: { > renderingContent: { > facetOrdering: { > facets: { > order: ["brand", "size", "color"], > }, > values: { > brand: { > order: ["Lacoste"], > sortRemainingBy: "alpha", > }, > size: { > order: ["S", "M", "L", "XL"], > sortRemainingBy: "hidden", > }, > color: { > sortRemainingBy: "count", > }, > }, > }, > }, > }, > }); > ``` > > ```kotlin Kotlin expandable theme={"system"} > val response = client.setSettings( > indexName = "INDEX_NAME", > indexSettings = IndexSettings( > renderingContent = RenderingContent( > facetOrdering = FacetOrdering( > facets = Facets(order = listOf("brand", "size", "color")), > values = mapOf( > "brand" to Value( > order = listOf("Lacoste"), > sortRemainingBy = SortRemainingBy.entries.first { it.value == "alpha" }, > ), > "size" to Value( > order = listOf("S", "M", "L", "XL"), > sortRemainingBy = SortRemainingBy.entries.first { it.value == "hidden" }, > ), > "color" to Value( > sortRemainingBy = SortRemainingBy.entries.first { it.value == "count" }, > ), > ), > ), > ), > ), > ) > ``` > > ```php PHP expandable theme={"system"} > $response = $client->setSettings( > 'INDEX_NAME', > ['renderingContent' => ['facetOrdering' => ['facets' => ['order' => [ > 'brand', > 'size', > 'color', > ]], > 'values' => ['brand' => ['order' => [ > 'Lacoste', > ], > 'sortRemainingBy' => 'alpha', > ], > 'size' => ['order' => [ > 'S', > 'M', > 'L', > 'XL', > ], > 'sortRemainingBy' => 'hidden', > ], > 'color' => [ > 'sortRemainingBy' => 'count', > ], > ], > ]]], > ); > ``` > > ```python Python expandable theme={"system"} > response = client.set_settings( > index_name="INDEX_NAME", > index_settings={ > "renderingContent": { > "facetOrdering": { > "facets": { > "order": ["brand", "size", "color"], > }, > "values": { > "brand": { > "order": ["Lacoste"], > "sortRemainingBy": "alpha", > }, > "size": { > "order": ["S", "M", "L", "XL"], > "sortRemainingBy": "hidden", > }, > "color": { > "sortRemainingBy": "count", > }, > }, > }, > }, > }, > ) > ``` > > ```ruby Ruby expandable theme={"system"} > response = client.set_settings( > "INDEX_NAME", > Algolia::Search::IndexSettings.new( > rendering_content: Algolia::Search::RenderingContent.new( > facet_ordering: Algolia::Search::FacetOrdering.new( > facets: Algolia::Search::Facets.new(order: ["brand", "size", "color"]), > values: { > brand: Algolia::Search::Value.new(order: ["Lacoste"], sort_remaining_by: "alpha"), > size: Algolia::Search::Value.new(order: ["S", "M", "L", "XL"], sort_remaining_by: "hidden"), > color: Algolia::Search::Value.new(sort_remaining_by: "count") > } > ) > ) > ) > ) > ``` > > ```scala Scala expandable theme={"system"} > val response = Await.result( > client.setSettings( > indexName = "INDEX_NAME", > indexSettings = IndexSettings( > renderingContent = Some( > RenderingContent( > facetOrdering = Some( > FacetOrdering( > facets = Some( > Facets( > order = Some(Seq("brand", "size", "color")) > ) > ), > values = Some( > Map( > "brand" -> Value( > order = Some(Seq("Lacoste")), > sortRemainingBy = Some(SortRemainingBy.withName("alpha")) > ), > "size" -> Value( > order = Some(Seq("S", "M", "L", "XL")), > sortRemainingBy = Some(SortRemainingBy.withName("hidden")) > ), > "color" -> Value( > sortRemainingBy = Some(SortRemainingBy.withName("count")) > ) > ) > ) > ) > ) > ) > ) > ) > ), > Duration(100, "sec") > ) > ``` > > ```swift Swift expandable theme={"system"} > let response = try await client.setSettings( > indexName: "INDEX_NAME", > indexSettings: IndexSettings(renderingContent: SearchRenderingContent(facetOrdering: SearchFacetOrdering( > facets: SearchFacets(order: ["brand", "size", "color"]), > values: [ > "brand": SearchValue(order: ["Lacoste"], sortRemainingBy: SearchSortRemainingBy.alpha), > "size": SearchValue(order: ["S", "M", "L", "XL"], sortRemainingBy: SearchSortRemainingBy.hidden), > "color": SearchValue(sortRemainingBy: SearchSortRemainingBy.count), > ] > ))) > ) > ``` ### Hide facet values The following example shows three facets: `author`, `brand`, and `on_sale` (in that order). The `author` facet is sorted by number of hits, the `brand` facet is ordered alphabetically, always shows the facet value "Kensington" in the first position (pinned) and never shows the value "Bosch" (hidden). The `on_sale` facet only shows the pinned true value. ```js JavaScript icon="code" expandable theme={"system"} const response = await client.setSettings({ indexName: "INDEX_NAME", indexSettings: { renderingContent: { facetOrdering: { facets: { order: ["author", "brand", "on_sale"], }, values: { author: { sortRemainingBy: "count", }, brand: { order: ["Kensington"], hide: ["Bosch"], sortRemainingBy: "alpha", }, on_sale: { order: ["true"], sortRemainingBy: "hidden", }, }, }, }, }, }); ``` ## Build the UI with InstantSearch The simplest way to control facet display is to use InstantSearch. This UI library has built-in widgets that understand the [`renderingContent`](/doc/api-reference/api-parameters/renderingContent) data returned by searches. This data includes everything you need to show facets how you want. ### Facet display requirements for InstantSearch To make your UI compatible with the facet display feature: * InstantSearch.js version 4.72.0 or later (see [Upgrade guide](/doc/guides/building-search-ui/upgrade-guides/js)). * Add refinement widgets for the different facets. * Add a [`dynamicWidgets`](/doc/api-reference/widgets/dynamic-widgets/js) container around the widgets you want to sort. * In those widgets, remove any custom `sortBy` or set `facetOrdering` to `true`. ```js JavaScript icon=code theme={"system"} // This example assumes you're including InstantSearch.js in your web // page via a CDN. If you're using it with a package manager, you // should adjust the way you import InstantSearch.js and its widgets. const { dynamicWidgets, menu, hierarchicalMenu } = instantsearch.widgets; search.addWidgets([ dynamicWidgets({ container: "#dynamic-widgets", fallbackWidget: ({ container, attribute }) => menu({ container, attribute, limit: 10 }), widgets: [ (container) => hierarchicalMenu({ limit: 10, attributes: [ "hierarchicalCategories.lvl0", "hierarchicalCategories.lvl1", ], }), ], }), ]); ``` ### Optimize dynamic widget network requests Algolia automatically mounts the appropriate widgets for the search results. * In versions 4.32 to 4.35, dynamic widgets request all facets and generate an extra network request for this. To avoid this, set `facets` to `[]` (empty). * In version 4.36 and later, to avoid an extra request, `facets` is set to `[*]` by default. If you prefer to do two network requests with only the relevant facets returned, set `facets` to `[]`. ```js JavaScript icon=code theme={"system"} // This example assumes you're including InstantSearch.js in your web // page via a CDN. If you're using it with a package manager, you // should adjust the way you import InstantSearch.js and its widgets. const { dynamicWidgets, menu, hierarchicalMenu } = instantsearch.widgets; search.addWidgets([ dynamicWidgets({ container: "#dynamic-widgets", fallbackWidget: ({ container, attribute }) => menu({ container, attribute, limit: 10 }), widgets: [ (container) => hierarchicalMenu({ limit: 10, attributes: [ "hierarchicalCategories.lvl0", "hierarchicalCategories.lvl1", ], }), ], // do two network requests facets: [], }), ]); ``` ### Widget limitation > [!NOTE] > > This applies to InstantSearch.js version 4.36 and later. Dynamic widgets can show up to 1,000 facets. If you do have more than that, here's how to display the ones with the most results. 1. Add an `applicable_facets` facet to every object: ```json JSON icon=braces theme={"system"} { "applicable_facets": ["special_type", "color"], "special_type": "stretch", "color": "blue" } ``` 2. Configure the dynamic widget so it doesn't ask for all facets. Many of them will be hidden anyway because of the 1,000 facet limit. To do this, set `facets` to `[]` (empty). ```js JavaScript icon=code theme={"system"} dynamicWidgets({ facets: [], }); ``` 3. Ensure that `applicable_facets` is always requested. Such as from a hidden menu: ```js JavaScript icon=code theme={"system"} connectMenu(() => {})({ attribute: "applicable_facets" }); ``` 4. Use `transformItems` of `dynamicWidgets` to use `applicable_facets` for dynamic widget rendering: ```js JavaScript icon=code theme={"system"} dynamicWidgets({ facets: [], transformItems(_items, { results }) { return Object.keys(results._rawResults[0].facets.applicable_facets); }, }); ``` ## Build the UI without InstantSearch If you aren't using InstantSearch to build your UI, you need to build frontend logic to: * Interpret the [`renderingContent`](/doc/api-reference/api-parameters/renderingContent) parameter returned along search results, * Order facets and values based on your interpretation of the [`renderingContent`](/doc/api-reference/api-parameters/renderingContent) parameter values. A custom implementation of frontend search requires: * Reading the facet order * Reading the facet value order ### Read the facet order If you hardcode the list of facets to display on a page, you can read that from `result.renderingContent.facetOrdering.facets.order` (all keys are optional). This is the list of attributes that you chose to display and can loop over to display individual facet lists. ### Read the facet value order You need to list the attribute you want to display facets of in `searchParameters.facets`. Read the possible results from `result.facets[facetName]`. After fetching those values, you can read the `result.renderingContent.facetOrdering.values[facetName]` object to sort them. This object has the following keys: * `order`: an array of facet values to pin at the start of the list * `hide`: an array of facet values to hide from the list * `sortRemainingBy`: a string that describes how to sort the remaining values. Either "alpha" (alphabetically, ascending), "count" (value retrieved from `facets[facetName][facetValue]`, descending), or "hidden", which only displays the ordered items. # Facet drop-down menus Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/facet-dropdown/js Learn how to display your facets as dropdowns with InstantSearch.js. All Search and Discovery user interfaces display, in addition to a search box, a series of filters that allow users to narrow down their search. These onscreen filters are called [facets](/doc/guides/managing-results/refine-results/faceting). A typical facet is an attribute like "brand" or "price", and facet values are the individual brands and prices. By clicking on a facet value, users can include and exclude whole categories of products. For example, by selecting "Blue" in the "Color" facet filter, a user can exclude every product except blue ones. For a long time, ecommerce websites have been displaying facets on the left side of the screen, providing direct access. But this placement reduces the space available to display products. With the rise of mobile-first design and touchscreen, online businesses have started to display facets at the top of their product listing. That's the case of [Lacoste](https://www.lacoste.com/gb/polo-shirts/all-polo-shirts/#query=Polo) which [increased its sales by +150% sales with Algolia's search](https://resources.algolia.com/customer-stories/casestudy-lacoste-en). ![Lacoste search interface with filters in drop-down menus](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/facet-dropdown/lacoste-dropdown.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=5e1459ce1a701d315084b77e4806186f) Dropdown faceting offers two benefits: * It increases facet visibility and accessibility, encouraging more usage. * It simplifies the screen, leaving more room for products and creating more on-screen breathing space, an important UX design choice. This guide shows how to turn a facet filter, commonly represented by a [`refinementList`](/doc/api-reference/widgets/refinement-list/js) or a [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js), widget, into a [custom widget](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js) with a drop-down menu layout. > [!NOTE] > > The provided code uses InstantSearch.js, which can be taken as-is or as a reference if you want to replicate the same pattern using [Vue](/doc/guides/building-search-ui/what-is-instantsearch/vue) InstantSearch. > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/facet-dropdown) > > Run and edit the Facet drop-down menus example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/facet-dropdown) > > Browse the source for the Facet drop-down menus example on GitHub. ## Wrapper elements By design, each drop-down menu refinement widget wrapper has two elements: * A button with a label and facet count * A drop-down menu box Both can be customized using [settings](#settings). Each of the drop-down menu come wrapped in its own `div`, which allows multiple facets in the same container, thus creating a horizontal list. ![Screenshot of a search interface with 'Brand' and 'Type' facet menus, showing options like 'Insignia™', 'Samsung', and 'HP' with counts.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/facet-dropdown/is/facet-dropdown-explained.jpg?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=fe0600ec2ed86d9399d037be710565c8) To ease the creation of a drop-down menu widget, this guide provides a factory wrapper function called `createDropdown` that takes two parameters: * The refinement widget you want to turn into a drop-down menu * An object for optional settings This function, located in the [`src/Dropdown.js`](https://codesandbox.io/p/sandbox/github/algolia/doc-code-samples/tree/master/instantsearch.js/facet-dropdown?file=/src/Dropdown.js) file, returns an [InstantSearch widget](/doc/guides/building-search-ui/what-is-instantsearch/js#predefined-widgets) that can be placed alongside other widgets on your search screen. > [!NOTE] > > All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. > If you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information. ```js JavaScript icon=code theme={"system"} const myFacetDropdown = createDropdown(instantsearch.widgets.refinementList, { /* optional settings */ }); ``` ### Quickstart First, you create your own drop-down menu widget. Later, you customize it. To create the drop-down menu, use a [`refinementList`](/doc/api-reference/widgets/refinement-list/js) widget on your `type` facet. **1. Prepare the HTML** * Import all JavaScript files, such as Algolia search, InstantSearch.js, and your own JS files. * Declare a placeholder `div` with the ID `type` for the drop-down menu widget for the `type` facet. In [`index.html`](https://codesandbox.io/p/sandbox/github/algolia/doc-code-samples/tree/master/instantsearch.js/facet-dropdown?file=/index.html): ```html HTML icon=code-xml theme={"system"}
    ``` **2. Create the widget** Import the `createDropdown` function from the [`src/dropdown.js`](https://codesandbox.io/p/sandbox/github/algolia/doc-code-samples/tree/master/instantsearch.js/facet-dropdown?file=/src/Dropdown.js) file before initializing an InstantSearch instance with your Algolia credentials and create the drop-down menu widget. In this case, use a [`refinementList`](/doc/api-reference/widgets/refinement-list/js) widget for the type facet. As a result, you create a customized `refinementListDropdown` widget for the `type` facet that can also be re-used for other facets. In [`src/app.js`](https://codesandbox.io/p/sandbox/github/algolia/doc-code-samples/tree/master/instantsearch.js/facet-dropdown?file=/src/app.js): ```js JavaScript icon=code theme={"system"} import { createDropdown } from "./Dropdown"; // Create the refinementListDropdown widget const refinementListDropdown = createDropdown( instantsearch.widgets.refinementList, { closeOnChange: true }, ); ``` > [!NOTE] > > Here, `instantsearch.widgets.refinementList` refers to InstantSearch's [`refinementList`](/doc/api-reference/widgets/refinement-list/js) widget. > See the [widget reference guide](/doc/api-reference/widgets/refinement-list/js). **3. Add your custom widget to the InstantSearch instance** Now that you've created your `refinementListDropdown` widget, you're ready to add it to your InstantSearch instance. ```js JavaScript icon=code theme={"system"} // Initialize InstantSearch and your widgets search.addWidgets([ instantsearch.widgets.searchBox({ container: '#searchbox', }), instantsearch.widgets.hits({...}), // Adding the refinementListDropdown widget on the `type` facet refinementListDropdown({ container: '#type', // The CSS Selector of the DOM element inside which the widget is inserted. attribute: 'type', // The name of the attribute in the records. searchable: true // Whether to add a search input to let users search for more facet values. }), ... ]); search.start(); ``` By design, the `createDropdown` function creates a widget that acts as a wrapper around the initial widget, which is passed as a parameter. That's why the created `refinementListDropdown` widget can take any of the settings available in the `refinementList` widget. For example, `{ limit: 5, showMore: true }` limits the default number of displayed facet values to "5". ![Screenshot of a 'Type' facet drop-down menu with items like 'Trend cases' and 'Ink cartridges' and count labels.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/facet-dropdown/is/facet-dropdown-type-dropdown.jpg?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=73c4fa42c1029f8d631057673cc8fe93) ## Settings The `createDropdown` function takes two parameters: * Required: a refinement widget to turn into a drop-down menu * Optional: an object with settings ```js JavaScript icon=code theme={"system"} createDropdown( baseWidget, ({ cssClasses, buttonText, buttonClassName, closeOnChange } = {}), ); ``` ### `cssClasses` **Type:** `object` An object that lets you [override the CSS class](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets) in your code. Here's an example with default values: ```js JavaScript icon=code theme={"system"} cssClasses = { root: "ais-Dropdown", button: "ais-Dropdown-button", buttonRefined: "ais-Dropdown-button--refined", closeButton: "ais-Dropdown-close", }; ``` ### `buttonText` **Type:** `string | function` This is the text displayed in the DropDown button. It can be a string or a function. By default, it shows the price of the active price refinement. Here's an example from the `priceMenuDropdown` widget. ```js JavaScript icon=code theme={"system"} buttonText({ items }) { const refinedItem = (items || []).find( (item) => item.label !== "All" && item.isRefined ); return refinedItem ? `Price (${refinedItem.label})` : "Price menu"; }, ``` ### `buttonClassName` **Type:** `string` Same as `buttonText`, but for the button CSS class name. Here's an example from the `priceMenuDropdown` widget. ```js JavaScript icon=code theme={"system"} buttonClassName({ items }) { const isRefined = (items || []).find( (item) => item.label !== "All" && item.isRefined ); return isRefined && "ais-Dropdown-button--refined"; }, ``` ### `closeOnChange` **Type:** `boolean | function` This argument can be a boolean or a function that returns `true` if you want the drop-down menu to close as soon as a user selects a value. If it's `false`, it doesn't close automatically, thus enabling users to select more than one facet value. Here's an example in which the code returns `true` if users are using a mobile device. ```js JavaScript icon=code theme={"system"} const MOBILE_WIDTH = 375; /* ... */ closeOnChange: () => window.innerWidth >= MOBILE_WIDTH; ``` ## Customize the UI The generated markup lets you customize the look and feel to your needs, to change the default aspect of the drop-down menu with a few lines of CSS. Here's the default version: ![Screenshot of a search interface with a 'Type' facet drop-down menu showing options like 'Trend cases,' 'Audio,' and 'Cables/connectors' with item counts.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/facet-dropdown/is/facet-dropdown-tweak-ui.jpg?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=3575335fe9492ddeb881df5c3784ab50) You can customize two aspects: * **Inline facet values** The `brandDropdown` widget uses an inline list. See the code in `app.css`. ![Screenshot of a search interface with a 'Brand (1)' facet drop-down menu showing selected 'Dynex™' and other facet values.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/facet-dropdown/is/facet-dropdown-inclined.jpg?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=2a2f1d5023aaeaa8f81d6497e467b72c) ```css CSS icon=paintbrush theme={"system"} .my-BrandDropdown .ais-RefinementList-list { width: 20rem; display: flex; flex-wrap: wrap; } ``` * **Fixed height drop-down menu** The hierarchical `categoriesDropdown` widget uses a fixed height list. See the code in `app.css`. ![Screenshot of a drop-down menu showing category facets like 'Appliances' and 'Audio' with item counts.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/facet-dropdown/is/facet-dropdown-fixed-height.jpg?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=1a6fc1873bc483ed9c51c20000f021ca) ```css CSS icon=paintbrush theme={"system"} #category .ais-HierarchicalMenu { height: 195px; overflow: auto; } ``` ## Mobile support On mobile devices, more than anywhere else, displaying facet refinements over the results is a must-have, considering the limited real estate. Drop-down widgets are a great fit, but you need to tweak the display so they can take advantage of the screen's width and height. ![Screenshot of a mobile search interface showing drop-down menu facets for 'Brand' with options like 'Insignia', 'Samsung', and 'brand names' displayed as tags.](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/facet-dropdown/is/facet-dropdown-mobile.jpg?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=255dd55f7c688c8a76ecbb5929a20906) The mobile version uses the following CSS media query: ```css CSS icon=paintbrush theme={"system"} @media only screen and (max-width: 375px) { /* ... */ } ``` It uses the following JavaScript settings at each widget level: ```js JavaScript icon=code theme={"system"} const MOBILE_WIDTH = 375; const refinementListDropdown = createDropdown( instantsearch.widgets.refinementList, { closeOnChange: () => window.innerWidth >= MOBILE_WIDTH }, ); ``` This demo gets you started by providing a basic look and feel for mobile devices. Refactor the provided code to match your current design and offer the best experience to your mobile users. # Geo search Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/geo-search/js Learn how to use geographical search in your InstantSearch.js app. You can use Algolia's [geographical search capabilities](/doc/guides/managing-results/refine-results/geolocation#geo-search) of Algolia with the [`geoSearch`](/doc/api-reference/widgets/geo-search/js) widget. The widget is implemented on top of [Google Maps](https://developers.google.com/maps/documentation/javascript/overview) but the core logic isn't tied to any map provider. You can build your own map widget with the [`connectGeoSearch`](/doc/api-reference/widgets/geo-search/js#customize-the-ui-with-connectgeosearch) connector and a different provider (such as [Leaflet](https://leafletjs.com)). Before diving into the usage keep in mind that you are responsible for loading the Google Maps library. You can find more information about that [in the Google Maps documentation](https://developers.google.com/maps/documentation/javascript/tutorial). ## Dataset This guide use a dataset of over 3,000 records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) of the 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", "links_count": 911, "_geoloc": { "lat": 40.639751, "lng": -73.778925 } } ``` Latitude and longitude are stored in the record to allow hits to be displayed on the map. You should store them in the `_geoloc` attribute to enable geo-filtering and geo-sorting. You can download the dataset on [GitHub](https://github.com/algolia/datasets/tree/master/airports). Have a look at how to import it in [Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-from-the-dashboard). ### Configure index settings When displaying on a map, you still want the relevance to be good. For that, 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.) as follows: * [Searchable attributes](/doc/guides/managing-results/must-do/searchable-attributes) should be set to enable search in the four textual attributes: `name`, `city`, `country`, and `iata_code`. * [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking): use the **number of other connected airports** `links_count` as a ranking metric. The more connections the better. ```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)"] ) ) ``` ## Hits on the map This is the most simple use case for the [`geoSearch`](/doc/api-reference/widgets/geo-search/js) widget. It displays your results on a Google Maps. In the example, the `height` of the parent container is explicitly set. This is a requirement of Google Maps: **if you don't set it, the map won't be displayed.** The widget will set the zoom and position of the map according to the hits retrieved by the search. In case the search doesn't return any results the map will fall back to its [`initialZoom`](/doc/api-reference/widgets/geo-search/js#param-initial-zoom) and [`initialPosition`](/doc/api-reference/widgets/geo-search/js#param-initial-position) . By default, once you move the map the widget will use [the bounding box](/doc/api-reference/api-parameters/insideBoundingBox) of the map to filter the results. You can find all the available options of the widget [in the documentation](/doc/api-reference/widgets/geo-search/js). Since the widget is built on top of Google Maps you might want to apply some options to your maps. For example you might want to switch on a satellite image rather than a normal street map. You can provide those extra options to the [`mapOptions`](/doc/api-reference/widgets/geo-search/js#param-map-options) attribute of the [`geoSearch`](/doc/api-reference/widgets/geo-search/js) widget and they will be forwarded to the Google Maps instance. > [!NOTE] > > All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. > If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "airports", searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: "#searchbox", }), instantsearch.widgets.geoSearch({ container: "#maps", googleReference: window.google, mapOptions: { mapTypeId: window.google.maps.MapTypeId.SATELLITE, }, }), ]); search.start(); ``` You can find a live example on [CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-hits). The source code is available on [GitHub](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-hits). ## Custom marker The standard marker API of Google Maps let you update the image of the marker. You can also use custom styling to add information to the marker with the [`customHTMLMarker`](/doc/api-reference/widgets/geo-search/js#param-custom-html-marker) option. The widget accept a regular template like any other InstantSearch.js widgets. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "airports", searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: "#searchbox", }), instantsearch.widgets.geoSearch({ container: "#maps", googleReference: window.google, templates: { HTMLMarker: (hit, { html }) => html` ${hit.city} - ${hit.airport_id} `, }, }), ]); search.start(); ``` You can find a live example on [CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-custom-marker). The source code is available on [GitHub](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-custom-marker). ## Control A common pattern that comes with a geographical search experience is the ability to control how the refinement behaves from the map. By default the widget will use [the map bounding box](/doc/api-reference/api-parameters/insideBoundingBox) to filter the results as soon the map has moved. But sometimes it's better for the experience to let users decide when to apply the refinement. This way, they can explore the map without having the results changing every time. For this pattern, the [`enableRefineControl`](/doc/api-reference/widgets/geo-search/js#param-enable-refine-control) option lets users control how the refinement behaves directly from the widget. You can also control the default value applied to the checkbox of the control component with [`enableRefineOnMapMove`](/doc/api-reference/widgets/geo-search/js#param-enable-refine-on-map-move). ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "airports", searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: "#searchbox", }), instantsearch.widgets.geoSearch({ container: "#maps", googleReference: window.google, enableRefineControl: true, enableRefineOnMapMove: false, }), ]); search.start(); ``` You can find a live example on [CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-control). The source code is available on [GitHub](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/geo-search-control). # Highlighting in InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/js How to use highlighting and snippeting in InstantSearch.js to demonstrate to searchers why a result matches a query. ## Highlighting Highlighting is a vital tool for showing searchers why a result matched their query by providing different styling to all matched query words. By default, Highlighting is enabled on all [`searchableAttributes`](/doc/api-reference/api-parameters/searchableAttributes) unless specified otherwise in [`attributesToHighlight`](/doc/api-reference/api-parameters/attributesToHighlight). > [!NOTE] > > Use one of the API clients or the Algolia dashboard, not InstantSearch, to configure > > `attributesToHighlight`. Below is an example of how to configure which attributes to highlight: ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { AttributesToHighlight = new List { "author", "title", "content" }, } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( attributesToHighlight: [ "author", "title", "content", ], ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetAttributesToHighlight( []string{"author", "title", "content"}))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings( "INDEX_NAME", new IndexSettings().setAttributesToHighlight(Arrays.asList("author", "title", "content")) ); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { attributesToHighlight: ['author', 'title', 'content'] }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings(attributesToHighlight = listOf("author", "title", "content")), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['attributesToHighlight' => [ 'author', 'title', 'content', ], ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "attributesToHighlight": [ "author", "title", "content", ], }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings( "INDEX_NAME", Algolia::Search::IndexSettings.new(attributes_to_highlight: ["author", "title", "content"]) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( attributesToHighlight = Some(Seq("author", "title", "content")) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings(attributesToHighlight: ["author", "title", "content"]) ) ``` **If `exactOnSingleWordQuery` is set to `word`, only exact matches will be highlighted: partial matches and prefixes won't be.** ### Response information The response includes a [`_highlightResult`](/doc/rest-api/search/search-single-index#response-hits) for each attribute, containing: * The attribute's value with highlighting * The match level (how many query words matched) * A boolean indicating if the whole attribute is highlighted * The matched words for each attribute. For example: ```json JSON icon=braces theme={"system"} { "value": "Actiontec - 4-Port Ethernet Broadband Router with Wireless-N - Black", "matchLevel": "full", "fullyHighlighted": false, "matchedWords": ["action"] } ``` #### Performance Algolia highlights the first 50,000 characters of the whole result set (5,000 logograms for CJK languages). This limit prevents impacting the user experience by ensuring a fast response time even with large results. #### Sanitize the results Since Algolia returns highlighted results as they're stored in the engine, make sure you sanitize your HTML. **Unsanitized or invalid HTML content, particularly with user-provided content, can be a security risk** and opens up the possibility of [cross-site-scripting attacks](https://en.wikipedia.org/wiki/Cross-site_scripting) on your site. ### Pre- and post-tags The primary configurations that you can set for highlighting are pre-tags and post-tags. This configuration lets you use any tag (HTML or otherwise) to highlight results in the UI. You can set the pre-tags and post-tags (that is, the strings before and after the matched query words) to any string value. They're set to `em` and `em` by default. You can configure this setting using the [`highlightPreTag`](/doc/api-reference/api-parameters/highlightPreTag) and [`highlightPostTag`](/doc/api-reference/api-parameters/highlightPostTag) parameters at either query or indexing time. ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { AttributesToHighlight = new List { "author", "title", "content" }, HighlightPreTag = "", HighlightPostTag = "", } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( attributesToHighlight: [ "author", "title", "content", ], highlightPreTag: "", highlightPostTag: "", ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetAttributesToHighlight( []string{"author", "title", "content"}).SetHighlightPreTag("").SetHighlightPostTag(""))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings( "INDEX_NAME", new IndexSettings() .setAttributesToHighlight(Arrays.asList("author", "title", "content")) .setHighlightPreTag("") .setHighlightPostTag("") ); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { attributesToHighlight: ['author', 'title', 'content'], highlightPreTag: '', highlightPostTag: '', }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( attributesToHighlight = listOf("author", "title", "content"), highlightPreTag = "", highlightPostTag = "", ), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['attributesToHighlight' => [ 'author', 'title', 'content', ], 'highlightPreTag' => '', 'highlightPostTag' => '', ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "attributesToHighlight": [ "author", "title", "content", ], "highlightPreTag": '', "highlightPostTag": "", }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings( "INDEX_NAME", Algolia::Search::IndexSettings.new( attributes_to_highlight: ["author", "title", "content"], highlight_pre_tag: "", highlight_post_tag: "" ) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( attributesToHighlight = Some(Seq("author", "title", "content")), highlightPreTag = Some(""), highlightPostTag = Some("") ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( attributesToHighlight: ["author", "title", "content"], highlightPreTag: "", highlightPostTag: "" ) ) ``` **Pre-tag and post-tag settings are typically used together.** ### Highlighting using InstantSearch Algolia provides a `Highlight` widget in the InstantSearch libraries to highlight matches on the frontend. Refer to the [widget docs](/doc/api-reference/widgets/highlight/js) for usage notes and code examples. ## Snippeting Snippeting returns parts of the matched attribute, namely, the matched words and some words around them. Unlike highlighting, you must enable snippeting for each attribute you wish to snippet, although you can set the value `*` to snippet all attributes. The snippeted result wraps the matched words in the pre-highlighting and post-highlighting tags. > [!NOTE] > > Use one of the API clients or the Algolia dashboard, not InstantSearch, to configure > > `attributesToSnippet`. An example configuration of the [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet): ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { AttributesToSnippet = new List { "content:80", "description" }, } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( attributesToSnippet: [ "content:80", "description", ], ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetAttributesToSnippet( []string{"content:80", "description"}))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings( "INDEX_NAME", new IndexSettings().setAttributesToSnippet(Arrays.asList("content:80", "description")) ); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { attributesToSnippet: ['content:80', 'description'] }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings(attributesToSnippet = listOf("content:80", "description")), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['attributesToSnippet' => [ 'content:80', 'description', ], ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "attributesToSnippet": [ "content:80", "description", ], }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings( "INDEX_NAME", Algolia::Search::IndexSettings.new(attributes_to_snippet: ["content:80", "description"]) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( attributesToSnippet = Some(Seq("content:80", "description")) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings(attributesToSnippet: ["content:80", "description"]) ) ``` For each attribute, you can configure how snippet results are displayed by defining: * The number of words in the resulting snippet (`nbWords`) * The text used to replace removed words in the snippet (`snippetEllipsisText`). ### Number of words You can set the number of words to return when defining your [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet) with the syntax `attribute:nbWords`. For example, `body:20` returns twenty-word snippets for the attribute `body`. When undefined, the value defaults to 10. ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { AttributesToSnippet = new List { "body:20", "title" }, } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( attributesToSnippet: [ "body:20", "title", ], ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetAttributesToSnippet( []string{"body:20", "title"}))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings( "INDEX_NAME", new IndexSettings().setAttributesToSnippet(Arrays.asList("body:20", "title")) ); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { attributesToSnippet: ['body:20', 'title'] }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings(attributesToSnippet = listOf("body:20", "title")), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['attributesToSnippet' => [ 'body:20', 'title', ], ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "attributesToSnippet": [ "body:20", "title", ], }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings( "INDEX_NAME", Algolia::Search::IndexSettings.new(attributes_to_snippet: ["body:20", "title"]) ) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( attributesToSnippet = Some(Seq("body:20", "title")) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings(attributesToSnippet: ["body:20", "title"]) ) ``` With `nbWords` set to 6: ```txt theme={"system"} "As Gregor Samsa awoke one morning …" ``` And `nbWords` set to 10: ```txt theme={"system"} "As Gregor Samsa awoke one morning from uneasy dreams he …" ``` ### Ellipsis text The engine inserts text to show where words have been removed from the snippeted text. The default replacement text is `…` (Unicode `U+20216`): an ellipsis character. However, you can change this with the [`snippetEllipsisText`](/doc/api-reference/api-parameters/snippetEllipsisText) setting: ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { SnippetEllipsisText = "[…]" } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( snippetEllipsisText: "[…]", ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetSnippetEllipsisText("[…]"))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings("INDEX_NAME", new IndexSettings().setSnippetEllipsisText("[…]")); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { snippetEllipsisText: '[…]' }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings(snippetEllipsisText = "[…]"), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['snippetEllipsisText' => '[…]', ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "snippetEllipsisText": "[…]", }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings( "INDEX_NAME", Algolia::Search::IndexSettings.new(snippet_ellipsis_text: "[…]") ) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( snippetEllipsisText = Some("[…]") ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings(snippetEllipsisText: "[…]") ) ``` The engine adds an extra space (Unicode `U+0020`) before and after `snippetEllipsisText`. For example: ```txt theme={"system"} "… awoke one morning … " ``` ### Sanitize the results Algolia removes all HTML tags from the snippeted results to ensure the text displays correctly within pre-highlighting and post-highlighting tags. #### An alternative to snippets If you want an attribute to keep its HTML formatting, instead of asking Algolia to shorten (snippet) it for you, create short-form records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in your data based on a subset of the original, longer-form records. Ensure that these short-form records are restricted to an appropriate length and contain valid HTML (all opening tags have matching closing tags). You can do this programmatically, for example, with: * RegExp. If you have a limited, known set of HTML, you can use regular expressions to split the content. Be aware of the [limitations of the RegExp approach](https://blog.codinghorror.com/parsing-html-the-cthulhu-way/). * HTML splitter code ### Snippeting using InstantSearch There's a `Snippet` widget in the InstantSearch libraries to snippet text attributes on the frontend. Refer to the [widget docs](/doc/api-reference/widgets/snippet/js) for usage notes and code examples. # Infinite scroll with InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/js Implement an infinite scroll experience with InstantSearch.js. An "infinite list" is a common way of displaying results. It's especially well-suited to mobile devices and has two variants: * **Infinite hits** with a "See more" button at the end of a batch of results. Implement this with InstantSearch's [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widget. * **Infinite scroll** uses a listener on the scroll event (called when users have scrolled to the end of the first batch of results). The following guidance implements such an infinite scroll. Find the complete example on [GitHub](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/infinite-scroll). > [!NOTE] > > If there aren't any results, [display a message to users and clear filters](/doc/guides/building-search-ui/going-further/conditional-display/js#handle-searches-without-results) so they can start over. ## Display a list of hits Render the results with the [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) connector. > [!NOTE] > > Read more about connectors in the [customizing widgets guide](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js). > [!NOTE] > > All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. > If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information. ```js JavaScript icon=code theme={"system"} const infiniteHits = instantsearch.connectors.connectInfiniteHits( (renderArgs, isFirstRender) => { const { items, showMore, widgetParams } = renderArgs; const { container } = widgetParams; if (isFirstRender) { container.appendChild(document.createElement('ul')); return; } container.querySelector('ul').innerHTML = items .map( item => `
  • ${instantsearch.highlight({ attribute: 'name', : item })}

    ${instantsearch.highlight({ attribute: 'description', hit: item })}

    $${item.price}

  • ` ) .join(''); } ); ``` ## Track the scroll position Once you have your results, the next step is to track the scroll position to determine when the rest of the content needs to be loaded (using the Intersection Observer API). Use the API to track when the bottom of the list (the "sentinel" element) enters the viewport. You can reuse the same element across different renders. The [Web Fundamentals](https://web.dev/articles/intersectionobserver/) website discusses the use of this API in more detail. ```js JavaScript icon=code theme={"system"} const infiniteHits = instantsearch.connectors.connectInfiniteHits( (renderArgs, isFirstRender) => { const { items, showMore, widgetParams } = renderArgs; const { container } = widgetParams; if (isFirstRender) { const sentinel = document.createElement("div"); container.appendChild(document.createElement("ul")); container.appendChild(sentinel); return; } // ... }, ); ``` > [!NOTE] > > This implementation uses the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). > To support [Internet Explorer 11](https://wikipedia.org/wiki/Internet_Explorer_11) you need a [polyfill](https://wikipedia.org/wiki/Polyfill_\(programming\)) for > `IntersectionObserver`. > A browser API is used in the example, but you can apply the concepts to any infinite scroll library. Once you have the reference to the "sentinel" element, create the Intersection Observer instance to determine when this element enters the page. Update the Intersection Observer's `options` object to adjust this code to your needs. ```js JavaScript icon=code theme={"system"} const infiniteHits = instantsearch.connectors.connectInfiniteHits( (renderArgs, isFirstRender) => { const { items, showMore, widgetParams } = renderArgs; const { container } = widgetParams; if (isFirstRender) { const sentinel = document.createElement("div"); container.appendChild(document.createElement("ul")); container.appendChild(sentinel); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { // In that case, you can refine } }); }); observer.observe(sentinel); return; } // ... }, ); ``` ## Retrieve more results Now that you can track when you reach the end of the results, use the [showMore](/doc/api-reference/widgets/infinite-hits/js#param-show-more) function inside the callback function of the observer. **Only trigger the function when there are still results to retrieve.** For this use case, the connector provides a parameter [isLastPage](/doc/api-reference/widgets/infinite-hits/js#param-is-last-page) that indicates if you still have results. ```js JavaScript icon=code theme={"system"} let lastRenderArgs; const infiniteHits = instantsearch.connectors.connectInfiniteHits( (renderArgs, isFirstRender) => { const { items, showMore, widgetParams } = renderArgs; const { container } = widgetParams; lastRenderArgs = renderArgs; if (isFirstRender) { const sentinel = document.createElement("div"); container.appendChild(document.createElement("ul")); container.appendChild(sentinel); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting && !lastRenderArgs.isLastPage) { showMore(); } }); }); observer.observe(sentinel); return; } // ... }, ); ``` ## Show more than 1,000 hits To ensure excellent performance, the default limit for the number of hits you can retrieve for a query is 1,000. ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { PaginationLimitedTo = 1000L } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( paginationLimitedTo: 1000, ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetPaginationLimitedTo(1000))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings("INDEX_NAME", new IndexSettings().setPaginationLimitedTo(1000L)); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { paginationLimitedTo: 1000 }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings(paginationLimitedTo = 1000L), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['paginationLimitedTo' => 1000, ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "paginationLimitedTo": 1000, }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings("INDEX_NAME", Algolia::Search::IndexSettings.new(pagination_limited_to: 1000)) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( paginationLimitedTo = Some(1000L) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings(paginationLimitedTo: Int64(1000)) ) ``` If you need to show more than 1,000 hits, you can set a different limit using the [`paginationLimitedTo`](/doc/api-reference/api-parameters/paginationLimitedTo) parameter. The higher you set this limit, the slower your search performance can become. > [!NOTE] > > Increasing the limit doesn't mean you can go until the end of the hits, > but just that Algolia will go as far as possible 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.) to retrieve results in a reasonable time. # Loading indicator with InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/loading-indicator/js Learn how to display a loading indicator with InstantSearch.js. You can display a loading indicator to inform users on a slow connection that their search is still in progress. Use InstantSearch's `status` indicator to decide when to show a loading indicator. The `status` can be one of the following: * `loading`: The search is in progress. * `stalled`: The search is in progress, but the response is taking longer than expected. * `error`: The search failed. * `idle`: The search succeeded. Best practice is to display a loading indicator only when `status` is `stalled`, not during a standard (fast) search. Tweak the timing of the `stalled` status by setting the [`stalledSearchDelay`](/doc/api-reference/widgets/instantsearch/js) option. You can then use this condition to display a loading indicator: ```js JavaScript theme={"system"} search.on('render', () => { const container = document.querySelector('#loading-indicator'); if (search.status === 'stalled') { container.innerText = 'Loading search results'; } else { container.innerText = ''; } }) ``` If you want the loading indicator to display as soon as the search starts, use the `loading` status in combination with `stalled`: ```js JavaScript theme={"system"} search.on('render', () => { const container = document.querySelector('#loading-indicator'); if (search.status === 'loading' || search.status === 'stalled') { container.innerText = 'Loading search results'; } else { container.innerText = ''; } }) ``` # Multi-index search Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/js Searching multiple indices in your InstantSearch.js app. > [!NOTE] > > You are currently reading the documentation for InstantSearch.js v4. > Read the migration guide to learn [how to upgrade from v3 to v4](/doc/guides/building-search-ui/upgrade-guides/js#upgrade-from-v3-to-v4). > You can still access the [V3 documentation for this page](https://algolia.com/old-docs/deprecated/instantsearch/js/v3/guides/multi-index-search/). Multi-index search (federated search) is a method for searching multiple data sources simultaneously. This means that when users enter a search term, Algolia will look for and display results from all these data sources. This doesn't necessarily mean that the results from Algolia indices are combined since their contents could be quite different. [Your approach](https://www.algolia.com/blog/product/federated-search-types/) may be to display the results from each 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.) separately. You could display the top-rated items from a movie index alongside the list of results from a book index. Or you could display category matches alongside the list of results from a product index ## Search multiple indices with InstantSearch The following demo uses an InstantSearch search box to display hits from two different indices: > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/multi-index-hits) > > Run and edit the Multi-index search example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js) > > Browse the source for the Multi-index search example on GitHub. This example uses a single [`searchBox`](/doc/api-reference/widgets/search-box/js) to search multiple indices. It adds the first [`hits`](/doc/api-reference/widgets/hits/js) widget at the top level, while the second one is scoped under an [`index`](/doc/api-reference/widgets/index-widget/js) widget. The second `hits` widget just displays results from the Algolia index referenced by the `index` widget that precedes it. The first `hits` widget displays results from the top level `instant_search` index. > [!NOTE] > > All examples in this guide assume you've included InstantSearch.js in your web page from a CDN. > If, instead, you're using it with a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js) for more information. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: 'instant_search', searchClient, }); search.addWidgets([ instantsearch.widgets.searchBox({ container: '#searchbox', }), instantsearch.widgets.hits({ container: '#hits-instant-search', templates: { item: (hit, { html, components }) => html`
    ${components.Highlight({ hit, attribute: 'name' })}
    `, }, }), instantsearch.widgets .index({ indexName: 'instant_search_price_desc' }) .addWidgets([ instantsearch.widgets.hits({ container: '#hits-instant-search-price-desc', templates: { item: (hit, { html, components }) => html`
    ${components.Highlight({ hit, attribute: 'name' })}
    `, }, }), ]), ]); search.start(); ``` You can scope widgets under an [`index`](/doc/api-reference/widgets/index-widget/js). The following example displays a different number of hits for the two sets of results. The `instant_search` index displays 8 results and `instant_search_price_desc` 16 results. To restrict the number of results per page, use the [`configure`](/doc/api-reference/widgets/configure/js) widget. Each widget is scoped under the targeted level. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: 'instant_search', searchClient, }); search.addWidgets([ instantsearch.widgets.configure({ hitsPerPage: 8, }), instantsearch.widgets.searchBox({ container: '#searchbox', }), instantsearch.widgets.hits({ container: '#hits-instant-search', templates: { item: (hit, { html, components }) => html`
    ${components.Highlight({ hit, attribute: 'name' })}
    `, }, }), instantsearch.widgets .index({ indexName: 'instant_search_price_desc' }) .addWidgets([ instantsearch.widgets.configure({ hitsPerPage: 16, }), instantsearch.widgets.hits({ container: '#hits-instant-search-price-desc', templates: { item: (hit, { html, components }) => html`
    ${components.Highlight({ hit, attribute: 'name' })}
    `, }, }), ]), ]); search.start(); ``` ## Search multiple indices with Autocomplete You can use the [Autocomplete library](/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete) in your InstantSearch app to build a dynamic multi-source search experience. For example, you may want to display Query Suggestions together with recent searches, create a multi-column layout that mixes facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) and item previews, or even dynamically change sources based on the query. > [!NOTE] > > Autocomplete isn't limited to Algolia indices: you can use static sources or fetch data from other APIs. For more information, see [Include multiple result types](/doc/ui-libraries/autocomplete/guides/including-multiple-result-types). For more information, see: * [Include multiple result types](/doc/ui-libraries/autocomplete/guides/including-multiple-result-types) * [Reshape sources](/doc/ui-libraries/autocomplete/guides/reshaping-sources) * [Create a multi-column layout](/doc/ui-libraries/autocomplete/guides/creating-a-multi-column-layout) ## Category display Algolia can help you display both category matches and results if you: * **Add categories to your Query Suggestions** either inline or [listed below a result](/doc/ui-libraries/autocomplete/introduction/sandboxes#search-suggestions-with-categories). For example, you might see the following in your Query Suggestions list "game of thrones in Books" * **Use multi-index search to display categories from a separate category index.** This is useful if you want to display categories and Query Suggestions at the same time. Clicking such a result typically [redirects](/doc/guides/building-search-ui/ui-and-ux-patterns/redirects/js) to a category page. The following is a sample dataset for a product index and a category index. ### Example product index ```json JSON icon=braces theme={"system"} [ { "name": "Fashion Krisp", "description": "A pair of red shoes with a comfortable fit.", "image": "/fashion-krisp.jpg", "price": 18.98, "likes": 284, "category": "Fashion > Women > Shoes > Court shoes" }, { "name": "Jiver", "description": "A blue shirt made of cotton.", "image": "/jiver.jpg", "price": 17.70, "likes": 338, "category": "Fashion > Men > Shirts > Dress shirt" } ] ``` ### Example category index ```json JSON icon=braces theme={"system"} [ { "name": "Court shoes", "category": "Fashion > Women > Shoes > Court shoes", "description": "A dress shoe with a low-cut front and a closed heel.", "url": "/women/shoes/court/" }, { "name": "Dress shirt", "category": "Fashion > Men > Shirts > Dress shirt", "description": "A long-sleeved, button-up formal shirt that is typically worn with a suit or tie.", "url": "/men/shirts/dress/" } ] ``` # Implement the OpenSearch protocol with InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/opensearch/js Learn how to integrate the OpenSearch protocol into your website with InstantSearch.js. > [!NOTE] > > This project is unrelated to AWS OpenSearch. This page describes how to implement the [OpenSearch protocol](https://github.com/dewitt/opensearch) with InstantSearch. The OpenSearch protocol lets you describe a search engine for your website, so that browsers or other search clients can use that search engine. ## How OpenSearch works In your website or web application, you include a link to an OpenSearch description document. In this document, you describe how browsers can interact with an underlying search engine. This interaction is based on URL templates, for example, `https://mysite.com?q={searchTerms}`, which tell browsers how to make a search request. Since the interaction between the browser and search engine is based on a URL, you need to enable [URL synchronization](/doc/guides/building-search-ui/going-further/routing-urls/js). ## Create an OpenSearch description document The OpenSearch description document is an XML file following the [OpenSearch specification](https://github.com/dewitt/opensearch/blob/master/opensearch-1-1-draft-6.md). For example: ```xml XML theme={"system"} Example Web Search Search example.com content. UTF-8 linkToImage ``` To adapt this example to your website, update these parts: * `ShortName` is the name of your app or website search. It must be 16 or fewer characters. * `Description` is a human readable description of your search engine. It must be 1024 or fewer characters. * `Url` has the `template` attribute, that browsers use to create the URL to your search engine. * `Image` has a URL (linkToImage) to a PNG image that represents your website, for example, the favicon. > [!NOTE] > > The XML document must be served with `Content-Type: application/opensearchdescription+xml` media type. For more information, see [OpenSearch description elements](https://github.com/dewitt/opensearch/blob/master/opensearch-1-1-draft-6.md#opensearch-description-elements). ## Include the OpenSearch description document in your website You can reference the XML document in the `head` of your web pages: ```html HTML theme={"system"} ``` For more information, see [Autodiscovery in HTML](https://github.com/dewitt/opensearch/blob/master/opensearch-1-1-draft-6.md#autodiscovery-in-htmlxhtml) # Pagination Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/pagination/js Organize your search results with pagination in InstantSearch.js. Pagination gives you complete control over how you retrieve query results. You can: * Implement standard paging * Retrieve specific record subsets that ignore page boundaries. * Set pagination defaults at indexing time * Override pagination default settings at query time. ## Pagination at indexing time At indexing time, you can set up two defaults: * [`hitsPerPage`](/doc/api-reference/api-parameters/hitsPerPage): the number of hits returned for every page * [`paginationLimitedTo`](/doc/api-reference/api-parameters/paginationLimitedTo): controls the number of hits retrieved for a given query ## Pagination at query time You can: * Retrieve a specific page with the [`page`](/doc/api-reference/api-parameters/page) parameter * Specify specific record subsets by using the [`offset`](/doc/api-reference/api-parameters/offset) and [`length`](/doc/api-reference/api-parameters/length) parameters * Override the default for `hitsPerPage` ## Response details Algolia includes information about pagination in the API response. ```json JSON icon=braces theme={"system"} { "page": 0, "nbHits": 40, "nbPages": 2, "hitsPerPage": 20, "exhaustiveNbHits": true } ``` * `page` returns the current page (this value **starts at 0**) * `hitsPerPage` returns the maximum number of hits returned for each page * `nbPages` returns the number of pages available for the current query * `nbHits` returns the number of hits that the search query matched * `exhaustiveNbHits` returns a boolean indicating if the nbHits count was exhaustive or approximate * `exhaustiveTypo` returns a boolean indicating if the typo-tolerance search was exhaustive or approximate (only included when typo-tolerance is enabled) ### No hits or is the search still in progress? Handle situations when there aren't any results (`nbHits` = 0) by [displaying a message to users and clearing filters](/doc/guides/building-search-ui/going-further/conditional-display/js#handle-searches-without-results) so they can start over. Sometimes, though, no hits can be reported if the user's device [can't access the network or the network connection is slow](/doc/guides/building-search-ui/going-further/improve-performance/js). To determine if there are no hits for a query or if the search is still in progress, examine the value of InstantSearch's [`status`](/doc/api-reference/widgets/instantsearch/js#param-status) property. Consider adding a [loading indicator](/doc/guides/building-search-ui/ui-and-ux-patterns/loading-indicator/js) to inform users on a slow connection that their search is still in progress. ### Exhaustive hits Usually, Algolia returns an accurate number of total hits. However, if a query returns many hits, Algolia approximates the count to avoid scanning 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.). This approximation protects other search and indexing operations. If your API response includes the [`exhaustive.nbHits`](/doc/rest-api/search/search-single-index#response-exhaustive) parameter, [the number of hits is approximate](https://support.algolia.com/hc/en-us/articles/4406975248145-Why-are-my-facet-and-hit-counts-not-accurate-). ## Retrieve specific pages At query time, you can pass in the [`page`](/doc/api-reference/api-parameters/page) parameter to access a specific results page. The [`hitsPerPage`](/doc/api-reference/api-parameters/hitsPerPage) parameter provides a way to set the default number of hits returned for each page. By default, its value is **20**. However, this can be overridden as an index setting or as a query parameter. ```js javaScript icon=code theme={"system"} index .search("query", { page: 2, hitsPerPage: 5, }) .then(({ hits }) => { console.log(hits); }); ``` ## Pagination limitations ### Default number of hits per query By default, Algolia includes up to 1,000 hits per query. ### Overriding the default limits You can adjust the number of hits in a paginated query with the [`paginationLimitedTo`](/doc/api-reference/api-parameters/paginationLimitedTo) parameter: ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { PaginationLimitedTo = 1000L } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( paginationLimitedTo: 1000, ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetPaginationLimitedTo(1000))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings("INDEX_NAME", new IndexSettings().setPaginationLimitedTo(1000L)); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { paginationLimitedTo: 1000 }, }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings(paginationLimitedTo = 1000L), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['paginationLimitedTo' => 1000, ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "paginationLimitedTo": 1000, }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings("INDEX_NAME", Algolia::Search::IndexSettings.new(pagination_limited_to: 1000)) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( paginationLimitedTo = Some(1000L) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings(paginationLimitedTo: Int64(1000)) ) ``` The maximum value is 20,000 hits per query. Increasing the pagination limit might decrease performance. Only set this number as large as necessary. ### Paging beyond the limit If you request a page that is out-of-range (`page >= nbPages`), Algolia doesn't return an error but 0 results. ## Retrieve a subset of records (with offset and length) In most cases, you only need `page` and `hitsPerPage` to add pagination. However, if you want to insert items, such as ads, into the results without affecting pagination, use the [`offset`](/doc/api-reference/api-parameters/offset) and [`length`](/doc/api-reference/api-parameters/length) parameters. They let you specify the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) to retrieve, not the page. The `offset` parameter lets you specify the starting record, and `length` sets the number of records returned. For example, if you have 100 records in your result set, broken down into 10 pages (`hitsPerPage=10`), retrieve these subsets as follows (`offset` is zero-based): * Records 50 to 80 (`offset=49` and `length = 30`). * Records 21 to 25 (`offset=20` and `length = 5`). > [!NOTE] > > With InstantSearch, you can only use `page` and `hitsPerPage` to implement pagination. # Query Suggestions Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js Query Suggestions suggest popular searches to guide users and help them search faster. Query Suggestions help users search by showing a list of popular searches as search suggestions as they type. By selecting suggestions, users type less and avoid searches that don't return any results. This feature isn't available on every plan. Refer to your [pricing plan](https://www.algolia.com/pricing) to see if it's included. Users often interact with Query Suggestions in an autocomplete menu. When typing, a list of suggestions appears below the search box. Choosing a suggestion initiates a search using that suggestion as the query. ![Autocomplete menu with a query in the search box and a list of Query Suggestions below](https://mintcdn.com/algolia/0u_XqgAn7MC5F_qG/images/guides/analytics/qs-example.png?fit=max\&auto=format\&n=0u_XqgAn7MC5F_qG\&q=85\&s=46015dd3876f85728809801647eba7f4) Query Suggestions can also help in these cases: * To help users **before** they type anything. This is often combined with featured categories or featured search results. * To help users continue if a search [didn't return any results](/doc/guides/building-search-ui/going-further/conditional-display/js#handle-searches-without-results). ## Explore Query Suggestions user interfaces For examples of Query Suggestions in action, see: * [Query Suggestions demo with InstantSearch.js](https://codesandbox.io/s/github/algolia/instantsearch/tree/master/examples/js/query-suggestions) * [Autocomplete examples with Query Suggestions](/doc/ui-libraries/autocomplete/introduction/sandboxes) For more information about building a Query Suggestions UI, see: * [Build a Query Suggestions UI with InstantSearch](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/query-suggestions/js) ## How Query Suggestions work Algolia stores Query Suggestions as records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in 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.). This index is rebuilt every day, or when you change its settings. Each update runs through these steps: 1. **Find the most frequent searches** from the [search analytics](/doc/guides/search-analytics/overview) of your source indices and their replicas. During the early stages of your implementation, when you haven't gathered enough analytics, you can also generate suggestions using external analytics or your facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) data. 2. **Normalize and complete suggestions** by removing duplicated, similar, or prefixed suggestions, depending on your [settings](#remove-duplicate-or-unhelpful-suggestions). Suggestions must match the following criteria: * Only contain alphanumeric characters * Return a minimum number of search results * Longer than a minimum number of letters * Not be a banned expression. Query Suggestions also deduplicate similar searches, keeping only the most frequently searched terms as suggestions. 3. **Rank the suggestions** by popularity (number of searches) in the last 30 days. For all similar searches, the individual counts are summed. Counts from external analytics are also added to the suggestion's popularity. Each suggestion in the Query Suggestions index has at least these attributes: * `objectID`. Unique record identifier, usually the suggestion itself * `query`. The suggestion * `popularity`. Number of searches in the last 30 days for the suggestion If you add suggestions from facets, more attributes are added. ## Set up Query Suggestions 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. Open the [**Query Suggestions**](https://dashboard.algolia.com/query-suggestions) page and click **New Query Suggestions Index**. 5. In the **Source index** field, enter the name of the index with the search results and provide a name for the Query Suggestions index. You can add more source indices later. 6. Click **Accept and Continue** to start building the Query Suggestions index. ### Augment Query Suggestions with external analytics If you don't have enough searches in your search analytics yet, or you want to add specific suggestions, you can upload external analytics from a JSON file. External analytics give Query Suggestions another data source. They don't replace your source index, which must contain your searchable records. 1. Prepare your suggestions in a JSON file. Each suggestion must have `query` and `count` attributes. Algolia adds the value of the `count` attribute to the value of the `popularity` attribute in the Query Suggestions index for that suggestion. To increase the popularity of existing suggestions, add them to this JSON file with a high value for the `count` attribute. For example: ```json JSON icon="braces" theme={"system"} [ { "query": "shoes", "count": 10031 }, { "query": "dress shirt", "count": 731 }, { "query": "yellow jacket", "count": 122 } ] ``` 2. [Import](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api) this JSON file into Algolia as a new index. This index is your external analytics index. 3. Create or open the Query Suggestions index you want to enrich. If you create a Query Suggestions index, set **Source index** to the index that contains your searchable records, not the external analytics index you imported in step 2. For example, if your searchable records are in `products`, use `products` as the source index. 4. In the **Data sources** section, click **Enable** next to **External Analytics**. 5. In the **External analytics index** field, enter the name of the index you imported in step 2. For example, if you imported your JSON file into `external_query_analytics`, use `external_query_analytics` as the external analytics index. ### Augment Query Suggestions with facet data If you don't have enough data from search analytics or external analytics, Query Suggestions can create suggestions from the combination of facet values. For example, for the facets `color` and `brand`, these Query Suggestions are created: * red nike * blue nike * red adidas * blue adidas > [!NOTE] > > Suggestions created from facets are less relevant than those created by search analytics since they aren't based on popularity. > But they can still enrich the suggestions, especially for rarer queries. > That's why it's best to keep suggestions from facets, even after you collect sufficient data from analytics. To augment your Query Suggestions with facet data: 1. Set appropriate [searchable attributes](/doc/guides/managing-results/must-do/searchable-attributes) **and** [attributes for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard). 2. On the [**Query Suggestions**](https://dashboard.algolia.com/query-suggestions) page in the Algolia dashboard, select your Query Suggestions index. 3. In the **Data sources** section, click **Enable** for **Queries by facets**. 4. Select two facet attributes and click **Add attributes**. Repeat for any combination of two facets for which you want to generate suggestions. To show categories from facets in your suggestions, see [Query Suggestions with categories](#query-suggestions-with-categories). ## Optimize Query Suggestions After the initial setup, you can optimize your experience with one of the following strategies. ### Improve relevance A Query Suggestions index is a regular Algolia index, so you can use the same features and settings to tune the relevance of your suggestions. * Use **Rules** to adjust the suggestions based on conditions. For example, you can [promote](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/promote-hits) or [hide](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/how-to-hide-hits) specific suggestions, [change the user's query](/doc/guides/managing-results/rules/detecting-intent), or [display a banner](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/add-banners). * Use [**synonyms**](/doc/guides/managing-results/optimize-search-results/adding-synonyms) to suggest similar search terms. This helps if users search for terms that aren't in your records (but those similar terms are). * Adjust **index settings** such as [typo tolerance](/doc/guides/managing-results/optimize-search-results/typo-tolerance/in-depth/configuring-typo-tolerance), or [tokenization](/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp/in-depth/tokenization). ### Query Suggestions with categories You can make your suggestions more targeted by adding categories: 1. Make sure you configure your category attribute as a [facet](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard). 2. On the [**Query Suggestions**](https://dashboard.algolia.com/query-suggestions) page, select your Query Suggestions index. 3. On the **Overview & Settings** tab, in the **Categories** section, click **Edit categories**. 4. In the **Categories attribute** field, choose a category to display. In the **Show top** field, select how many top categories to display. For example, if you chose `2`, the top two categories are added to every suggestion. ![Query Suggestions with inline categories](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/sandboxes/query-suggestions-with-inline-categories.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=8206a99376656b6af3e78d910e594ebe) #### How to determine top categories Query Suggestions determines the top categories with the following algorithm: 1. Perform a "strict" search with the suggestion in the source index—without features such as prefixing or typo-tolerance. 2. Add the most frequently found category values to the [`facets.exact_matches`](#query-suggestions-index-schema) attribute. 3. Get the top filters (most often applied) for this search term from analytics and add them to the [`facets.analytics`](#query-suggestions-index-schema) attribute. The category information is added to each record in your Query Suggestions index. For your user interface, it's better to rely on the `facets.exact_matches` attribute because it represents data from your index. For example, if users often 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.) by a specific category, but you don't have many items in that category, they won't see many results when accepting this suggestion. #### Display categories in your UI Once you have categorized your Query Suggestions, decide how to display them in your frontend. One option is to redirect users to an InstantSearch page on category selection. This page uses the chosen category as a filter with the user's initial query pre-filled in the search box. For an example of this, see [Build a Query Suggestions UI with InstantSearch](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/vue). ### Filter suggestions on category pages To show only suggestions that have matching source-index hits in the current category page, configure category page filtering: 1. Configure the source attribute as a [facet](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard). The attribute can't use the `filterOnly` modifier because Query Suggestions must retrieve its values when building suggestions. 2. On the [**Query Suggestions**](https://dashboard.algolia.com/query-suggestions) page, select your Query Suggestions index. 1) On the **Overview & Settings** tab, select the source index. 1. In the **Category page filtering** section, select the source facet. After the Query Suggestions index rebuilds, Algolia adds the matching facet values to each suggestion record and configures the required Query Suggestions index setting. For example, if you select `category` on the `products` source index, a suggestion record can include: ```json JSON icon="braces" theme={"system"} { "products": { "browsing_facet_values": ["Accessories", "Winter"] } } ``` Algolia also adds `filterOnly(products.browsing_facet_values)` to the Query Suggestions index's `attributesForFaceting` setting. You can then filter suggestions with `products.browsing_facet_values:`. If your Query Suggestions index has multiple source indices, configure a source facet for each applicable source index. Algolia creates a separate `browsing_facet_values` attribute for each source index. For an example of filtering Query Suggestions in your UI, see [Filter Query Suggestions with InstantSearch](/doc/guides/building-search-ui/ui-and-ux-patterns/autocomplete/query-suggestions/js#filter-suggestions-on-category-pages). ### Remove duplicate or unhelpful suggestions On the [**Query Suggestions**](https://dashboard.algolia.com/query-suggestions) page, select your Query Suggestions index, and adjust the following settings to improve the quality of the suggestions: 1. In the **Languages** field, add the language of your index. This lets Query Suggestions automatically remove duplicate suggestions such as singulars and plurals. 2. Adjust the **Minimum letters** setting to prevent short, incomplete suggestions. A suggestion must have at least this many letters to be included. 3. Adjust the **Minimum hits** setting to ensure enough search results are returned. A suggestion must return at least this many search results to be included. 4. On the **Banned expressions** tab, add expressions or patterns for excluding suggestions. ### Personalize Query Suggestions To add [personalized](/doc/guides/personalization/classic-personalization/what-is-personalization) Query Suggestions, follow these steps: 1. On the [**Query Suggestions**](https://dashboard.algolia.com/query-suggestions) page, select your Query Suggestions index. 2. On the **Overview & Settings** tab, in the **Settings** section, enable **Personalized Query Suggestions**. Algolia then retrieves the top 20 facet values (based on your [personalization strategy](/doc/guides/personalization/classic-personalization/personalizing-results#configure-your-personalization-strategy)) from your source index and adds new attributes to the suggestions in your Query Suggestions index for each facet defined in your personalization strategy. ### Generate different suggestions for different environments You can use [analytics tags](/doc/guides/search-analytics/guides/segments) to create different suggestions for different environments, such as mobile or desktop users, or for different geographical regions. 1. [Create a new Query Suggestions index](#set-up-query-suggestions) with a recognizable name. For example, include the analytics tag in the name, such as `products_query_suggestions_mobile`. 2. After creating the index, on the **Overview & Settings** tab, in the **Data sources** section, click **Manage** next to **Algolia Analytics API**. 3. Enter the appropriate analytics tags. For example, `products_query_suggestions_mobile`. This only includes suggestions with specific analytics tags. In your UI, you can target the different Query Suggestions indices depending on the user's device or geographical region. ## Troubleshoot missing Query Suggestions If suggestions are missing, the Query Suggestions API can help identify the issue. For example, run the following command to retrieve the most recent log entries. ```sh icon="square-terminal" theme={"system"} curl -H "x-algolia-api-key: $ALGOLIA_ADMIN_API_KEY" \ -H "x-algolia-application-id: $ALGOLIA_APPLICATION_ID" \ "https://query-suggestions.us.algolia.com/1/logs/$ALGOLIA_QUERY_SUGGESTIONS_INDEX" ``` The log entries provide the following details: * Source indices * Why suggestions were skipped * The number of [popular searches](#how-query-suggestions-work) * The number of suggestion candidates from [facets](#augment-query-suggestions-with-facet-data) * The [normalization](#how-query-suggestions-work) process For more information, see [Get a log file](/doc/rest-api/query-suggestions/get-log-file). ## Query Suggestions index schema The following example shows a record from a Query Suggestions index with categories from facets. ```jsonc JSON icon="braces" theme={"system"} { "query": "phone charger", // The suggestion "objectID": "phone charger", // Unique identifier "popularity": 2457, // Number of searches for this suggestion in the last 30 days "nb_words": 2, // Number of words in the suggestion "products": { // Name of the source index "exact_nb_hits": 42, // Number of search results for this suggestion "browsing_facet_values": ["Accessories", "Winter"], // Matching values of the configured category page facet "facets": { "exact_matches": { // Most frequent facet values found for this suggestion "brand": [ { "value": "Anker", "count": 12 }, // This means 12 of the search results have "Anker" as "brand" { "value": "Yootech", "count": 11 }, { "value": "TOZO", "count": 9 }, { "value": "Aukey", "count": 4 }, { "value": "Apple", "count": 2 } ] }, "analytics": { // Most frequently applied filters for this suggestion "brand": [ { "attribute": "brand", "count": 347, "operator": ":", "value": "Anker" }, { "attribute": "brand", "count": 203, "operator": ":", "value": "Apple" }, { "attribute": "brand", "count": 152, "operator": ":", "value": "TOZO" }, { "attribute": "brand", "count": 128, "operator": ":", "value": "Yootech" }, { "attribute": "brand", "count": 80, "operator": ":", "value": "Aukey" } ] } } } } ``` ## See also For more information, see these blog articles: * [How Query Suggestions increase conversions](https://www.algolia.com/blog/ux/autocomplete-how-search-suggestions-increase-conversions/) * [Build a Query Suggestion UX like Google's](https://www.algolia.com/blog/algolia/query-suggestion-ux-like-googles/) * [Three best practices for search autocomplete on mobile](https://www.algolia.com/blog/ecommerce/search-autocomplete-on-mobile/) # Recommendations in InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/recommend/js Show recommended items from Algolia Recommend in your InstantSearch.js app. InstantSearch.js provides widgets for displaying recommendations on your site with [Algolia Recommend](/doc/guides/algolia-recommend/overview). **Recommendations encourage users to discover more of your catalog based on what they're already interested in.** * [Frequently bought together](#frequently-bought-together) * [Related items](#related-items) * [Trending items](#trending-items) * [Looking similar](#looking-similar) **Recommendations are an ideal complement to search experiences.** If users land on an item that isn't exactly what they wanted, they can explore similar items. Conversely, when they find what they want, they can discover items that would complement their current selection. ### Frequently bought together Frequently bought together recommends items that users often buy together (based on [conversion events](/doc/guides/sending-events)). The component lets you pass a reference item. It then displays a list of recommended items, based on the [Frequently bought together](/doc/guides/algolia-recommend/overview#frequently-bought-together) model from Algolia Recommend. See the [`frequentlyBoughtTogether`](/doc/api-reference/widgets/frequently-bought-together/js) widget. ### Related items These models recommend items that are related to each other (based on [click and conversion events](/doc/guides/sending-events)). The component lets you pass a reference item. It then displays a list of recommended items, based on the [Related items](/doc/guides/algolia-recommend/overview#related-items) model from Algolia Recommend. See the [`relatedProduct`](/doc/api-reference/widgets/related-products/js) widget. ### Trending items The **Trending items** model looks for popular items in your product catalog. Trends can be global, for example, recommend the most popular items in your entire product catalog. Or it can also be within a specific facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) or category, for example, recommend the most popular winter sweaters. The [`trendingItems`](/doc/api-reference/widgets/trending-items/js) widget can fetch global trends or trends within a facet. It displays a list of trending items, based on the [Trending items](/doc/guides/algolia-recommend/overview#trending-items-and-trending-facets-value) model from Algolia Recommend. ### Looking similar This model recommends items that are visually related to each other, based on the images linked 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.). The component lets you pass a source item. It then displays a list of recommended items, based on the [Looking similar](/doc/guides/algolia-recommend/overview#looking-similar) model from Algolia Recommend. See the [`lookingSimilar`](/doc/api-reference/widgets/looking-similar/js) widget. # Redirects in InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/redirects/js Learn how to add redirects on specific searches with InstantSearch.js. Redirects can enhance the search experience. For example: * Searching for "help" redirects users to a support page * Searching for a category, such as, "TV" redirects users to a category page * Searching for a specific product or brand redirects users to a seasonal promotional page for it * Searching for specific keywords related to an ongoing event, such as, "masks", redirects users to a more specific landing page This guide explains how to add redirects to InstantSearch through [Rules](/doc/guides/managing-results/rules/detecting-intent) and using custom data. Redirects are handled by Autocomplete's [redirect plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url). However, if you don't use [Autocomplete](/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete), you must handle redirects in InstantSearch: * If a redirect was created in the Manual Editor, [handle it with InstantSearch's `queryRuleCustomData` widget](#handle-a-manual-editor-rule-redirect). * If a redirect was created in the Visual Editor, you must [create a custom InstantSearch widget to handle redirects](#handle-a-visual-editor-rule-redirect). ## Handle a Visual Editor rule redirect ### Configure the redirect with a custom widget If a redirect was created in the Visual Editor, the API response contains the `renderingContent.redirect` property which, in turn, contains the redirect `URL`. To access this property from InstantSearch, you must create a custom widget. Custom widgets have [lifecycle hooks](/doc/guides/building-search-ui/widgets/create-your-own-widgets/js#set-up-the-lifecycle) for each of the widget stages: `init`, `render`, and `dispose`. The `render` stage runs when Algolia sends results: in other words, when a user types something. Use it as follows: ```js JavaScript theme={"system"} search.addWidgets([ { render({ results }) { if ( results && results.renderingContent && results.renderingContent.redirect ) { window.location.href = results.renderingContent.redirect.url; } }, } ]); ``` ## Handle a Manual Editor rule redirect If a redirect was created in the Manual Editor, handle it with InstantSearch's [`queryRuleCustomData`](/doc/api-reference/widgets/query-rule-custom-data/js) widget. ### Configure a rule To set up a rule that returns custom data whenever 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.) matches specific keywords, see [Create a rule for returning custom data](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/redirects#create-a-rule-for-returning-custom-data). ### Configure the `queryRuleCustomData` widget After setting up a rule that returns the custom redirect data, you can use the [`queryRuleCustomData`](/doc/api-reference/widgets/query-rule-custom-data/js) widget to update the page location when `userData` contains a redirect. ```js JavaScript theme={"system"} search.addWidgets([ instantsearch.widgets.queryRuleCustomData({ container: '#queryRuleCustomData', templates: { default: '', }, transformItems(items) { const match = items.find(data => Boolean(data.redirect)); if (match && match.redirect) { window.location.href = match.redirect; } return []; }, }) ]); ``` ### Offer a suggested redirect rather than a hard redirect The preceding is an example of a "hard redirect", which forces users to a specific URL. This works in most situations, but sometimes you might want to suggest a redirect URL rather than force it onto them. To do this, create the rule similarly but distinguish between "hard" and "suggested" redirects by ensuring the returned data is a JSON object. The object's properties are: * **name** (used to display the link) * **URL** * **force** (`true` for a hard redirect, `false` for a suggested redirect). ```json JSON icon=braces theme={"system"} { "redirect": { "name": "help", "url": "https://www.algolia.com/support", "force": true } } ``` With this change applied, the [`queryRuleCustomData`](/doc/api-reference/widgets/query-rule-custom-data/js) widget that had been previously defined needs updating to decide what happens to a user based upon the returned data. To do this, the redirect check (within `transformItems`) needs updating to decide whether to force the redirect onto users or not. The default template also needs setting to display a user message with the specific redirect URL. ```diff JavaScript theme={"system"} search.addWidgets([ instantsearch.widgets.queryRuleCustomData({ container: '#queryRuleCustomData', templates: { - default: '', + default({ items }, { html }) { + const customData = items.find(data => Boolean(data.redirect)); + + if (!customData) { + return null; + } + + return html` + + `; + }, }, transformItems(items) { const match = items.find(data => Boolean(data.redirect)); - if (match && match.redirect) { + if (match && match.redirect && match.redirect.force) { window.location.href = match.redirect; + return []; } - return []; + return items; }, }) ]); ``` # Structured results in InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/structured-results/js Discover structured results, a feature to showcase compelling, targeted content in InstantSearch.js. One of the most interesting additions to search result pages are what's commonly called "structured results." * Google calls them [Knowledge Graph Cards](https://blog.google/products/search/about-knowledge-graph-and-knowledge-panels/), * Bing calls them [Visually Rich Snippets](https://www.bing.com/webmasters/help/marking-up-your-site-with-structured-data-3a93e731), * [DuckDuckGo](https://duckduckgo.com/?q=new+york+red+bulls\&t=h_\&ia=web) and [Baidu](http://www.baidu.com/s?ie=utf-8\&f=8\&rsv_bp=0\&rsv_idx=1\&tn=baidu\&wd=shanghai\&rsv_pq=c60de6e300062bc4\&rsv_t=5f92QVPi1SZAv5LTbiv8%2BknY2koOuywEhKyxU76njHxjQdFem7QhV7roOx0\&rqlang=cn\&rsv_enter=1\&rsv_sug3=3\&rsv_sug2=0\&inputT=743\&rsv_sug4=743) all have their own flavors as well. Traditional search, which consists of displaying long lists of search results once users press `Enter` on their keyboard, usually falls short of piquing their curiosity and triggering engagement. This is when new ways of searching emerge. Instead, a successful search should attempt to communicate clearly and cleanly with a user on multiple levels when applicable, where different types of results help guide users to the information they want. Sometimes, it's desirable to showcase a particular piece of relevant content against the rest of a user's standard query search results. This is why many believe in the value of leveraging "structured results". How about your search? ### Uses for structured results You'll want to use structured results whenever there is a singular piece of information you want to stand out from the rest for a given query. Here are a few examples: * Movie titles on an entertainment website * User details and avatar on a customer relationship management (CRM) system * Sponsored brands on an ecommerce search ([view code on GitHub](https://github.com/algolia/demo-structured-result)). You can see a common thread among these examples - a need to search two different types of structured data - and Algolia happens to handle these cases out of the box. ## An example First, take a look at the ecommerce example mentioned in the preceding section. You can view the code and follow along [on GitHub](https://github.com/algolia/demo-structured-result). Imagine a scenario where certain brands within your ecommerce site have requested that their brand will be displayed if it happens to match a user's query. In your search, if you determine that someone is searching for a particular brand name, you can embed a section displaying the brand and its name within the main search interface. ### How will the data look? One 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.) will be the main index to host traditional results - in your case, tech products - regardless of a match on the brand. Another index will be used to store your brands as records (A record is a searchable object in an Algolia index. Each record consists of named attributes.), like this: ```json JSON icon="braces" theme={"system"} { "name": "Bosch", "url": "https://logo.clearbit.com/bosch.com", "objectID": "3414165441" } ``` ### Tweaking the relevance Now that you added a new index for structured results to use in conjunction with your main index, outline what you'd like to emphasize in your structured results: * Only display a single result that matches a user's search * Continue to handle typos, but be more stringent * Ensure that you have all words matching in order (so "The Bad News" and "The Bad News Bears" would match but "Bears Bad News" would not) Next, determine the corresponding settings that would be configured in Algolia. In this case, you only need to care about results with a minimal number of typos, especially if you only render one result. Set typo tolerance to `min` in the [**Typo-Tolerance** section](https://dashboard.algolia.com/explorer/configuration/typo-tolerance/) of the Algolia dashboard, or with the API: ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { TypoTolerance = new TypoTolerance(Enum.Parse("Min")) } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( typoTolerance: TypoToleranceEnum.fromJson("min"), ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetTypoTolerance(search.TypoToleranceEnumAsTypoTolerance(search.TypoToleranceEnum("min"))))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings("INDEX_NAME", new IndexSettings().setTypoTolerance(TypoToleranceEnum.MIN)); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { typoTolerance: 'min' } }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings(typoTolerance = TypoToleranceEnum.entries.first { it.value == "min" }), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['typoTolerance' => 'min', ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "typoTolerance": "min", }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings("INDEX_NAME", Algolia::Search::IndexSettings.new(typo_tolerance: "min")) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( typoTolerance = Some(TypoToleranceEnum.withName("min")) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings(typoTolerance: SearchTypoTolerance .searchTypoToleranceEnum(SearchTypoToleranceEnum.min)) ) ``` Next, decrease your typo tolerance by increasing the minimum characters from 4 to 5 for a single typo. Change this with the **Min chars to accept 1 typo** setting in the same section, or with the API: ```cs C# theme={"system"} var response = await client.SetSettingsAsync( "INDEX_NAME", new IndexSettings { MinWordSizefor1Typo = 5 } ); ``` ```dart Dart theme={"system"} final response = await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings( minWordSizefor1Typo: 5, ), ); ``` ```go Go theme={"system"} response, err := client.SetSettings(client.NewApiSetSettingsRequest( "INDEX_NAME", search.NewEmptyIndexSettings().SetMinWordSizefor1Typo(5))) if err != nil { // handle the eventual error panic(err) } ``` ```java Java theme={"system"} UpdatedAtResponse response = client.setSettings("INDEX_NAME", new IndexSettings().setMinWordSizefor1Typo(5)); ``` ```js JavaScript theme={"system"} const response = await client.setSettings({ indexName: 'theIndexName', indexSettings: { minWordSizefor1Typo: 5 } }); ``` ```kotlin Kotlin theme={"system"} var response = client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings(minWordSizefor1Typo = 5), ) ``` ```php PHP theme={"system"} $response = $client->setSettings( 'INDEX_NAME', ['minWordSizefor1Typo' => 5, ], ); ``` ```python Python theme={"system"} response = client.set_settings( index_name="INDEX_NAME", index_settings={ "minWordSizefor1Typo": 5, }, ) ``` ```ruby Ruby theme={"system"} response = client.set_settings("INDEX_NAME", Algolia::Search::IndexSettings.new(min_word_sizefor1_typo: 5)) ``` ```scala Scala theme={"system"} val response = Await.result( client.setSettings( indexName = "INDEX_NAME", indexSettings = IndexSettings( minWordSizefor1Typo = Some(5) ) ), Duration(100, "sec") ) ``` ```swift Swift theme={"system"} let response = try await client.setSettings( indexName: "INDEX_NAME", indexSettings: IndexSettings(minWordSizefor1Typo: 5) ) ``` These configuration changes make the typo tolerance more stringent. The next section addresses the last point: ensuring that all words match in order. ### Display the data Federated search lets you perform a single query against several datasets. You can use it with your structured brand result: ```js JavaScript theme={"system"} const appID = "ALGOLIA_APPLICATION_ID"; const apiKey = "ALGOLIA_SEARCH_API_KEY"; const searchClient = algoliasearch( appID, apiKey ); const search = instantsearch({ searchClient, indexName: 'mainIndex' }); search.addWidgets([ // Add your widgets here index({ indexName: 'structuredIndex' }).addWidgets([ configure({ page: 0, hitsPerPage: 1, getRankingInfo: true }) // We'll add the structured results widget here ]) ]); search.start(); ``` In this snippet, you're taking advantage of the [`index`](/doc/api-reference/widgets/index-widget/js) widget to nest multiple indices that share the same [`uiState`](/doc/api-reference/widgets/ui-state/js) (or "search state"). When a user searches in the app, it searches in both the main and the structured indices. You can use the [`configure`](/doc/api-reference/widgets/configure/js) widget to: * Stay on the first page (`0` since [`page`](/doc/api-reference/api-parameters/page) is zero-based) because the main index can be paginated, and the structured index would then "inherit" from its pagination state. * Set [`hitsPerPage`](/doc/api-reference/api-parameters/hitsPerPage) to `1` because you only show the first result. * Set [`getRankingInfo`](/doc/api-reference/api-parameters/getRankingInfo) to `true` so that Algolia returns metadata about the hit's ranking. When rendering results, take the first result with the [`connectHits`](/doc/api-reference/widgets/hits/js#customize-the-ui-with-connecthits) connector and do a conditional check: ```js JavaScript theme={"system"} const structuredResults = connectHits(({ items, widgetParams }) => { const result = items[0]; const { container } = widgetParams; if ( result && result._rankingInfo.words - 1 === result._rankingInfo.proximityDistance ) { // Render the result container.innerHTML = `
    ${result.name}

    ${result.name}

    `; return; } // Render nothing container.innerHTML = ''; }); search.addWidgets([ // Add your widgets here index({ indexName: 'structuredIndex' }).addWidgets([ configure({ page: 0, hitsPerPage: 1, getRankingInfo: true }), structuredResults({ container: document.querySelector('#structured-results') }) ]) ]); // ... ``` By comparing the number of exact word matches to 1 + the total distance between all the words in the match, you can make sure that all matched words are sorted. In other terms, when all matching words against a user's query are sequential and adjacent, the proximity (or distance between search terms) is always equal to `exact word matches - 1` There's no requirement as to when to display your content, this part is up to you. Another constraint you may impose on the rendering of the structured result could be to check that the count of exact word matches is equal to the number of words in the value of the attribute being queried. For example, you may only want to show a brand when all words of that brand have been typed. Check the [`getRankingInfo`](/doc/api-reference/api-parameters/getRankingInfo) documentation to learn about other useful metrics you can access in the results. ## Another example, a different use case Consider an entertainment website that displays structured results on movies. If you can determine that someone is searching for a specific movie, you will embed a section displaying the movie details within the main search interface. That is, you'd like to communicate to users that there might be a salient piece of content worth showcasing. ### How will the data look? Consider how to structure your data. First, you need a main index, as with the ecommerce example. This is where people will always see traditional results even if there's no structured data match. Think about a major search engine when you search a new movie: you might get structured results with the cast, showtimes, and video clips at the same time. In Algolia, that would require three extra indices—one for each result type. This example is a bit simpler, as it only has a single data type to show structured results: movies. Yet, you might also want to match actors, directors, and other entities. Here's how a sample movie record could look like: ```json JSON icon=braces theme={"system"} { "title": "The Shawshank Redemption", "year": 1994, "image": "https://image.tmdb.org/t/p/w154/9O7gLzmreU0nGkIB6K3BsJbzvNv.jpg", "color": "#8C634B", "score": 9.97764206054169, "rating": 5, "actors": ["Tim Robbins", "Morgan Freeman", "Bob Gunton"], "objectID": "439817390" } ``` Records with this structure would be stored in another index. Keep in mind though that if your new index doesn't contain any new data, it could be a [replica](/doc/api-reference/api-parameters/replicas) of your primary index. (for example, searchable attributes or custom ranking order). You can display the data and render the result exactly the same way as in the . ## Conclusion The movie and ecommerce examples you went over are just two scenarios among many that may call for structured data, all following a similar pattern of giving users the ability to search through multiple indices and showcase results accordingly. Search paradigms continue to evolve, and the rise of "structured results" is part of an ongoing itch to enrich a user's process of discovery while browsing content. Algolia enables users the flexibility to roll out their own flavor of "structured results" by making simple the ability to search against multiple types of content and customizing how the search is performed. # Upgrade InstantSearch.js Source: https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/js Upgrade your InstantSearch.js version ## Upgrade templates Starting from InstantSearch.js v4.46.0, **string-based and Hogan.js templates are deprecated.** If you're using Hogan.js templates or HTML strings using string-based templates, you can replace them either with safe HTML strings using the provided `html` tagged template, or JSX templates. These new templating options are safer against cross-site scripting (XSS) attacks, perform better, and are more accessible, because they reuse and patch the existing DOM instead of replacing it entirely at each render. For more information, see: * [Using HTML strings](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#using-html-strings) * [Using JSX](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#using-jsx) ### Interpolation You can interpolate dynamic values using [template literals placeholders](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#string_interpolation). All available dynamic values are exposed within the scope of the template function. ```js Tagged templates theme={"system"} toggleRefinement({ // ... templates: { labelText({ count }, { html }) { return html`Free shipping (${count})`; }, }, }); ``` ```js Hogan.js theme={"system"} // Don't do this, use `html` instead toggleRefinement({ // … templates: { labelText: 'Free shipping ({{ count }})', }, }); ``` ```js Classic HTML strings theme={"system"} // Don't do this, use `html` instead toggleRefinement({ // ... templates: { labelText({ count }) { return `Free shipping (${count})`; }, }, }); ``` #### Highlighting and snippeting In both [`hits`](/doc/api-reference/widgets/hits/js) and [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widgets, templates expose a set of built-in components to handle highlighting and snippeting. ```js Tagged templates theme={"system"} hits({ // ... templates: { item(hit, { html, components }) { return html`

    ${components.Highlight({ hit, attribute: 'name' })}

    ${components.Snippet({ hit, attribute: 'description' })}

    `; }, }, }); ``` ```js Hogan.js theme={"system"} // Don't do this, use `html` instead hits({ // ... templates: { item: `

    {{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}

    {{#helpers.snippet}}{ "attribute": "description" }{{/helpers.snippet}}

    `, }, }); ``` ```js Classic HTML strings theme={"system"} // Don't do this, use `html` instead hits({ // ... templates: { item(hit) { return `

    ${instantsearch.highlight({ attribute: 'name', hit })}

    ${instantsearch.snippet({ attribute: 'description', hit })}

    `; }, }, }); ``` ### Loops You can use plain JavaScript to build dynamic templates. For example, you can use [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)to loop over an array and display a list. ```js Tagged templates theme={"system"} ratingMenu({ // … templates: { item({ url, stars }, { html }) { return html` ${stars.map((_, index) => html``)} `; }, }, }); ``` ```js Hogan.js theme={"system"} // Don't do this, use `html` instead ratingMenu({ // … templates: { item: ` {{#stars}} {{/stars}} `, }, }); ``` ```js Classic HTML strings theme={"system"} // Don't do this, use `html` instead ratingMenu({ // … templates: { item({ url, stars }) { return ` ${stars.map((_, index) => ``)} `; }, }, }); ``` > [!TIP] > > Passing a unique `key` attribute is helpful when mapping over items. > It helps the virtual DOM keep track of each element when they change, and update the UI efficiently. ### Conditional rendering To conditionally render a part of your UI, you can use a [short-circuit operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND) or a [ternary](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator). Since templates are functions, you can also use conditional `return` statements to provide alternative templates. ```js Tagged templates theme={"system"} stats({ // … templates: { text({ nbHits }, { html }) { if (nbHits === 0) { return html`

    No results.

    `; } return html`

    ${nbHits} result${nbHits > 1 ? 's' : ''} found.

    `; }, }, }); ``` ```js Hogan.js theme={"system"} // Don't do this, use `html` instead stats({ // … templates: { text: ` {{#hasNoResults}}

    No results.

    {{/hasNoResults}} {{^hasNoResults}}

    {{nbHits}} result{{#hasManyResults}}results{{/hasManyResults}} found.

    {{/hasNoResults}} `, }, }); ``` ```js Classic HTML strings theme={"system"} // Don't do this, use `html` instead stats({ // … templates: { text({ nbHits }) { if (nbHits === 0) { return `

    No results.

    `; } return `

    ${nbHits} result${nbHits > 1 ? 's' : ''} found.

    `; }, }, }); ``` ## Upgrade event tracking **Starting from v4.55.0, InstantSearch simplifies the event tracking process with the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option.** You no longer need to install the [`search-insights`](/doc/libraries/search-insights) library or set up the [`insights`](/doc/api-reference/widgets/insights/js) middleware yourself. Here are some benefits when using the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option: * It better handles the user token (A user token is a pseudonymous ID that represents an individual user across Algolia searches and events. It links queries, clicks, and conversions to a user profile, enabling user-level analytics, personalization, and recommendations.). Once you set it, all search and event tracking calls include the token. * It automatically sends default events from built-in widgets such as [`refinementList`](/doc/api-reference/widgets/refinement-list/js), [`menu`](/doc/api-reference/widgets/menu/js), etc. You can also change the event payloads, or remove them altogether. * It lets you send custom events from your custom widgets. * It simplifies forwarding events to third-party trackers. If you've been tracking events directly with [`search-insights`](/doc/libraries/search-insights) or with the [`insights`](/doc/api-reference/widgets/insights/js) middleware, follow these steps: 1. Upgrade InstantSearch.js to v4.55.0 or greater 2. [Migrate](#use-the-insights-option) from using the [`insights`](/doc/api-reference/widgets/insights/js) middleware to the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option 3. Either [update](#update-search-insights) or [remove](#remove-search-insights) the [`search-insights`](/doc/libraries/search-insights) library ### Use the Insights option **Starting from v4.55.0, InstantSearch lets you enable event tracking with the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option.** You no longer need to set up the [`insights`](/doc/api-reference/widgets/insights/js) middleware yourself. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // … insights: true, }); ``` If you had already set up the [`insights`](/doc/api-reference/widgets/insights/js) middleware in your code, you can now remove it and move its configuration to the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option. ```diff JavaScript icon=code theme={"system"} - import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares'; const search = instantsearch({ insights: { + insightsInitParams: { + useCookie: false, + }, }, }); - search.use( - createInsightsMiddleware({ - insightsInitParams: { - useCookie: false, - }, - }) - ); ``` ### Update search-insights **Starting from v4.55.0, InstantSearch can load [`search-insights`](/doc/libraries/search-insights) for you so the [`insightsClient`](/doc/api-reference/widgets/insights/js#param-insights-client) option is no longer required.** If you prefer loading it yourself, make sure to update [`search-insights`](/doc/libraries/search-insights) to at least v2.4.0 and forward the reference to [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights). If you're using the UMD bundle with a `script` tag, make sure to update the full code snippet (not just the version): If you're using a package manager, you can upgrade it to the latest version. ```sh Command line icon=square-terminal theme={"system"} npm install search-insights ``` Now you can pass the reference to the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option. ```js UMD theme={"system"} const search = instantsearch({ insights: { insightsClient: window.aa, }, }); ``` ```js Module theme={"system"} import aa from 'search-insights'; const search = instantsearch({ insights: { insightsClient: aa, }, }); ``` Otherwise, you can [remove it and let InstantSearch handle it for you](#remove-search-insights). ### Remove search-insights **Starting from v4.55.0, InstantSearch loads [`search-insights`](/doc/libraries/search-insights) for you from jsDelivr if not detected in the page.** If you've installed [`search-insights`](/doc/libraries/search-insights), you can now remove it. If you're using the UMD bundle with a `script` tag, you can remove the snippet in any page that uses InstantSearch: ```diff HTML icon=code-xml theme={"system"} - ``` If you're using a package manager, you can remove it from your dependencies. ```sh Command line icon=square-terminal theme={"system"} npm uninstall search-insights ``` Then you can remove the reference to [`search-insights`](/doc/libraries/search-insights) from your code: ```diff UMD theme={"system"} const search = instantsearch({ insights: { - insightsClient: window.aa, }, }); ``` ```diff Module theme={"system"} - const aa = require('search-insights'); // or - import aa from 'search-insights'; const search = instantsearch({ insights: { - insightsClient: aa, }, }); ``` > [!NOTE] > > InstantSearch loads [`search-insights`](/doc/libraries/search-insights) from the jsDelivr CDN, which requires that your site or app allows script execution from foreign resources. > Check the [security best practices](/doc/guides/security/security-best-practices) for recommendations. ### No longer needed configuration #### `clickAnalytics: true` ```js JavaScript icon=code theme={"system"} instantsearch.widgets.configure({ clickAnalytics: true, }), ``` This isn't needed anymore because it's automatically added by the middleware. #### `getInsightsAnonymousUserToken` ```js JavaScript icon=code theme={"system"} instantsearch.widgets.configure({ userToken: instantsearch.getInsightsAnonymousUserToken() }); ``` You no longer need this. Once you integrate the `insights` middleware, it initially sets the anonymous `userToken`. As soon as you set another `userToken`, it automatically syncs it between search and analytics calls. #### `insightsClient` at InstantSearch ```js JavaScript icon=code theme={"system"} const search = instantsearch({ searchClient, indexName: 'INDEX_NAME', insightsClient: window.aa, }); ``` Now that you are using the `Insights` middleware, you don't need to pass `aa` to `instantsearch()` anymore. ### `hits` and `infiniteHits` template You might have templates in your [`hits`](/doc/api-reference/widgets/hits/js) or [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widgets that look like the following: ```html HTML icon=code-xml theme={"system"} ``` These are now deprecated. Instead, you can use the `sendEvent` function. ```js JavaScript icon=code theme={"system"} instantsearch.widgets.hits({ container: '#hits', templates: { item(hit, { html, sendEvent, components }) { return html`

    ${components.Highlight({ attribute: 'name', hit })}

    `; } } }) ``` ### `analytics` widget The [`analytics`](/doc/api-reference/widgets/analytics/js) widget has been deprecated in favor of the [`insights`](/doc/api-reference/widgets/insights/js) middleware. When the built-in widgets trigger [default events](/doc/guides/building-search-ui/events/js#default-events), you can intercept them using the [`insights`](/doc/api-reference/widgets/insights/js) middleware's [`onEvent`](/doc/api-reference/widgets/insights/js#param-on-event) hook and send them to third-party trackers. ```js JavaScript icon=code theme={"system"} import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares'; // Or, use `instantsearch.middlewares.createInsightsMiddleware()` const indexName = ''; const search = instantsearch({ indexName, // ... }); const insightsMiddleware = createInsightsMiddleware({ insightsClient: window.aa, onEvent: (event, aa) => { const { insightsMethod, payload, widgetType, eventType } = event; // Send the event to Algolia aa(insightsMethod, payload); // Send click events on Hits to a third-party tracker if (widgetType === 'ais.hits' && eventType === 'click') { thirdPartyTracker.send('Product Clicked', payload); } } }); search.use(insightsMiddleware); ``` If you want to send events every time 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.) or refinements change, you can add a custom middleware to listen to [`uiState`](/doc/api-reference/widgets/ui-state/js) changes with the `onStateChange` function. ```js JavaScript icon=code theme={"system"} const indexName = ''; const search = instantsearch({ indexName, // ... }); const analyticsMiddleware = () => { return { onStateChange({ uiState }) { // Google Analytics (Universal Analytics) window.ga('set', 'page', window.location.pathname + window.location.search); window.ga('send', 'pageView'); // Google Analytics V4 gtag('event', 'page_view', { page_location: window.location.pathname + window.location.search, }); // Google Tag Manager (GTM) // You can use `uiState` to make payloads for third-party trackers. dataLayer.push({ 'event': 'search', 'Search Query': uiState[indexName].query, 'Brand': uiState[indexName].refinementList['brand'].join(','), }); }, subscribe() {}, unsubscribe() {}, } } search.use(analyticsMiddleware); ``` You can also use external libraries like `lodash.debounce` to debounce the events you send. ```js JavaScript icon=code theme={"system"} import debounce from 'lodash.debounce'; const indexName = ''; const search = instantsearch({ indexName, // ... }); const sendEventDebounced = debounce((uiState) => { // Google Analytics window.ga('set', 'page', window.location.pathname + window.location.search); window.ga('send', 'pageView'); // Google Analytics V4 gtag('event', 'page_view', { page_location: window.location.pathname + window.location.search, }); // Google Tag Manager (GTM) dataLayer.push({ 'event': 'search', 'Search Query': uiState[indexName].query, 'Brand': uiState[indexName].refinementList['brand'].join(','), }); }, 3000); const analyticsMiddleware = () => { return { onStateChange({ uiState }) { sendEventDebounced(uiState); }, subscribe() {}, unsubscribe() {}, } } search.use(analyticsMiddleware); ``` ## Upgrade to Recommend The [`relatedProducts`](/doc/api-reference/widgets/related-products/js) widget is now available instead of the previous experimental widget `EXPERIMENTAL_relatedItems`. To migrate, you change the widget name and update the props: ```diff JavaScript icon=code theme={"system"} - import { EXPERIMENTAL_relatedItems } from 'instantsearch.js/es/widgets'; + import { relatedProducts } from 'instantsearch.js/es/widgets'; search.addWidgets([ - EXPERIMENTAL_relatedItems({ - hit: { objectID: '123' }, - }), - hits({ container: '#related-products' }), + relatedProducts({ + container: '#related-products', + objectIDs: ['123'], + }), ]); ``` ## Upgrade from v3 to v4 The latest version of InstantSearch focuses on federated search and bundle size. This required some changes that might impact your app. ### Federated search (multi-index) If you were already using federated search (for example by synchronizing two [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) instances with the [`searchFunction`](/doc/api-reference/widgets/instantsearch/js#param-search-function)), the implementation is now simpler. Instead, try the new [`index`](/doc/api-reference/widgets/index-widget/js) widget, to which you can attach more widgets. For example: ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: 'primary', /* ... */ }); search.addWidgets([ searchBox(), hits(), index({ indexName: 'secondary' }).addWidgets([ searchBox(), hits(), ]) ]); ``` To learn more, see [Multi-index search with InstantSearch.js](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/js) ### Routing Even if you aren't using multi-index search, the way in which [`uiState`](/doc/api-reference/widgets/ui-state/js) is stored has changed. It used to look like this: ```json JSON icon=braces theme={"system"} { "query": "value", "page": 5 } ``` It now looks like this: ```json JSON icon=braces theme={"system"} { "indexName": { "query": "value", "page": 5 } } ``` If you are using the default state mapping ([`simpleStateMapping`](/doc/api-reference/widgets/simple-state-mapping/js)) with the current version, you can replace it with [`singleIndexStateMapping('yourIndexName')`](/doc/api-reference/widgets/single-index-state-mapping/js). You have to change the code as followed: ```diff JavaScript icon=code theme={"system"} instantsearch({ indexName: 'myIndex', routing: { - stateMapping: instantsearch.stateMappings.simple(), + stateMapping: instantsearch.stateMappings.singleIndex('myIndex'), } // ... }); ``` If you are using a custom state mapping, you have to loop over the outer level of the `index` widget and add this extra level to the `routeToState`. You can check the [source](https://github.com/algolia/instantsearch/blob/master/packages/instantsearch.js/src/lib/stateMappings/singleIndex.ts) for a reference on how to implement this. For example, a `stateMapping` that maps a subset of properties would change like this: ```js JavaScript icon=code theme={"system"} // Before const stateMapping = { stateToRoute(uiState) { return { query: uiState.query, page: uiState.page, // ... }; }, routeToState(routeState) { return { query: routeState.query, page: routeState.page, // ... }; }, }; // After const stateMapping = { stateToRoute(uiState) { const indexUiState = uiState[indexName]; return { query: indexUiState.query, page: indexUiState.page, // ... }; }, routeToState(routeState) { return { [indexName]: { query: routeState.query, page: routeState.page, // ... }, }; }, }; ``` #### Configure The [`configure`](/doc/api-reference/widgets/configure/js) widget is now included in [`uiState`](/doc/api-reference/widgets/ui-state/js). If you want to exclude it from the URL you can use the default `stateMapping`s or exclude it in your custom state mapping. A good reason to exclude the configure widget from the UI state is to prevent users from adding any search parameters. You must exclude this widget in both the `stateToRoute`, to keep it from appearing in the URL and `routeToState`, so that the URL doesn't apply to the state. Check the [`stateMapping` source code](https://github.com/algolia/instantsearch/blob/master/packages/instantsearch.js/src/lib/stateMappings/simple.ts) for implementation details. ### Algolia search helper This release includes version 3 of the [`algoliasearch-helper` package](https://www.npmjs.com/package/algoliasearch-helper/). **If you're using the built-in widgets or connectors, nothing changes for you.** This version of `algoliasearch-helper` no longer includes [Lodash](https://lodash.com/), which reduces its bundle size (from 27.5 KB to 9.1 KB Gzipped). If you're using any methods from the `helper`, `searchParameters` or `searchResults`, refer to the package's [change log](https://instantsearchjs.netlify.app/algoliasearch-helper-js/upgrade.html). ### Search parameters The option `searchParameters` was removed from the [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) widget. You can replace it with the [`configure`](/doc/api-reference/widgets/configure/js) widget: ```diff JavaScript icon=code theme={"system"} const search = instantsearch({ // ... - searchParameters: { - hitsPerPage: 5, - }, }); + search.addWidgets([ + instantsearch.widgets.configure({ + hitsPerPage: 5, + }), + ]); ``` You can now add [`initialUiState`](/doc/api-reference/widgets/instantsearch/js#param-initial-ui-state) to your `InstantSearch` widget. This overwrites specific `searchParameters` that are otherwise set during widget instantiation. `initialUiState` is only taken into account if a widget owning that "state" is mounted. A warning appears in development mode explaining which widget needs to be added for the UI state to have an effect. An example is a [`refinementList`](/doc/api-reference/widgets/refinement-list/js) widget: ```diff JavaScript icon=code theme={"system"} const search = instantsearch({ // ... - searchParameters: { - disjunctiveFacets: ['brand'], - disjunctiveFacetsRefinements: { - brand: ['Apple'], - }, - }, + initialUiState: { + refinementList: { + brand: ['Apple'], + }, + }, }); search.addWidgets([ refinementList({ attribute: 'brand' }), ]); // or when there should be no display for this widget: const virtualRefinementList = connectRefinementList(() => null); search.addWidgets([ virtualRefinementList({ attribute: 'brand' }), ]); ``` ### `addWidget` and `removeWidget` The `search.addWidget` function is deprecated in favor of `search.addWidgets`, which accepts an array of widgets. Using arrays makes it clearer to see the structure of nested widgets. `search.removeWidget` is deprecated in favor of `search.removeWidgets`, which also accepts an array of widgets. ### Custom widgets Because version 3 of the `algoliasearch-helper` package is used, you have to replace the `getConfiguration` lifecycle with `getWidgetSearchParameters` and `getWidgetState`. This also means that your custom widget takes part in the routing. You can exclude it from the URL with `stateMapping`. ```js JavaScript icon=code theme={"system"} const widget = { getConfiguration(searchParams) { return { disjunctiveFacets: ['myAttribute'], }; }, getWidgetSearchParameters(searchParameters, { uiState }) { return searchParameters .addDisjunctiveFacetRefinement( 'myAttribute', uiState.myWidgetName.myAttribute ); }, getWidgetState(uiState, { searchParameters }) { return { ...uiState, myWidgetName: { myAttribute: searchParameters.getDisjunctiveRefinements('myAttribute') } }; } }; ``` Becomes: ```js JavaScript icon=code theme={"system"} const widget = { getWidgetSearchParameters(searchParameters, { uiState }) { return searchParameters .addDisjunctiveFacet('myAttribute') .addDisjunctiveFacetRefinement( 'myAttribute', uiState.myWidgetName.myAttribute ); }, getWidgetState(uiState, { searchParameters }) { return { ...uiState, myWidgetName: { myAttribute: searchParameters.getDisjunctiveRefinements('myAttribute') } }; } }; ``` ### `connectAutocomplete` The `indices` option was removed in favor of `index` widgets (see the [federated search section](#federated-search-multi-index)). For example, the following code: ```js JavaScript icon=code theme={"system"} const autocomplete = connectAutocomplete(() => {/* ... */}); search.addWidget( autocomplete({ indices: [{ name: 'additional' }] }) ); ``` Should be replaced with this: ```js JavaScript icon=code theme={"system"} const autocomplete = connectAutocomplete(() => {/* ... */}); search.addWidgets([ index({ indexName: 'additional' }), autocomplete() ]); ``` ### `onHistoryChange` The `onHistoryChange` function is removed. To subscribe to changes in the URL, create a custom `router` and attach to the `write` hook. For example: ```js JavaScript icon=code theme={"system"} const router = historyRouter() const originalWrite = router.write.bind(router) router.write = state => { console.log('listen to route state here'); originalWrite(state) } ``` ### `router` The `dispose` function on the `router` interface is now required and its signature is updated (see the example below). This change only impacts you if you use a custom router. ```ts TypeScript icon=code theme={"system"} interface Router { // ... // Before dispose?({ helper, state }: { helper: AlgoliaSearchHelper, state: SearchParameters }): SearchParameters | void // After dispose(): void } ``` ### `pagination` The `noRefinementRoot` CSS class gets added once there are no more possible refinements. It no longer gets applied on the first page, which was the wrong behavior. ## Upgrade from v2 to v3 InstantSearch v3 introduces some breaking changes in the widgets' naming, options, and markup. ### What's new #### Search from your own search client Since InstantSearch.js 2.8.0, it's possible to [search from your own backend](/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-instantsearch/js) using the [`searchClient`](/doc/api-reference/widgets/instantsearch/js#param-search-client) option. This option is now the way to plug InstantSearch to either Algolia's [official JavaScript client](https://github.com/algolia/algoliasearch-client-javascript) or to yours. This means that the official search client isn't bundled in InstantSearch, allowing for more flexibility. This results in a smaller bundle size when you're not searching directly from the frontend, or that you use multiple instances of the search client. #### Predictable DOM structure InstantSearch.js v3 becomes more predictable by generating a DOM that follows Algolia's InstantSearch.css specification. All flavors are now aligned, which makes it easier to migrate from one library flavor to another. The options and the DOM output are similar whether you use React InstantSearch, Vue InstantSearch, or InstantSearch.js. #### Optimize InstantSearch based on your environment The new bundle comes in two forms: * **Development bundle.** This is a heavier bundle that helps developers better debug in the development phase of an InstantSearch app. It's more verbose and gives clues and warnings about the library usage. * **Production bundle.** This is a lighter bundle that doesn't include any development warnings. Use this when deploying your app in a production environment. ### Imports #### UMD (Universal Module Definition) New naming has been introduced for the UMD imports. This makes it clearer which InstantSearch.js bundle to use either in development or in production. The production bundle will get lighter over time as it won't include the runtime warnings and documentation. ##### Before ```txt theme={"system"} dist ├── instantsearch.js ├── instantsearch.js.map ├── instantsearch.min.js └── instantsearch.min.js.map ``` ##### After ```txt theme={"system"} dist ├── instantsearch.development.js ├── instantsearch.development.js.map ├── instantsearch.production.min.js └── instantsearch.production.min.js.map ``` #### CommonJS (CJS) ##### Before ```js JavaScript icon=code theme={"system"} const instantsearch = require('instantsearch.js'); ``` ##### After ```js JavaScript icon=code theme={"system"} const instantsearch = require('instantsearch.js').default; ``` #### ES (ECMAScript) No changes. ### InstantSearch The variables `appId` and `apiKey` are replaced by `searchClient`. #### Earlier usage 1. [Import `InstantSearch.js`](/doc/guides/building-search-ui/installation/js) 2. Initialize InstantSearch ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: 'indexName', appId: 'appId', apiKey: 'apiKey', }); search.start(); ``` ##### New usage 1. [Import `algoliasearch`](/doc/libraries/sdk/install) (prefer the [lite version](https://github.com/algolia/algoliasearch-client-javascript#search-onlylite-client) for search only) 2. [Import `InstantSearch.js`](/doc/guides/building-search-ui/installation/js) 3. Initialize InstantSearch with the `searchClient` option ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: 'indexName', searchClient: algoliasearch('appId', 'apiKey'), }); search.start(); ``` #### `transformData` is replaced by `transformItems` Since InstantSearch.js first public release, you can customize the values used in the widgets. This method was letting you map 1-1 the values with other values. With React InstantSearch, a slightly different API was implemented that lets you map over the list of values and to change their content. ##### Earlier usage ```js JavaScript icon=code theme={"system"} search.addWidgets([ instantsearch.widget.refinementList({ container: '#facet', attributeName: 'facet', transformData: { item: data => { data.count = 0; return data; }, }, })] ); ``` ##### New usage ```js JavaScript icon=code theme={"system"} search.addWidgets([ instantsearch.widget.refinementList({ container: '#facet', attribute: 'facet', transformItems: items => items.map(item => ({ ...item, count: 0, })), }) ]); ``` #### Highlights and snippets Highlighting is a powerful tool for showing users why results match their queries. Earlier versions of InstantSearch used internal mechanisms or Hogan.js to implement highlighting within widget templates. Algolia now provides `html` tagged templates. For more information, see: * [Highlights and snippets](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) * [`html` tagged templates](#upgrade-templates) ##### Earlier usage ```js string theme={"system"} search.addWidgets([ instantsearch.widget.hits({ container: '#hits', templates: { item: '{{{ _highlightResult.name.value }}}', }, }) ]); ``` ```js Hogan.js theme={"system"} search.addWidgets([ instantsearch.widget.hits({ container: '#hits', templates: { item: '{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}', }, }) ]); ``` ##### New usage ```js JavaScript icon=code theme={"system"} search.addWidgets([ instantsearch.widget.hits({ container: '#hits', templates: { item: (hit, { components }) => components.Highlight({ attribute: 'name', hit }), }, }) ]); ``` #### `urlSync` is dropped If you were using the `urlSync` option, migrate to the new [`routing`](/doc/api-reference/widgets/instantsearch/js#param-routing) feature. Here are the elements you need to migrate: * `urlSync: true` becomes `routing: true` * `threshold` becomes `routing: { router: instantsearch.routers.history({ writeDelay: 400 }) }` * `mapping` and `trackedParameters` are replaced with `stateMapping`. Read "[User friendly URLs](/doc/guides/building-search-ui/going-further/routing-urls/js#seo-friendly-urls)" to know how to configure it * `useHash` is removed but can be achieved using an advanced configuration of the [history router](/doc/guides/building-search-ui/going-further/routing-urls/js) * `getHistoryState` is removed but can be achieved using an advanced configuration of the [history router](/doc/guides/building-search-ui/going-further/routing-urls/js) #### `collapsible` is dropped `collapsible` is replaced by the [`collapsed`](/doc/api-reference/widgets/panel/js#param-collapsed) option in the [`panel`](/doc/api-reference/widgets/panel/js) widget. #### `autoHideContainer` is dropped `autoHideContainer` is replaced by the [`hidden`](/doc/api-reference/widgets/panel/js#param-hidden) option in the [`panel`](/doc/api-reference/widgets/panel/js) widget. #### `createAlgoliaClient` is dropped `createAlgoliaClient` is replaced by `searchClient`. #### `createQueryString` is dropped URL synchronization is done with Routing alone. ### Widgets #### Breadcrumb ##### CSS classes | Before | After | | - | - | | `ais-breadcrumb` | `ais-Breadcrumb` | | | `ais-Breadcrumb--noRefinement` | | `ais-breadcrumb` | `ais-Breadcrumb-list` | | `ais-breadcrumb--separator` | `ais-Breadcrumb-separator` | | `ais-breadcrumb--label` | `ais-Breadcrumb-link` | | `ais-breadcrumb--disabledLabel` | | | | `ais-Breadcrumb-item` | | | `ais-Breadcrumb-item--selected` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    ``` #### ClearRefinements (formerly ClearAll) ##### Options | Before | After | | - | - | | `excludeAttributes` | `excludedAttributes` | ##### CSS classes | Before | After | | - | - | | `ais-clear-all` | `ais-ClearRefinements` | | `ais-clear-all--body` | | | `ais-clear-all--link` | | | | `ais-ClearRefinements-button` | | | `ais-ClearRefinements-button--disabled` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    ``` #### CurrentRefinements (formerly CurrentRefinedValues) ##### Options | Before | After | | - | - | | `attributes` | `includedAttributes` | | | `excludedAttributes` | | `onlyListedAttributes` | use `includedAttributes` | | `clearsQuery` | use `excludedAttributes` | | `clearAll` | use the `clearRefinements` widget | * `clearsQuery` can be replaced by `excludedAttributes: []`. ##### CSS classes | Before | After | | - | - | | `ais-current-refined-values` | `ais-CurrentRefinements` | | `ais-current-refined-values--list` | `ais-CurrentRefinements-list` | | `ais-current-refined-values--item` | `ais-CurrentRefinements-item` | | `ais-current-refined-values--link` | `ais-CurrentRefinements-label` | | `ais-current-refined-values--count` | | | | `ais-CurrentRefinements-category` | | | `ais-CurrentRefinements-categoryLabel` | | | `ais-CurrentRefinements-delete` | | | `ais-CurrentRefinements-query` | | `ais-current-refined-values--clear-all` | | ##### Markup ###### Default ```html HTML icon=code-xml theme={"system"}
    • Category: Movies & TV Shows Others
    ``` ###### With `includesQuery` and a query ```html HTML icon=code-xml theme={"system"}
    • Category: Movies & TV Shows Others
    • Query: My query
    ``` #### Geographical search ##### Options | Before | After | | - | - | | `customHTMLMarker.template` | `templates.HTMLMarker` | | `paddingBoundingBox` | Removed | | `enableGeolocationWithIP` | Removed - use the Configure widget instead (see below) | | `position` | Removed - use the Configure widget instead (see below) | | `radius` | Removed - use the Configure widget instead (see below) | | `precision` | Removed - use the Configure widget instead (see below) | Since `paddingBoundingBox` conflicted with the `routing` option it was removed to support URLSync for the GeoSearch widget. ##### `enableGeolocationWithIP` **Before:** ```js JavaScript icon=code theme={"system"} instantsearch.widgets.geoSearch({ googleReference: window.google, enableGeolocationWithIP: true, container, }); ``` **After:** ```js JavaScript icon=code theme={"system"} instantsearch.widgets.configure({ aroundLatLngViaIP: true, }); instantsearch.widgets.geoSearch({ googleReference: window.google, container, }); ``` ##### `position` **Before:** ```js JavaScript icon=code theme={"system"} instantsearch.widgets.geoSearch({ googleReference: window.google, position: { lat: 40.71, lng: -74.01 }, container, }); ``` **After:** ```js JavaScript icon=code theme={"system"} instantsearch.widgets.configure({ aroundLatLng: '40.71, -74.01', }); instantsearch.widgets.geoSearch({ googleReference: window.google, container, }); ``` ##### `radius` **Before:** ```js JavaScript icon=code theme={"system"} instantsearch.widgets.geoSearch({ googleReference: window.google, radius: 1000, container, }); ``` **After:** ```js JavaScript icon=code theme={"system"} instantsearch.widgets.configure({ aroundRadius: 1000, }); instantsearch.widgets.geoSearch({ googleReference: window.google, container, }); ``` ##### `precision` **Before:** ```js JavaScript icon=code theme={"system"} instantsearch.widgets.geoSearch({ googleReference: window.google, precision: 1000, container, }); ``` **After:** ```js JavaScript icon=code theme={"system"} instantsearch.widgets.configure({ aroundPrecision: 1000, }); instantsearch.widgets.geoSearch({ googleReference: window.google, container, }); ``` ##### CSS classes | Before | After | | - | - | | `ais-geo-search` | `ais-GeoSearch` | | `ais-geo-search--map` | `ais-GeoSearch-map` | | `ais-geo-search--controls` | `ais-GeoSearch-tree` | | `ais-geo-search--control` | `ais-GeoSearch-control` | | `ais-geo-search--toggle-label` | `ais-GeoSearch-label` | | `ais-geo-search--toggle-label-active` | `ais-GeoSearch-label--selected` | | `ais-geo-search--toggle-input` | `ais-GeoSearch-input` | | `ais-geo-search--redo` | `ais-GeoSearch-redo` | | | `ais-GeoSearch-redo--disabled` | | `ais-geo-search--clear` | `ais-GeoSearch-reset` | ##### Markup With the control element: ```html HTML icon=code-xml theme={"system"}
    ``` With the redo button: ```html HTML icon=code-xml theme={"system"}
    ``` #### `Hits` ##### Options | Before | After | | - | - | | `escapeHits` | `escapeHTML` | | `showMoreLabel` | `loadMoreLabel` | * `escapeHTML` becomes `true` by default. * `allItems` template has been removed in favor of `connectHits` ##### CSS classes | Before | After | | - | - | | `ais-hits` | `ais-Hits` | | `ais-hits--empty` | `ais-Hits--empty` | | | `ais-Hits--list` | | `ais-hits--item` | `ais-Hits--item` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    1. Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    2. Hit 4397400: Google - Chromecast - Black
    3. Hit 4397400: Google - Chromecast - Black
    4. Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    5. Hit 4397400: Google - Chromecast - Black
    6. Hit 4397400: Google - Chromecast - Black
    7. Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    8. Hit 4397400: Google - Chromecast - Black
    ``` #### `HitPerPage` (formerly `HitsPerPageSelector`) ##### Options | Before | After | | - | - | | `attributeName` | `attribute` | ##### CSS classes | Before | After | | - | - | | | `ais-HitsPerPage` | | `ais-hits-per-page-selector` | `ais-HitsPerPage-select` | | `ais-hits-per-page--item` | `ais-HitsPerPage-option` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    ``` #### Infinite hits ##### Options | Before | After | | - | - | | `escapeHits` | `escapeHTML` | | `loadMoreLabel` | `templates.showMoreText` | * `escapeHTML` defaults to `true` ##### CSS classes | Before | After | | - | - | | `ais-infinite-hits` | `ais-InfiniteHits` | | `ais-infinite-hits--empty` | `ais-InfiniteHits--empty` | | | `ais-InfiniteHits--list` | | `ais-infinite-hits--item` | `ais-InfiniteHits--item` | | `ais-infinite-hits--showmore` | | | `ais-infinite-hits--showmoreButton` | `ais-InfiniteHits-loadMore` | | | `ais-InfiniteHits-loadMore--disabled` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    1. Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    2. Hit 4397400: Google - Chromecast - Black
    3. Hit 4397400: Google - Chromecast - Black
    4. Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    5. Hit 4397400: Google - Chromecast - Black
    6. Hit 4397400: Google - Chromecast - Black
    7. Hit 5477500: Amazon - Fire TV Stick with Alexa Voice Remote - Black
    8. Hit 4397400: Google - Chromecast - Black
    ``` #### Hierarchical menu ##### Options | Before | After | | - | - | | | `showMore` | | | `showMoreLimit` | ##### CSS classes | Before | After | | - | - | | `ais-hierarchical-menu` | `ais-HierarchicalMenu` | | | `ais-HierarchicalMenu--noRefinement` | | | `ais-HierarchicalMenu-searchBox` | | `ais-hierarchical-menu--list` | `ais-HierarchicalMenu-list` | | | `ais-HierarchicalMenu-list--child` | | | `ais-HierarchicalMenu-list--lvl0` | | | `ais-HierarchicalMenu-list--lvl1` | | `ais-hierarchical-menu--item` | `ais-HierarchicalMenu-item` | | `ais-hierarchical-menu--item__active` | `ais-HierarchicalMenu-item--selected` | | `ais-hierarchical-menu--item__parent` | `ais-HierarchicalMenu-item--parent` | | `ais-hierarchical-menu--link` | `ais-HierarchicalMenu-link` | | `ais-hierarchical-menu--label` | `ais-HierarchicalMenu-label` | | `ais-hierarchical-menu--count` | `ais-HierarchicalMenu-count` | | `ais-hierarchical-menu--noResults` | `ais-HierarchicalMenu-noResults` | | | `ais-HierarchicalMenu-showMore` | | | `ais-HierarchicalMenu-showMore--disabled` | ##### Markup ###### Default ```html HTML icon=code-xml theme={"system"} ``` ###### Show more disabled ```html HTML icon=code-xml theme={"system"} ``` #### `Menu` ##### Options | Before | After | | - | - | | `attributeName` | `attribute` | | `showMore.limit` | `showMoreLimit` | | `showMore.templates.active` | `templates.showMoreText` | | `showMore.templates.inactive` | `templates.showMoreText` | * `showMore` is now a boolean option (`showMore.templates` are now in `templates`) * `sortBy` defaults to `['isRefined', 'name:asc']` * An object containing `isShowingMore` is passed to `showMoreText` template to toggle between the two states: ```js JavaScript icon=code theme={"system"} { showMoreText: ` {{#isShowingMore}} Show less {{/isShowingMore}} {{^isShowingMore}} Show more {{/isShowingMore}} ` } ``` ##### CSS classes | Before | After | | - | - | | `ais-menu` | `ais-Menu` | | `ais-menu--list` | `ais-Menu-list` | | `ais-menu--item` | `ais-Menu-item` | | `ais-menu--item__active` | `ais-Menu-item--selected` | | `ais-menu--link` | `ais-Menu-link` | | | `ais-Menu-label` | | `ais-menu--count` | `ais-Menu-count` | | | `ais-Menu-noResults` | | | `ais-Menu-showMore` | | | `ais-Menu-showMore--disabled` | ##### Markup ###### Default ```html HTML icon=code-xml theme={"system"} ``` ###### Show more disabled ```html HTML icon=code-xml theme={"system"} ``` #### Menu select ##### Options | Before | After | | - | - | | `attributeName` | `attribute` | ##### CSS classes | Before | After | | - | - | | `ais-menu-select` | `ais-MenuSelect` | | | `ais-MenuSelect--noRefinement` | | `ais-menu-select--select` | `ais-MenuSelect-select` | | `ais-menu-select--option` | `ais-MenuSelect-option` | | `ais-menu-select--header` | | | `ais-menu-select--footer` | | ##### Markup ```html HTML icon=code-xml theme={"system"}
    ``` #### `NumericMenu` (formerly `NumericRefinementList`) ##### Options | Before | After | | - | - | | `attributeName` | `attribute` | | `options` | `items` | The item `name` attribute is now named `label`. ##### CSS classes | Before | After | | - | - | | `ais-refinement-list` | `ais-NumericMenu` | | | `ais-NumericMenu--noRefinement` | | `ais-refinement-list--list` | `ais-NumericMenu-list` | | `ais-refinement-list--item` | `ais-NumericMenu-item` | | `ais-refinement-list--item__active` | `ais-NumericMenu-item--selected` | | `ais-refinement-list--label` | `ais-NumericMenu-label` | | `ais-refinement-list--radio` | `ais-NumericMenu-radio` | | | `ais-NumericMenu-labelText` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    ``` #### `NumericSelector` Widget removed. #### `Pagination` ##### Options | Before | After | | - | - | | `maxPages` | `totalPages` | | `showFirstLast` | `showFirst` `showLast` | | | `showNext` | | | `showPrevious` | | `labels` | `templates` | | `labels.previous` | `templates.previous` | | `labels.next` | `templates.next` | | `labels.first` | `templates.first` | | `labels.last` | `templates.last` | ##### CSS classes | Before | After | | - | - | | | `ais-Pagination` | | | `ais-Pagination--noRefinement` | | `ais-pagination` | `ais-Pagination-list` | | `ais-pagination--item` | `ais-Pagination-item` | | `ais-pagination--item__first` | `ais-Pagination-item--firstPage` | | `ais-pagination--item__last` | `ais-Pagination-item--lastPage` | | `ais-pagination--item__previous` | `ais-Pagination-item--previousPage` | | `ais-pagination--item__next` | `ais-Pagination-item--nextPage` | | | `ais-Pagination-item--page` | | `ais-pagination--item__active` | `ais-Pagination-item--selected` | | `ais-pagination--item__disabled` | `ais-Pagination-item--disabled` | | `ais-pagination--link` | `ais-Pagination-link` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    ``` #### `PriceRanges` Widget removed. #### `RangeInput` ##### Options | Before | After | | - | - | | `attributeName` | `attribute` | | `labels` | `templates` | | `labels.separator` | `templates.separatorText` | | `labels.submit` | `templates.submitText` | ##### CSS classes | Before | After | | - | - | | `ais-range-input` | `ais-RangeInput` | | | `ais-RangeInput--noRefinement` | | `ais-range-input--body` | | | `ais-range-input--form` | `ais-RangeInput-form` | | `ais-range-input--fieldset` | | | | `ais-RangeInput-label` | | `ais-range-input--labelMin` | | | `ais-range-input--labelMax` | | | | `ais-RangeInput-input` | | `ais-range-input--inputMin` | `ais-RangeInput-input--min` | | `ais-range-input--inputMax` | `ais-RangeInput-input--max` | | `ais-range-input--separator` | `ais-RangeInput-separator` | | `ais-range-input--submit` | `ais-RangeInput-submit` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    to
    ``` #### `RangeSlider` ##### Options | Before | After | | - | - | | `attributeName` | `attribute` | ##### CSS classes | Before | After | | - | - | | `ais-range-slider` | `ais-RangeSlider` | | `ais-range-slider--disabled` | `ais-RangeSlider--disabled` | | `ais-range-slider--handle` | `ais-RangeSlider-handle` | | `ais-range-slider--tooltip` | `ais-RangeSlider-tooltip` | | `ais-range-slider--value` | `ais-RangeSlider-value` | | `ais-range-slider--marker-horizontal` | `ais-RangeSlider-marker--horizontal` | | `ais-range-slider--marker-large` | `ais-RangeSlider-marker--large` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    $750
    $5,000
    1
    2,500
    5,000
    ``` #### `RatingMenu` (formerly `StarRating`) ##### Options | Before | After | | - | - | | `attributeName` | `attribute` | | `labels.andUp` | Removed | The value for the label `andUp` is now inline inside `templates.item`. ##### CSS classes | Before | After | | - | - | | `ais-star-rating` | `ais-RatingMenu` | | `ais-star-rating--list` | `ais-RatingMenu-list` | | `ais-star-rating--item` | `ais-RatingMenu-item` | | `ais-star-rating--item__active` | `ais-RatingMenu-item--selected` | | `ais-star-rating--item__disabled` | `ais-RatingMenu-item--disabled` | | `ais-star-rating--link` | `ais-RatingMenu-link` | | `ais-star-rating--star` | `ais-RatingMenu-starIcon` | | | `ais-RatingMenu-starIcon--full` | | `ais-star-rating--star__empty` | `ais-RatingMenu-starIcon--empty` | | `ais-star-rating--count` | `ais-RatingMenu-count` | ##### Markup ```html HTML icon=code-xml theme={"system"} ``` #### `RefinementList` ##### Options | Before | After | | - | - | | `attributeName` | `attribute` | | `searchForFacetValues` | `searchable` | | `searchForFacetValues.placeholder` | `searchablePlaceholder` | | `searchForFacetValues.isAlwaysActive` | `searchableIsAlwaysActive` | | `searchForFacetValues.escapeFacetValues` | `searchableEscapeFacetValues` | | `searchForFacetValues.templates.noResults` | `templates.searchableNoResults` | | `showMore.templates.active` | `templates.showMoreText` | | `showMore.templates.inactive` | `templates.showMoreText` | * `searchablePlaceholder` defaults to `"Search..."` * `searchableEscapeFacetValues` defaults to `true` * `searchableIsAlwaysActive` defaults to `true` * `showMore` is now a boolean option (`searchForFacetValues.templates` and `showMore.templates` are now in `templates`) * An object containing `isShowingMore` is passed to `showMoreText` template to toggle between the two states: ```js JavaScript icon=code theme={"system"} { showMoreText: ` {{#isShowingMore}} Show less {{/isShowingMore}} {{^isShowingMore}} Show more {{/isShowingMore}} ` } ``` ##### CSS classes | Before | After | | - | - | | `ais-refinement-list` | `ais-RefinementList` | | | `ais-RefinementList--noRefinement` | | | `ais-RefinementList-noResults` | | `ais-refinement-list--header` | | | `ais-refinement-list--body` | | | `ais-refinement-list--footer` | | | `ais-refinement-list--list` | `ais-RefinementList-list` | | `ais-refinement-list--item` | `ais-RefinementList-item` | | `ais-refinement-list--item__active` | `ais-RefinementList-item--selected` | | `ais-refinement-list--label` | `ais-RefinementList-label` | | `ais-refinement-list--checkbox` | `ais-RefinementList-checkbox` | | | `ais-RefinementList-labelText` | | `ais-refinement-list--count` | `ais-RefinementList-count` | | | `ais-RefinementList-showMore` | | | `ais-RefinementList-showMore--disabled` | ##### Markup ###### Default ```html HTML icon=code-xml theme={"system"}
    ``` ###### Show more disabled ```html HTML icon=code-xml theme={"system"}
    ``` ###### With search and no results ```html HTML icon=code-xml theme={"system"}
    No results.
    ``` #### `SearchBox` ##### Options | Before | After | | - | - | | `poweredBy` | use the `poweredBy` widget | | `wrapInput` | use the `connectSearchBox` connector | | `searchOnEnterKeyPressOnly` | `searchAsYouType` (default: `true`) | | `reset` | `showReset` | | `magnifier` | `showSubmit` | | `loadingIndicator` | `showLoadingIndicator` (default: `true`) | With the drop of `wrapInput`, it was decided not to accept `input`s as containers anymore. If you want complete control over the rendering, use the [`connectSearchBox`](/doc/api-reference/widgets/search-box/js#customize-the-ui-with-connectsearchbox) connector. The search box doesn't support [`poweredBy`](/doc/api-reference/widgets/powered-by/js). [Algolia requires](https://www.algolia.com/policies/free-services-terms/) that you use this widget if you're on a community plan (open source, not-for-profit, or [DocSearch](https://docsearch.algolia.com/)). Configuration options for `reset`, `submit` and `loadingIndicator` have been moved to `templates` and `cssClasses`. For example, for `reset`: * `reset.template` => `templates.reset` * `reset.cssClasses.root` => `cssClasses.reset` `autofocus` is now set to `false` by default and doesn't support the `"auto"` value anymore. ##### CSS classes | Before | After | | - | - | | `ais-search-box` | `ais-SearchBox` | | | `ais-SearchBox-form` | | `ais-search-box--input` | `ais-SearchBox-input` | | `ais-search-box--magnifier-wrapper` | | | `ais-search-box--magnifier` | `ais-SearchBox-submit` | | | `ais-SearchBox-submitIcon` | | `ais-search-box--reset-wrapper` | | | `ais-search-box--reset` | `ais-SearchBox-reset` | | | `ais-SearchBox-resetIcon` | | `ais-search-box--loading-indicator-wrapper` | | | `ais-search-box--loading-indicator` | `ais-SearchBox-loadingIndicator` | | | `ais-SearchBox-loadingIcon` | ##### Markup ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectChat` Use `connectChat` to create a chat widget with your own rendering logic. It passes the chat state and actions to your render function. If you don't provide a chat instance, the connector creates one. Import it from the package manager build. `connectChat` isn't available from the CDN bundle. ```js Package manager theme={"system"} import { connectChat } from 'instantsearch.js/es/connectors'; ``` ```js JavaScript icon=code expandable theme={"system"} const container = document.querySelector('#chat'); const renderChat = (renderOptions, isFirstRender) => { const { messages, input, setInput, sendMessage, status, stop, clearError } = renderOptions; if (isFirstRender) { container.innerHTML = `
    `; container.querySelector('input').addEventListener('input', (event) => { setInput(event.target.value); }); container.querySelector('form').addEventListener('submit', (event) => { event.preventDefault(); const prompt = container.querySelector('input').value.trim(); if (prompt && !container.querySelector('form button').disabled) { clearError(); sendMessage({ text: prompt }); setInput(''); container.querySelector('input').value = ''; } }); container.querySelector('[data-stop]').addEventListener('click', () => { stop(); }); } container.querySelector('[data-messages]').replaceChildren( ...messages.map((message) => { const item = document.createElement('p'); item.textContent = `${message.role}: ${message.parts .filter((part) => part.type === 'text') .map((part) => part.text) .join(' ')}`; return item; }) ); container.querySelector('input').value = input; container.querySelector('form button').disabled = !input.trim() || (status !== 'ready' && status !== 'error'); container.querySelector('[data-stop]').hidden = status !== 'streaming'; }; const customChat = connectChat(renderChat); search.addWidgets([ customChat({ agentId: 'AGENT_ID', disableTriggerValidation: true, }), ]); ``` This example displays the chat inline, so it sets `disableTriggerValidation: true`. If your custom panel has an opener that doesn't use an InstantSearch chat entry point, set this option to `true` too. Supported entry points include the [`chatTrigger`](/doc/api-reference/widgets/chat-trigger/js) widget, a custom widget built with `connectChatTrigger`, and AI mode on `searchBox` or `autocomplete`. ### Create a render function `connectChat(renderFn, unmountFn?)` takes a render function and an optional cleanup function. InstantSearch calls `renderFn(renderOptions, isFirstRender)` at initialization and when the chat state or search results change. `isFirstRender` is `true` for the initial call. InstantSearch calls `unmountFn` when it removes the widget. #### Render options * `id` (`string`): conversation identifier. * `messages` (`UIMessage[]`): conversation messages, including streamed message parts. * `status` (`'ready' | 'submitted' | 'streaming' | 'error'`): request status. Clear an error with `clearError` before sending another message. * `error` (`Error | undefined`): last request error. * `input` (`string`): current text in the message input. Sending a message doesn't clear this value automatically. * `setInput(input: string)`: update the text in the message input. * `open` (`boolean`), `setOpen(open: boolean)`, and `focusInput()`: panel state and controls. `focusInput` opens the panel and requests prompt focus. Handle DOM focus in a custom interface. * `sendMessage(message, options?)`: add a user message and request an assistant response. For text, pass `{ text: 'MESSAGE' }`. Returns `Promise`. * `regenerate(options?)`: regenerate an assistant response. Pass `{ messageId }` to select a message, or omit it to regenerate the latest assistant message. Returns `Promise`. * `stop()`: stop the active response. The `onFinish` callback receives `isAbort: true`. * `resumeStream(options?)`: reconnect to an active response. Set [`resume: true`](#param-resume) to resume when the connector initializes. * `clearError()`: clear the last request error. * `setMessages(messagesOrUpdater)`: replace the local message list, or update it with a function. Call `regenerate` to request a response after editing messages. * `clearMessages()`: stop an active response, clear messages and feedback, and start a new conversation ID. * `adoptConversation({ id, messages, source })`: take over a conversation started by another widget, such as `resultCard`. An empty chat continues the conversation under its `id`. A chat with messages appends the new messages to its own conversation. The adopted messages get `metadata.source` set to `source`. Returns `false` and adopts nothing while the chat is generating a response. * `addToolResult(options)`: submit the result of a client-side tool call. Pass `tool` and `toolCallId`, with either `output` or `state: 'output-error'` and `errorText`. * `tools` (`ClientSideTools`): configured tools with `addToolResult` and search helpers bound for rendering. * `records` (`ChatRecordsStore`): records returned by chat tools, keyed by `objectID`. * `suggestions` (`string[] | undefined`): suggestions from the last assistant message's `data-suggestions` part. * `sendChatMessageFeedback(messageId, vote)`: send feedback on an assistant message. Pass `0` for negative feedback or `1` for positive feedback. Available only when you provide an `agentId` and set `feedback: true`. * `feedbackState` (`Record`): feedback state by message ID. * `indexUiState` and `setIndexUiState(nextUiState)`: read or update the parent index UI state. * `sendEvent(...)`: send a chat event with the InstantSearch Insights middleware. `sendMessage`, `regenerate`, and `resumeStream` accept request options for `headers`, `body`, and `metadata`. The render options also include `instantSearchInstance` and `widgetParams`. ## Streaming and resumption Assistant responses stream over [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). The chat widget exposes the streaming lifecycle through its connector render state if you want to drive a custom UI. ### Status values The render state exposes `status`, which can be one of: * `'ready'`. The chat is idle and ready to accept a new message. * `'submitted'`. A user message was submitted and the assistant hasn't started responding yet. * `'streaming'`. The assistant is streaming a response. * `'error'`. The last response finished with an error. Call `clearError()` before sending a new message. Each message part also carries a `state` of `'streaming'` or `'done'` while the response streams. ### Stop and resume The render state exposes two methods: * `stop()`. Aborts the current streaming response. The `onFinish` callback runs with `isAbort: true`. * `resumeStream()`. Reconnects to an in-flight stream. Call this on mount, or pass [`resume: true`](#param-resume) to do it automatically. To customize the stop behavior with `connectChat`, read `status` and `stop` from the render options: ```js JavaScript icon=code theme={"system"} const renderChat = (renderOptions, isFirstRender) => { const { status, stop } = renderOptions; if (isFirstRender) { const button = document.createElement('button'); button.textContent = 'Stop'; button.addEventListener('click', () => stop()); document.querySelector('#chat').appendChild(button); } document.querySelector('#chat button').hidden = status !== 'streaming'; }; ``` To resume an ongoing stream after a reload, set [`resume: true`](#param-resume) on the widget, or call `resumeStream()` from a custom render function. # Chat Source: https://www.algolia.com/doc/api-reference/widgets/chat/react Displays a chat interface to interact with a generative AI assistant built with Algolia Agent Studio. For more information, see [Agent Studio](/doc/guides/algolia-ai/agent-studio). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { Chat } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=chat) > > Preview this widget and its behavior. ## About this widget `Chat` is a widget to display a chat interface that interacts with a generative AI assistant. See also: [Agent Studio](/doc/guides/algolia-ai/agent-studio) > [!NOTE] > > `Chat` renders the chat panel but doesn't provide a way to open it. > Add one entry point to the same `InstantSearch` instance: the [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react), [`PromptSuggestions`](/doc/api-reference/widgets/prompt-suggestions/react), [`CompareBar`](/doc/api-reference/widgets/compare-bar/react), or [`ResultCard`](/doc/api-reference/widgets/result-card/react) widget, [AI mode](/doc/guides/building-search-ui/going-further/chat-customization/ai-search-experience/react) on a `SearchBox` or autocomplete, or an [inline layout](#param-layout-component). > Otherwise, the widget logs a development warning. > To silence it, set [`disableTriggerValidation`](#param-disable-trigger-validation) to `true`. ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Chat, ChatTrigger } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **agentId** `string` The unique identifier of the agent to connect to. You can find the `agentId` in the [Agent Studio dashboard](https://dashboard.algolia.com/generativeAi/agent-studio/agents). ```jsx JavaScript icon=code theme={"system"} ``` **feedback** `boolean` Whether to enable feedback (thumbs up/down) on assistant messages. Only available when using `agentId`. ```jsx JavaScript icon=code theme={"system"} ``` **requestOptions** `object` Request options to send with the built-in Agent Studio completion requests. Use this option only with `agentId`. It isn't available with a custom `transport` or custom `chat` instance. The object accepts the following properties: * `queryParameters` (`Record`). Query parameters to append to each completion request. * `headers` (`Record | Headers`). Headers to send with each completion request. InstantSearch always keeps `compatibilityMode=ai-sdk-5`, even if you set `compatibilityMode` in `queryParameters`. Custom headers can't override the required Algolia identity headers (`x-algolia-application-id`, `x-algolia-api-key`) or the `x-algolia-agent` header that identifies InstantSearch. When users regenerate an assistant message, InstantSearch sends `cache=false` even if `requestOptions.queryParameters.cache` is `true`. ```jsx JavaScript icon=code theme={"system"} ``` **transport** `HttpChatTransportInitOptions` A custom transport object to handle the communication between the chat widget and the agent. The API endpoint must be compatible with [Vercel AI SDK 5](https://ai-sdk.dev/docs/ai-sdk-ui/transport#custom-transport-configuration). If you also set [`agentId`](#param-agent-id), the widget sends messages through the custom transport and still uses `agentId` for [`feedback`](#param-feedback) and for click and conversion events from tool results. ```jsx JavaScript icon=code theme={"system"} ; ``` ```ts HttpChatTransportInitOptions icon="file-type" theme={"system"} type HttpChatTransportInitOptions = { api?: string; credentials?: Resolvable; headers?: Resolvable | Headers>; body?: Resolvable; fetch?: FetchFunction; // ... }; ``` **getSearchPageURL** `(nextUiState: object) =\> string` A function to return the URL of the main search page with the `nextUiState`. This is used to go to the main search page when the user clicks on "View all" in the search tool. ```jsx JavaScript icon=code theme={"system"} { return `/search?${qs.stringify(nextUiState)}`; }} /> ``` **tools** `Record\` An object defining the client-side tools that the agent can use to interact with your application. The object's keys are tool names that must match the tools defined in the [Agent Studio dashboard](/doc/guides/algolia-ai/agent-studio/how-to/dashboard#client-side-tools). The widget includes default renderers for the search, grouped results, and product comparison tools. Import the exported tool constants and use them as keys to customize the default rendering: * `SearchIndexToolType` (`'algolia_search_index'`). Displays search results from an Algolia index in a carousel. * `GroupedResultsToolType` (`'algolia_grouped_results'`). Displays grouped results from the tool's streaming input in carousels. * `CompareProductsToolType` (`'algolia_compare_products'`). Displays a comparison table with one column per product and one row per comparison criterion. The agent provides product object IDs, comparison criteria, and optional introductory text. Each criterion includes a label and one value per product. The widget builds each product header from the selected product and search result records in the conversation. If you provide an `itemComponent`, the widget uses it to render the header. Otherwise, it uses the record's `name`, `title`, or `objectID`. Products without records and missing comparison values appear as `—`. To add `algolia_compare_products` to your agent as a client-side tool, enable a comparison configuration in Agent Studio. If you manage the agent through the API, add the tool to the agent's `tools` yourself. For more information, see [Compare products in chat](/doc/guides/algolia-ai/agent-studio/how-to/product-comparison#render-a-comparison-table-from-product-records). The widget matches an override against the exact tool name. If your agent can still send the deprecated name, register your override under both constants. ```jsx JavaScript icon=code theme={"system"} import { CompareProductsToolType, GroupedResultsToolType, SearchIndexToolType, } from 'react-instantsearch'; ``` To customize only the appearance of each result in the built-in carousel, use the [`itemComponent`](#param-components-props) prop instead. To replace the whole carousel layout, override the `SearchIndexToolType` tool's `layoutComponent`. The tool result is on `context.message.output` (`hits`, `nbHits`, `queryID`): ```jsx JavaScript icon=code expandable theme={"system"} { const items = context.message.output?.hits || []; return (
    {items.map((item) => (
    {item.name}

    {item.name}

    ${item.price}
    ))}
    ); }, }, }} /> ``` For a step-by-step walkthrough, see [Customize the chat results carousel](/doc/guides/building-search-ui/going-further/chat-customization/results-carousel/react). Each tool is an object with the following properties: * `layoutComponent`. A React component that renders the tool call in the chat. It receives a single `context` prop holding the tool's own data alongside the shared chat state: * `context.message`. The tool call message. It contains `input` (parameters from the agent) and `output` (the result you provide with `addToolResult`). For more information about the message structure, see the [Vercel AI SDK documentation](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#messages.ui-message). * `context.records`. The records the conversation holds, keyed by `objectID`: those the chat's tools have fetched and the products users selected for a comparison. A tool that receives only object IDs reads the full record with `context.records.get(objectID)` rather than searching for it again. It also provides `has(objectID)` and `getAll()`. * `context.indexUiState`. The current InstantSearch UI state. * `context.setIndexUiState`. Updates the InstantSearch UI state (for example, to refine filters or update the query based on the tool call). * `context.applyFilters`. Applies filters to the InstantSearch UI state from the tool call. * `context.addToolResult`. Sends the tool's output back to the agent. Call this at least once before the next message. For more information, see the [Vercel AI SDK documentation](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#add-tool-result). * `context.onClose`. Dismisses the tool's UI in the chat. * `context.sendEvent`. Sends `click` or `conversion` events related to the tool call. For more information, see the [`insights`](/doc/api-reference/widgets/insights/react) middleware documentation. * `context` also carries the shared chat state available to `actionsComponent`, [`emptyComponent`](#param-empty-component), [`loaderComponent`](#param-loader-component), and [`messagesErrorComponent`](#param-components-props), such as `messages`, `status`, and `stop`. The root-level props, such as `message` and `addToolResult`, are deprecated. The widget also passes them alongside `context` so components written for earlier API versions keep working. Read these props from `context` instead. * `cancelOutput`. Optional function that returns the output to report when the chat starts a new message while the tool call is waiting for a result. For example, a confirmation prompt can return `{ confirmed: false }`. The function receives `{ toolCallId, input }`. If you don't provide it, the chat reports the tool call as failed. This only changes the output reported to the agent. The tool keeps waiting locally, so you can still submit a result until the [`timeout`](#param-tools) settles it. An interactive tool, such as a confirmation prompt that waits for users, needs `timeout: false` to stay open. * `onToolCall`. Optional handler invoked when the agent calls the tool. Receives a parameter object with: * `input`. The parameters the agent passed to the tool. * `addToolResult`. Sends the tool's output back to the agent. For more information, see the [Vercel AI SDK documentation](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat#on-tool-call). Call it with `{ output }` to report a result, or with `{ state: 'output-error', errorText }` to report a failure the agent can read and explain. * `signal`. An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that aborts when the tool call stops mattering: the `timeout` elapsed, or the response was stopped. Pass it to your own `fetch` calls so abandoned work doesn't keep running. * `toolCallId`. The unique identifier of the tool call. * `toolName`. The name of the tool being invoked. * `dynamic`. Whether the tool is dynamically registered. `onToolCall` can be `async`. When it throws or rejects, the chat settles the tool call as failed with the error's message, so the turn finishes and the agent can respond to the failure instead of hanging. * `timeout`. Optional number of milliseconds for `onToolCall` to submit a result, `20000` by default. When the deadline passes, the chat aborts the handler's `signal` and settles the tool call as failed. Set it to `false` to wait indefinitely, which is what an interactive tool needs, such as a confirmation prompt that waits for users. * `retryOnError`. Optional boolean, `false` by default. When `true`, the default failed state shows a **Retry** button next to the error message. Clicking it regenerates the assistant response, so the agent can call the tool again. Turn it on only when repeating the operation is safe, because the first attempt may have completed its write before failing. The button only appears when the tool has an `onToolCall` handler, the chat is idle, and the shopper hasn't sent another message since. With a `layoutComponent`, the default state doesn't render, so handle recovery yourself with `context.onReload`. To change the button text, use the [`translations.message.toolErrorRetryText`](#param-translations) translation. * `streamInput`. Optional boolean. When `true`, the default loader is suppressed as the tool's input is streamed from the agent. Use the partial `input` in your `layout` template to render the streaming state as input chunks arrive. * `shouldRender`. Optional function that decides whether this tool call renders at all. It receives the chat context, plus `message` (the tool call under consideration) and `parentMessage` (the chat message it belongs to). Returning `false` skips the tool call and keeps the loader visible, so a tool can step aside for another one that renders in the same turn. When you omit it, the tool always renders. Overriding a built-in tool's `layoutComponent` keeps that tool's own `shouldRender` unless you pass your own. * `matchesToolName`. Optional function that claims the tool names your agent's server derives from this tool. It receives the incoming tool name and returns `true` when this tool handles it, for example `matchesToolName: (toolName) => toolName.startsWith('my_tool_')`. `Chat` first looks for a tool registered under the exact name and only then asks the other tools whether they claim it, so a claim never shadows another registration. When you omit it, the tool only handles calls sent under its own key. The built-in `SearchIndexToolType` tool already claims the index-suffixed names the Algolia MCP Server exposes, such as `algolia_search_index_products`. A failed tool call renders its error text in the conversation, in a `.ais-ChatMessage-toolError` element, even for a tool with no `layoutComponent`. With a `layoutComponent`, your own component renders the failed state, so read `context.message.state` and `context.message.errorText` to handle it. With [`persistence`](#param-persistence), a reload can interrupt a tool call before its result arrives. On the next page load, `Chat` settles the tool calls that were still pending as failed, so a restored conversation doesn't wait forever on a handler that no longer exists. It doesn't run `onToolCall` again, because the first attempt may have completed its write. Tool calls with `timeout: false`, and tool calls for tools with no `onToolCall` handler, keep waiting instead: those are the ones users can still answer. With [`resume`](#param-resume), `Chat` waits for the resume attempt to finish first, so a stream that's still running settles its own tool calls. ```jsx JavaScript icon=code expandable theme={"system"}

    Add {context.message.input.objectID} to the cart?

    , // if the shopper sends a new message before answering, tell the agent // the item wasn't added rather than reporting a failure cancelOutput: () => ({ text: "not added to cart", done: false }), onToolCall: ({ addToolResult }) => addToolResult({ output: {} }), }, viewProduct: { layoutComponent: ({ context }) => { if (!context.message.output) { return Loading product...; } return

    {context.message.output.productName}

    {context.message.output.brand}

    ; }, // fetching details is safe to repeat, so offer a Retry button on failure retryOnError: true, // give the lookup 5 seconds instead of the default 20 timeout: 5000, onToolCall: async ({ input, addToolResult, signal }) => { addToolResult({ // fetch product details from your index output: await fetchProductDetails(input.objectID, { signal }), }); }, }, }} />; ``` ```ts Tool icon="file-type" expandable theme={"system"} type Tool = { layoutComponent?: (props: ToolLayoutComponentProps) => JSX.Element; cancelOutput?: (params: { toolCallId: string; input: unknown }) => unknown; onToolCall?: (params: { input: unknown; addToolResult: ( props: | { state?: "output-available"; output: unknown } | { state: "output-error"; errorText: string } ) => Promise; signal: AbortSignal; toolCallId: string; toolName: string; dynamic?: boolean; }) => void | PromiseLike; timeout?: number | false; retryOnError?: boolean; streamInput?: boolean; shouldRender?: (context: ClientSideToolShouldRenderContext) => boolean; matchesToolName?: (toolName: string) => boolean; }; type ToolLayoutComponentProps = { context: ChatComponentContext & { message: ChatToolMessage; records?: ChatRecordsStore; insightsEventContext?: ChatInsightsEventContext; indexUiState: object; setIndexUiState: (state: object) => void; addToolResult: AddToolResultWithOutput; applyFilters: (params: ApplyFiltersParams) => SearchParameters; sendEvent: SendEventForHits; }; }; type ApplyFiltersParams = { query?: string; facetFilters?: string[][]; numericFilters?: string[]; }; ``` **context** `Record\ | () =\> Record\` Extra context to send with each user message (for example, the current page or selected locale). The widget sends this context with every message, but doesn't show it in the chat UI. `context` can be a static object or a function that returns an object at send time. The widget attaches it to the latest user message as `metadata.turnContext`, following the Agent Studio [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context) contract. The context never appears as a chat bubble. If the message already carries turn context, such as the `turnContext` passed to [`openChat`](/doc/api-reference/widgets/chat-trigger/react#param-open-chat), the widget merges both. On a key both provide, the message's value wins for that turn. The context must be a flat object that maps strings to strings (up to 32 keys, 4,096 bytes total). Agent Studio validates the payload and rejects malformed contexts. For details, see [Per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context). > [!WARNING] > > The widget sends `context` to the agent in plain text. > Don't put secrets, access tokens, or personally identifiable information you don't intend to share with the model in this field. ```jsx Static object theme={"system"} ``` ```jsx Function theme={"system"} ({ locale: navigator.language, currentPage: window.location.pathname, })} /> ``` **initialMessages** `UIMessage[]` Messages to pre-populate the chat with when it's initialized. These messages are added without triggering an AI response. `initialMessages` only applies when the chat doesn't have any existing messages. When `resume` is enabled, `initialMessages` is ignored. ```jsx JavaScript icon=code theme={"system"} ``` **initialUserMessage** `string` A message to send automatically when the chat is initialized. `initialUserMessage` is only sent when the chat doesn't have any existing messages. It's sent after `initialMessages` are applied. When `resume` is enabled, this message isn't sent. ```jsx JavaScript icon=code theme={"system"} ``` **resume** `boolean` (default: false) Whether to resume an ongoing chat generation stream when the widget mounts. Use this when restoring a chat session after a page reload to continue receiving an in-flight assistant response. ```jsx JavaScript icon=code theme={"system"} ``` **persistence** `boolean | \{ messages?: boolean; open?: boolean \}` (default: true) Whether to persist and restore the conversation and the open state of the chat panel from `sessionStorage`. When `true` (the default), the widget saves both the messages and whether the panel is open, and restores them when the widget re-mounts (for example, after a page reload). Set it to `false` to keep everything in memory only, so it's cleared when the page reloads. To control each policy separately, pass an object with the `messages` and `open` booleans. Only what you set to `true` is persisted: * Omitted or `true`: persists the messages and the open state. * `false` or `{}`: persists nothing. * `{ messages: true }`: persists the messages, but always starts with the panel closed. * `{ open: true }`: persists the open state, but doesn't persist messages. With a custom `chat` instance, the instance owns message persistence, so only the `open` option is available. The open state is persisted by default: pass `{ open: false }` to opt out. > [!NOTE] > > Changing the props of `Chat` rebuilds its internal chat instance, which drops the open state and any messages that persistence doesn't cover. > Keep prop references stable, especially functions, or pass your own `chat` instance to keep the conversation. > In development, the widget warns when a prop change is about to discard state. ```jsx Disable persistence theme={"system"} ``` ```jsx Persist only the open state theme={"system"} ``` ```jsx Custom chat instance theme={"system"} ``` **disableTriggerValidation** `boolean` (default: false) Whether to skip the validation that requires a way to open the chat. By default, `Chat` logs a development warning unless the chat has an entry point: a `ChatTrigger`, `PromptSuggestions`, `CompareBar`, or `ResultCard` widget, [AI mode](/doc/guides/building-search-ui/going-further/chat-customization/ai-search-experience/react) on a `SearchBox` or autocomplete, or an [inline layout](#param-layout-component). Set this to `true` when you open the chat programmatically and don't render any of those. ```jsx JavaScript icon=code theme={"system"} ``` **onFinish** `(options: \{ message, messages, isAbort, isDisconnect, isError \}) =\> void` A callback called when the assistant response has finished streaming, including when the stream is aborted, disconnected, or fails. The callback receives an object with: * `message`: the final assistant message. * `messages`: the full message list including the new message. * `isAbort`: `true` if the stream was stopped with `stop()`. * `isDisconnect`: `true` if the connection was lost. * `isError`: `true` if the stream finished with an error. ```jsx JavaScript icon=code theme={"system"} { if (isError) { console.error("Chat stream failed", message); return; } if (!isAbort) { analytics.track("chat_message_completed", { messageId: message.id }); } }} /> ``` **sendAutomaticallyWhen** `(options: \{ messages: UIMessage[] \}) =\> boolean | Promise\` A function that decides whether to resubmit the conversation to the agent without user input. It receives an object with `messages`, the current message list, and returns a boolean or a promise that resolves to one. When it returns `true`, `Chat` sends the conversation again so the agent can continue its turn. By default, `Chat` uses `lastAssistantMessageIsCompleteWithToolCalls`. It returns `true` when the last step of the last assistant message has at least one client-side tool call, and every one of those calls has an output or an error. This is what lets the agent act on the results of your [`tools`](#param-tools) and keep answering. The function ignores tool calls the agent executes on its own side, because their results don't come from your app. `Chat` evaluates the function only for a response with at least one required client-side tool call. It waits for the response to finish streaming and for every required client-side tool call in that response to have a result, whichever comes last. It evaluates each response at most once, and skips a stream that's stopped or fails. Return `false` to stop `Chat` from continuing turns on its own, for example when your tools end the turn and users send the next message. With a custom `chat` instance, set `sendAutomaticallyWhen` on the instance instead. ```jsx Never continue automatically theme={"system"} false} /> ``` ```jsx Continue for one tool only expandable theme={"system"} import { lastAssistantMessageIsCompleteWithToolCalls } from "instantsearch.js/es/lib/ai-lite"; { if (!lastAssistantMessageIsCompleteWithToolCalls({ messages })) { return false; } const lastMessage = messages[messages.length - 1]; const lastStepStartIndex = lastMessage.parts.reduce( (lastIndex, part, index) => part.type === "step-start" ? index : lastIndex, -1 ); return lastMessage.parts.slice(lastStepStartIndex + 1).some( (part) => part.type === "tool-applyFilters" && !part.providerExecuted ); }} />; ``` **requiresSearch** `boolean` (default: true) Whether the widget makes InstantSearch require a main search request. By default (`true`), the widget contributes to the main search. Set it to `false` to opt the widget out of the main search: if it's the only widget on the page, no main search request is sent. ```jsx JavaScript icon=code theme={"system"} ``` **showReasoning** `boolean` (default: true) Whether to display the reasoning an agent sends. Each assistant message shows its reasoning as a single collapsed disclosure. Expanding it lists the reasoning steps in the order they arrived, and while reasoning is in progress the collapsed row shows the latest step. The reader's expanded or collapsed choice persists for the rest of the turn. Set it to `false` to hide reasoning in this widget. It also hides a custom [`reasoningComponent`](#param-reasoning-component). This prop can't make an agent send reasoning. Whether reasoning reaches the browser depends on the agent's own [`sendReasoning`](/doc/guides/algolia-ai/agent-studio/how-to/agent-configuration) setting, which is off by default. Copied assistant messages include the answer text, not the reasoning. ```jsx JavaScript icon=code theme={"system"} ``` **loaderPosition** `string` (default: messages-end) Where the loader appears while a chat turn is in progress. * `messages-end`. Renders the loader in its own row after the last message. * `message-inline`. Renders the loader inside the streaming assistant message, after the content received so far. Before the first assistant part arrives, the loader appears in its own row. ```jsx JavaScript icon=code theme={"system"} ``` To place the loader elsewhere in the chat panel, use CSS. See [Control when and where the chat loader shows](/doc/guides/building-search-ui/going-further/chat-customization/loader-behavior/react). **shouldShowLoader** `(context) =\> boolean` Controls when the loader appears. The function receives the loader context and `defaultValue`, which indicates whether the widget would show the loader by default. The [timing options](#param-loader-show-delay) still apply to the returned value. Relevant context properties include: * `status`. The current chat status: `'ready'`, `'submitted'`, `'streaming'`, or `'error'`. * `phase`. The current phase of the turn: `'submitted'`, `'tool'`, `'reasoning'`, or `'thinking'`. * `message`. The assistant message associated with the loader, when available. * `messages`. The full conversation. * `tools`. The tools available to the agent. * `defaultValue`. Whether the widget would show the loader by default. ```jsx JavaScript icon=code theme={"system"} phase === "tool" || defaultValue} /> ``` **loaderShowDelay** `number` (default: 250) The delay, in milliseconds, before the loader reappears after being hidden earlier in the same turn. The first loader in a turn isn't delayed. Set this option to `0` to show the loader immediately after loading starts. ```jsx JavaScript icon=code theme={"system"} ``` **loaderMinDuration** `number` (default: 200) The minimum time, in milliseconds, that the loader remains visible while the turn is running. This prevents the loader from briefly appearing and disappearing. The loader hides immediately after the turn ends. ```jsx JavaScript icon=code theme={"system"} ``` **emptyComponent** `(props: ChatComponentPropsWithContext\) =\> JSX.Element` A component to customize the welcome screen shown before the first message, when the chat doesn't have any messages yet. Use it to display a greeting and starter prompts that send a message when clicked. It receives `context`, the supported API for shared chat state and callbacks: * `context.sendMessage`. Sends a message to the agent. Call it with `{ text }` to submit a starter prompt as the first message. * `context.status`. The current chat status. * `context.onClose`. Dismisses the chat. * `context.setInput`. Sets the value of the prompt input without sending it. The deprecated root-level `sendMessage`, `status`, `onClose`, and `setInput` props are also passed for compatibility. Read these values from `context`. ```jsx JavaScript icon=code expandable theme={"system"} import { Chat } from "react-instantsearch"; function WelcomeScreen({ context }) { const prompts = ["What are your best-selling shoes?", "Show me new arrivals"]; return (

    How can I help you today?

    {prompts.map((prompt) => ( ))}
    ); } ; ``` To render the default greeting (heading, subheading, and an optional banner) inside your component, use the exported `ChatGreeting` component from `react-instantsearch`. For a step-by-step walkthrough, see [Show starter prompts on the chat welcome screen](/doc/guides/building-search-ui/going-further/chat-customization/welcome-screen/react). **layoutComponent** `(props: ChatLayoutOwnProps) =\> JSX.Element` A component to customize the overall layout of the chat widget. Use `ChatInlineLayout` for an inline (non-overlay) layout, `ChatSidePanelLayout` for a side panel on the right side of the page, or `ChatOverlayLayout` for the default floating overlay. To display a custom welcome screen before the first message, see [Show starter prompts on the chat welcome screen](/doc/guides/building-search-ui/going-further/chat-customization/welcome-screen/react). Use the side panel layout to keep the chat widget beside your page content on wider screens. Unlike the inline layout, the side panel requires an entry point to open the widget. By default, `ChatSidePanelLayout` adjusts `document.body`. Set its `parentElement` prop to a CSS selector to adjust another element. * When the side panel opens, the layout adds the value of `--ais-chat-width` to the selected element's inline `margin-right`. * When the side panel closes, the layout restores the previous inline margin. * On wider screens, maximizing the chat widget changes the panel width to `--ais-chat-maximized-width` without increasing the reserved margin, so the maximized panel can overlap page content. At viewport widths of 680px or less, the side panel fills the viewport. This breakpoint is based on the viewport width, not the width of `parentElement`. ```jsx JavaScript icon=code theme={"system"} import { Chat, ChatSidePanelLayout } from "react-instantsearch"; function SidePanelLayout(props) { return ( ); } ``` **loaderComponent** `(props) =\> JSX.Element` A component to customize the loader shown while waiting for an AI response. Import the built-in `ChatMessageLoader` component from `react-instantsearch` to render the default loader, or provide your own component. The component receives: * `translations`. The resolved loader translations. * `context`. The same context as [`shouldShowLoader`](#param-should-show-loader), without `defaultValue`. * `inline`. Whether the loader renders inside an assistant message instead of in its own row. It's `true` when `loaderPosition` is `message-inline` and an assistant message is available. ```jsx JavaScript icon=code theme={"system"} import { Chat, ChatMessageLoader } from "react-instantsearch"; ( )} /> ``` To replace the loading state, see [Customize the chat loader](/doc/guides/building-search-ui/going-further/chat-customization/loading-message/react). **reasoningComponent** `(props: ChatMessageReasoningComponentProps) =\> JSX.Element` A component that renders the reasoning of an assistant message. It replaces the built-in disclosure. Unlike the built-in disclosure, which groups the whole message into one row, this component renders once per reasoning part, at that part's own position in the stream. The component receives: * `part`. The reasoning part to render. Its `text` property holds the reasoning text. * `partIndex`. The index of the reasoning part in the message's `parts` array. * `isStreaming`. Whether this reasoning part is still being produced. * `message`. The message the reasoning part belongs to. * `context`. The shared chat state, such as `messages`, `status`, and `tools`. The component changes how reasoning renders, not whether it renders: [`showReasoning={false}`](#param-show-reasoning) hides this component along with the built-in disclosure. ```jsx JavaScript icon=code theme={"system"} (

    {part.text}

    )} /> ``` **components props** `() =\> JSX.Element` Components to customize the rendering of the widget. `actionsComponent`, [`emptyComponent`](#param-empty-component), [`loaderComponent`](#param-loader-component), and `messagesErrorComponent` receive a `context` prop that groups the current chat state and callbacks. The other components use the props described below. * `context.messages`, `context.status`, `context.error`, `context.activePart`. The conversation and where it stands. For the `status` values, see [Status values](#status-values). * `context.open`, `context.maximized`, `context.isClearing`. Panel state, for conditional rendering. * `context.tools`. The tools registered for the assistant. * `context.sendMessage`, `context.regenerate`, `context.stop`, `context.setInput`. Drive the conversation from a custom control. * `context.onReload`, `context.onNewConversation`, `context.onClose`. Regenerate a message, clear the conversation, or close the panel. ```jsx JavaScript icon=code theme={"system"} (

    {errorMessage}

    )} /> ``` * `itemComponent`. Custom component for each result. Receives an object containing a single record. * `headerCloseIconComponent`. Header: close icon component. * `headerMaximizeIconComponent`. Header: maximize icon component. Receives a prop containing `{ maximized: boolean }` for conditional rendering. * `headerMinimizeIconComponent`. Header: minimize icon component. * `headerTitleIconComponent`. Header: title icon component (defaults to sparkles). * `messagesErrorComponent`. Messages: custom error component. Guardrail fallback responses render as assistant messages, not through this component. By default, the widget shows a generic message (`Sorry, we are not able to generate a response at the moment. Please contact support.`) and a "Start a new conversation" button that clears the conversation. The component receives: * `errorMessage`. The raw error message from the API or transport layer. * `onNewConversation`. Callback that clears the conversation and starts a new one. Use it to render a "Start a new conversation" action for errors where retrying fails again. * `assistantMessageLeadingComponent`. Assistant message: custom leading component, such as an avatar. * `assistantMessageFooterComponent`. Assistant message: custom footer component. * `userMessageLeadingComponent`. User message: custom leading component, such as an avatar. * `userMessageFooterComponent`. User message: custom footer component. * `promptFooterComponent`. Prompt: custom footer component. * `promptHeaderComponent`. Prompt: custom header component. * `suggestionsComponent`. Custom component to render [follow-up prompt suggestions](/doc/guides/algolia-ai/agent-studio/how-to/agent-configuration#follow-up-prompt-suggestions). Receives a prop containing `{ suggestions: string[], onSuggestionClick: (str: string) => void }`. The widget renders it once the response finishes, so no suggestion placeholder competes with the streaming answer, and the component doesn't receive a loading state. To customize the button that opens the chat, use the [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) widget's `toggleIconComponent` prop instead. To add a disclaimer or policy notice, see [Add a legal notice to the chat widget](/doc/guides/building-search-ui/going-further/chat-customization/legal-notice/react). ```jsx JavaScript icon=code theme={"system"} ✨} headerMaximizeIconComponent={({ maximized }) => ( {maximized ? "🔽" : "🔼"} )} promptFooterComponent={() => ( Privacy policy )} /> ``` **messagesProps** `object` Props forwarded to the messages section of the widget. Use `assistantMessageProps` and `userMessageProps` to customize each role separately: * `textComponent`. A component that renders each text part of the message. It replaces the default markdown rendering. It receives: * `part`. The text part to render. Its `text` property holds the text content. * `message`. The message the text part belongs to. * `messages`. The full conversation, when available. * `status`. The current chat status: `'ready'`, `'submitted'`, `'streaming'`, or `'error'`. * `partIndex`. The index of the text part in the message's `parts` array. * `parseMarkdown`. The default value is `true`. The widget renders text and reasoning parts as Markdown, including links, code blocks, and emphasis. Set `parseMarkdown` to `false` to render reasoning parts and text parts that use the default renderer as plain text. Line breaks are preserved and links aren't clickable. Use this option for user messages when Markdown characters such as `*` or `_` should remain literal. If you provide a `textComponent`, it takes precedence for text parts. `parseMarkdown` then only controls reasoning parts. ```jsx JavaScript icon=code theme={"system"} ( {part.text} ), }, userMessageProps: { textComponent: ({ part }) => ( {part.text} ), }, }} /> ``` To render user messages as plain text while keeping the default Markdown rendering for assistant messages: ```jsx JavaScript icon=code theme={"system"} ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `container`. The container element. * `header`. The header section of the widget. * `root`. The root element. * `clear`. The clear button. * `close`. The close button. * `maximize`. The maximize button. * `title`. The title element. * `titleIcon`. The title icon element. * `messages`. The messages section of the widget. * `root`. The root element. * `content`. The scrollable content. * `scroll`. The scroll container. * `scrollToBottom`. The scroll to bottom button. * `scrollToBottomHidden`. The hidden state of the scroll to bottom button. * `status`. The visually hidden region that announces replies to screen readers. * `message`. The message in the messages section. * `root`. The root element. * `container`. The message container. * `leading`. The leading element, such as an avatar. * `content`. The content element. * `message`. The message text element. * `reasoning`. The reasoning disclosure. * `reasoningHeader`. The reasoning disclosure header. * `reasoningIcon`. The reasoning icon. * `reasoningLabel`. The reasoning label. * `reasoningChevron`. The reasoning disclosure chevron. * `reasoningBody`. The reasoning body. * `reasoningText`. The reasoning text. * `actions`. The action buttons container. * `footer`. The footer element. * `prompt`. The prompt section of the widget. * `root`. The root element. * `actions`. The actions container. * `body`. The body element. * `footer`. The footer element. * `header`. The header element. * `submit`. The submit button. * `textarea`. The text area element. ```jsx JavaScript icon=code theme={"system"} ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `header`. The header section of the widget. * `clearLabel`. Accessible label for the clear button. * `closeLabel`. Accessible label for the close button. * `maximizeLabel`. Accessible label for the maximize button. * `minimizeLabel`. Accessible label for the minimize button. * `title`. Title to display. * `messages`. The messages section of the widget. * `conversationLabel`. Accessible name of the scrollable message history (default: `'Conversation'`). Keyboard users can focus the history to scroll it. * `copyToClipboardLabel`. Accessible label for the copy to clipboard action. * `errorStatusText`. Text announced to screen readers when the chat ends in an error (default: `'Sorry, something went wrong. Please try again.'`). * `feedbackThankYouText`. Text shown after feedback is submitted (default: `'Thanks for your feedback!'`). * `loaderText`. Text to display in the loader. Pass a function to customize the text based on the turn context, for example `({ phase }) => phase === 'tool' ? 'Searching...' : 'Thinking...'`. * `regenerateLabel`. Accessible label for the regenerate action. * `respondingStatusText`. Text announced to screen readers when a reply starts (default: `'Responding…'`). * `responseCompleteStatusText`. Function that returns the text announced to screen readers once a reply is complete, instead of chunk by chunk while it streams (default: `'Reply received.'`). It receives `{ message }`, the reply, so you can announce its text or a summary of it. * `scrollToBottomLabel`. Accessible label for the scroll to bottom button. * `sendingFeedbackLabel`. Accessible label for the feedback spinner (default: `'Sending feedback...'`). * `thumbsDownLabel`. Accessible label for the thumbs down button (default: `'Dislike'`). * `thumbsUpLabel`. Accessible label for the thumbs up button (default: `'Like'`). * `message`. Individual message in the messages section. * `messageLabel`. Accessible label for the message. * `actionsLabel`. Accessible label for the actions container. * `reasoningLabel`. Label for a reasoning disclosure. * `toolErrorRetryText`. Text for the retry button of a failed tool call (default: `'Retry'`). The button only shows for tools with [`retryOnError: true`](#param-tools). * `prompt`. The prompt section of the widget. * `disclaimer`. Disclaimer text shown in the prompt footer. * `emptyMessageTooltip`. The tooltip for the submit button when message is empty. * `sendMessageTooltip`. The tooltip for the send button. * `stopResponseTooltip`. The tooltip for the stop button. * `textareaLabel`. Accessible label for the text area. * `textareaPlaceholder`. Placeholder text for the text area. To add richer prompt or message notices, see [Add a legal notice to the chat widget](/doc/guides/building-search-ui/going-further/chat-customization/legal-notice/react). ```jsx JavaScript icon=code theme={"system"} ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ``` ## Hook Use `useChat` to build a chat interface with your own React components. The Hook connects to InstantSearch and creates a chat instance unless you provide one. Render your custom component inside `InstantSearch` in place of `Chat`. * For a chat panel, provide a way to open it, such as [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) or a custom component built with `useChatTrigger`. * If you display the chat inline or use a custom opener that InstantSearch doesn't recognize, set `disableTriggerValidation: true`. ```jsx JavaScript icon=code expandable theme={"system"} import { useChat } from "react-instantsearch"; function CustomChat({ agentId }) { const { messages, input, setInput, sendMessage, status, stop, error, clearError, } = useChat({ agentId, disableTriggerValidation: true }); return (
    {messages.map((message) => (
    {message.role} {message.parts.map((part, index) => part.type === "text" ?

    {part.text}

    : null )}
    ))} {status === "error" && error &&

    {error.message}

    }
    { event.preventDefault(); if (input.trim() && (status === "ready" || status === "error")) { clearError(); sendMessage({ text: input }); setInput(""); } }} > setInput(event.target.value)} /> {status === "streaming" && ( )}
    ); } ``` ### Parameters Pass one configuration object to `useChat`. Provide [`agentId`](#param-agent-id) or [`transport`](#param-transport), or pass a preconfigured `chat` instance. For shared options such as `context`, `tools`, and `persistence`, see [Props](#props). If you pass `chat`, configure its transport and message persistence on the instance. You can set `id` and `messages` to initialize the conversation. When the Hook creates the chat instance, it also accepts `generateId`, `onError`, and `onData`. Changing option values or callback functions can replace the Hook's internal chat instance and clear conversation state that isn't persisted. ### APIs `useChat` returns the following state values and methods: * `id` (`string`): conversation identifier. * `messages` (`UIMessage[]`): conversation messages, including streamed message parts. * `status` (`'ready' | 'submitted' | 'streaming' | 'error'`): request status. Clear an error with `clearError` before sending another message. * `error` (`Error | undefined`): last request error. * `input` (`string`): current text in the message input. Sending a message doesn't clear this value automatically. * `setInput(input: string)`: update the text in the message input. * `open` (`boolean`), `setOpen(open: boolean)`, and `focusInput()`: panel state and controls. `focusInput` opens the panel and requests prompt focus. Handle DOM focus in a custom interface. * `sendMessage(message, options?)`: add a user message and request an assistant response. For text, pass `{ text: "MESSAGE" }`. Returns `Promise`. * `regenerate(options?)`: regenerate an assistant response. Pass `{ messageId }` to select a message, or omit it to regenerate the latest assistant message. Returns `Promise`. * `stop()`: stop the active response. The `onFinish` callback receives `isAbort: true`. * `resumeStream(options?)`: reconnect to an active response. Set [`resume`](#param-resume) to `true` to resume when the Hook initializes. * `clearError()`: clear the last request error. * `setMessages(messagesOrUpdater)`: replace the local message list, or update it with a function. Call `regenerate` to request a response after editing messages. * `clearMessages()`: stop an active response, clear messages and feedback, and start a new conversation ID. * `adoptConversation({ id, messages, source })`: take over a conversation started by another widget, such as `ResultCard`. An empty chat continues the conversation under its `id`. A chat with messages appends the new messages to its own conversation. The adopted messages get `metadata.source` set to `source`. Returns `false` and adopts nothing while the chat is generating a response. * `addToolResult(options)`: submit the result of a client-side tool call. Pass `tool` and `toolCallId`, with either `output` or `state: 'output-error'` and `errorText`. * `tools` (`ClientSideTools`): configured tools with `addToolResult` and search helpers bound for rendering. * `records` (`ChatRecordsStore`): records returned by chat tools, keyed by `objectID`. * `suggestions` (`string[] | undefined`): suggestions from the last assistant message's `data-suggestions` part. * `sendChatMessageFeedback(messageId, vote)`: send feedback on an assistant message. Pass `0` for negative feedback or `1` for positive feedback. Available only when you provide an `agentId` and set `feedback: true`. * `feedbackState` (`Record`): feedback state by message ID. * `indexUiState` and `setIndexUiState(nextUiState)`: read or update the parent index UI state. * `sendEvent(...)`: send a chat event with the InstantSearch Insights middleware. `sendMessage`, `regenerate`, and `resumeStream` accept request options for `headers`, `body`, and `metadata`. During server rendering and hydration, the Hook presents a closed chat with no messages and `status: 'ready'` until the browser initializes it. ## Streaming and resumption Assistant responses stream over [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). The `useChat` Hook exposes the streaming lifecycle if you want to drive a custom UI. ### Status values `useChat` returns a `status` value, which can be one of: * `'ready'`. The chat is idle and ready to accept a new message. * `'submitted'`. A user message was submitted and the assistant hasn't started responding yet. * `'streaming'`. The assistant is streaming a response. * `'error'`. The last response finished with an error. Call `clearError()` before sending a new message. Each message part also carries a `state` of `'streaming'` or `'done'` while the response streams. ### Stop and resume `useChat` also returns: * `stop()`. Aborts the current streaming response. The `onFinish` callback runs with `isAbort: true`. * `resumeStream()`. Reconnects to an in-flight stream. Call this on mount, or pass [`resume`](#param-resume) to `Chat` to do it automatically. ```jsx JavaScript icon=code theme={"system"} import { useChat } from "react-instantsearch"; function StopButton() { const { status, stop } = useChat({ agentId: "YOUR_AGENT_ID" }); if (status !== "streaming") { return null; } return ; } ``` To resume an ongoing stream after a reload, set [`resume`](#param-resume) on `Chat`, or call `resumeStream()` from a custom component. # clearRefinements Source: https://www.algolia.com/doc/api-reference/widgets/clear-refinements/js Lets users reset all applied search refinements. ```ts Signature theme={"system"} clearRefinements({ container: string | HTMLElement, // Optional parameters includedAttributes?: string[], excludedAttributes?: string[], templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { clearRefinements } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { clearRefinements } = instantsearch.widgets; // or directly use instantsearch.widgets.clearRefinements() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=clearRefinements) > > Preview this widget and its behavior. ## About this widget The `clearRefinements` widget displays a button that lets users clean every refinement applied to the search. You can control which attributes are impacted by the button with the options. ## Examples ```js JavaScript icon=code theme={"system"} clearRefinements({ container: "#clear-refinements", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} clearRefinements({ container: "#clear-refinements", }); ``` ```js HTMLElement theme={"system"} clearRefinements({ container: document.querySelector("#clear-refinements"), }); ``` **includedAttributes** `string[]` (default: \[]) The attributes to include in the refinements to clear (all by default). Can't be used with [`excludedAttributes`](/doc/api-reference/widgets/clear-refinements/js#param-excluded-attributes). In the example below, only the `categories` attribute is included in the refinements to clear. ```js JavaScript icon=code theme={"system"} clearRefinements({ // ... includedAttributes: ["categories"], }); ``` **excludedAttributes** `string[]` (default: \['query']) The attributes to exclude from the refinements to clear. Can't be used with [`includedAttributes`](/doc/api-reference/widgets/clear-refinements/js#param-included-attributes). In the example below, the attribute `brand` is excluded from the refinements to clear. ```js JavaScript icon=code theme={"system"} clearRefinements({ // ... excludedAttributes: ["brand"], }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} clearRefinements({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `button`. The button element. * `disabledButton`. The button element when it's unavailable. ```js JavaScript icon=code theme={"system"} clearRefinements({ // ... cssClasses: { root: "MyCustomClearRefinements", button: [ "MyCustomClearRefinementsButton", "MyCustomClearRefinementsButton--subclass", ], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} clearRefinements({ // ... transformItems(items) { return items.filter((item) => item !== "brand"); }, }); // or, combined with results clearRefinements({ // ... transformItems(items, { results }) { return results.nbHits === 0 ? items : items.filter((item) => item !== "brand"); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **resetLabel** `string | function` The template for the content of the button. ```js function theme={"system"} clearRefinements({ // ... templates: { resetLabel({ hasRefinements }, { html }) { return html`${hasRefinements ? "Clear refinements" : "No refinements"}`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. clearRefinements({ // ... templates: { resetLabel: "Clear refinements", }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectClearRefinements` If you want to create your own UI of the `clearRefinements` widget, you can use connectors. To use `connectClearRefinements`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectClearRefinements } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectClearRefinements } = instantsearch.connectors; // or directly use instantsearch.connectors.connectClearRefinements() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderClearRefinements = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customClearRefinements = connectClearRefinements(renderClearRefinements); // 3. Instantiate search.addWidgets([ customClearRefinements({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderClearRefinements = (renderOptions, isFirstRender) => { const { canRefine, refine, createURL, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderClearRefinements = (renderOptions, isFirstRender) => { const { canRefine } = renderOptions; if (!canRefine) { document.querySelector("#clear-refinements").innerHTML = ""; return; } }; ``` **refine** `function` Clears all the currently refined values and triggers a new search. ```js JavaScript icon=code theme={"system"} const renderClearRefinements = (renderOptions, isFirstRender) => { const { refine } = renderOptions; const container = document.querySelector("#clear-refinements"); if (isFirstRender) { const button = document.createElement("button"); button.textContent = "Clear refinements"; button.addEventListener("click", () => { refine(); }); container.appendChild(button); } }; ``` **createURL** `function` Generates a URL for the next state. ```js JavaScript icon=code theme={"system"} const renderClearRefinements = (renderOptions, isFirstRender) => { const { createURL } = renderOptions; document.querySelector("#clear-refinements").innerHTML = ` Clear refinements `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderClearRefinements = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customClearRefinements({ container: document.querySelector("#clear-refinements"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```jsx JavaScript icon=code theme={"system"} const customClearRefinements = connectClearRefinements( renderClearRefinements ); search.addWidgets([ customClearRefinements({ // Optional parameters includedAttributes: string[], excludedAttributes: string[], transformItems: function, }) ]); ``` #### Instance options **includedAttributes** `string[]` (default: \[]) The attributes to include in the refinements to clear (all by default). Can't be used with [`excludedAttributes`](/doc/api-reference/widgets/clear-refinements/js#param-excluded-attributes). In the example below, only the `categories` attribute is included in the refinements to clear. ```js JavaScript icon=code theme={"system"} customClearRefinements({ includedAttributes: ["categories"], }); ``` **excludedAttributes** `string[]` (default: \['query']) The attributes to exclude from the refinements to clear. Can't be used with [`includedAttributes`](/doc/api-reference/widgets/clear-refinements/js#param-included-attributes). In the example below, the attribute `brand` is excluded from the refinements to clear. ```js JavaScript icon=code theme={"system"} customClearRefinements({ excludedAttributes: ["brand"], }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customClearRefinements({ transformItems(items) { return items.filter((item) => item !== "brand"); }, }); // or: combined with results customClearRefinements({ transformItems(items, { results }) { return results.nbHits === 0 ? items : items.filter((item) => item !== "brand"); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderClearRefinements = (renderOptions, isFirstRender) => { const { canRefine, refine, widgetParams } = renderOptions; if (isFirstRender) { const button = document.createElement("button"); button.textContent = "Clear refinements"; button.addEventListener("click", () => { refine(); }); widgetParams.container.appendChild(button); } widgetParams.container.querySelector("button").disabled = !canRefine; }; // Create the custom widget const customClearRefinements = connectClearRefinements(renderClearRefinements); // Instantiate the custom widget search.addWidgets([ customClearRefinements({ container: document.querySelector("#clear-refinements"), }), ]); ``` # colorRefinementList Source: https://www.algolia.com/doc/api-reference/widgets/color-refinement-list/js Shows facet values as colors for filtering search results. > [!NOTE] > > This widget is [only available in React InstantSearch v6](https://www.algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/color-refinement-list/). > Vote on this [open issue](https://alg.li/0zdqHL) if you want this widget to be > available in InstantSearch.js, and help prioritize its development. ## About this widget This widget filters results based on color facet values. It only displays the most relevant colors for the current search context. > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/react-instantsearch-widget-color-refinement-list?file=/example/index.tsx) > > Run and edit the colorRefinementList example in CodeSandbox. # compareBar Source: https://www.algolia.com/doc/api-reference/widgets/compare-bar/js Shows selected products and sends the selected records to the chat widget for an agent to compare. ```ts Signature theme={"system"} compareBar({ container: string | HTMLElement, configurationId: string, // Optional parameters minItems?: number, maxItems?: number, transformItems?: function, templates?: object, cssClasses?: object, translations?: object, }); ``` ## Import ```js Package manager theme={"system"} import { compareBar } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { compareBar } = instantsearch.widgets; ``` ## About this widget Use the `compareBar` widget to let users compare products with an [Agent Studio](/doc/guides/algolia-ai/agent-studio) agent. The widget renders a comparison bar with the products users selected for comparison and a **Compare** button. When users select **Compare**, the widget opens the [`chat`](/doc/api-reference/widgets/chat/js) widget on the same index and sends a comparison request with the selected records attached. The widget clears the selection after sending the request. The chat displays the agent's comparison of the attached records without showing the request as a user message. The widget doesn't include controls for selecting products. Add a **Compare** checkbox to each product in your [`hits`](/doc/api-reference/widgets/hits/js) item template. Use `search.renderState..compare` to read and update the shared selection, as shown in the [example](#examples). To build a custom comparison bar or **Compare** button, use the [`connectCompare`](#customize-the-ui-with-connectcompare) connector. All comparison widgets on the same InstantSearch instance share the same selection. The comparison bar stays hidden while the selection is empty. Because `compareBar` opens the `chat` widget, you don't need a [`chatTrigger`](/doc/api-reference/widgets/chat-trigger/js) widget. The widget requires a comparison configuration, which controls the comparison goal and instructions. Create it in the Agent Studio dashboard and pass its ID as [`configurationId`](#param-configuration-id). See also: [Compare products in chat](/doc/guides/algolia-ai/agent-studio/how-to/product-comparison) ## Examples ```js JavaScript icon=code expandable theme={"system"} import instantsearch from 'instantsearch.js'; import { chat, compareBar, hits, searchBox } from 'instantsearch.js/es/widgets'; import 'instantsearch.css/components/chat.css'; const search = instantsearch({ indexName: 'instant_search', searchClient }); search.addWidgets([ searchBox({ container: '#searchbox' }), hits({ container: '#hits', templates: { item(hit, { html }) { // The selection `compareBar` owns. It's undefined until the bar mounts. const { isSelected, toggleItem, canAddItems } = search.renderState.instant_search?.compare ?? {}; const selected = isSelected?.(hit.objectID) ?? false; return html`

    ${hit.name}

    `; }, }, }), compareBar({ container: '#compare-bar', minItems: 2, maxItems: 3, configurationId: 'CONFIGURATION_ID', }), chat({ container: '#chat', agentId: 'AGENT_ID', }), ]); search.start(); ``` ## Options **container** `string | HTMLElement` *required* The CSS selector or `HTMLElement` that identifies the widget's container. ```js string theme={"system"} compareBar({ container: '#compare-bar', }); ``` ```js HTMLElement theme={"system"} compareBar({ container: document.querySelector('#compare-bar'), }); ``` **configurationId** `string` *required* The ID of the comparison configuration that controls how the agent compares products. Create comparison configurations in the **Components** section of your agent in the Agent Studio dashboard. For more information, see [Compare products in chat](/doc/guides/algolia-ai/agent-studio/how-to/product-comparison). The widget sends `__ALGOLIA_COMPARISON___` as the user message, with `comparison_configuration_id` and `selected_products` as [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context). `selected_products` holds the selected records as a JSON string, with internal attributes such as `_highlightResult` removed. Agent Studio replaces the placeholder with the configured agent instructions, and the chat doesn't show a user message for it. If the configuration doesn't exist or is turned off, the placeholder reaches the agent unchanged, so only set the ID of an enabled configuration. ```js JavaScript icon=code theme={"system"} compareBar({ // ... configurationId: 'CONFIGURATION_ID', }); ``` **minItems** `number` (default: 2) The minimum number of products users must select before they can compare. The **Compare** button remains unavailable until users select this number of products. The value must be at least `2`. Set `minItems` to the **Minimum products** value in your comparison configuration. ```js JavaScript icon=code theme={"system"} compareBar({ // ... minItems: 2, }); ``` **maxItems** `number` (default: 3) The maximum number of products users can select. Users can't add products after reaching this limit, and the bar shows a hint. The value must be greater than or equal to [`minItems`](#param-min-items). Set `maxItems` to the **Maximum products** value in your comparison configuration. ```js JavaScript icon=code theme={"system"} compareBar({ // ... maxItems: 4, }); ``` **transformItems** `function` A function that receives the selected records and returns the records to display in the comparison bar. The widget sends these transformed records to the agent when users select **Compare**. Use this function to remove long attributes, such as product descriptions. Return one record for each selected record and preserve its `objectID`. The selection itself, including `isSelected` and the limits, uses the original records. ```js JavaScript icon=code theme={"system"} compareBar({ // ... transformItems(items) { return items.map(({ description, ...item }) => item); }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} compareBar({ // ... templates: { // ... }, }); ``` **cssClasses** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `header`. The header element. * `title`. The title element. * `count`. The selection count element. * `list`. The list of selected items. * `item`. Each selected item. * `itemLabel`. The label of each selected item. * `itemRemove`. The button that removes an item from the selection. * `hint`. The hint shown when users select fewer than `minItems` or reach `maxItems`. * `actions`. The container of the action buttons. * `clearButton`. The button that clears the selection. * `compareButton`. The button that starts the comparison. ```js JavaScript icon=code theme={"system"} compareBar({ // ... cssClasses: { root: 'MyCustomCompareBar', compareButton: [ 'MyCustomCompareBarButton', 'MyCustomCompareBarButton--subclass', ], }, }); ``` **translations** `object` A dictionary of translations to customize the UI text and support internationalization. * `title` (`string`). The title of the comparison bar. The default is `Compare`. * `countText` (`(count: number, maxItems: number) => string`). The selection count. The default is `${count} of ${maxItems}`. * `compareButtonText` (`(count: number) => string`). The label of the **Compare** button. The default is `Compare ${count}`. * `clearButtonText` (`string`). The label of the **Clear all** button. The default is `Clear all`. * `removeItemLabel` (`(itemLabel: string) => string`). The accessible label of each remove button. The default is `Remove ${itemLabel} from comparison`. * `minItemsHint` (`(minItems: number) => string`). The hint shown while the selection is below `minItems`. The default is `Select at least ${minItems} products to compare.`. * `limitReachedHint` (`string`). The hint shown when the selection reaches `maxItems`. The default is `Compare limit reached — remove a product to add another.`. ```js JavaScript icon=code theme={"system"} compareBar({ // ... translations: { title: 'Your comparison', compareButtonText: (count) => `Compare ${count} products`, }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **item** `function` A template to replace the label of each selected item. It receives the selected record as `item`. The label uses `name` or `title`, falling back to `objectID`. Each record must have an `objectID`. ```js JavaScript icon=code theme={"system"} compareBar({ // ... templates: { item({ item }, { html }) { return html` ${item.name} `; }, }, }); ``` ## HTML output ```html HTML icon=code-xml expandable theme={"system"}

    Compare

    2 of 3
    • Trail Jacket
    • Storm Shell
    ``` The widget doesn't render anything if the selection is empty. Below `minItems` or at `maxItems`, it adds a `div.ais-CompareBar-hint` element before the actions. The `instantsearch.css` chat theme anchors the bar to the bottom center of the viewport. Import `instantsearch.css/components/chat.css` to style it. ## Customize the UI with `connectCompare` To create a custom UI for the `compareBar` widget, use `connectCompare`. Use it for a custom comparison bar, or for a **Compare** button outside the bar, such as in a dialog. Every `compare` widget on the same InstantSearch instance reads and writes the same selection. For a toggle on every hit, read the selection from `search.renderState..compare` in the `hits` item template instead of mounting a widget per hit, as in the [example](#examples). Import `connectCompare` based on how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectCompare } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectCompare } = instantsearch.connectors; // or directly use instantsearch.connectors.connectCompare() ``` Then complete these three steps: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderCompare = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customCompare = connectCompare(renderCompare); // 3. Instantiate search.addWidgets([ customCompare({ // instance params }), ]); ``` ### Create a render function InstantSearch calls this rendering function before the first search (`init` lifecycle step), each time results return from Algolia (`render` lifecycle step), and each time the selection changes. ```js JavaScript icon=code theme={"system"} const renderCompare = (renderOptions, isFirstRender) => { const { items, isSelected, toggleItem, removeItem, clearItems, canAddItems, canCompare, minItems, maxItems, compare, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Update the rendering with the new state }; ``` #### Render options **items** `object[]` The selected records, in selection order, after `transformItems`. Each item has at least an `objectID`. **isSelected** `(objectID: string) =\> boolean` Whether the record with this `objectID` is in the selection. **addItem** `(item: object) =\> void` Adds a record to the selection. It doesn't add the record if it's already selected or the selection has reached `maxItems`. The record needs an `objectID`, and a `name` or `title` to label it. **removeItem** `(objectID: string) =\> void` Removes the record with this `objectID` from the selection. **toggleItem** `(item: object) =\> void` Adds the record to the selection when it isn't selected, and removes it otherwise. **clearItems** `() =\> void` Empties the selection. **canAddItems** `boolean` Whether the selection is below `maxItems`. **canCompare** `boolean` Whether the selection has reached `minItems`. **minItems** `number` The resolved [`minItems`](#param-min-items) value. **maxItems** `number` The resolved [`maxItems`](#param-max-items) value. **compare** `() =\> boolean` Opens the `chat` widget on the same index, sends the compare request with the selected records attached, and clears the selection. Returns `true` when it submits the message and `false` when the selection is below `minItems`, the page doesn't have a `chat` widget, or the chat is busy. **widgetParams** `object` All original widget options forwarded to the render function. ### Create and instantiate the custom widget Create a custom widget from the rendering function, then instantiate it. You can pass two types of parameters: * **Instance parameters**: predefined options that configure the widget. * **Custom parameters**: parameters you define for the custom widget. Both instance and custom parameters are available through `renderOptions.widgetParams` in the render function. ```js JavaScript icon=code theme={"system"} const customCompare = connectCompare(renderCompare); search.addWidgets([ customCompare({ minItems: 2, maxItems: 3, configurationId: 'CONFIGURATION_ID', }), ]); ``` #### Instance options The connector accepts the same options as the widget: [`configurationId`](#param-configuration-id) (required), [`minItems`](#param-min-items), [`maxItems`](#param-max-items), and [`transformItems`](#param-transform-items). Pass the same values to every `compare` widget so they use the same configuration and limits. ### Full example This example adds a **Compare** checkbox to each hit and renders a minimal bar. The hits template reads the selection the bar owns, so checking a hit updates the bar. ```js JavaScript icon=code expandable theme={"system"} import instantsearch from 'instantsearch.js'; import { connectCompare } from 'instantsearch.js/es/connectors'; import { chat, hits, searchBox } from 'instantsearch.js/es/widgets'; const search = instantsearch({ indexName: 'instant_search', searchClient }); const compareBarCustom = connectCompare((renderOptions, isFirstRender) => { const { items, canCompare, compare, clearItems } = renderOptions; const container = document.querySelector('#compare-bar'); if (isFirstRender) { container.addEventListener('click', (event) => { if (event.target.matches('[data-compare]')) { compare(); } if (event.target.matches('[data-clear]')) { clearItems(); } }); } container.hidden = items.length === 0; container.innerHTML = ` ${items.map((item) => item.name).join(', ')} `; }); search.addWidgets([ searchBox({ container: '#searchbox' }), hits({ container: '#hits', templates: { item(hit, { html }) { // Every selection change re-renders the hits, so the // checkboxes stay in sync with the bar. const { isSelected, toggleItem, canAddItems } = search.renderState.instant_search?.compare ?? {}; const selected = isSelected?.(hit.objectID) ?? false; return html`

    ${hit.name}

    `; }, }, }), compareBarCustom({ minItems: 2, maxItems: 3, configurationId: 'CONFIGURATION_ID', }), chat({ container: '#chat', agentId: 'AGENT_ID', }), ]); search.start(); ``` # configure Source: https://www.algolia.com/doc/api-reference/widgets/configure/js Adds search parameters to your requests. ```ts Signature theme={"system"} configure(searchParameters: object); ``` ## Import ```js Package manager theme={"system"} import { configure } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { configure } = instantsearch.widgets; // or directly use instantsearch.widgets.configure() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=configure) > > Preview this widget and its behavior. ## About this widget The `configure` widget lets you provide raw search parameters to the Algolia API without rendering anything. Any props you add to this widget is forwarded to Algolia. For more information about the different parameters you can set, see the [search parameters API reference](/doc/api-reference/search-api-parameters). ## Examples ```js JavaScript icon=code theme={"system"} configure({ hitsPerPage: 8, enablePersonalization: true, }); ``` ## Options **searchParameters** `object` *required* A list of [search parameters](/doc/api-reference/search-api-parameters) to enable when the widget mounts. ```js JavaScript icon=code theme={"system"} configure({ hitsPerPage: 8, distinct: true, clickAnalytics: true, enablePersonalization: true, }); ``` ## HTML output This widget doesn't have any HTML output. ## Customize the UI with `connectConfigure` If you want to create your own UI of the `configure` widget, you can use connectors. To use `connectConfigure`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectConfigure } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectConfigure } = instantsearch.connectors; // or directly use instantsearch.connectors.connectConfigure() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderConfigure = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customConfigure = connectConfigure(renderConfigure); // 3. Instantiate search.addWidgets([ customConfigure({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderConfigure = (renderOptions, isFirstRender) => { const { refine, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **refine** `function` Removes the provided `searchParameters` and applies the one provided to the function. ```js JavaScript icon=code theme={"system"} const renderConfigure = (renderOptions, isFirstRender) => { const { refine } = renderOptions; if (isFirstRender) { const button = document.createElement("button"); button.textContent = 'Sets "hitsPerPage" to 4'; button.addEventListener("click", () => { refine({ hitsPerPage: 4 }); }); document.querySelector("#configure").appendChild(button); } }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderConfigure = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = `
    ${JSON.stringify(widgetParams.searchParameters, null, 2)}
    `; }; // ... search.addWidgets([ customConfigure({ container: document.querySelector("#configure"), searchParameters: { hitsPerPage: 8, }, }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customConfigure = connectConfigure(renderConfigure); search.addWidgets([customConfigure({ searchParameters: {} })]); ``` #### Instance options **searchParameters** `object` *required* A list of [search parameters](/doc/api-reference/search-api-parameters) to enable when this widget renders. ```js JavaScript icon=code theme={"system"} customConfigure({ searchParameters: { hitsPerPage: 8, distinct: true, clickAnalytics: true, }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderConfigure = (renderOptions, isFirstRender) => { const { refine, widgetParams } = renderOptions; if (isFirstRender) { const button = document.createElement("button"); const pre = document.createElement("pre"); button.addEventListener("click", () => { refine({ hitsPerPage: widgetParams.searchParameters.hitsPerPage === 8 ? 4 : 8, }); }); widgetParams.container.appendChild(button); widgetParams.container.appendChild(pre); } widgetParams.container.querySelector("button").textContent = `Sets "hitsPerPage" to ${ widgetParams.searchParameters.hitsPerPage === 8 ? 4 : 8 }`; widgetParams.container.querySelector("pre").innerHTML = JSON.stringify( widgetParams.searchParameters, null, 2, ); }; // Create the custom widget const customConfigure = connectConfigure(renderConfigure, () => {}); // Instantiate the custom widget search.addWidgets([ customConfigure({ container: document.querySelector("#configure"), searchParameters: { hitsPerPage: 8, }, }), ]); ``` # Configure Source: https://www.algolia.com/doc/api-reference/widgets/configure/react Adds search parameters to your requests. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { Configure } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=configure) > > Preview this widget and its behavior. ## About this widget `Configure` is a renderless component that lets you forward [search parameters](/doc/api-reference/search-api-parameters) to Algolia. Any prop you pass to this component is forwarded as a [search parameter](/doc/api-reference/search-api-parameters) to Algolia. > [!WARNING] > > Don't make the widget itself conditional, for example, in a ternary statement. > Doing so creates an infinite loop. > [!TIP] > > You can also create your own UI with [`useConfigure`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Configure } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **...searchParameters** `SearchParameters` A list of [search parameters](/doc/api-reference/search-api-parameters) to enable. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `Configure` widget with `useConfigure`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useConfigure` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useConfigure } from "react-instantsearch"; function CustomConfigure(props) { const { refine } = useConfigure(props); return null; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **...searchParameters** `SearchParameters` A list of [search parameters](/doc/api-reference/search-api-parameters) to enable. It returns an object with a [`refine`](/doc/api-reference/widgets/configure/react#param-refine) function that you can use to replace the provided search parameters with new ones. ```jsx JavaScript icon=code theme={"system"} const configureApi = useConfigure({ hitsPerPage: 4, analytics: false, distinct: true, }); ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **refine** `(value: SearchParameters) =\> void` Replaces the provided search parameters with new ones. ```jsx JavaScript icon=code theme={"system"} const { refine } = useConfigure({ hitsPerPage: 4, }); return ( ); ``` ### Example ```jsx javascript theme={"system"} import React from "react"; import { useConfigure } from "react-instantsearch"; function CustomConfigure(props) { useConfigure(props); return null; } ``` ```tsx TypeScript icon=code theme={"system"} import React from "react"; import { useConfigure, UseConfigureProps } from "react-instantsearch"; function CustomConfigure(props: UseConfigureProps) { useConfigure(props); return null; } ``` # currentRefinements Source: https://www.algolia.com/doc/api-reference/widgets/current-refinements/js Shows applied refinements and lets users remove them. ```ts Signature theme={"system"} currentRefinements({ container: string | HTMLElement, // Optional parameters includedAttributes?: string[], excludedAttributes?: string[], cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { currentRefinements } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { currentRefinements } = instantsearch.widgets; // or directly use instantsearch.widgets.currentRefinements() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=currentRefinements) > > Preview this widget and its behavior. ## About this widget The `currentRefinements` widget displays a list of [refinements](/doc/api-reference/widgets/refinement-list/js) applied to the search. ## Examples ```js JavaScript icon=code theme={"system"} currentRefinements({ container: "#current-refinements", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} currentRefinements({ container: "#current-refinements", }); ``` ```js HTMLElement theme={"system"} currentRefinements({ container: document.querySelector("#current-refinements"), }); ``` **includedAttributes** `string[]` (default: \[]) The attributes to include in the widget (all by default). Can't be used with [`excludedAttributes`](/doc/api-reference/widgets/current-refinements/js#param-excluded-attributes). In the example below, only the `categories` attribute is included in the widget. ```js JavaScript icon=code theme={"system"} currentRefinements({ // ... includedAttributes: ["categories"], }); ``` **excludedAttributes** `string[]` (default: \['query']) The attributes to exclude from the widget. Can't be used with [`includedAttributes`](/doc/api-reference/widgets/current-refinements/js#param-included-attributes). In the example below, the `brand` attribute is excluded from the widget. ```js JavaScript icon=code theme={"system"} currentRefinements({ // ... excludedAttributes: ["brand"], }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `noRefinementRoot`. The root element if there are no refinements. * `list`. The list element. * `item`. The list items. * `label`. The label element of each item. * `category`. The container of each item value. * `categoryLabel`. The text element of each item value. * `delete`. The button element of each item value. ```js JavaScript icon=code theme={"system"} currentRefinements({ // ... cssClasses: { root: "MyCustomCurrentRefinements", list: [ "MyCustomCurrentRefinementsList", "MyCustomCurrentRefinementsList--subclass", ], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} currentRefinements({ // ... transformItems(items) { return items.filter((item) => item.attribute !== "brand"); }, }); // or, combined with results currentRefinements({ // ... transformItems(items, { results }) { return results.nbHits === 0 ? items : items.filter((item) => item.attribute !== "brand"); }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    • Category: Movies & TV Shows Others
    • Brand: Algolia
    ``` ## Customize the UI with `connectCurrentRefinements` If you want to create your own UI of the `currentRefinements` widget, you can use connectors. To use `connectCurrentRefinements`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectCurrentRefinements } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectCurrentRefinements } = instantsearch.connectors; // or directly use instantsearch.connectors.connectCurrentRefinements() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderCurrentRefinements = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customCurrentRefinements = connectCurrentRefinements( renderCurrentRefinements, ); // 3. Instantiate search.addWidgets([ customCurrentRefinements({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderCurrentRefinements = (renderOptions, isFirstRender) => { const { items, canRefine, refine, createURL, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` All the currently refined items, grouped by attribute. With each item: * `indexName: string`. The index name on which the refinement is applied. * `indexId: string`. The index id on which the refinement is applied. * `attribute: string`. The attribute on which the refinement is applied. * `label: string`. The textual representation of this attribute. * `refine: function`. Removes the refinement. * `refinements: object[]`. Currently applied refinements. With each [refinement](/doc/api-reference/widgets/refinement-list/js): * `type: 'facet' | 'exclude' | 'disjunctive' | 'hierarchical' | 'numeric' | 'query'`. The type of the refinement. * `attribute: string`. The attribute on which the refinement is applied. * `label: string`. The label of the refinement to display. * `value: string`. The raw value of the refinement. * `operator?: string`. The value of the operator (only if applicable). * `exhaustive?: boolean`. Whether the count is exhaustive (only if applicable). * `count?: number`. The number of found items (only if applicable). ```js JavaScript icon=code theme={"system"} const renderListItem = (item) => `
  • ${item.attribute}
      ${item.refinements .map( (refinement) => `
    • ${refinement.value} (${refinement.count})
    • `, ) .join("")}
  • `; const renderCurrentRefinements = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector("#current-refinements").innerHTML = `
      ${items.map(renderListItem).join("")}
    `; }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderCurrentRefinements = (renderOptions, isFirstRender) => { const { canRefine } = renderOptions; if (!canRefine) { document.querySelector("#current-refinements").innerHTML = ""; return; } }; ``` **refine** `function` Clears a single [refinement](/doc/api-reference/widgets/refinement-list/js) and triggers a new search. ```js JavaScript icon=code theme={"system"} const createDataAttributes = (refinement) => Object.keys(refinement) .map((key) => `data-${key}="${refinement[key]}"`) .join(" "); const renderListItem = (item) => `
  • ${item.label}
      ${item.refinements .map( (refinement) => `
    • ${refinement.label} (${refinement.count})
    • `, ) .join("")}
  • `; const renderCurrentRefinements = (renderOptions, isFirstRender) => { const { items, refine } = renderOptions; const container = document.querySelector("#current-refinements"); container.innerHTML = `
      ${items.map(renderListItem).join("")}
    `; [...container.querySelectorAll("button")].forEach((element) => { element.addEventListener("click", (event) => { const item = Object.keys(event.currentTarget.dataset).reduce( (acc, key) => ({ ...acc, [key]: event.currentTarget.dataset[key], }), {}, ); refine(item); }); }); }; ``` **createURL** `function` Generates a URL for the next state. ```js JavaScript icon=code theme={"system"} const renderListItem = ({ item, createURL }) => `
  • ${item.label}
  • `; const renderCurrentRefinements = (renderOptions, isFirstRender) => { const { items, createURL } = renderOptions; document.querySelector("#current-refinements").innerHTML = `
      ${items.map((item) => renderListItem({ item, createURL })).join("")}
    `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderCurrentRefinements = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customCurrentRefinements({ container: document.querySelector("#current-refinements"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customCurrentRefinements = connectCurrentRefinements( renderCurrentRefinements ); search.addWidgets([ customCurrentRefinements({ // Optional parameters includedAttributes: string[], excludedAttributes: string[], transformItems: function, }) ]); ``` #### Instance options **includedAttributes** `string[]` (default: \[]) The attributes to include in the widget (all by default). Can't be used with [`excludedAttributes`](/doc/api-reference/widgets/current-refinements/js#param-excluded-attributes). In the example below, only the `categories` attribute is included in the widget. ```js JavaScript icon=code theme={"system"} customCurrentRefinements({ includedAttributes: ["categories"], }); ``` **excludedAttributes** `string[]` (default: \['query']) The attributes to exclude from the widget. Can't be used with [`includedAttributes`](/doc/api-reference/widgets/current-refinements/js#param-included-attributes). In the example below, the `brand` attribute is excluded from the widget. ```js JavaScript icon=code theme={"system"} customCurrentRefinements({ excludedAttributes: ["brand"], }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customCurrentRefinements({ transformItems(items) { return items.filter((item) => item.attribute !== "brand"); }, }); /* or, combined with results */ customCurrentRefinements({ transformItems(items, { results }) { return results.nbHits === 0 ? items : items.filter((item) => item.attribute !== "brand"); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const createDataAttributes = (refinement) => Object.keys(refinement) .map((key) => `data-${key}="${refinement[key]}"`) .join(" "); const renderListItem = (item) => `
  • ${item.label}
      ${item.refinements .map( (refinement) => `
    • ${refinement.label} (${refinement.count})
    • `, ) .join("")}
  • `; const renderCurrentRefinements = (renderOptions, isFirstRender) => { const { items, refine, widgetParams } = renderOptions; widgetParams.container.innerHTML = `
      ${items.map(renderListItem).join("")}
    `; [...widgetParams.container.querySelectorAll("button")].forEach((element) => { element.addEventListener("click", (event) => { const item = Object.keys(event.currentTarget.dataset).reduce( (acc, key) => ({ ...acc, [key]: event.currentTarget.dataset[key], }), {}, ); refine(item); }); }); }; // Create the custom widget const customCurrentRefinements = connectCurrentRefinements( renderCurrentRefinements, ); // Instantiate the custom widget search.addWidgets([ customCurrentRefinements({ container: document.querySelector("#current-refinements"), }), ]); ``` # dynamicWidgets Source: https://www.algolia.com/doc/api-reference/widgets/dynamic-widgets/js Renders matching refinement widgets in the facet order returned by Algolia. ```ts Signature theme={"system"} dynamicWidgets({ container: string | HTMLElement, widgets: function[], // Optional parameters transformItems?: function, fallbackWidget?: function, facets?: ['*']|[], maxValuesPerFacet?: number, }); ``` ## Import ```js Package manager theme={"system"} import { dynamicWidgets } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { dynamicWidgets } = instantsearch.widgets; // Use instantsearch.widgets.dynamicWidgets() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=js) > > Preview this widget and its behavior. ## About this widget This widget displays matching refinement widgets in the order provided by [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering) in the search response. Configure the order with [facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/js) , override it with [Algolia Rules](/doc/guides/managing-results/rules/rules-overview) , or generate it automatically with [Dynamic Facets](/doc/guides/algolia-ai/dynamic-facets) . Dynamic Facets orders facets and facet values based on recent search data. ### Requirements Declare each facet attribute in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting). Configure the facet order in the [Algolia dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or with [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering). By default, `dynamicWidgets` requests all facets with `facets: ["*"]` and requests up to 20 values per facet. These defaults help avoid a second request after the matching widgets mount. Override these defaults with [`facets`](#param-facets) and [`maxValuesPerFacet`](#param-max-values-per-facet). > [!NOTE] > > `dynamicWidgets` requires InstantSearch.js 4.72.0 or later. ## Examples ```js Default theme={"system"} dynamicWidgets({ container: "#dynamic-widgets", widgets: [ (container) => refinementList({ container, attribute: "brand" }), (container) => hierarchicalMenu({ container, attributes: [ "hierarchicalCategories.lvl0", "hierarchicalCategories.lvl1", ], }), ], fallbackWidget: ({ container, attribute }) => panel({ templates: { header: attribute } })(menu)({ container, attribute }), }); ``` ```js Request customization theme={"system"} dynamicWidgets({ container: "#dynamic-widgets", widgets: [ (container) => refinementList({ container, attribute: "brand" }), (container) => hierarchicalMenu({ container, attributes: [ "hierarchicalCategories.lvl0", "hierarchicalCategories.lvl1", ], }), ], fallbackWidget: ({ container, attribute }) => panel({ templates: { header: attribute } })(menu)({ container, attribute }), // Send a second request after the widgets mount. facets: [], // Set this to at least the number of pinned values. maxValuesPerFacet: 100, }); ``` ## Options **container** `string | HTMLElement` *required* A CSS selector or `HTMLElement` that contains the widget. ```js String theme={"system"} dynamicWidgets({ // ... container: "#dynamic-widgets", }); ``` ```js HTMLElement theme={"system"} dynamicWidgets({ // ... container: document.querySelector("#dynamic-widgets"), }); ``` **widgets** `Array\<(container: HTMLElement) =\> Widget\>` *required* An array of functions that create refinement widgets. Each function receives a container and returns a widget. The returned widget must define an `attribute` or `attributes` option so that `dynamicWidgets` can match and order it. The function can also return a custom widget created with a connector. ```js Widget theme={"system"} dynamicWidgets({ // ... widgets: [(container) => refinementList({ container, attribute: "brand" })], }); ``` ```js Hierarchical icon=code theme={"system"} dynamicWidgets({ // ... widgets: [ (container) => hierarchicalMenu({ container, attributes: [ "hierarchicalCategories.lvl0", "hierarchicalCategories.lvl1", ], }), ], }); ``` ```js Panel theme={"system"} dynamicWidgets({ // ... widgets: [ (container) => panel({ templates: { header: "Brand" }, })(refinementList)({ container, attribute: "brand", }), ], }); ``` ```js Custom widget theme={"system"} const customRefinementList = connectRefinementList(() => { // Implement custom widget }); dynamicWidgets({ // ... widgets: [ (container) => customRefinementList({ container, attribute: "brand", }), ], }); ``` **fallbackWidget** `(args: \{ attribute: string, container: HTMLElement \}) =\> Widget` A function that's called for every attribute you want to display. It receive a container and attribute and is expected to return a widget. The function can also return custom widgets created with connectors. ```js Widget theme={"system"} dynamicWidgets({ // ... fallbackWidget: ({ container, attribute }) => refinementList({ container, attribute }), }); ``` ```js Panel theme={"system"} dynamicWidgets({ // ... fallbackWidget: ({ container, attribute }) => panel({ templates: { header: attribute }, })(refinementList)({ container, attribute }), }); ``` ```js Custom widget theme={"system"} const customRefinementList = connectRefinementList(() => { // implement custom widget }); dynamicWidgets({ // ... fallbackWidget: ({ container, attribute }) => customRefinementList({ container, attribute }), }); ``` **transformItems** `function` Transforms the facet attributes before rendering. The second argument includes the search results, which you can use to derive a different attribute list. ```js JavaScript icon=code theme={"system"} dynamicWidgets({ // ... transformItems(items, { results }) { return items; }, }); ``` **facets** `['*']|[]` (default: \['\*']) The facets to apply before dynamic widgets are mounted. Setting the value to `['*']` requests all facets and avoids an additional network request. ```js Default theme={"system"} dynamicWidgets({ // ... facets: ["*"], }); ``` ```js No facets theme={"system"} dynamicWidgets({ // ... facets: [], }); ``` **maxValuesPerFacet** `number` (default: 20) The maximum number of values to request for each facet. Set this value to at least the highest `limit` or `showMoreLimit` used by a dynamic widget. This helps avoid an additional request after the widget mounts. Set `maxValuesPerFacet` to at least the largest number of pinned values configured for any facet. Otherwise, some pinned values might not appear. ```js JavaScript icon=code theme={"system"} dynamicWidgets({ // ... maxValuesPerFacet: 500, }); ``` ## Customize the UI with `connectDynamicWidgets` Use `connectDynamicWidgets` to build a custom UI for `dynamicWidgets`. Import the connector using the method that matches your InstantSearch.js installation. ```js Package manager theme={"system"} import { connectDynamicWidgets } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectDynamicWidgets } = instantsearch.connectors; // Use instantsearch.connectors.connectDynamicWidgets() ``` Create a render function, pass it to `connectDynamicWidgets`, and add the resulting widget to the search: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderDynamicWidgets = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customDynamicWidgets = connectDynamicWidgets(renderDynamicWidgets); // 3. Instantiate search.addWidgets([ customDynamicWidgets({ // instance params }), ]); ``` ### Create a render function InstantSearch.js calls the render function before the first search during the `init` lifecycle and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderDynamicWidgets = (renderOptions, isFirstRender) => { const { attributesToRender, widgetParams } = renderOptions; if (isFirstRender) { // Set up the initial DOM and event listeners. } // Render the widget }; ``` #### Rendering options **attributesToRender** `string[]` The ordered facet attributes to render, derived from [`renderingContent.facetOrdering.facets.order`](/doc/api-reference/api-parameters/renderingContent#param-facets-order). ```js JavaScript icon=code theme={"system"} const renderDynamicWidgets = (renderOptions, isFirstRender) => { const { attributesToRender } = renderOptions; document.querySelector("#dynamic-widgets").innerHTML = `
      ${attributesToRender .map( (attribute) => `
    • ${attribute}
    • `, ) .join("")}
    `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderDynamicWidgets = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customDynamicWidgets({ // ... container: document.querySelector("#dynamic-widgets"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customDynamicWidgets = connectDynamicWidgets( renderDynamicWidgets ); search.addWidgets([ customDynamicWidgets({ widgets: object[], // Optional parameters transformItems?: function, facets?: string[], maxValuesPerFacet?: number, }) ]); ``` #### Instance options **widgets** `object[]` *required* The widgets to add to the parent index. Update their DOM positions in the render function to match the order in `attributesToRender`. ```js JavaScript icon=code theme={"system"} customDynamicWidgets({ widgets: [ refinementList({ // ... }), customWidget({ // ... }), ], }); ``` **transformItems** `function` A function to transform the attributes to render, or using a different source to determine the attributes to render. ```js JavaScript icon=code theme={"system"} customDynamicWidgets({ // ... transformItems(items, { results }) { return items; }, }); ``` **facets** `['*']|[]` (default: \['\*']) The facets to apply before dynamic widgets get mounted. Setting the value to `['*']` will request all facets and avoid an additional network request once the widgets are added. ```js Default theme={"system"} customDynamicWidgets({ // ... facets: ["*"], }); ``` ```js No facets theme={"system"} customDynamicWidgets({ // ... facets: [], }); ``` **maxValuesPerFacet** `number` (default: 20) The default number of facet values to request. Set this value at least as high as the highest `limit` and `showMoreLimit` of dynamic widgets, to prevent a second network request once that widget mounts. To avoid pinned items not showing in the result, make sure you choose a `maxValuesPerFacet` at least as high as all the most pinned items you have. ```js JavaScript icon=code theme={"system"} customDynamicWidgets({ // ... maxValuesPerFacet: 500, }); ``` ### Full example ```html HTML icon=code-xml theme={"system"}
    ``` ```js JavaScript icon=code expandable theme={"system"} // Create a mapping between widgets and their attribute const { rangeSlider, refinementList } = instantsearch.widgets; const widgets = { brand: { widget: (container) => refinementList({ attribute: "brand", container }), container: document.createElement("div"), }, price: { widget: (container) => rangeSlider({ attribute: "price", container }), container: document.createElement("div"), }, }; // Create a render function const renderDynamicWidgets = (renderOptions, isFirstRender) => { const { attributesToRender, widgetParams } = renderOptions; if (isFirstRender) { const ul = document.createElement("ul"); widgetParams.container.appendChild(ul); } const ul = widgetParams.container.querySelector("ul"); ul.innerHTML = ""; attributesToRender.forEach((attribute) => { if (!widgets[attribute]) { return; } const container = widgets[attribute].container; ul.appendChild(container); }); }; // Create the custom widget const customDynamicWidgets = connectDynamicWidgets(renderDynamicWidgets); // Instantiate search.addWidgets([ customDynamicWidgets({ container: document.querySelector("#dynamic-widgets"), widgets: Object.values(widgets).map(({ widget, container }) => widget(container), ), }), ]); ``` # feeds Source: https://www.algolia.com/doc/api-reference/widgets/feeds/js Use the feeds widget to display multiple independent result sets from a multifeed composition. This widget is **in beta** and is subject to change in minor versions. ```ts Signature theme={"system"} feeds({ container: string | HTMLElement, widgets: (container: HTMLElement, feedID: string) => Widget[] | null | undefined, isolated: false, // Optional parameters transformFeeds?: (feedIDs: string[]) => string[], }); ``` ## Import ```js JavaScript icon=code theme={"system"} import { feeds } from 'instantsearch.js/es/widgets'; ``` > [!NOTE] > > The `feeds` widget isn't available from the UMD build. > Use InstantSearch.js with a packaging system. ## About this widget Use the `feeds` widget to display multiple independent result sets from a [multifeed composition](/doc/guides/compositions/multifeed-compositions). For each feed in the composition, the widget creates a scoped container and calls the `widgets` function to add InstantSearch widgets for that feed. Requires a composition-based InstantSearch instance (the `compositionID` option must be set on [`instantsearch`](/doc/api-reference/widgets/instantsearch/js)). ## Examples ```js JavaScript icon=code theme={"system"} import instantsearch from 'instantsearch.js'; import { feeds, hits } from 'instantsearch.js/es/widgets'; import { liteClient as algoliasearch } from 'algoliasearch/lite'; const searchClient = algoliasearch( 'ALGOLIA_APPLICATION_ID', 'ALGOLIA_SEARCH_API_KEY' ); const search = instantsearch({ indexName: 'instant_search', searchClient, compositionID: 'YourCompositionID', }); search.addWidgets([ feeds({ container: '#feeds', isolated: false, widgets(container, feedID) { return [ hits({ container, templates: { item(hit, { html, components }) { return html`

    ${components.Highlight({ attribute: 'name', hit })}

    ${hit.description}

    `; }, }, }), ]; }, }), ]); search.start(); ``` ## Options **container** `string | HTMLElement` *required* The CSS selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} feeds({ container: '#feeds', widgets(container) { return []; }, }); ``` ```js HTMLElement theme={"system"} feeds({ container: document.querySelector('#feeds'), widgets(container) { return []; }, }); ``` **widgets** `function` *required* A function that returns the widgets to use for each feed. Called once for each `feedID` when a new feed appears in the composition response. It receives: * `container`. An `HTMLElement` scoped to this feed. Pass it to the `container` option of your widget. * `feedID`. The unique identifier of the feed, as defined in the composition configuration. Return `null` or `undefined` to render the feed container without any widgets. ```js JavaScript icon=code theme={"system"} feeds({ // ... widgets(container, feedID) { return [ hits({ container, templates: { item(hit, { html }) { return html`

    ${hit.name}

    `; }, }, }), ]; }, }); ``` **isolated** `boolean` *required* Whether each feed runs an isolated search with its own search parameters. Only `false` is supported, meaning all feeds share the same global search state. The option is required to make the choice explicit. ```js JavaScript icon=code theme={"system"} feeds({ // ... isolated: false, widgets(container) { return []; }, }); ``` **transformFeeds** `function` A function to transform, reorder, or filter the feed IDs before rendering. Receives the array of feed IDs from the composition response and must return an array of feed IDs (strings). ```js JavaScript icon=code theme={"system"} feeds({ // ... isolated: false, transformFeeds(feedIDs) { // render only the first two feeds return feedIDs.slice(0, 2); }, widgets(container) { return []; }, }); ``` # frequentlyBoughtTogether Source: https://www.algolia.com/doc/api-reference/widgets/frequently-bought-together/js Shows recommendations from the "Frequently bought together" model. > [!NOTE] > > If you're using the deprecated [`recommend-js`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js) UI library, > see [`frequentlyBoughtTogether`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js/frequentlyBoughtTogether). ```ts Signature theme={"system"} frequentlyBoughtTogether({ container: string | HTMLElement, objectIDs: string[], // Optional parameters limit?: number, threshold?: number, queryParameters?: object, escapeHTML?: boolean, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { frequentlyBoughtTogether } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { frequentlyBoughtTogether } = instantsearch.widgets; // or directly use instantsearch.widgets.frequentlyBoughtTogether() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=frequentlyBoughtTogether) > > Preview this widget and its behavior. ## About this widget Use the `frequentlyBoughtTogether` widget to display a list of frequently bought together items. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) ## Examples ```js JavaScript icon=code theme={"system"} frequentlyBoughtTogether({ container: "#frequentlyBoughtTogether", objectIDs: ["5723537"], templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, }, }); ``` ### With a carousel layout Display the `frequentlyBoughtTogether` widget as a [scrollable carousel](#param-layout). ```js JavaScript icon=code theme={"system"} import { carousel } from "instantsearch.js/es/templates"; // or const { carousel } = instantsearch.templates; frequentlyBoughtTogether({ container: "#frequentlyBoughtTogether", objectIDs: ["5723537"], templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, layout: carousel(), }, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} frequentlyBoughtTogether({ // ... container: '#frequentlyBoughtTogether', }); ``` ```js HTMLElement theme={"system"} frequentlyBoughtTogether({ container: document.querySelector("#frequentlyBoughtTogether"), }); ``` **objectIDs** `string[]` *required* The list of `objectIDs` to get recommendations for. If you specify multiple `objectIDs`, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```js JavaScript icon=code theme={"system"} frequentlyBoughtTogether({ // ... objectIDs: ["1", "2", "3"], }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} frequentlyBoughtTogether({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} frequentlyBoughtTogether({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```js JavaScript icon=code theme={"system"} frequentlyBoughtTogether({ // ... queryParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} frequentlyBoughtTogether({ // ... escapeHTML: false, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} frequentlyBoughtTogether({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The widget's root element. * `emptyRoot`. The container element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of recommendations. * `item`. The list item. ```js JavaScript icon=code theme={"system"} frequentlyBoughtTogether({ // ... cssClasses: { root: "MyCustomFrequentlyBoughtTogether", list: [ "MyCustomFrequentlyBoughtTogether", "MyCustomFrequentlyBoughtTogether--subclass", ], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} frequentlyBoughtTogether({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **empty** `string | function` The template to use when there are no recommendations. ```js function theme={"system"} frequentlyBoughtTogether({ // ... templates: { empty(_, { html }) { return html`

    No recommendations.

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. frequentlyBoughtTogether({ // ... templates: { empty: "No recommendations.", }, }); ``` **header** `string | function` The template to use for the recommendations header. This template receives the recommendations as well as the `cssClasses` object. ```js function theme={"system"} frequentlyBoughtTogether({ // ... templates: { header({ cssClasses, items }, { html }) { return html`

    Recommendations (${items.length})

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. frequentlyBoughtTogether({ // ... templates: { header: "

    Recommendations ({{items.length}})

    ", }, }); ``` **item** `string | function` The template to use for each recommendation. This template receives an object containing a single record. ```js function theme={"system"} frequentlyBoughtTogether({ // ... templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. frequentlyBoughtTogether({ // ... templates: { item: `

    {{ name }}

    {{ description }}

    `, }, }); ``` **layout** `function` The template to use to wrap all items. InstantSearch.js provides an out-of-the-box carousel layout template with the following options: `cssClasses`: * `root`. The carousel's root element. * `list`. The list of recommendations. * `item`. The list item. * `navigation`. The navigation controls. * `navigationPrevious`. The "Previous" navigation control. * `navigationNext`. The "Next" navigation control. `templates`: * `previous`. The content of the "Previous" navigation control. * `next`. The content of the "Next" navigation control. ```js Carousel theme={"system"} import { carousel } from 'instantsearch.js/es/templates'; // or const { carousel } = instantsearch.templates; frequentlyBoughtTogether({ // ... templates: { layout: carousel({ cssClasses: { root: 'MyCustomCarousel', }, templates: { previous({ html }) { return html`

    Previous

    `; }, next({ html }) { return html`

    Next

    `; }, }, }), }, }); ``` ```js Custom theme={"system"} frequentlyBoughtTogether({ // ... templates: { layout({ items }, { html }) { return html`
      ${items.map((item) => html`
    • ${item.objectID}

    • `)}
    `; }, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Frequently Bought Together

    1. ...
    2. ...
    3. ...
    ``` ## Customize the UI with `connectFrequentlyBoughtTogether` If you want to create your own UI of the `frequentlyBoughtTogether` widget, you can use connectors. To use `connectFrequentlyBoughtTogether`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectFrequentlyBoughtTogether } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectFrequentlyBoughtTogether } = instantsearch.connectors; // or directly use instantsearch.connectors.connectFrequentlyBoughtTogether() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderFrequentlyBoughtTogether = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customFrequentlyBoughtTogether = connectFrequentlyBoughtTogether( renderFrequentlyBoughtTogether ); // 3. Instantiate search.addWidgets([ customFrequentlyBoughtTogether({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderFrequentlyBoughtTogether = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` The matched recommendations from the Algolia API. ```js JavaScript icon=code theme={"system"} const renderFrequentlyBoughtTogether = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector('#frequentlyBoughtTogether').innerHTML = `
      ${items .map(item => `
    • ${item.name}
    • `) .join('')}
    `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderFrequentlyBoughtTogether = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = '...'; }; // ... search.addWidgets([ customFrequentlyBoughtTogether({ // ... container: document.querySelector('#frequentlyBoughtTogether'), }) ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customFrequentlyBoughtTogether = connectFrequentlyBoughtTogether( renderFrequentlyBoughtTogether ); search.addWidgets([ customFrequentlyBoughtTogether({ objectIDs: string[], // Optional parameters limit: number, threshold: number, queryParameters: object, escapeHTML: boolean, transformItems: function, }) ]); ``` #### Instance options **objectIDs** `string[]` *required* The list of object IDs for which to get recommendations. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). > For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```js JavaScript icon=code theme={"system"} customFrequentlyBoughtTogether({ // ... objectIDs: ["1", "2", "3"], }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} customFrequentlyBoughtTogether({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} customFrequentlyBoughtTogether({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```js JavaScript icon=code theme={"system"} customFrequentlyBoughtTogether({ // ... queryParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} customFrequentlyBoughtTogether({ // ... escapeHTML: false, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customFrequentlyBoughtTogether({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderFrequentlyBoughtTogether = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; widgetParams.container.innerHTML = `
      ${items .map(item => `
    • ${item.name}
    • `) .join('')}
    `; }; // Create the custom widget const customFrequentlyBoughtTogether = connectFrequentlyBoughtTogether( renderFrequentlyBoughtTogether ); // Instantiate the custom widget search.addWidgets([ customFrequentlyBoughtTogether({ container: document.querySelector('#frequentlyBoughtTogether'), objectIDs: ['5723537'], }) ]); ``` # geoSearch Source: https://www.algolia.com/doc/api-reference/widgets/geo-search/js Lets users perform location-based searches inside a bounding box on a map. ```ts Signature theme={"system"} geoSearch({ container: string | HTMLElement, googleReference: object, // Optional parameters initialZoom?: number, initialPosition?: object, mapOptions?: object, builtInMarker?: object, customHTMLMarker?: object, enableRefine?: boolean, enableClearMapRefinement?: boolean, enableRefineControl?: boolean, enableRefineOnMapMove?: boolean, templates?: object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { geoSearch } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { geoSearch } = instantsearch.widgets; // or directly use instantsearch.widgets.geoSearch() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=geosearch\&flavor=js) > > Preview this widget in a live map search. ## About this widget The `geoSearch` widget lets you search for results based on their position within a specified area (a bounding box). It also provides features such as "search on map interactions". > [!NOTE] > > The `geoSearch` widget doesn't let you [search around a central point](/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-around-a-location) > or [within polygons](/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-inside-a-polygonal-area). > If you want this, you need to build your own UI on top of the Algolia API. ### Requirements * Your hit records must have a [`_geoloc` attribute](/doc/guides/managing-results/refine-results/geolocation#enabling-geo-search-by-adding-geolocation-data-to-records) so they can be displayed on the map. * You must load the [Google Maps library](https://developers.google.com/maps/documentation/javascript/overview) and pass a reference to the widget: the library doesn't come with InstantSearch.js. * You must explicitly set the map container's `height`. For example: ```css CSS theme={"system"} .ais-GeoSearch-map { height: 500px; // You can change this height } ``` ## Examples ```js JavaScript icon=code theme={"system"} geoSearch({ container: "#geo-search", googleReference: window.google, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} geoSearch({ // ... container: "#geo-search", }); ``` ```js HTMLElement theme={"system"} geoSearch({ // ... container: document.querySelector("#geo-search"), }); ``` **googleReference** `object` *required* The reference to the global `window.google` object. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... googleReference: window.google, }); ``` **initialZoom** `number` (default: 1) By default, the map sets the zoom level based on the displayed markers (results). However, after InstantSearch has applied a refinement, there may be no results. When this happens, a specific zoom level is required to render the map. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... initialZoom: 4, }); ``` **initialPosition** `object` (default: \{lat: 0, lng: 0}) By default, the map sets the position based on the displayed markers (results). However, after InstantSearch has applied a refinement, there may be no results. When this happens, a specific position is required to render the map. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... initialPosition: { lat: 48.864716, lng: 2.349014, }, }); ``` **mapOptions** `object` The [options forwarded to the Google Maps constructor](https://developers.google.com/maps/documentation/javascript/reference#MapOptions). ```js JavaScript icon=code theme={"system"} geoSearch({ // ... mapOptions: { streetViewControl: true, }, }); ``` **builtInMarker** `object` The options for customizing the built-in Google Maps markers. This is ignored when the [`customHTMLMarker`](#param-custom-html-marker) is provided. The object accepts multiple attributes: * `createOptions: function`. A function to create the [options passed to the Google Maps marker](https://developers.google.com/maps/documentation/javascript/reference#MapOptions). The function is called with `item`, which is the `hit` tied to the marker. * `events: object`. An object that takes event types (such as `click` and `mouseover`) as keys and listeners as values. The listener is called with an object that contains properties `event`, `item`, `marker`, and `map`. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... builtInMarker: { createOptions(item) { return { title: item.name, }; }, events: { click({ event, item, marker, map }) { console.log(item); }, }, }, }); ``` **customHTMLMarker** `object` The options for customizing the HTML marker. InstantSearch.js provides an alternative to the built-in Google Maps markers. You can use plain HTML to build your custom markers (see [`templates.HTMLMarker`](/doc/api-reference/widgets/geo-search/js#param-html-marker)). The `customHTMLMarker` object accepts several attributes: * `createOptions: function`. A function to create the options passed to the `HTMLMarker`. The only supported option is `anchor`. It lets you shift the marker position from the center of the element. * `events: object`. An object that takes event types (such as `click` and `mouseover`) as keys and listeners as values. The listener is called with an object that contains properties `event`, `item`, `marker`, and `map`. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... customHTMLMarker: { createOptions(item) { return { anchor: { x: 0, y: 0, }, }; }, events: { click({ event, item, marker, map }) { console.log(item); }, }, }, }); ``` **enableRefine** `boolean` (default: true) If `true`, the map is used for refining the search. Otherwise, it's only for display purposes. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... enableRefine: false, }); ``` **enableClearMapRefinement** `boolean` (default: true) If `true`, a button is displayed on the map to allow users to clear any refinements. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... enableClearMapRefinement: false, }); ``` **enableRefineControl** `boolean` (default: true) If `true`, users can toggle the option [`enableRefineOnMapMove`](/doc/api-reference/widgets/geo-search/js#param-enable-refine-on-map-move) directly from the map. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... enableRefineControl: false, }); ``` **enableRefineOnMapMove** `boolean` (default: true) If `true`, refine is triggered as you move the map. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... enableRefineOnMapMove: false, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The widget's root element. * `map`. The map element. * `control`. The control element. * `label`. The label for the control element. * `selectedLabel`. The control element's selected label. * `input`. The control element's input. * `redo`. The "Redo search" button. * `disabledRedo`. The "Redo search" button when it's unavailable. * `reset`. The "Reset refinement" button. ```js JavaScript icon=code theme={"system"} geoSearch({ // ... cssClasses: { root: "MyCustomGeoSearch", map: ["MyCustomGeoSearchMap", "MyCustomGeoSearchMap--subclass"], }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **HTMLMarker** `string | function` The template to use for the marker. ```js function theme={"system"} geoSearch({ // ... templates: { HTMLMarker(_, { html }) { return html`

    Your custom HTML Marker

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. geoSearch({ // ... templates: { HTMLMarker: "

    Your custom HTML Marker

    ", }, }); ``` **reset** `string | function` The template for the reset button. ```js function theme={"system"} geoSearch({ // ... templates: { reset(_, { html }) { return html`Clear the map refinement`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. geoSearch({ // ... templates: { reset: "Clear the map refinement", }, }); ``` **toggle** `string | function` The template for the toggle label. ```js function theme={"system"} geoSearch({ // ... templates: { toggle(_, { html }) { return html`Search as I move the map`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. geoSearch({ // ... templates: { toggle: "Search as I move the map", }, }); ``` **redo** `string | function` The template for the redo label. ```js function theme={"system"} geoSearch({ // ... templates: { redo(_, { html }) { return html`Redo search here`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. geoSearch({ // ... templates: { redo: "Redo search here", }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectGeoSearch` If you want to create your own UI of the `geoSearch` widget, you can use connectors. To use `connectGeoSearch`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectGeoSearch } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectGeoSearch } = instantsearch.connectors; // or directly use instantsearch.connectors.connectGeoSearch() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderGeoSearch = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customGeoSearch = connectGeoSearch(renderGeoSearch); // 3. Instantiate search.addWidgets([ customGeoSearch({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderGeoSearch = (renderOptions, isFirstRender) => { const { items, position, currentRefinement, refine, sendEvent, clearMapRefinement, isRefinedWithMap, toggleRefineOnMapMove, isRefineOnMapMove, setMapMoveSinceLastRefine, hasMapMoveSinceLastRefine, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options The following rendering option example code snippets use [Leaflet](https://leafletjs.com/examples/quick-start) to render the map. If you prefer not to use Leaflet, you can use another library (such as [Google Maps](https://developers.google.com/maps/documentation/javascript/overview) or [Mapbox](https://www.mapbox.com)). **items** `object[]` Hits that matched the search request. ```js JavaScript icon=code theme={"system"} let let markers = []; const renderGeoSearch = (renderOptions, isFirstRender) => { const { items } = renderOptions; if (isFirstRender) { map = L.map(document.querySelector("#geo-search")); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: '© OpenStreetMap contributors', }).addTo(map); } markers.forEach((marker) => marker.remove()); markers = items.map(({ _geoloc }) => L.marker([_geoloc.lat, _geoloc.lng]).addTo(map), ); if (markers.length) { map.fitBounds(L.featureGroup(markers).getBounds()); } }; ``` **position** `object` The current search position, when applicable. ```js JavaScript icon=code theme={"system"} let map = null; let markers = []; const renderGeoSearch = (renderOptions, isFirstRender) => { const { items, position } = renderOptions; if (isFirstRender) { map = L.map(document.querySelector("#geo-search")); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: '© OpenStreetMap contributors', }).addTo(map); } markers.forEach((marker) => marker.remove()); markers = items.map(({ _geoloc }) => L.marker([_geoloc.lat, _geoloc.lng]).addTo(map), ); if (markers.length) { map.fitBounds(L.featureGroup(markers).getBounds()); } else { map.setView( position || { lat: 48.864716, lng: 2.349014, }, 12, ); } }; ``` **currentRefinement** `object` The search's bounding box: * `northEast: { lat: number, lng: number }`. The top right corner of the map view. * `southWest: { lat: number, lng: number }`. The bottom left corner of the map view. **refine** `function` Sets a bounding box to filter the results from the given map bounds. The function accepts an object with: * `northEast: { lat: number, lng: number }`. The top right corner of the map view. * `southWest: { lat: number, lng: number }`. The bottom left corner of the map view. **sendEvent** `(eventType, hit, eventName) =\> void` The function to send `click` or `conversion` events. The `view` event is automatically sent when this connector renders hits. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware. * `eventType: 'click' | 'conversion'` * `hit: Hit | Hit[]` * `eventName: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent("click", hit, "Location Starred"); // or sendEvent("conversion", hit, "Restaurant Saved"); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedObjectIDsAfterSearch', payload: { eventName: 'Product Added', index: '', objectIDs: [''], positions: [], queryID: '', }, widgetType: 'ais.geoSearch', } */ ``` **clearMapRefinement** `function` Resets the current bounding box refinement. **isRefinedWithMap** `function` Returns `true` if the current refinement is set with the map bounds. **toggleRefineOnMapMove** `function` Toggles whether users can refine on map move. **isRefineOnMapMove** `function` Returns `true` if users can refine on map move. **setMapMoveSinceLastRefine** `function` Indicates that the map has moved since the last refinement. This function should be called on each map move. It helps ensure the map is only re-rendered when it has moved. **hasMapMoveSinceLastRefine** `function` Returns `true` if the map has moved since the last refinement. **widgetParams** `object` All original widget options forwarded to the render function. ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customGeoSearch = connectGeoSearch(renderGeoSearch); search.addWidgets([ customGeoSearch({ // Optional parameters enableRefineOnMapMove, transformItems, }), ]); ``` #### Instance options **enableRefineOnMapMove** `boolean` (default: true) If `true`, refine is triggered as you move the map. ```js JavaScript icon=code theme={"system"} customGeoSearch({ enableRefineOnMapMove: false, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customGeoSearch({ transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); // or, combined with results customGeoSearch({ transformItems(items, { results }) { return items.query.length === 0 ? items : items.map((item) => ({ ...item, name: `${item.name} (matching "${results.query}")`, })); }, }); ``` ### Full example ```html HTML theme={"system"} ``` ```js JavaScript theme={"system"} // Create the render function let map = null; let markers = []; let isUserInteraction = true; const renderGeoSearch = (renderOptions, isFirstRendering) => { const { items, currentRefinement, refine, clearMapRefinement, widgetParams } = renderOptions; const { initialZoom, initialPosition, container } = widgetParams; if (isFirstRendering) { const element = document.createElement("div"); element.style.height = "100%"; const button = document.createElement("button"); button.textContent = "Clear the map refinement"; map = L.map(element); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: '© OpenStreetMap contributors', }).addTo(map); map.on("moveend", () => { if (isUserInteraction) { const ne = map.getBounds().getNorthEast(); const sw = map.getBounds().getSouthWest(); refine({ northEast: { lat: ne.lat, lng: ne.lng }, southWest: { lat: sw.lat, lng: sw.lng }, }); } }); button.addEventListener("click", () => { clearMapRefinement(); }); container.appendChild(element); container.appendChild(button); } container.querySelector("button").hidden = !currentRefinement; markers.forEach((marker) => marker.remove()); markers = items.map(({ _geoloc }) => L.marker([_geoloc.lat, _geoloc.lng]).addTo(map), ); isUserInteraction = false; if (!currentRefinement && markers.length) { map.fitBounds(L.featureGroup(markers).getBounds(), { animate: false, }); } else if (!currentRefinement) { map.setView(initialPosition, initialZoom, { animate: false, }); } isUserInteraction = true; }; // Create the custom widget const customGeoSearch = connectGeoSearch(renderGeoSearch); // Instantiate the custom widget search.addWidgets([ customGeoSearch({ container: document.querySelector("#geo-search"), initialZoom: 12, initialPosition: { lat: 48.864716, lng: 2.349014, }, }), ]); ``` # hierarchicalMenu Source: https://www.algolia.com/doc/api-reference/widgets/hierarchical-menu/js Shows a hierarchical menu to navigate nested categories. ```ts Signature theme={"system"} hierarchicalMenu({ container: string | HTMLElement, attributes: string[], // Optional parameters limit?: number, showMore?: boolean, showMoreLimit?: number, separator?: string, rootPath?: string, showParentLevel?: boolean, sortBy?: string[] | function, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { hierarchicalMenu } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { hierarchicalMenu } = instantsearch.widgets; // or directly use instantsearch.widgets.hierarchicalMenu() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=hierarchicalMenu) > > Preview this widget and its behavior. ## About this widget The `hierarchicalMenu` widget displays a hierarchical navigation menu, based on facet attributes. To create a hierarchical menu: 1. Decide on an appropriate [facet hierarchy](/doc/guides/managing-results/refine-results/faceting#hierarchical-facets) 2. Determine your [`attributes`](#param-attributes) for faceting from 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) 3. Display the UI with the hierarchical menu widget. To learn more, see [Facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/js). ### Requirements The objects to use in the hierarchical menu must follow this structure: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories": { "lvl0": "products", "lvl1": "products > fruits" }, { "objectID": "8976987", "name": "orange", "categories": { "lvl0": "products", "lvl1": "products > fruits" } } ] ``` You can also provide more than one path for each level: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories": { "lvl0": ["products", "goods"], "lvl1": ["products > fruits", "goods > to eat"] } } ] ``` By default, the separator is `>` (with spaces), but you can use a different one by using the [`separator`](#param-separator) option. > [!NOTE] > > By default, the count of the refined root level is updated to match the count of the actively refined parent level. > Keep the root level count intact by setting [`persistHierarchicalRootCount`](/doc/api-reference/widgets/instantsearch/js#param-future-persist-hierarchical-root-count) in [`instantsearch`](/doc/api-reference/widgets/instantsearch/js). ## Examples ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ container: "#hierarchical-menu", attributes: [ "categories.lvl0", "categories.lvl1", "categories.lvl2", "categories.lvl3", ], }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} hierarchicalMenu({ // ... container: '#hierarchical-menu', }); ``` ```js HTMLElement theme={"system"} hierarchicalMenu({ // ... container: document.querySelector("#hierarchical-menu"), }); ``` **attributes** `string[]` *required* The name of the attributes to generate the menu with. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ // ... attributes: [ "categories.lvl0", "categories.lvl1", "categories.lvl2", "categories.lvl3", ], }); ``` **limit** `number` (default: 10) How many facet values to retrieve. When you enable the [`showMore`](#param-show-more) feature, this is the number of facet values to display before clicking the "Show more" button. ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ // ... limit: 5, }); ``` **showMore** `boolean` (default: false) Whether to display a button that expands the number of items. ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ // ... showMore: true, }); ``` **showMoreLimit** `number` The maximum number of displayed items (only used when [`showMore`](#param-show-more) is set to `true`). ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ // ... showMoreLimit: 20, }); ``` **separator** `string` (default: >) The level separator used in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ // ... separator: " / ", }); ``` **rootPath** `string` (default: null) The prefix path to use if the first level isn't the root level. Make sure to also include the root path in your [UI state](/doc/api-reference/widgets/ui-state/js), for example, by setting [`initialUiState`](/doc/api-reference/widgets/instantsearch/js#param-initial-ui-state) or calling [`setUiState()`](/doc/api-reference/widgets/instantsearch/js#param-set-ui-state). ```js JavaScript icon=code theme={"system"} instantsearch({ // ... initialUiState: { YourIndexName: { hierarchicalMenu: { "categories.lvl0": ["Computers & Tablets"], }, }, }, }).addWidgets([ hierarchicalMenu({ // ... rootPath: "Computers & Tablets", }), ]); ``` **showParentLevel** `boolean` (default: true) Whether to show the siblings of the selected parent level of the current refined value. This option doesn't impact the root level. All root items are always visible. ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ // ... showParentLevel: false, }); ``` **sortBy** `string[] | function` (default: Uses facetOrdering if set, \['name:asc']) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` You can also give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). ```js string[] theme={"system"} hierarchicalMenu({ // ... sortBy: ['isRefined'], }); ``` ```js function theme={"system"} hierarchicalMenu({ // ... sortBy(a, b) { return a.name < b.name ? 1 : -1; }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `noRefinementRoot`. The root element if there are no refinements. * `list`. The list of results. * `childList`. The child list element. * `item`. The list items. * `selectedItem`. The selected item of the list. * `parentItem`. The parent item of the list. * `link`. The link of each item. * `selectedItemLink`. The link of each selected item. * `label`. The label of each item. * `count`. The count of each item. * `showMore`. The "Show more" button. * `disabledShowMore`. The "Show more" button when it's unavailable. ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ // ... cssClasses: { root: "MyCustomHierarchicalMenu", list: [ "MyCustomHierarchicalMenuList", "MyCustomHierarchicalMenuList--subclass", ], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} hierarchicalMenu({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or, combined with results hierarchicalMenu({ // ... transformItems(items, { results }) { return items.map((item) => ({ ...item, label: item.isRefined ? `${item.label} (page ${results.page + 1}/${results.nbPages})` : item.label, })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **item** `string | function` The template for each item. It exposes: * `label: string`. The label of the item. * `value: string`. The value of the item. * `count: number`. The number of results matching the value. * `isRefined: boolean` Whether the item is selected. * `url: string`. The URL with the applied refinement. ```js function theme={"system"} hierarchicalMenu({ // ... templates: { item(data, { html }) { return html` ${data.label} ${data.count.toLocaleString()} `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. hierarchicalMenu({ // ... templates: { item: ` {{label}} {{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}} `, }, }); ``` **showMoreText** `string | function` The template for the "Show more" button text. It exposes: * `isShowingMore: boolean`. Whether the list is expanded. ```js function theme={"system"} hierarchicalMenu({ // ... templates: { showMoreText(data, { html }) { return html`${data.isShowingMore ? 'Show less' : 'Show more'}`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. hierarchicalMenu({ // ... templates: { showMoreText: ` {{#isShowingMore}} Show less {{/isShowingMore}} {{^isShowingMore}} Show more {{/isShowingMore}} `, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` ## Customize the UI with `connectHierarchicalMenu` If you want to create your own UI of the `hierarchicalMenu` widget, you can use connectors. To use `connectHierarchicalMenu`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectHierarchicalMenu } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectHierarchicalMenu } = instantsearch.connectors; // or directly use instantsearch.connectors.connectHierarchicalMenu() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderHierarchicalMenu = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customHierarchicalMenu = connectHierarchicalMenu(renderHierarchicalMenu); // 3. Instantiate search.addWidgets([ customHierarchicalMenu({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { items, isShowingMore, canToggleShowMore, canRefine, refine, sendEvent, toggleShowMore, createURL, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` > [!NOTE] > > If SEO is important for your search page, ensure that your custom HTML is optimized for search engines: > > * Use `` tags with `href` attributes to allow search engine bots to follow links. > * Use semantic HTML and include [structured data](https://developers.google.com/search/docs/appearance/structured-data) when relevant. > > For more guidance, see the [SEO checklist](/doc/guides/building-search-ui/resources/seo/js). #### Rendering options **items** `object[]` The list of available items, with each item: * `label: string`. The label of the item. * `value: string`. The value of the item. * `count: number`. The number results matching this value. * `isRefined: boolean`. Whether the item is selected. * `data: object[]|null`. The list of children for the current item. ```js JavaScript icon=code theme={"system"} const renderList = (items) => ` `; const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { items } = renderOptions; const children = renderList(items); document.querySelector("#hierarchical-menu").innerHTML = children; }; ``` **isShowingMore** `boolean` Whether the list is expanded. ```js JavaScript icon=code theme={"system"} const renderList = (items) => ` `; const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { items, isShowingMore } = renderOptions; document.querySelector("#hierarchical-menu").innerHTML = ` ${renderList(items)} `; }; ``` **canToggleShowMore** `boolean` Whether users can click the "Show more" button. ```js JavaScript icon=code theme={"system"} const renderList = (items) => ` `; const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { items, canToggleShowMore } = renderOptions; document.querySelector("#hierarchical-menu").innerHTML = ` ${renderList(items)} `; }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { items, canRefine } = renderOptions; if (!canRefine) { document.querySelector("#hierarchical-menu").innerHTML = ""; return; } }; ``` **refine** `function` Sets the path of the hierarchical filter and triggers a new search. ```js JavaScript icon=code theme={"system"} const renderList = (items) => ` `; const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { items, refine } = renderOptions; const container = document.querySelector("#hierarchical-menu"); container.innerHTML = renderList(items); [...container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.target.dataset.value); }); }); }; ``` **sendEvent** `(eventType, facetValue) =\> void` The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. * You can learn more about the [`insights`](/doc/api-reference/widgets/insights/js) middleware. * `eventType: 'click'` * `facetValue: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent('click', 'Laptop'); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['category:"Laptop"'], index: '', }, widgetType: 'ais.hierarchicalMenu', } */ ``` **toggleShowMore** `function` Toggles the number of displayed values between [`limit`](/doc/api-reference/widgets/hierarchical-menu/js#param-limit) and [`showMoreLimit`](/doc/api-reference/widgets/hierarchical-menu/js#param-show-more-limit). ```js JavaScript icon=code theme={"system"} const renderList = (items) => ` `; const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { items, isShowingMore, toggleShowMore } = renderOptions; const container = document.querySelector("#hierarchical-menu"); if (isFirstRender) { const list = document.createElement("div"); const button = document.createElement("button"); button.addEventListener("click", () => { toggleShowMore(); }); container.appendChild(list); container.appendChild(button); } container.querySelector("div").innerHTML = renderList(items); container.querySelector("button").textContent = isShowingMore ? "Show less" : "Show more"; }; ``` **createURL** `function` Generates a URL for the next state. ```js JavaScript icon=code theme={"system"} const renderList = ({ items, createURL }) => ` `; const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { items, createURL } = renderOptions; const children = renderList({ items, createURL }); document.querySelector("#hierarchical-menu").innerHTML = children; }; ``` **widgetParams** `object` All original widget options are forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = '...'; }; // ... search.addWidgets([ customHierarchicalMenu({ // ... container: document.querySelector('#hierarchical-menu'), }) ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```jsx JavaScript icon=code theme={"system"} const customHierarchicalMenu = connectHierarchicalMenu( renderHierarchicalMenu ); search.addWidgets([ customHierarchicalMenu({ attributes: string[], // Optional parameters limit?: number, showMoreLimit?: number, separator?: string, rootPath?: string, showParentLevel?: boolean, sortBy?: string[] | function, transformItems?: function, }) ]); ``` #### Instance options **attributes** `string[]` *required* The name of the attributes to generate the menu with. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```js JavaScript icon=code theme={"system"} customHierarchicalMenu({ attributes: [ "categories.lvl0", "categories.lvl1", "categories.lvl2", "categories.lvl3", ], }); ``` **limit** `number` (default: 10) The number of facet values to retrieve. When [`isShowingMore`](/doc/api-reference/widgets/hierarchical-menu/js#param-is-showing-more) is `false`, this is the number of facet values displayed before clicking the "Show more" button. ```js JavaScript icon=code theme={"system"} customHierarchicalMenu({ // ... limit: 5, }); ``` **showMoreLimit** `number` The maximum number of displayed items (only used when the `showMore` feature is implemented). ```js JavaScript icon=code theme={"system"} customHierarchicalMenu({ // ... showMoreLimit: 20, }); ``` **separator** `string` (default: >) The level separator used in the records. ```js JavaScript icon=code theme={"system"} customHierarchicalMenu({ // ... separator: " / ", }); ``` **rootPath** `string` The prefix path to use if the first level isn't the root level. Make sure to also include the root path in your [UI state](/doc/api-reference/widgets/ui-state/js), for example, by setting [`initialUiState`](/doc/api-reference/widgets/instantsearch/js#param-initial-ui-state) or calling [`setUiState()`](/doc/api-reference/widgets/instantsearch/js#param-set-ui-state). ```js JavaScript icon=code theme={"system"} customHierarchicalMenu({ // ... rootPath: "Computers & Tablets", }); ``` **showParentLevel** `boolean` (default: true) Whether to show the siblings of the selected parent level of the current refined value. ```js JavaScript icon=code theme={"system"} customHierarchicalMenu({ // ... showParentLevel: false, }); ``` **sortBy** `string[] | function` (default: Uses facetOrdering if set, \['name:asc']) How to sort refinements. Must be one or more of the following strings: * `"count:asc"` * `"count:desc"` * `"name:asc"` * `"name:desc"` * `"isRefined"` You can also give a function that receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) If `facetOrdering` is set for this facet in `renderingContent`, and no value for `sortBy` is passed to this widget, `facetOrdering` is used, and the default order as a fallback. ```js string[] theme={"system"} customHierarchicalMenu({ // ... sortBy: ['isRefined'], }); ``` ```js function theme={"system"} customHierarchicalMenu({ // ... sortBy(a, b) { return a.name < b.name ? 1 : -1; }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customHierarchicalMenu({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); /* or, combined with results */ customHierarchicalMenu({ // ... transformItems(items, { results }) { return items.map((item) => ({ ...item, label: item.isRefined ? `${item.label} (page ${results.page + 1}/${results.nbPages})` : item.label, })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderList = ({ items, createURL }) => ` `; const renderHierarchicalMenu = (renderOptions, isFirstRender) => { const { items, isShowingMore, refine, toggleShowMore, createURL, widgetParams, } = renderOptions; if (isFirstRender) { const list = document.createElement("div"); const button = document.createElement("button"); button.addEventListener("click", () => { toggleShowMore(); }); widgetParams.container.appendChild(list); widgetParams.container.appendChild(button); } const children = renderList({ items, createURL }); widgetParams.container.querySelector("div").innerHTML = children; widgetParams.container.querySelector("button").textContent = isShowingMore ? "Show less" : "Show more"; [...widgetParams.container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.target.dataset.value); }); }); }; // Create the custom widget const customHierarchicalMenu = connectHierarchicalMenu(renderHierarchicalMenu); // Instantiate the custom widget search.addWidgets([ customHierarchicalMenu({ container: document.querySelector("#hierarchical-menu"), attributes: [ "categories.lvl0", "categories.lvl1", "categories.lvl2", "categories.lvl3", ], limit: 5, showMoreLimit: 10, }), ]); ``` # Highlight Source: https://www.algolia.com/doc/api-reference/widgets/highlight/js Visually highlights matched terms in search results. ```ts Signature theme={"system"} components.Highlight({ attribute: string, hit: object, // Optional parameters highlightedTagName?: string, nonHighlightedTagName?: string, separator?: string, cssClasses?: object, }); ``` ## About this widget Displays the [highlighted attributes](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/js) of your search results. It isn't a widget but a component available from the [`hits`](/doc/api-reference/widgets/hits/js) or [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widgets templates. ## Examples ```js JavaScript icon=code theme={"system"} hits({ container: "#hits", templates: { item(hit, { html, components }) { return html`

    ${components.Highlight({ attribute: "name", hit })}

    ${hit.description}

    `; }, }, }); ``` ## Options **attribute** `string` *required* The highlighted attribute. Specify a dot-separated value for nested objects, like `actor.name` ```js JavaScript icon=code theme={"system"} components.Highlight({ // ... attribute: "actor.name", }); ``` **hit** `object` *required* The original [`hit` object](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response#hits) provided to the function. It contains all the data for that result, including the highlighted parts. > [!NOTE] > > The `hit` object must contain a `_highlightResult[attribute].value` property. ```js JavaScript icon=code theme={"system"} components.Highlight({ // ... hit: item, }); ``` **highlightedTagName** `string` (default: mark) The HTML element that wraps the highlight. ```js JavaScript icon=code theme={"system"} components.Highlight({ // ... highlightedTagName: "em", }); ``` **nonHighlightedTagName** `string` (default: span) The HTML element that wraps the non-highlighted parts of the string. ```js JavaScript icon=code theme={"system"} components.Highlight({ // ... nonHighlightedTagName: "div", }); ``` **separator** `string` (default: , ) The character between each item when the attribute to highlight is an array. ```js JavaScript icon=code theme={"system"} components.Highlight({ // ... separator: " - ", }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The component's root element. * `highlighted`. The highlighted parts. * `nonHighlighted`. The non-highlighted parts. * `separator`. The separator elements between highlighted parts. ```js JavaScript icon=code theme={"system"} components.Highlight({ // ... cssClasses: { root: "MyCustomHighlight", highlighted: ["MyCustomHighlightPart", "MyCustomHighlightPart--subclass"], }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"} This is the highlighted text ``` # history Source: https://www.algolia.com/doc/api-reference/widgets/history-router/js Synchronizes the search UI state with the browser URL in InstantSearch.js. > [!NOTE] > > You are viewing the documentation for **InstantSearch.js v4**. > To upgrade from v3, see the [migration guide](/doc/guides/building-search-ui/upgrade-guides/js#upgrade-from-v3-to-v4). > Looking for the v3 version of this page? > [View the v3 docs](https://algolia.com/old-docs/deprecated/instantsearch/js/v3/api-reference/history-router). ```ts Signature theme={"system"} history({ // Optional parameters windowTitle?: function, createURL?: function, parseURL?: function, writeDelay?: number, getLocation?: function, push?: function, start?: function, dispose?: function, cleanUrlOnDispose?: boolean, }); ``` ## Import ```js Package manager theme={"system"} import { history } from "instantsearch.js/es/lib/routers"; ``` ```js CDN theme={"system"} const { history } = instantsearch.routers; // or directly use instantsearch.routers.history() ``` ## About this widget The `history` router is the default for the `instantsearch` object's [routing](/doc/api-reference/widgets/instantsearch/js#param-routing) option. The router provides an API that lets you customize some of its behaviors. To get a sense of what you can do with the API, see [Routing URLs](/doc/guides/building-search-ui/going-further/routing-urls/js). ## Examples ```js JavaScript icon=code theme={"system"} instantsearch({ // ... routing: { router: history(), }, }); ``` ## Options **windowTitle** `function` This function lets you dynamically customize the window title based on the provided `routeState`. It's called every time users refine the UI and the history timer completes. ```js JavaScript icon=code theme={"system"} history({ windowTitle(routeState) { const indexState = routeState.indexName || {}; if (!indexState.query) { return "MyWebsite - Results page"; } return `MyWebsite - Results for: ${indexState.query}`; }, }); ``` **createURL** `function` This function lets you directly change the format of URLs that are created and rendered for the browser URL bar or widgets. It's called every time InstantSearch needs to create a URL. The provided options are: * `qsModule` (object). A module that can parse 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.) string or convert an object to a query string. You can get more information from the [`qs` documentation](https://github.com/ljharb/qs). * `location` (object). An alias to the implementation defined in [`getLocation`](/doc/api-reference/widgets/history-router/react#param-get-location). By default, it returns `window.location`. * `routeState` (object). The `routeState` created by the provided `stateMapping`. When absent, this is an untouched `uiState`. ```js JavaScript icon=code theme={"system"} history({ createURL({ qsModule, location, routeState }) { const { origin, pathname, hash } = location; const indexState = routeState["instant_search"] || {}; const queryString = qsModule.stringify(routeState); if (!indexState.query) { return `${origin}${pathname}${hash}`; } return `${origin}${pathname}?${queryString}${hash}`; }, }); ``` **parseURL** `function` This function is responsible for parsing the URL string back into a `routeState`. It must be customized if you customized the `createURL` function. It's called every time a user loads or reloads a page, or when they click the back or next buttons of the browser. The provided options are: * `qsModule` (object). A module that can parse a query string or convert an object to a query string. You can get more information from the [`qs` documentation](https://github.com/ljharb/qs). * `location` (object). An alias to the implementation defined in [`getLocation`](/doc/api-reference/widgets/history-router/react#param-get-location). By default, it returns `window.location`. ```js JavaScript icon=code theme={"system"} history({ parseURL({ qsModule, location }) { return qsModule.parse(location.search.slice(1)); }, }); ``` **writeDelay** `number` (default: 400) This option controls the number of milliseconds the router waits before writing the new URL to the browser. You can think about it this way: "400 ms after the last user action, the URL is pushed to the browser". This helps reduce: * The number of different history entries. If you type "phone", you don't want to have 5 history entries and thus have to click 5 times on the back button to go back to the previous search state * The performance overhead of updating the browser URL too often. Updating the browser URL too often causes recurring performance issues that are hard to track: these issues result from browser extensions reacting to every change. 400 ms is a typically a pretty good `writeDelay`. ```js JavaScript icon=code theme={"system"} history({ writeDelay: 400, }); ``` **getLocation** `function` This function lets you return a custom implementation of [`Location`](https://developer.mozilla.org/en-US/docs/Web/API/Location). It enables support for routing in server environments where `window` isn't available. By default, it generates an error if there isn't a custom implementation outside a browser environment. ```js JavaScript icon=code theme={"system"} history({ getLocation() { if (typeof window === "undefined") { const url = "..."; // retrieved from the server context return new URL(url); } return window.location; }, }); ``` **push** `function` This function lets you customize the behavior of the router when it pushes a new URL so that you can delegate it to a third party router or set a custom history state. It's called every time users refine the UI and the history timer completes. The provided option is: * `url` (string). The URL which needs to be pushed to the browser. ```js JavaScript icon=code theme={"system"} history({ push(url) { thirdPartyRouter.push(url); }, }); ``` **start** `function` This function is called when the router starts. You can use it to synchronize InstantSearch's router with a third party router by hooking events to it. The provided option is: * `onUpdate: () => void`. A function that needs to call to inform InstantSearch that the URL has changed. ```js JavaScript icon=code theme={"system"} history({ start(onUpdate) { thirdPartyRouter.addEventListener("routeChange", () => { onUpdate(); }); }, }); ``` **dispose** `function` This function is called when the router gets disposed of. You can use it to detach events you may have set in `start`. The provided option is: ```js JavaScript icon=code theme={"system"} let handler; history({ start(onUpdate) { handler = () => onUpdate(); thirdPartyRouter.addEventListener("routeChange", handler); }, dispose() { thirdPartyRouter.removeEventListener("routeChange", handler); }, }); ``` **cleanUrlOnDispose** `boolean` (default: true) This option controls whether the URL is cleaned up from active refinements when the router is disposed of. It's useful when the search experience is in a modal, to remove query parameters from the URL when the modal is closed. ```js JavaScript icon=code theme={"system"} history({ cleanUrlOnDispose: false, }); ``` # hitsPerPage Source: https://www.algolia.com/doc/api-reference/widgets/hits-per-page/js Lets users change the number of search results per page. ```ts Signature theme={"system"} hitsPerPage({ container: string | HTMLElement, items: object[], // Optional parameters cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { hitsPerPage } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { hitsPerPage } = instantsearch.widgets; // or directly use instantsearch.widgets.hitsPerPage() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=hitsPerPage) > > Preview this widget and its behavior. ## About this widget The `hitsPerPage` widget displays a menu for letting users change the number of displayed hits. If you only want to configure the number of hits per page without displaying a widget, you can use the [`configure`](/doc/api-reference/widgets/configure/js) widget with the [`hitsPerPage`](/doc/api-reference/api-parameters/hitsPerPage) search parameter. ## Examples ```js JavaScript icon=code theme={"system"} hitsPerPage({ container: "#hits-per-page", items: [ { label: "8 hits per page", value: 8, default: true }, { label: "16 hits per page", value: 16 }, ], }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} hitsPerPage({ // ... container: "#hits-per-page", }); ``` ```js HTMLElement theme={"system"} hitsPerPage({ // ... container: document.querySelector("#hits-per-page"), }); ``` **items** `object[]` *required* The list of available options, with each item: * `label: string`. The label to display in the option. * `value: number`. The number of hits to display per page. * `default: boolean`. The default hits per page on first search. ```js JavaScript icon=code theme={"system"} hitsPerPage({ // ... items: [ { label: "8 hits per page", value: 8, default: true }, { label: "16 hits per page", value: 16 }, ], }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `select`. The `select` element. * `option`. The `option` elements of the `select`. ```js JavaScript icon=code theme={"system"} hitsPerPage({ // ... cssClasses: { root: "MyCustomHitsPerPage", select: [ "MyCustomHitsPerPageSelect", "MyCustomHitsPerPageSelect--subclass", ], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} hitsPerPage({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or, combined with results hitsPerPage({ // ... transformItems(items, { results }) { return items.map((item) => ({ ...item, label: item.isRefined && results ? `${item.label} (${results.nbPages} pages in total)` : item.label, })); }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectHitsPerPage` If you want to create your own UI of the `hitsPerPage` widget, you can use connectors. To use `connectHitsPerPage`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectHitsPerPage } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectHitsPerPage } = instantsearch.connectors; // or directly use instantsearch.connectors.connectHitsPerPage() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderHitsPerPage = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customHitsPerPage = connectHitsPerPage(renderHitsPerPage); // 3. Instantiate search.addWidgets([ customHitsPerPage({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderHitsPerPage = (renderOptions, isFirstRender) => { const { items, canRefine, refine, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` The list of items the widget can display, with each item: * `label: string`. The label to display in the option. * `value: number`. The number of hits to display per page. * `isRefined: boolean`. Indicates if the item is the current refined value. ```js JavaScript icon=code theme={"system"} const renderHitsPerPage = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector("#hits-per-page").innerHTML = ` `; }; ``` **canRefine** `boolean` Whether the search can be refined. ```js JavaScript icon=code theme={"system"} const renderHitsPerPage = (renderOptions, isFirstRender) => { // `canRefine` is only available from v4.45.0 // Use `hasNoResults` in earlier minor versions. const { items, canRefine } = renderOptions; document.querySelector("#hits-per-page").innerHTML = ` `; }; ``` **refine** `function` Sets the number of hits per page and triggers a search. ```js JavaScript icon=code theme={"system"} const renderHitsPerPage = (renderOptions, isFirstRender) => { const { items, refine } = renderOptions; const container = document.querySelector("#hits-per-page"); if (isFirstRender) { const select = document.createElement("select"); select.addEventListener("change", (event) => { refine(event.target.value); }); container.appendChild(select); } container.querySelector("select").innerHTML = ` ${items .map( (item) => ` `, ) .join("")} `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderHitsPerPage = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customHitsPerPage({ container: document.querySelector("#hits-per-page"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customHitsPerPage = connectHitsPerPage(renderHitsPerPage); search.addWidgets([ customHitsPerPage({ items, transformItems, }), ]); ``` #### Instance options **items** `object[]` *required* The list of available options, with each item: * `label: string`. The label to display in the option. * `value: number`. The number of hits to display per page. * `default: boolean`. The default hits per page on first search. ```js JavaScript icon=code theme={"system"} customHitsPerPage({ items: [ { label: "8 hits per page", value: 8, default: true }, { label: "16 hits per page", value: 16 }, ], }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customHitsPerPage({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or, combined with results customHitsPerPage({ // ... transformItems(items, { results }) { return items.map((item) => ({ ...item, label: item.isRefined && results ? `${item.label} (${results.nbPages} pages in total)` : item.label, })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderHitsPerPage = (renderOptions, isFirstRender) => { // `canRefine` is only available from v4.45.0 // Use `hasNoResults` in earlier minor versions. const { items, canRefine, refine, widgetParams } = renderOptions; if (isFirstRender) { const select = document.createElement("select"); select.addEventListener("change", (event) => { refine(event.target.value); }); widgetParams.container.appendChild(select); } const select = widgetParams.container.querySelector("select"); select.disabled = !canRefine; select.innerHTML = ` ${items .map( (item) => ` `, ) .join("")} `; }; // Create the custom widget const customHitsPerPage = connectHitsPerPage(renderHitsPerPage); // Instantiate the custom widget search.addWidgets([ customHitsPerPage({ container: document.querySelector("#hits-per-page"), items: [ { label: "8 hits per page", value: 8, default: true }, { label: "16 hits per page", value: 16 }, ], }), ]); ``` # hits Source: https://www.algolia.com/doc/api-reference/widgets/hits/js Shows a list of search results. ```ts Signature theme={"system"} hits({ container: string | HTMLElement, // Optional parameters escapeHTML?: boolean, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { hits } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { hits } = instantsearch.widgets; // or directly use instantsearch.widgets.hits() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=hits) > > Preview this widget and its behavior. ## About this widget Use the `hits` widget to display a list of search results. To set the number of search results, use the [`hitsPerPage`](/doc/api-reference/widgets/hits-per-page/js) or [`configure`](/doc/api-reference/widgets/configure/js) widget. See also: * [Structured results](/doc/guides/building-search-ui/ui-and-ux-patterns/structured-results/js) * [Multi-index search](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/js) * [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/js#handle-searches-without-results) ## Examples ```js JavaScript icon=code theme={"system"} hits({ container: "#hits", templates: { item(hit, { html, components }) { return html`

    ${components.Highlight({ attribute: "name", hit })}

    ${hit.description}

    `; }, }, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} hits({ container: "#hits", }); ``` ```js HTMLElement theme={"system"} hits({ container: document.querySelector("#hits"), }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from hits string values. ```js JavaScript icon=code theme={"system"} hits({ // ... escapeHTML: false, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} hits({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The widget's root element. * `emptyRoot`. The container element without results. * `list`. The list of results. * `item`. The list of items. * `bannerRoot`. The root element of the banner. * `bannerImage`. The image element of the banner. * `bannerLink`. The anchor element of the banner. ```js JavaScript icon=code theme={"system"} hits({ // ... cssClasses: { root: "MyCustomHits", list: ["MyCustomHitsList", "MyCustomHitsList--subclass"], }, }); ``` **transformItems** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. If you transform an attribute used in the [`highlight`](/doc/api-reference/widgets/highlight/js) widget, you must also transform the corresponding `item._highlightResult[attribute].value`. ```js JavaScript icon=code theme={"system"} hits({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); // or, combined with results hits({ // ... transformItems(items, { results }) { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **empty** `string | function` The template to use when there are no results. It exposes the `results` object. ```js function theme={"system"} hits({ // ... templates: { empty(results, { html }) { return html`No results for ${results.query}`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. hits({ // ... templates: { empty: "No results for {{ query }}", }, }); ``` **item** `string | function` The template to use for each result. This template receives an object containing a single record. The record has a new property `__hitIndex` for the relative position of the record in the list of displayed hits. You can use [Algolia's highlighting feature](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) with the [`highlight`]() function, directly from the [template system](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). ```js JavaScript icon=code theme={"system"} hits({ // ... templates: { item(hit, { html, components }) { return html`

    ${hit.__hitIndex}: ${components.Highlight({ attribute: "name", hit })}

    ${hit.description}

    `; }, }, }); ``` **banner** `string | function` The template to use for the banner. This template receives an object containing the `banner` data returned within the `renderingContent` property from the Algolia API response and a `classNames` object representing the CSS classes provided to the widget. It's rendered above the results. ```js function theme={"system"} hits({ // ... templates: { banner({ banner }, { html }) { return html``; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. hits({ // ... templates: { banner: ``, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    1. ...
    2. ...
    3. ...
    ``` ## Customize the UI with `connectHits` If you want to create your own UI of the `hits` widget, you can use connectors. To use `connectHits`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectHits } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectHits } = instantsearch.connectors; // or directly use instantsearch.connectors.connectHits() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderHits = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customHits = connectHits(renderHits); // 3. Instantiate search.addWidgets([ customHits({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderHits = (renderOptions, isFirstRender) => { const { items, results, banner, sendEvent, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Render options **items** `object[]` The matched hits from the Algolia API. You can use [Algolia's highlighting feature](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) with the [`highlight`]() function, directly from the connector's render function. ```js JavaScript icon=code theme={"system"} const renderHits = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector("#hits").innerHTML = `
      ${items .map( (item) => `
    • ${instantsearch.highlight({ attribute: "name", hit: item })}
    • `, ) .join("")}
    `; }; ``` **results** `object` The complete response from the Algolia API. It contains the `hits`, metadata about the page, the number of hits, and so on. Unless you need to access metadata, use `items` instead. Consider the [`stats`]() widget if you want to build a widget that displays metadata about the search. ```js JavaScript icon=code theme={"system"} const renderHits = (renderOptions, isFirstRender) => { const { results } = renderOptions; if (isFirstRender) { return; } document.querySelector("#hits").innerHTML = `
      ${results.hits.map((item) => `
    • ${item.name}
    • `).join("")}
    `; }; ``` **banner** `object` The banner data returned within the `renderingContent` property from the Algolia API response. It's rendered above the results by default and can be used to display promotional content. ```js JavaScript icon=code theme={"system"} const renderHits = (renderOptions, isFirstRender) => { const { items, banner } = renderOptions; document.querySelector("#hits").innerHTML = `
      ${items .map( (item) => `
    • ${instantsearch.highlight({ attribute: "name", hit: item })}
    • `, ) .join("")}
    `; }; ``` **sendEvent** `(eventType, hit, eventName) =\> void` The function to send `click` or `conversion` events. The `view` event is automatically sent when this connector renders hits. * `eventType: 'click' | 'conversion'` * `hit: Hit | Hit[]` * `eventName: string` For more about these events, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware documentation. ```js Example theme={"system"} // For example, sendEvent("click", hit, "Product Added"); // or sendEvent("conversion", hit, "Order Completed"); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedObjectIDsAfterSearch', payload: { eventName: 'Product Added', index: '', objectIDs: [''], positions: [], queryID: '', }, widgetType: 'ais.hits', } */ ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderHits = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customHits({ container: document.querySelector("#hits"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customHits = connectHits(renderHits); search.addWidgets([ customHits({ // Optional parameters escapeHTML, transformItems, }), ]); ``` #### Instance options **escapeHTML** `boolean` (default: true) Escapes HTML entities from hits string values. ```js JavaScript icon=code theme={"system"} customHits({ escapeHTML: false, }); ``` **transformItems** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. If you transform an attribute used in the [`highlight`](/doc/api-reference/widgets/highlight/js) widget, you must also transform the corresponding `item._highlightResult[attribute].value`. ```js JavaScript icon=code theme={"system"} customHits({ transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); // or: combined with results customHits({ transformItems(items, { results }) { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderHits = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; widgetParams.container.innerHTML = `
      ${items .map( (item) => `
    • ${instantsearch.highlight({ attribute: "name", hit: item })}
    • `, ) .join("")}
    `; }; // Create the custom widget const customHits = connectHits(renderHits); // Instantiate the custom widget search.addWidgets([ customHits({ container: document.querySelector("#hits"), }), ]); ``` ## Click and conversion events If the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option is `true`, the `hits` widget automatically sends a `click` event with the following shape to the Insights API whenever users click a hit. ```jsonc JSON icon=braces theme={"system"} { "eventType": "click", "insightsMethod": "clickedObjectIDsAfterSearch", "payload": { "eventName": "Hit Clicked" // … }, "widgetType": "ais.hits" } ``` To customize this event, use the `sendEvent` function in your [`item`](#param-item) template and send a custom `click` event. ```js JavaScript icon=code theme={"system"} hits({ templates: { item(hit, { html, components, sendEvent }) { return html`

    ${components.Highlight({ attribute: "name", hit })}

    ${hit.description}

    `; }, }, }); ``` The `sendEvent` function also accepts an object as a fourth argument to send directly to the Insights API. You can use it, for example, to send special `conversion` events with a subtype. ```js JavaScript icon=code theme={"system"} hits({ templates: { item(hit, { html, components, sendEvent }) { return html`

    ${components.Highlight({ attribute: "name", hit })}

    ${hit.description}

    `; }, }, }); ``` > [!NOTE] > > Use strings to represent monetary values in major currency units (for example, '5.45'). > This avoids floating-point rounding issues, especially when performing calculations. See also: [Send click and conversion events with InstantSearch.js](/doc/guides/building-search-ui/events/js) # index Source: https://www.algolia.com/doc/api-reference/widgets/index-widget/js Scopes a group of widgets to a specific index in multi-index search interfaces. ```ts Signature theme={"system"} index({ // Required unless isolated is true indexName?: string, // Optional parameters indexId?: string, isolated?: boolean, }); ``` ## Import ```js Package manager theme={"system"} import { index } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { index } = instantsearch.widgets; // or directly use instantsearch.widgets.index() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=js) > > Preview this widget and its behavior. ## About this widget This widget lets you apply widgets to a specific index. It's useful when building interfaces that target multiple indices such as [federated searches](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/js). The position of `index` in the widgets tree affects which search parameters apply. Widgets that create search parameters forward them to their child `index` widgets. ```js JavaScript icon=code theme={"system"} search.addWidgets([ searchBox(), hits(), index({ indexName: "instant_search" }).addWidgets([ // The index inherits from the parent's `searchBox` search parameters hits(), ]), ]); ``` The only exception to this rule is when two widgets own the same part of your UI state, like two [`searchBox`](/doc/api-reference/widgets/search-box/js) widgets or two [`refinementList`](/doc/api-reference/widgets/refinement-list/js) widgets on the same attribute. In that case, the latter takes precedence and overrides the search parameters. ```js JavaScript icon=code theme={"system"} search.addWidgets([ searchBox(), hits(), index({ indexName: "instant_search" }).addWidgets([ // The index does not inherit from the parent's `searchBox` search parameters searchBox(), hits(), ]), ]); ``` The same rule applies when you nest multiple `index` widgets. ## Examples ```js JavaScript icon=code theme={"system"} index({ indexName: "instant_search" }).addWidgets([ // Add widgets // ... ]); ``` ## Options **indexName** `string` The index to search into. This option is required unless [`isolated`](#param-isolated) is `true`. ```js JavaScript icon=code theme={"system"} index({ indexName: "instant_search", }); ``` **indexId** `string` (default: value provided for indexName) An identifier for the `index` widget. Providing an `indexId` lets different `index` widgets target the same Algolia index. It's helpful for [`routing`](/doc/api-reference/widgets/instantsearch/js#param-routing). It lets you find the refinements that match an `index` widget. ```js JavaScript icon=code theme={"system"} index({ // ... indexId: "instant_search_one", }); ``` **isolated** `boolean` (default: false) By default, an `index` widget is included in the main InstantSearch request. When `isolated` is `true`, the widget performs its own search requests instead. It doesn't send an initial search request when InstantSearch starts. Instead, it searches only after one of its child widgets changes the search state. When `isolated` is `true`, the index: * Searches separately instead of being included in the main search request. * Isn't included in the URL or UI state. * Isn't included in server-side rendering. You can omit [`indexName`](#param-index-name) when `isolated` is `true`. `EXPERIMENTAL_isolated` is a deprecated alias. Use `isolated` instead. ```js JavaScript icon=code theme={"system"} index({ indexName: "instant_search", isolated: true, }); ``` ## Methods **addWidgets** Adds one or more widgets to the index. ```js JavaScript icon=code theme={"system"} const index = index({ indexName: "instant_search", }); const searchBox = searchBox({ // ... }); const hits = hits({ // ... }); index.addWidgets([searchBox, hits]); ``` **removeWidgets** Removes one or more widgets from the `index` widget. To be properly removed, the widget instances you pass must have a `dispose()` method. ```js JavaScript icon=code theme={"system"} const index = index({ indexName: "instant_search", }); const searchBox = searchBox({ // ... }); const hits = hits({ // ... }); index.removeWidgets([searchBox, hits]); ``` **updateWidget** Replaces one widget with another, in place. Use it to change a widget's parameters without losing the state that widget owns: the previous widget's [UI state](/doc/api-reference/widgets/ui-state/js) is handed over to the new one. State the new widget doesn't claim anymore, such as a refinement on an `attribute` that changed, is dropped, as when you remove a widget. The new widget also keeps the position of the widget it replaces, so the order in which widgets contribute to the search parameters is unchanged. The previous widget must have a `dispose()` method, and the new one an `init()` or a `render()` method. ```js JavaScript icon=code theme={"system"} const searchIndex = index({ indexName: "instant_search", }); const previousBrands = refinementList({ container: "#brands", attribute: "brand", limit: 5, }); searchIndex.addWidgets([previousBrands]); const nextBrands = refinementList({ container: "#brands", attribute: "brand", limit: 10, }); // The brands users refined by are preserved searchIndex.updateWidget(previousBrands, nextBrands); ``` # Index Source: https://www.algolia.com/doc/api-reference/widgets/index-widget/react Scopes a group of widgets to a specific index in multi-index search interfaces. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { Index } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=react) > > Preview this widget and its behavior. ## About this widget `Index` is the provider component for an Algolia index. It's useful when building interfaces that target multiple indices such as [federated searches](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/react). The position of `Index` in the widget tree affects which search parameters apply. Widgets that create search parameters forward them to their children `Index` widgets. ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Index } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( {/* Widgets */} {/* Widgets */} ); } ``` ## Props **indexName** `string` The main index to search into. This prop is required unless [`isolated`](#param-isolated) is `true`. ```jsx JavaScript icon=code theme={"system"} {/* Widgets */}; ``` **indexId** `string` An identifier for the `Index` widget. Providing an `indexId` lets different ` {/* Widgets */} ; ``` **isolated** `boolean` (default: false) By default, an `Index` component is included in the main InstantSearch request. When `isolated` is `true`, the component performs its own search requests instead. When `isolated` is `true`, the index: * Searches separately instead of being included in the main search request. * Isn't included in the URL or UI state. * Isn't included in server-side rendering. You can omit [`indexName`](#param-index-name) when `isolated` is `true`. `EXPERIMENTAL_isolated` is a deprecated alias. Use `isolated` instead. ```jsx JavaScript icon=code theme={"system"} {/* Widgets */} ``` # infiniteHits Source: https://www.algolia.com/doc/api-reference/widgets/infinite-hits/js Shows search results with a button to load more. ```ts Signature theme={"system"} infiniteHits({ container: string | HTMLElement, // Optional parameters escapeHTML?: boolean, showPrevious?: boolean, templates?: object, cssClasses?: object, transformItems?: function, cache?: object, }); ``` ## Import ```js Package manager theme={"system"} import { infiniteHits } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { infiniteHits } = instantsearch.widgets; // or directly use instantsearch.widgets.infiniteHits() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=infiniteHits) > > Preview this widget and its behavior. ## About this widget The `infiniteHits` widget displays a list of results with a "Show more" button at the bottom of the list. As an alternative to this approach, [the infinite scroll guide](/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/js) describes how to create an automatically scrolling infinite hits experience. To configure the number of hits to show, use the [`hitsPerPage`](/doc/api-reference/widgets/hits-per-page/js) widget or the [`configure`](/doc/api-reference/widgets/configure/js) widget. See also: [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/js#handle-searches-without-results) ## Examples ```js JavaScript icon=code theme={"system"} infiniteHits({ container: "#infinite-hits", templates: { item(hit, { html, components }) { return html`

    ${components.Highlight({ attribute: "name", hit })}

    ${hit.description}

    `; }, }, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} infiniteHits({ container: '#infinite-hits', }); ``` ```js HTMLElement theme={"system"} infiniteHits({ container: document.querySelector("#infinite-hits"), }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from hits string values. ```js JavaScript icon=code theme={"system"} infiniteHits({ // ... escapeHTML: false, }); ``` **showPrevious** `boolean` (default: false) Enable the button to load previous results. The button is only displayed if the routing option is enabled in [`instantsearch`](/doc/api-reference/widgets/instantsearch/js#param-routing) and users aren't on the first page. Override this behavior with [connectors](#customize-the-ui-with-connectinfinitehits). ```js JavaScript icon=code theme={"system"} infiniteHits({ // ... showPrevious: true, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} infiniteHits({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The widget's root element. * `emptyRoot`. The root container without results. * `list`. The list of results. * `item`. The list item. * `loadPrevious`. The "Show previous" button. * `loadMore`. The "Show more" button. * `disabledLoadPrevious`. The "Show previous" button when it's unavailable. * `disabledLoadMore`. The "Show more" button when it's unavailable. * `bannerRoot`. The root element of the banner. * `bannerImage`. The image element of the banner. * `bannerLink`. The anchor element of the banner. ```js JavaScript icon=code theme={"system"} infiniteHits({ // ... cssClasses: { root: "MyCustomInfiniteHits", list: ["MyCustomInfiniteHits", "MyCustomInfiniteHits--subclass"], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. If you transform an attribute used in the [`highlight`](/doc/api-reference/widgets/highlight/js) widget, you must also transform the corresponding `item._highlightResult[attribute].value`. ```js JavaScript icon=code theme={"system"} infiniteHits({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); // or, combined with results infiniteHits({ // ... transformItems(items, { results }) { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }, }); ``` **cache** `object` The widget caches all loaded hits. By default, it uses its own internal in-memory cache implementation. Alternatively, use [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) to retain the cache even if users reload the page. You can also implement your own cache object with `read` and `write` methods. This can be handy if you need to persist the data across sessions or if you expect the cached data to grow larger than the browser's [5 MB allowed storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria#web_storage). ```js Package manager theme={"system"} import { createInfiniteHitsSessionStorageCache } from 'instantsearch.js/es/lib/infiniteHitsCache'; const sessionStorageCache = createInfiniteHitsSessionStorageCache(); infiniteHits({ // ... cache: sessionStorageCache, }); ``` ```js CDN theme={"system"} const { createInfiniteHitsSessionStorageCache } = instantsearch; // or directly use instantsearch.createInfiniteHitsSessionStorageCache() const sessionStorageCache = createInfiniteHitsSessionStorageCache(); infiniteHits({ // ... cache: sessionStorageCache, }); ``` ```js custom theme={"system"} const customCache = { read({ state }) { const { cachedKey, cachedHits } = // read from your cache const currentKey = convertStateToKey(state); if (isEqual(cachedKey, currentKey)) { return cachedHits; } else { return null; } }, write({ state, hits }) { const currentKey = convertStateToKey(state); // write in your cache }, } function isEqual(obj1, obj2) { // perform deep equal } function convertStateToKey(state) { const { page, ...rest } = state || {}; // Regardless of the `page`, // check if the rest of the state is the same. return rest; } infiniteHits({ // ... cache: customCache }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **empty** `string | function` The template to use when there are no results. It exposes the `results` object. ```js function theme={"system"} infiniteHits({ // ... templates: { empty(results, { html }) { return html`

    No results ${results.query}

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. infiniteHits({ // ... templates: { empty: "No results for {{ query }}", }, }); ``` **item** `string | function` The template to use for each result. This template receives an object containing a single record. The record has a new property `__hitIndex` for the relative position of the record in the list of displayed hits. You can use [Algolia's highlighting feature](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) with the [`highlight`](/doc/api-reference/widgets/highlight/js) function, directly from the template. ```js JavaScript icon=code theme={"system"} infiniteHits({ // ... templates: { item(hit, { html, components }) { return html`

    ${hit.__hitIndex}: ${components.Highlight({ attribute: 'name', hit })}

    ${hit.description}

    `; }, }, }); ``` **banner** `string | function` The template to use for the banner. This template receives an object containing the `banner` data returned within the `renderingContent` property from the Algolia API response and a `className` string representing the CSS classes provided to the widget. It's rendered above the results. ```js function theme={"system"} infiniteHits({ // ... templates: { banner({ banner }, { html }) { return html``; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. infiniteHits({ // ... templates: { banner: ``, }, }); ``` **showPreviousText** `string | function` (default: Show previous results) The template to use for the "Show previous" label. ```js function theme={"system"} infiniteHits({ // ... templates: { showPreviousText(data, { html }) { return html`Show previous`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. infiniteHits({ // ... templates: { showPreviousText: "Show previous", }, }); ``` **showMoreText** `string | function` (default: Show more results) The template to use for the "Show more" label. ```js function theme={"system"} infiniteHits({ // ... templates: { showMoreText(data, { html }) { return html`Show more`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. infiniteHits({ // ... templates: { showMoreText: "Show more", }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    1. ...
    2. ...
    3. ...
    ``` ## Customize the UI with `connectInfiniteHits` If you want to create your own UI of the `infiniteHits` widget, you can use connectors. To use `connectInfiniteHits`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectInfiniteHits } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectInfiniteHits } = instantsearch.connectors; // or directly use instantsearch.connectors.connectInfiniteHits() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderInfiniteHits = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customInfiniteHits = connectInfiniteHits(renderInfiniteHits); // 3. Instantiate search.addWidgets([ customInfiniteHits({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderInfiniteHits = (renderOptions, isFirstRender) => { const { items, currentPageHits, results, banner, sendEvent, isFirstPage, isLastPage, showPrevious, showMore, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` The matched hits from the Algolia API. This returns the combined hits for all the pages that have been requested so far. You can use [Algolia's highlighting feature](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) with the [`highlight`](/doc/api-reference/widgets/highlight/js) function, directly from the connector's render function. ```js JavaScript icon=code theme={"system"} const renderInfiniteHits = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector('#infinite-hits').innerHTML = `
      ${items .map( item => `
    • ${instantsearch.highlight({ attribute: 'name', hit: item })}
    • ` ) .join('')}
    `; }; ``` **currentPageHits** `object[]` The matched hits from the Algolia API for the current page. Unlike the `hits` parameter, this only returns the hits for the currently requested page. This can be useful if you want to handle how the new page of hits is going to be added to the list of search results. You can use [Algolia's highlighting feature](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) with the [`highlight`](/doc/api-reference/widgets/highlight/js) function, directly from the connector's render function. ```js JavaScript icon=code theme={"system"} const renderInfiniteHits = (renderOptions, isFirstRender) => { const { currentPageHits } = renderOptions; const container = document.querySelector('#infinite-hits'); if (isFirstRender) { const ul = document.createElement('ul'); container.appendChild(ul); return; } container.querySelector('ul').insertAdjacentHTML('beforeend', currentPageHits .map( hit => `
  • ${instantsearch.highlight({ attribute: 'name', hit: item })}
  • ` ) .join('')); }; ``` **results** `object` The complete response from the Algolia API. It contains the `hits`, metadata about the page, the number of hits, and so on. Unless you need to access metadata, use `items` instead. Also consider the [`stats`](/doc/api-reference/widgets/stats/js) widget if you want to build a widget that displays metadata about the search. ```js JavaScript icon=code theme={"system"} const renderInfiniteHits = (renderOptions, isFirstRender) => { const { results } = renderOptions; if (isFirstRender) { return; } document.querySelector('#infinite-hits').innerHTML = `
      ${results.hits.map(item => `
    • ${item.name}
    • `).join('')}
    `; }; ``` **banner** `object` The banner data returned within the `renderingContent` property from the Algolia API response. It's rendered above the results by default and can be used to display promotional content. ```js JavaScript icon=code theme={"system"} const renderHits = (renderOptions, isFirstRender) => { const { items, banner } = renderOptions; document.querySelector('#infinite-hits').innerHTML = `
      ${items .map( item => `
    • ${instantsearch.highlight({ attribute: 'name', hit: item })}
    • ` ) .join('')}
    `; }; ``` **sendEvent** `(eventType, hit, eventName) =\> void` The function to send `click` or `conversion` events. The `view` event is automatically sent when this connector renders items. * `eventType: 'click' | 'conversion'` * `hit: Hit | Hit[]` * `eventName: string` For more information about these events, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware documentation. ```js JavaScript icon=code theme={"system"} // For example, sendEvent('click', hit, 'Product Added'); // or sendEvent('conversion', hit, 'Order Completed'); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedObjectIDsAfterSearch', payload: { eventName: 'Product Added', index: '', objectIDs: [''], positions: [], queryID: '', }, widgetType: 'ais.infiniteHits', } */ ``` **isFirstPage** `boolean` Indicates whether the first page of hits has been reached. ```js JavaScript icon=code theme={"system"} const renderInfiniteHits = (renderOptions, isFirstRender) => { const { items, isFirstPage } = renderOptions; document.querySelector('#infinite-hits').innerHTML = `
      ${items.map(item => `
    • ${item.name}
    • `).join('')}
    `; }; ``` **isLastPage** `boolean` Indicates whether the last page of hits has been reached. ```js JavaScript icon=code theme={"system"} const renderInfiniteHits = (renderOptions, isFirstRender) => { const { items, isLastPage } = renderOptions; document.querySelector('#infinite-hits').innerHTML = `
      ${items.map(item => `
    • ${item.name}
    • `).join('')}
    `; }; ``` **showPrevious** `function` Loads the previous page of hits. ```js JavaScript icon=code theme={"system"} const renderInfiniteHits = (renderOptions, isFirstRender) => { const { items, showPrevious } = renderOptions; const container = document.querySelector('#infinite-hits'); if (isFirstRender) { const ul = document.createElement('ul'); const button = document.createElement('button'); button.textContent = 'Show previous'; button.addEventListener('click', () => { showPrevious(); }); container.appendChild(button); container.appendChild(ul); return; } container.querySelector('ul').innerHTML = ` ${items.map(item => `
  • ${item.name}
  • `).join('')} `; }; ``` **showMore** `function` Loads the next page of hits. ```js JavaScript icon=code theme={"system"} const renderInfiniteHits = (renderOptions, isFirstRender) => { const { items, showMore } = renderOptions; const container = document.querySelector('#infinite-hits'); if (isFirstRender) { const ul = document.createElement('ul'); const button = document.createElement('button'); button.textContent = 'Show more'; button.addEventListener('click', () => { showMore(); }); container.appendChild(ul); container.appendChild(button); return; } container.querySelector('ul').innerHTML = ` ${items.map(item => `
  • ${item.name}
  • `).join('')} `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderInfiniteHits = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = '...'; }; // ... search.addWidgets([ customInfiniteHits({ container: document.querySelector('#infinite-hits'), }) ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customInfiniteHits = connectInfiniteHits( renderInfiniteHits ); search.addWidgets([ customInfiniteHits({ // Optional parameters escapeHTML: boolean, showPrevious: boolean, transformItems: function, }) ]); ``` #### Instance options **escapeHTML** `boolean` (default: true) Escapes HTML entities from hits string values. ```js JavaScript icon=code theme={"system"} customInfiniteHits({ escapeHTML: false, }); ``` **showPrevious** `boolean` (default: false) Enable the button to load previous results. ```js JavaScript icon=code theme={"system"} customInfiniteHits({ showPrevious: true, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. If you transform an attribute used in the [`highlight`](/doc/api-reference/widgets/highlight/js) widget, you must also transform the corresponding `item._highlightResult[attribute].value`. ```js JavaScript icon=code theme={"system"} customInfiniteHits({ transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); // or: combined with results customInfiniteHits({ transformItems(items, { results }) { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderInfiniteHits = (renderOptions, isFirstRender) => { const { items, widgetParams, showPrevious, isFirstPage, showMore, isLastPage, } = renderOptions; if (isFirstRender) { const ul = document.createElement("ul"); const previousButton = document.createElement("button"); previousButton.className = "previous-button"; previousButton.textContent = "Show previous"; previousButton.addEventListener("click", () => { showPrevious(); }); const nextButton = document.createElement("button"); nextButton.className = "next-button"; nextButton.textContent = "Show more"; nextButton.addEventListener("click", () => { showMore(); }); widgetParams.container.appendChild(previousButton); widgetParams.container.appendChild(ul); widgetParams.container.appendChild(nextButton); return; } widgetParams.container.querySelector(".previous-button").disabled = isFirstPage; widgetParams.container.querySelector(".next-button").disabled = isLastPage; widgetParams.container.querySelector("ul").innerHTML = ` ${items .map( (item) => `
  • ${instantsearch.highlight({ attribute: "name", hit: item })}
  • `, ) .join("")} `; }; // Create the custom widget const customInfiniteHits = connectInfiniteHits(renderInfiniteHits); // Instantiate the custom widget search.addWidgets([ customInfiniteHits({ container: document.querySelector("#infinite-hits"), showPrevious: true, }), ]); ``` ## Click and conversion events If the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option is `true`, the `infiniteHits` widget automatically sends a `click` event with the following shape to the Insights API when a user clicks on a hit. ```json JSON icon=braces theme={"system"} { "eventType": "click", "insightsMethod": "clickedObjectIDsAfterSearch", "payload": { "eventName": "Hit Clicked" // ... }, "widgetType": "ais.infiniteHits" } ``` To customize this event, use the `sendEvent` function in your [`item`](#param-item) template and send a custom `click` event. ```js JavaScript icon=code theme={"system"} infiniteHits({ templates: { item(hit, { html, components, sendEvent }) { return html`

    ${components.Highlight({ attribute: "name", hit })}

    ${hit.description}

    `; }, }, }); ``` The `sendEvent` function also accepts an object as a fourth argument to send directly to the Insights API. You can use it, for example, to send special `conversion` events with a subtype. ```js JavaScript icon=code theme={"system"} infiniteHits({ templates: { item(hit, { html, components, sendEvent }) { return html`

    ${components.Highlight({ attribute: "name", hit })}

    ${hit.description}

    `; }, }, }); ``` > [!NOTE] > > Use strings to represent monetary values in major currency units (for example, '5.45'). > This avoids floating-point rounding issues, especially when performing calculations. See also: [Send click and conversion events with InstantSearch.js](/doc/guides/building-search-ui/events/js) # insights Source: https://www.algolia.com/doc/api-reference/widgets/insights/js Middleware for setting the user token and sending click and conversion events to the Insights API. ```ts Signature theme={"system"} createInsightsMiddleware({ insightsClient: null | InsightsClient, insightsInitParams?: object, onEvent?: (event: InsightsEvent, aa: null | InsightsClient) => void, }); ``` ## Import ```js Package manager theme={"system"} import { createInsightsMiddleware } from "instantsearch.js/es/middlewares"; ``` ```js CDN theme={"system"} const { createInsightsMiddleware } = instantsearch.middlewares; // or directly use instantsearch.middlewares.createInsightsMiddleware() ``` ## About this widget > [!NOTE] > > Starting from v4.55.0, you can use the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) > option instead of setting up the Insights middleware yourself. The `createInsightsMiddleware` creates an insights middleware to help you achieve the following: 1. Set the `userToken` for insights purposes (Click Analytics, Personalization, etc.) 2. Automatically send events from built-in widgets (you can turn this off) 3. Lets you send events from your own custom widgets ### Requirements * [`search-insights`](https://github.com/algolia/search-insights.js) v1.6.2 or later. * [InstantSearch.js](https://github.com/algolia/instantsearch) v4.8.3 or later. See also: [Send click and conversion events with InstantSearch.js](/doc/guides/building-search-ui/events/js) ## Examples ```js JavaScript icon=code theme={"system"} const insightsMiddleware = createInsightsMiddleware({ insightsClient, insightsInitParams, onEvent, }); search.use(insightsMiddleware); ``` ## Options **insightsClient** `InsightsClient | null` The [Insights client](/doc/libraries/search-insights?client=javascript) is used to send events. It synchronizes the [user token](/doc/guides/sending-events/concepts/usertoken#exclude-users-who-want-to-opt-out-of-analytics-recommend-and-personalization) between search, Recommend, and analytics calls. When the middleware is enabled, it adds `userToken` and `clickAnalytics` to Recommend requests and fills missing values in the widget's existing `fallbackParameters`. Values in the widget's `queryParameters` and `fallbackParameters` take precedence. To turn off `userToken` synchronization and automatic event sending, set this to `null`. ```html UMD theme={"system"} ``` ```js ESM theme={"system"} import aa from "search-insights"; import { createInsightsMiddleware } from "instantsearch.js/es/middlewares"; const insightsMiddleware = createInsightsMiddleware({ insightsClient: aa, }); search.use(insightsMiddleware); // Get the user token (synchronously or asynchronously). const userToken = /*...*/; // The `insights` middleware receives a notification // and attaches the `userToken` to search and Recommend calls onwards. aa("setUserToken", userToken); ``` **insightsInitParams** `object` Insights parameters to forward to the Insights client's [`init`](/doc/libraries/search-insights/init) method. With `search-insights >= v1.7.0 and < 2.0.0`, the Insights client accepts `useCookie` and `userToken` parameters in the [`init`](/doc/libraries/search-insights/init) method. You can pass `useCookie: false` to prevent the usage of cookies to store an anonymous `userToken`. You can also pass a custom `userToken` while creating `insights` middleware, if you have one. With `search-insights >= 2.0.0`, the default value of `useCookie` is `false`. > [!NOTE] > > You can set an authenticated user token and a user token in `insightsInitParams`. > Both tokens are passed to the Insights API for sending events. > If both are set, InstantSearch uses the authenticated user token for search queries. ```js Cookie theme={"system"} createInsightsMiddleware({ insightsInitParams: { useCookie: true, }, }); ``` ```js Tokens theme={"system"} createInsightsMiddleware({ insightsInitParams: { userToken: "user-token", authenticatedUserToken: "auth-user-token", }, }); ``` **onEvent** `(event: InsightsEvent, aa: null | InsightsClient) =\> void` By default, the middleware sends events to Algolia using the provided `insightsClient`. You can also control events and send them yourself by implementing an `onEvent` method for the middleware to call instead. The method lets you access data, and filter or modify the payload. You can also use it to send events to third-party trackers. If you want to use `onEvent` to send events to third-party trackers, but don't want to send them to Algolia, you can set `insightsClient` to `null`, and you don't need the `search-insights` library in your application. **event properties** **event.insightsMethod** `string` The Insights method, such as, `'viewedObjectIDs'`, `'clickedObjectIDsAfterSearch'`. For more information, see the [Insights API reference](/doc/libraries/search-insights). **event.payload** `[key: string]: any` Event payload. **event.widgetType** `string` Widget type given by connectors, such as `'ais.refinementList` or `'ais.hits`. **event.eventType** `string` Event type, such as `'view'`, `'click'`, `'conversion'`, or anything else if you customized it. ```js JavaScript icon=code theme={"system"} createInsightsMiddleware({ insightsClient: window.aa, onEvent: (event, aa) => { const { insightsMethod, payload, widgetType, eventType } = event; // Send the event to Algolia if (insightsMethod) { aa(insightsMethod, payload); } // Send the event to a third-party tracker if (widgetType === "ais.hits" && eventType === "click") { thirdPartyTracker.send("Product Clicked", payload); } }, }); ``` ## Custom events **Connectors** Many of the InstantSearch connectors expose the `sendEvent` method. If you use these connectors to create custom widgets, you can use the method to send custom events. Here's a list of connectors that expose `sendEvent`. * `connectAutocomplete`: [`autocomplete`](/doc/api-reference/widgets/autocomplete/js#param-indices) * `connectGeoSearch`: [`geoSearch`](/doc/api-reference/widgets/geo-search/js#param-send-event) * `connectHierarchicalMenu`: [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js#param-send-event) * `connectHits`: [`hits`](/doc/api-reference/widgets/hits/js#param-send-event) * `connectInfiniteHits`: [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js#param-send-event) * `connectMenu`: [`menu`](/doc/api-reference/widgets/menu/js#param-send-event) and [`menuSelect`](/doc/api-reference/widgets/menu-select/js#param-send-event) \* * `connectNumericMenu`: [`numericMenu`](/doc/api-reference/widgets/numeric-menu/js#param-send-event) * `connectRange`: [`rangeInput`](/doc/api-reference/widgets/range-input/js#param-send-event) and [`rangeSlider`](/doc/api-reference/widgets/range-slider/js#param-send-event) \* * `connectRatingMenu`: [`ratingMenu`](/doc/api-reference/widgets/rating-menu/js#param-send-event) \* * `connectRefinementList`: [`refinementList`](/doc/api-reference/widgets/refinement-list/js#param-send-event) * `connectToggleRefinement`: [`toggleRefinement`](/doc/api-reference/widgets/toggle-refinement/js#param-send-event) > [!NOTE] > > \* The `sendEvent` method provided to `connectNumericMenu`, `connectRange`, and `connectRatingMenu` can't be used to send [clicked filter](/doc/libraries/sdk/v1/methods/clicked-filters) events. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRendering) => { const { sendEvent } = renderOptions; // `sendEvent` from `connectRefinementList` can send `click` events. const clickedFacetValue = "Apple"; // This sends an event like the following: /* { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['brand:"Apple"'], index: '', }, widgetType: 'ais.refinementList', } */ sendEvent("click", clickedFacetValue); // Or, you can send a custom payload sendEvent({ eventType: "click", insightsMethod: "clickedFilters", payload: { eventName: "Filter Applied", filters: [`brand:${JSON.stringify(clickedFacetValue)}`], index: "", }, widgetType: "ais.refinementList", }); }; const customRefinementList = connectRefinementList(renderRefinementList); ``` **Hits** The [`hits`](/doc/api-reference/widgets/hits/js) and [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widgets provide templates with an exposed `sendEvent` function. The `sendEvent(eventType, payload, eventName)` function triggers an event. When clicking the button, InstantSearch sends it to Algolia, or forwards it to `onEvent` if provided. * `eventType: 'click' | 'conversion'` * `payload: Hit` * `eventName: string` ```js JavaScript icon=code theme={"system"} hits({ // or `infiniteHits` // ... templates: { item: (hit, { html, components, sendEvent }) => { return html`

    ${components.Highlight({ attribute: "title", hit })}

    `; }, }, }); ``` # instantsearch Source: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js Root component of an InstantSearch.js app. > [!NOTE] > > You are viewing the documentation for **InstantSearch.js v4**. > To upgrade from v3, see the [migration guide](/doc/guides/building-search-ui/upgrade-guides/js#upgrade-from-v3-to-v4). > Looking for the v3 version of this page? > [View the v3 docs](https://algolia.com/old-docs/deprecated/instantsearch/js/v3/api-reference/instantsearch). ```ts Signature theme={"system"} instantsearch({ indexName: string, searchClient: object, numberLocale?: string, searchFunction?: function, initialUiState?: object, onStateChange?: function, stalledSearchDelay?: number, routing?: boolean | object, insights?: boolean | object, insightsClient?: function, future?: { preserveSharedStateOnUnmount: boolean, persistHierarchicalRootCount: boolean, }, }); ``` ## Import ```js Package manager theme={"system"} import instantsearch from 'instantsearch.js'; ``` ```js CDN theme={"system"} window.instantsearch; ``` ## About this widget The `instantsearch` object is the root wrapper component for all widgets. Two parameters are required: * [`indexName`](#param-index-name). The main 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 which to search. * [`searchClient`](/doc/api-reference/widgets/instantsearch/js#param-search-client). The search client for InstantSearch.js. The [search client](https://github.com/algolia/algoliasearch-client-javascript) needs an `appId` and an `apiKey`, which you can find in the [Algolia dashboard](https://dashboard.algolia.com/account/api-keys). See also: [Get started with InstantSearch.js](/doc/guides/building-search-ui/getting-started/js) ### Middleware InstantSearch.js provides middleware to help you connect to other systems: * **Insights**. Use the [`insights`](/doc/api-reference/widgets/insights/js) middleware to [send click and conversion events](/doc/guides/building-search-ui/events/js) * **Generic**. With the [`middleware`](/doc/api-reference/widgets/middleware/js) API, you can inject logic into InstantSearch.js. For example, to [send events to Google Analytics](/doc/guides/building-search-ui/going-further/integrate-google-analytics/js). ## Examples ```js JavaScript icon=code theme={"system"} const search = instantsearch({ indexName: "INDEX_NAME", searchClient: algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"), }); // Add widgets // ... search.start(); ``` ## Options **indexName** `string` *required* The main index to search into. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... indexName: "INDEX_NAME", }); ``` **searchClient** `object` *required* Provides a search client to `instantsearch`. Read [the custom backend guidance](/doc/guides/building-search-ui/going-further/backend-search/js) on implementing a custom search client. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... searchClient: algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"), }); ``` **numberLocale** `string` The locale used to display numbers. This is passed to [`Number.prototype.toLocaleString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString). ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... numberLocale: "fr", }); ``` **searchFunction** `function` *deprecated* Use [`onStateChange`](#param-on-state-change) instead. This option lets you take control of search behavior. For example, to avoid doing searches at page load. When this option is set, `search.helper` won't emit events. Instead, use [`onStateChange`](#param-on-state-change) or widgets to handle search behavior. A hook is called for every search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.) (with [Algolia's search helper](/doc/guides/building-search-ui/upgrade-guides/js#algolia-search-helper) as a parameter). Begin searching by calling [`helper.search()`](https://instantsearchjs.netlify.app/algoliasearch-helper-js/reference.html#AlgoliaSearchHelper#search). When modifying the state of the helper within `search-function`, the helper resets the page to 0. To keep the current page, store the page information, modify the state, and reapply pagination. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... searchFunction(helper) { if (helper.state.query) { helper.search(); } }, }); // Example which avoids the page resetting to 0 const search = instantsearch({ // ... searchFunction(helper) { const page = helper.getPage(); // Retrieve the current page helper .setQuery("Hello") // this call resets the page .setPage(page) // we re-apply the previous page .search(); }, }); ``` **initialUiState** `object` Adds a [`uiState`](/doc/api-reference/widgets/ui-state/js) to your `instantsearch` instance, which provides an initial state to your widgets. Since this sets the initial state for your UI, you need to include the corresponding widgets in your app. For example, `page: 5` as initial state only has an effect if you include the `pagination` widget as well. Replace `YourIndexName` with the name of your Algolia index. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... initialUiState: { YourIndexName: { // Sets the initial search query for the searchBox widget query: "phone", // Sets the initial page number for the pagination widget page: 5, }, }, }); ``` **onStateChange** `function` Triggered when the state changes. This can be helpful for performing custom logic on a state change. When using `onStateChange`, the instance is under your control. **You're responsible for updating the UI state** (with `setUiState`). ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... onStateChange({ uiState, setUiState }) { // Custom logic setUiState(uiState); }, }); ``` **stalledSearchDelay** `number` (default: 200) A time period (in ms) after which the search is considered to have stalled. Read the [slow network guide](/doc/guides/building-search-ui/going-further/improve-performance/js) for more information. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... stalledSearchDelay: 500, }); ``` **routing** `boolean | object` (default: false) The router configuration used to save the UI state into the URL or any client-side persistence. **routing attributes** **routing.router** `object` This object stores the UI state. By default, it uses an instance of the [`history`](/doc/api-reference/widgets/history-router/js) router with its default parameters. **router attributes** **router.onUpdate** `function` Adds an event listener that makes InstantSearch aware of external changes to the storage medium (such as the URL). Typically you'll set up a listener for `popstate`, which triggers a callback with the current `routeState`. **router.read** `function` Reads the routing storage and returns a `routeState` object. **router.write** `function` Writes the `routeState` object into the routing storage. **router.createURL** `function` Transforms the `routeState` object into a URL. It receives an object and returns a string (which may be empty). **router.dispose** `function` Cleans up all event listeners. **routing.stateMapping** `object` Transforms the [`uiState`](/doc/api-reference/widgets/ui-state/js) into the object saved by the router. The default value is provided by [`simple`](/doc/api-reference/widgets/simple-state-mapping/js). **stateMapping attributes** **stateMapping.stateToRoute** `function` Transforms a `ui-state` representation into a `routeState` object. It receives an object that contains the UI state of all the widgets on the page. It can return any object that's readable by `routeToState`. **stateMapping.routeToState** `function` Transforms `routeState` into a `ui-state` representation. It receives an object that contains the UI state stored by the router. It can return any object that's readable by `stateToRoute`. For more information, see [Sync your URLs](/doc/guides/building-search-ui/going-further/routing-urls/js). You can't use [`initialUiState`](#param-initial-ui-state) with routing as the two options override each other. * Use `initialUiState` for simple and static use cases. * Use `routing` for anything complex or dynamic. ```js boolean theme={"system"} const search = instantsearch({ // ... routing: true, }); ``` ```js object theme={"system"} const search = instantsearch({ // ... routing: { router: instantsearch.routers.history(), stateMapping: instantsearch.stateMappings.simple(), }, }); ``` **insights** `boolean | object` (default: false) Enables the Insights middleware and loads the [`search-insights`](https://github.com/algolia/search-insights.js) library (if not already loaded). The Insights middleware sends view and click events automatically, and lets you set up your own [click and conversion events](/doc/guides/building-search-ui/events/js). To use this option with an object, refer to the [Insights middleware options](/doc/api-reference/widgets/insights/js#options). ```js boolean theme={"system"} const search = instantsearch({ // ... insights: true, }); ``` ```js object theme={"system"} const search = instantsearch({ // ... insights: { insightsClient: window.aa, insightsInitParams: { useCookie: false, // … }, // … }, }); ``` **insightsClient** `function` *deprecated* Use [`insights`](#param-insights) instead. This function is exposed by the `search-insights` library (usually `window.aa`) and is required for [sending click and conversion events](/doc/guides/building-search-ui/events/js) with the [`insights`](/doc/api-reference/widgets/insights/js) middleware. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... insightsClient: window.aa, }); ``` **future** `object` Test new InstantSearch features without affecting others. **future attributes** **future.preserveSharedStateOnUnmount** `boolean` (default: false) Changes the way `dispose` is used in the InstantSearch lifecycle. If `false` (the default), each widget unmounting will also remove its state, even if multiple widgets read that UI state. If `true`, each widget unmounting will only remove its state if it's the last of its type. This lets you dynamically add and remove widgets without losing their state. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... future: { preserveSharedStateOnUnmount: true, }, }); ``` **future.persistHierarchicalRootCount** `boolean` (default: false) Whether to display a constant facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) value count at the root of a hierarchical menu with active refinements. * If `false` (default), the facet value count at the root level shows the facet value count of the refined (child) facet's parent. * If `true`, the facet value count at the root level shows the sum of the facet value counts of all its children, with or without refined children. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... future: { persistHierarchicalRootCount: true, }, }); ``` ## Methods **addWidgets** `function` Adds widgets to the `instantsearch` instance. You can add widgets to `instantsearch` before and after you start it. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); const searchBox = instantsearch.widgets.searchBox({ // ... }); const hits = instantsearch.widgets.hits({ // ... }); search.addWidgets([searchBox, hits]); ``` **addWidget** *deprecated* Use [`addWidgets([widget])`](#param-add-widgets) instead. Adds a widget to the `instantsearch` instance. You can add widgets to `instantsearch` before and after you start it. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); const searchBox = instantsearch.widgets.searchBox({ // ... }); search.addWidget(searchBox); ``` **start** `function` Finalizes the setup of `instantsearch` and triggers the first search. Call this method after you have added all your required widgets to `instantsearch`. You can also add widgets after `instantsearch` has started, but these widgets aren't considered during searches made before you add them. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); search.start(); ``` **removeWidgets** Removes widgets from the `instantsearch` instance. You can only do this after you [start `instantsearch`](#param-start). The widgets you remove from `instantsearch` **must implement a `dispose()` method** to reset the search parameters they manage. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); const searchBox = instantsearch.widgets.searchBox({ // ... }); const hits = instantsearch.widgets.hits({ // ... }); search.removeWidgets([searchBox, hits]); ``` **removeWidget** *deprecated* Use [`removeWidgets([widget])`](#param-remove-widgets) instead. Removes a widget from the `instantsearch` instance. You can only do this after you [start `instantsearch`](#param-start). The widget you remove from `instantsearch` **must implement a `dispose()` method** to reset the search parameters they manage. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); const searchBox = instantsearch.widgets.searchBox({ // ... }); search.removeWidget(searchBox); ``` **dispose** Removes all widgets from the instance. This method doesn't trigger a search. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); search.dispose(); ``` **setUiState** Injects a [`uiState`](/doc/api-reference/widgets/ui-state/js) into the instance without relying on internal events (such as connectors' `refine` or widget interactions). For this option to work, the widgets responsible for each UI state attribute must be mounted. For example, a [`searchBox`](/doc/api-reference/widgets/search-box/js) is necessary to provide a `query`. ```js object theme={"system"} const search = instantsearch({ // ... }); search.start(); search.setUiState({ // Replace INDEX_NAME with your index name INDEX_NAME: { query: 'phone' } }); ``` ```js function theme={"system"} const search = instantsearch({ // ... }); search.start(); search.setUiState(uiState => { return { ...uiState, // Replace INDEX_NAME with your index name INDEX_NAME: { ...uiState.INDEX_NAME, query: 'phone' } } }); ``` **refresh** Clears the Algolia responses cache and triggers a new search. For more information, read the [InstantSearch caching guide](/doc/guides/building-search-ui/going-further/improve-performance/js#caching). ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); search.refresh(); ``` ## Properties **status** `string` The status of the in-progress search. Possible values are: * `idle`. No search is in progress. * `loading`. The search is loading. Use `loading` for immediate feedback on an action. For loading indicators, use `'stalled'` instead. * `stalled`. The search is stalled. This is triggered after [`stalledSearchDelay`](#param-stalled-search-delay) expires. * `error`. The search failed. The error is available in the `error` property. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); search.start(); search.on("render", () => { console.log(search.status); }); ``` **error** `Error | undefined` The error that occurred during the search. This is only defined when `status` is `'error'`. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); search.start(); // Error handler prevents uncaught errors search.on("error", () => {}); search.on("render", () => { console.log(search.status === "error" && search.error); }); ``` **renderState** `RenderState | undefined` The `renderState` provides access to all the data to render the widgets, including the methods to refine the search. More information can be found in the [`renderState`](/doc/api-reference/widgets/render-state/js) page. ```js JavaScript icon=code theme={"system"} const indexName = ""; const search = instantsearch({ indexName, // ... }); search.addWidgets([ searchBox({ container: "", }), ]); search.start(); console.log(search.renderState[indexName].searchBox); /* { query: string; refine: Function; clear: Function; isSearchStalled: boolean; widgetParams: object; } */ ``` ## Events **render** Triggered when all widgets are rendered. This happens after every search request. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); search.on("render", () => { // Do something on render }); ``` **error** Triggered when calling the API reports an error. ```js JavaScript icon=code theme={"system"} const search = instantsearch({ // ... }); search.on("error", ({ error }) => { // Do something on error }); ``` # InstantSearch Source: https://www.algolia.com/doc/api-reference/widgets/instantsearch/react Root component for React InstantSearch apps. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} ``` ## Import ```js JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; ``` ## About this widget `InstantSearch` is the root wrapper component for all widgets and Hooks. It takes two parameters: * [`indexName`](/doc/api-reference/widgets/instantsearch/react#param-index-name). Your main search index * [`searchClient`](/doc/api-reference/widgets/instantsearch/react#param-search-client). The search client for React InstantSearch. The [search client](https://github.com/algolia/algoliasearch-client-javascript) needs an `appId` and an `apiKey`, which you can find in the [Algolia dashboard](https://dashboard.algolia.com/account/api-keys). See also: [Get started with React InstantSearch](/doc/guides/building-search-ui/getting-started/react) ### Middleware React InstantSearch provides middleware to help you connect to other systems: * **Insights.** Use the [`insights`](/doc/api-reference/widgets/insights/js) middleware to [send click and conversion events](/doc/guides/building-search-ui/events/react) * **Generic.** With the [`middleware`](/doc/api-reference/widgets/middleware/react) API, you can inject logic into React InstantSearch. For example, to [send events to Google Analytics](/doc/guides/building-search-ui/going-further/integrate-google-analytics/react). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( {/* Widgets */} ); } ``` ## Props **indexName** `string` *required* The main index in which to search. ```jsx JavaScript icon=code theme={"system"} {/* Widgets */} ; ``` **searchClient** `object` *required* Provides a search client to `InstantSearch`. Read [the custom backend guidance](/doc/guides/building-search-ui/going-further/backend-search/react) on implementing a custom search client. The client uses a cache to avoid unnecessary search operations. Use a stable reference to the same search client instance rather than creating a new one on each render. Avoid inlining the function call to `algoliasearch` as the prop value, and consider instantiating the client outside your React components. ```jsx JavaScript icon=code theme={"system"} const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( {/* Widgets */} ); } ``` **initialUiState** `object` Provides an initial state to your React InstantSearch widgets using the [`ui-state`](/doc/api-reference/widgets/ui-state/js) object from InstantSearch.js. Since this sets the initial state for your UI, you need to include the corresponding widgets in your app. For example, `page: 5` as initial state only has an effect if you include the `pagination` widget as well. Replace `YourIndexName` with the name of your Algolia index. ```jsx JavaScript icon=code theme={"system"} {/* Widgets */} ; ``` **onStateChange** `function` Triggered when the state changes. This can be helpful for performing custom logic on a state change. When using `onStateChange`, the instance is under your control. **You're responsible for updating the UI state** (with `setUiState`). ```jsx JavaScript theme={"system"} const onStateChange = ({ uiState, setUiState }) => { // Custom logic setUiState(uiState); }; function App() { return ( {/* Widgets */} ); } ``` ```tsx TypeScript theme={"system"} import type { InstantSearchProps } from "react-instantsearch"; const onStateChange: InstantSearchProps["onStateChange"] = ({ uiState, setUiState, }) => { // Custom logic setUiState(uiState); }; function App() { return ( {/* Widgets */} ); } ``` **stalledSearchDelay** `number` (default: 200) A time period (in ms) after which the search is considered to have stalled. Read the [slow network guide](/doc/guides/building-search-ui/going-further/improve-performance/react) for more information. ```jsx JavaScript icon=code theme={"system"} {/* Widgets */} ; ``` **routing** `boolean | object` (default: false) The router configuration used to save the UI state into the URL or any client-side persistence. **routing props** **routing.router** `object` This object stores the UI state. By default, it uses an instance of the [`history`](/doc/api-reference/widgets/history-router/react) router with its default parameters. **router props** **router.onUpdate** `function` Adds an event listener that makes InstantSearch aware of external changes to the storage medium (such as the URL). Typically you'll set up a listener for `popstate`, which triggers a callback with the current `routeState`. **router.read** `function` Reads the routing storage and returns a `routeState` object. **router.write** `function` Writes the `routeState` object into the routing storage. **router.createURL** `function` Transforms the `routeState` object into a URL. It receives an object and returns a string (which may be empty). **router.dispose** `function` Cleans up all event listeners. **routing.stateMapping** `object` Transforms the [`uiState`](/doc/api-reference/widgets/ui-state/react) into the object saved by the router. The default value is provided by [`simple`](/doc/api-reference/widgets/simple-state-mapping/react). **stateMapping props** **stateMapping.stateToRoute** `function` Transforms a `ui-state` representation into a `routeState` object. It receives an object that contains the UI state of all the widgets on the page. It can return any object that's readable by `routeToState`. **stateMapping.routeToState** `function` Transforms `routeState` into a `ui-state` representation. It receives an object that contains the UI state stored by the router. It can return any object that's readable by `stateToRoute`. For more information, see [Sync your URLs](/doc/guides/building-search-ui/going-further/routing-urls/react). You can't use [`initialUiState`](#param-initial-ui-state) with routing as the two options override each other. * Use `initialUiState` for simple and static use cases * Use `routing` for anything complex or dynamic. ```jsx JavaScript icon=code theme={"system"} {/* Widgets */} ; ``` **insights** `boolean | InsightsProps` (default: false) Enables the Insights middleware and loads the [`search-insights`](https://github.com/algolia/search-insights.js) library (if not already loaded). The Insights middleware sends view and click events automatically, and lets you set up your own [click and conversion events](/doc/guides/building-search-ui/events/react). To use this option with an object, refer to the [Insights middleware options](/doc/api-reference/widgets/insights/react#options). ```jsx boolean theme={"system"} {/* Widgets */} ; ``` ```jsx object theme={"system"} {/* Widgets */} ; ``` **future** `object` Test new InstantSearch features without affecting others. **future props** **future.preserveSharedStateOnUnmount** `boolean` (default: false) Changes the way `dispose` is used in the InstantSearch lifecycle. * If `false` (the default), each widget unmounting will also remove its state, even if multiple widgets read that UI state. * If `true`, each widget unmounting will only remove its state if it's the last of its type. This lets you to dynamically add and remove widgets without losing their state. ```jsx JavaScript icon=code theme={"system"} {/* Widgets */} ; ``` **future.persistHierarchicalRootCount** `boolean` (default: false) Whether to display a constant facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) value count at the root of a hierarchical menu with active refinements. * If `false` (default), the facet value count at the root level shows the facet value count of the refined (child) facet's parent. * If `true`, the facet value count at the root level shows the sum of the facet value counts of all its children, with or without refined children. ```jsx JavaScript icon=code theme={"system"} {/* Widgets */} ; ``` # InstantSearch.js Source: https://www.algolia.com/doc/api-reference/widgets/js Overview of InstantSearch.js widgets. To get started with InstantSearch.js, see [What is InstantSearch?](/doc/guides/building-search-ui/what-is-instantsearch/js) ## Basics | | | | - | - | | [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) | The root wrapper component for all widgets. | | [`index`](/doc/api-reference/widgets/index-widget/js) | This widget lets you apply widgets to a specific Algolia index. | | [`searchBox`](/doc/api-reference/widgets/search-box/js) | A widget to let users perform a text-based query. | | [`configure`](/doc/api-reference/widgets/configure/js) | The widget lets you provide raw search parameters to the Algolia API without rendering anything. | | [`panel`](/doc/api-reference/widgets/panel/js) | A widget that wraps other widgets in a consistent panel design. It also reacts when the widget can no longer refine. | | [`autocomplete`](/doc/api-reference/widgets/autocomplete/js) | A widget that shows query results as you type in a search box. | | [`chat`](/doc/api-reference/widgets/chat/js) | A widget to display a chat interface that interacts with a [generative AI assistant](/doc/guides/algolia-ai/agent-studio). | | [`chatTrigger`](/doc/api-reference/widgets/chat-trigger/js) | A widget to display a button that opens the [`chat`](/doc/api-reference/widgets/chat/js) widget's overlay. | | [`feeds`](/doc/api-reference/widgets/feeds/js) | A widget to display multiple independent result sets from a [multifeed composition](/doc/guides/compositions/multifeed-compositions). | | [`voiceSearch`](/doc/api-reference/widgets/voice-search/js) | A widget to let users perform a voice-based query. | | [`insights`](/doc/api-reference/widgets/insights/js) | A middleware to help set the user token for insights purposes, and send events from built-in and custom widgets. | | [`middleware`](/doc/api-reference/widgets/middleware/js) | With the middleware API, you can inject logic into InstantSearch.js. | | [`renderState`](/doc/api-reference/widgets/render-state/js) | The `renderState` provides access to all the data to render the widgets, including the methods to refine the search. | ## Results | | | | - | - | | [`hits`](/doc/api-reference/widgets/hits/js) | A widget to display a list of results. | | [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) | A widget to display a list of results with a "Show more" button. | | [`Highlight`](/doc/api-reference/widgets/highlight/js) | Highlighting visually emphasizes matching terms in your search results to help them stand out. | | [`ReverseHighlight`](/doc/api-reference/widgets/reverse-highlight/js) | A function that returns any attribute from a hit into its highlighted form, when relevant. | | [`Snippet`](/doc/api-reference/widgets/snippet/js) | A function that displays attributes in your search results in a shorter form (a snippet). | | [`ReverseSnippet`](/doc/api-reference/widgets/reverse-snippet/js) | A function that returns any attribute from a hit into its snippeted form, when relevant. | ## Recommendations | | | | - | - | | [`frequentlyBoughtTogether`](/doc/api-reference/widgets/frequently-bought-together/js) | A widget to display a list of frequently bought together items. | | [`relatedProducts`](/doc/api-reference/widgets/related-products/js) | A widget to display a list of related products and related content. | | [`trendingItems`](/doc/api-reference/widgets/trending-items/js) | A widget to display a list of trending items. | | [`trendingFacets`](/doc/api-reference/widgets/trending-facets/js) | A widget to display a list of trending facets. | | [`lookingSimilar`](/doc/api-reference/widgets/looking-similar/js) | A widget to display a list of visually similar products. | ## Refinements | | | | - | - | | [`refinementList`](/doc/api-reference/widgets/refinement-list/js) | One of the most common widgets you can find in a search UI. With this widget, users can filter the dataset based on facets. | | [`colorRefinementList`](/doc/api-reference/widgets/color-refinement-list/js) | A widget that filters results based on color facet values. It helps users quickly visualize the kind of color that products have. Not available in InstantSearch.js. | | [`dynamicWidgets`](/doc/api-reference/widgets/dynamic-widgets/js) | Widget to conditionally render other widgets based on the facet merchandising settings of the index. | | [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js) | Widget that displays a hierarchical menu. | | [`rangeSlider`](/doc/api-reference/widgets/range-slider/js) | A widget that provides a user-friendly way to filter the results, based on a single numeric range. | | [`menu`](/doc/api-reference/widgets/menu/js) | A widget that displays a menu that lets users choose a single value for a specific attribute. | | [`currentRefinements`](/doc/api-reference/widgets/current-refinements/js) | A widget that displays a list of refinements applied to the search. | | [`rangeInput`](/doc/api-reference/widgets/range-input/js) | A widget that allows a user to select a numeric range using a minimum and maximum input. | | [`menuSelect`](/doc/api-reference/widgets/menu-select/js) | A widget that allows a user to select a single value to refine in a drop-down menu. | | [`toggleRefinement`](/doc/api-reference/widgets/toggle-refinement/js) | A widget that provides an on/off filtering feature based on an attribute value. | | [`numericMenu`](/doc/api-reference/widgets/numeric-menu/js) | A widget that displays a list of numeric filters in a list. Those numeric filters are pre-configured with creating the widget. | | [`ratingMenu`](/doc/api-reference/widgets/rating-menu/js) | A widget that lets users refine search results by clicking on stars. | | [`clearRefinements`](/doc/api-reference/widgets/clear-refinements/js) | A widget that displays a button that lets users clean every refinement applied to the search. | ## Pagination | | | | - | - | | [`pagination`](/doc/api-reference/widgets/pagination/js) | A widget that displays a pagination system which lets users change the current page of search results. | | [`hitsPerPage`](/doc/api-reference/widgets/hits-per-page/js) | A widget that displays a drop-down menu to let users change the number of displayed hits. | ## Metadata | | | | - | - | | [`breadcrumb`](/doc/api-reference/widgets/breadcrumb/js) | The breadcrumb widget is a secondary navigation scheme that lets users see where the current page is in relation to the facet's hierarchy. | | [`stats`](/doc/api-reference/widgets/stats/js) | A widget that displays the total number of matching hits and the time it took to get them. | | [`poweredBy`](/doc/api-reference/widgets/powered-by/js) | A widget to display the Algolia logo to redirect to the Algolia website. | | [`analytics`](/doc/api-reference/widgets/analytics/js) | A widget that pushes the current state of the search to the analytics platform of your choice. | | [`queryRuleCustomData`](/doc/api-reference/widgets/query-rule-custom-data/js) | A widget to display custom data from Rules. | | [`queryRuleContext`](/doc/api-reference/widgets/query-rule-context/js) | A widget to set Rule contexts without rendering anything. | ## Sorting | | | | - | - | | [`sortBy`](/doc/api-reference/widgets/sort-by/js) | A widget that displays a list of indices, allowing a user to change the way hits are sorted with replica indices. | | [`relevantSort`](/doc/api-reference/widgets/relevant-sort/js) | A widget that displays the current search mode when searching in a virtual replica index, and allows users to switch between Relevant and regular sorting, which is more exhaustive and can return less relevant results. | ## Geo search | | | | - | - | | [`geoSearch`](/doc/api-reference/widgets/geo-search/js) | A widget that displays search results on a Google Map. | ## Routing | | | | - | - | | [`simple`](/doc/api-reference/widgets/simple-state-mapping/js) | A state mapping used by default routing. | | [`singleIndex`](/doc/api-reference/widgets/single-index-state-mapping/js) | A state mapping that enables backward compatibility with version 3.x. | | [`history`](/doc/api-reference/widgets/history-router/js) | A router used by default routing. | | [`uiState`](/doc/api-reference/widgets/ui-state/js) | An object that represents a search UI state. | # lookingSimilar Source: https://www.algolia.com/doc/api-reference/widgets/looking-similar/js Shows visually similar recommendations based on a provided item. > [!NOTE] > > If you're using the deprecated [`recommend-js`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js) UI library, > see [`lookingSimilar`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js/lookingSimilar). ```ts Signature theme={"system"} lookingSimilar({ container: string | HTMLElement, objectIDs: string[], // Optional parameters limit?: number, threshold?: number, queryParameters?: object, fallbackParameters?: object, escapeHTML?: boolean, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { lookingSimilar } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { lookingSimilar } = instantsearch.widgets; // or directly use instantsearch.widgets.lookingSimilar() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=lookingSimilar) > > Preview this widget and its behavior. ## About this widget Use the `lookingSimilar` widget to display a list of similar-looking products based on the image URLs you referenced when [setting up this Recommend model](/doc/guides/algolia-recommend/how-to/lookingSimilar). See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) ## Examples ```js JavaScript icon=code theme={"system"} lookingSimilar({ container: "#lookingSimilar", objectIDs: ["5723537"], templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, }, }); ``` ### With a carousel layout Display the `lookingSimilar` widget as a [scrollable carousel](#param-layout). ```js JavaScript icon=code theme={"system"} import { carousel } from "instantsearch.js/es/templates"; // or const { carousel } = instantsearch.templates; lookingSimilar({ container: "#lookingSimilar", objectIDs: ["5723537"], templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, layout: carousel(), }, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} lookingSimilar({ // ... container: '#lookingSimilar', }); ``` ```js HTMLElement theme={"system"} lookingSimilar({ container: document.querySelector("#lookingSimilar"), }); ``` **objectIDs** `string[]` *required* The list of object IDs for which to get recommendations. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```js JavaScript icon=code theme={"system"} lookingSimilar({ // ... objectIDs: ["1", "2", "3"], }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} lookingSimilar({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} lookingSimilar({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```js JavaScript icon=code theme={"system"} lookingSimilar({ // ... queryParameters: { filters: "category:Book", }, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as an additional 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.) set when there aren't enough recommendations. ```js JavaScript icon=code theme={"system"} lookingSimilar({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} lookingSimilar({ // ... escapeHTML: false, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} lookingSimilar({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The widget's root element. * `emptyRoot`. The container element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of recommendations. * `item`. The list item. ```js JavaScript icon=code theme={"system"} lookingSimilar({ // ... cssClasses: { root: "MyCustomLookingSimilar", list: ["MyCustomLookingSimilar", "MyCustomLookingSimilar--subclass"], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} lookingSimilar({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **empty** `string | function` The template to use when there are no recommendations. ```js function theme={"system"} lookingSimilar({ // ... templates: { empty(_, { html }) { return html`

    No recommendations.

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. lookingSimilar({ // ... templates: { empty: "No recommendations.", }, }); ``` **header** `string | function` The template to use for the recommendations header. This template receives the recommendations as well as the `cssClasses` object. ```js function theme={"system"} lookingSimilar({ // ... templates: { header({ cssClasses, items }, { html }) { return html`

    Recommendations (${items.length})

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. lookingSimilar({ // ... templates: { header: "

    Recommendations ({{items.length}})

    ", }, }); ``` **item** `string | function` The template to use for each recommendation. This template receives an object containing a single record. ```js function theme={"system"} lookingSimilar({ // ... templates: { item(item, { html }) { return html`

    ${item.name}

    ${item.description}

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. lookingSimilar({ // ... templates: { item: `

    {{ name }}

    {{ description }}

    `, }, }); ``` **layout** `function` The template to use to wrap all items. InstantSearch.js provides an out-of-the-box carousel layout template with the following options: `cssClasses`: * `root`. The carousel's root element. * `list`. The list of recommendations. * `item`. The list item. * `navigation`. The navigation controls. * `navigationPrevious`. The "Previous" navigation control. * `navigationNext`. The "Next" navigation control. `templates`: * `previous`. The content of the "Previous" navigation control. * `next`. The content of the "Next" navigation control. ```js Carousel theme={"system"} import { carousel } from 'instantsearch.js/es/templates'; // or const { carousel } = instantsearch.templates; lookingSimilar({ // ... templates: { layout: carousel({ cssClasses: { root: 'MyCustomCarousel', }, templates: { previous({ html }) { return html`

    Previous

    `; }, next({ html }) { return html`

    Next

    `; }, }, }), }, }); ``` ```js Custom theme={"system"} lookingSimilar({ // ... templates: { layout({ items }, { html }) { return html`
      ${items.map((item) => html`
    • ${item.objectID}

    • `)}
    `; }, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Looking Similar

    1. ...
    2. ...
    3. ...
    ``` ## Customize the UI with `connectLookingSimilar` If you want to create your own UI of the `lookingSimilar` widget, you can use connectors. To use `connectLookingSimilar`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```ts Package manager theme={"system"} import { connectLookingSimilar } from 'instantsearch.js/es/connectors'; ``` ```ts CDN theme={"system"} const { connectLookingSimilar } = instantsearch.connectors; // or directly use instantsearch.connectors.connectLookingSimilar() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderLookingSimilar = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customLookingSimilar = connectLookingSimilar(renderLookingSimilar); // 3. Instantiate search.addWidgets([ customLookingSimilar({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderLookingSimilar = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` The matched recommendations from the Algolia API. ```js JavaScript icon=code theme={"system"} const renderLookingSimilar = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector('#lookingSimilar').innerHTML = `
      ${items .map(item =>`
    • ${item.name}
    • `) .join('')}
    `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderLookingSimilar = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = '...'; }; // ... search.addWidgets([ customLookingSimilar({ // ... container: document.querySelector('#lookingSimilar'), }) ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customLookingSimilar = connectLookingSimilar( renderLookingSimilar ); search.addWidgets([ customLookingSimilar({ objectIDs: string[], // Optional parameters limit: number, threshold: number, queryParameters: object, fallbackParameters: object, escapeHTML: boolean, transformItems: function, }) ]); ``` #### Instance options **objectIDs** `string[]` *required* The list of object IDs for which to get recommendations. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). > For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```js JavaScript icon=code theme={"system"} customLookingSimilar({ // ... objectIDs: ["1", "2", "3"], }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} customLookingSimilar({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} customLookingSimilar({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```js JavaScript icon=code theme={"system"} customLookingSimilar({ // ... queryParameters: { filters: "category:Book", }, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as additional filters when there aren't enough recommendations. ```js JavaScript icon=code theme={"system"} customLookingSimilar({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} customLookingSimilar({ // ... escapeHTML: false, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customLookingSimilar({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderLookingSimilar = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; widgetParams.container.innerHTML = `
      ${items .map(item => `
    • ${item.name}
    • `) .join('')}
    `; }; // Create the custom widget const customLookingSimilar = connectLookingSimilar( renderLookingSimilar ); // Instantiate the custom widget search.addWidgets([ customLookingSimilar({ container: document.querySelector('#lookingSimilar'), objectIDs: ['5723537'], }) ]); ``` # menuSelect Source: https://www.algolia.com/doc/api-reference/widgets/menu-select/js Lets users refine search results using a drop-down menu. ```ts Signature theme={"system"} menuSelect({ container: string | HTMLElement, attribute: string, // Optional parameters limit?: number, sortBy?: string[] | function, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { menuSelect } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { menuSelect } = instantsearch.widgets; // or directly use instantsearch.widgets.menuSelect() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=menuSelect) > > Preview this widget and its behavior. ## About this widget The `menuSelect` widget allows a user to select a single value to refine inside a [`select`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) element. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. ## Examples ```js JavaScript icon=code theme={"system"} menuSelect({ container: "#menu-select", attribute: "brand", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} menuSelect({ // ... container: "#menu-select", }); ``` ```js HTMLElement theme={"system"} menuSelect({ // ... container: document.querySelector("#menu-select"), }); ``` **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} menuSelect({ // ... attribute: "brand", }); ``` **limit** `number` (default: 10) The maximum number of values to display. ```js JavaScript icon=code theme={"system"} menuSelect({ // ... limit: 20, }); ``` **sortBy** `string[] | function` (default: Uses facetOrdering if set, \['name:asc']) How to sort refinements. Must be one or more of the following strings: * `"count:asc"` * `"count:desc"` * `"name:asc"` * `"name:desc"` * `"isRefined"` It's also possible to give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). If `facetOrdering` is set for this facet in `renderingContent`, and no value for `sortBy` is passed to this widget, `facetOrdering` is used, and the default order as a fallback. ```js string[] theme={"system"} menuSelect({ // ... sortBy: ["isRefined"], }); ``` ```js function theme={"system"} menuSelect({ // ... sortBy(a, b) { return a.name < b.name ? 1 : -1; }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} menuSelect({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `noRefinementRoot`. The root element if there are no refinements. * `select`. The `select` element. * `option`. The `option` elements of the `select`. ```js JavaScript icon=code theme={"system"} menuSelect({ // ... cssClasses: { root: "MyCustomMenuSelect", select: [ "MyCustomMenuSelectElement", "MyCustomMenuSelectElement--subclass", ], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} menuSelect({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **item** `string | function` The template to customize each `option` element. It exposes: * `label: string`. The label to display in the option. * `value: string`. The value for the option. * `count: number`. The number of hits that match this value. * `isRefined: boolean`. Indicates if it's the current refined value. ```js function theme={"system"} menuSelect({ // ... templates: { item(item, { html }) { return html`${item.label} - (${item.count.toLocaleString()})`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. menuSelect({ // ... templates: { item: "{{label}} ({{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}})", }, }); ``` **defaultOption** `string | function` The template to customize the first option of the select. ```js function theme={"system"} menuSelect({ // ... templates: { defaultOption(data, { html }) { return html`See all`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. menuSelect({ // ... templates: { defaultOption: "See all", }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectMenu` If you want to create your own UI of the `menuSelect` widget, you can use connectors. > [!NOTE] > > This connector is also used to build the [`menu`](/doc/api-reference/widgets/menu/js) widget. To use `connectMenu`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectMenu } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectMenu } = instantsearch.connectors; // or directly use instantsearch.connectors.connectMenu() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderMenuSelect = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customMenuSelect = connectMenu(renderMenuSelect); // 3. Instantiate search.addWidgets([ customMenuSelect({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderMenuSelect = (renderOptions, isFirstRender) => { const { items, canRefine, refine, sendEvent, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` > [!NOTE] > > If SEO is important for your search page, ensure that your custom HTML is optimized for search engines: > > * Use `` tags with `href` attributes to allow search engine bots to follow links. > * Use semantic HTML and include [structured data](https://developers.google.com/search/docs/appearance/structured-data) when relevant. > > For more guidance, see the [SEO checklist](/doc/guides/building-search-ui/resources/seo/js). #### Rendering options **items** `object[]` The elements that can be refined for the current search results. With each item: * `value: string`. The value of the menu item. * `label: string`. The label of the menu item. * `count: number`. The number of matched results after a refinement is applied. * `isRefined: boolean`. Indicates if the refinement is applied. ```js JavaScript icon=code theme={"system"} const renderMenuSelect = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector("#menu-select").innerHTML = ` `; }; ``` **canRefine** `boolean` Returns `true` if a refinement can be applied. ```js JavaScript icon=code theme={"system"} const renderMenuSelect = (renderOptions, isFirstRender) => { const { items, canRefine } = renderOptions; document.querySelector("#menu-select").innerHTML = ` `; }; ``` **refine** `function` Sets the refinement and triggers a search. ```js JavaScript icon=code theme={"system"} const renderMenuSelect = (renderOptions, isFirstRender) => { const { items, refine } = renderOptions; const container = document.querySelector("#menu-select"); if (isFirstRender) { const select = document.createElement("select"); select.addEventListener("change", (event) => { refine(event.target.value); }); container.appendChild(select); } container.querySelector("select").innerHTML = ` ${items .map( (item) => ``, ) .join("")} `; }; ``` **sendEvent** `(eventType, facetValue) =\> void` The function to send `click` events. The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware. * `eventType: 'click'` * `facetValue: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent("click", "Apple"); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['brand:"Apple"'], index: '', }, widgetType: 'ais.menu', } */ ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderMenuSelect = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customMenuSelect({ container: document.querySelector("#menu-select"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```jsx JavaScript icon=code theme={"system"} const customMenuSelect = connectMenu( renderMenuSelect ); search.addWidgets([ customMenuSelect({ attribute: string, // Optional parameters limit: number, sortBy: string[] | function, transformItems: function, }) ]); ``` #### Instance options **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} customMenuSelect({ attribute: "brand", }); ``` **limit** `number` (default: 10) The maximum number of values to display. ```js JavaScript icon=code theme={"system"} customMenuSelect({ // ... limit: 20, }); ``` **sortBy** `string[] | function` (default: \['isRefined', 'name:asc']) How to sort refinements. Must be one or more of the following strings: * `"count:asc"` * `"count:desc"` * `"name:asc"` * `"name:desc"` * `"isRefined"` It's also possible to give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). ```js string[] theme={"system"} customMenuSelect({ // ... sortBy: ["name:asc"], }); ``` ```js function theme={"system"} customMenuSelect({ // ... sortBy(a, b) { return a.name < b.name ? 1 : -1; }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customMenuSelect({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); ``` ### Full example ```html HTML theme={"system"} ``` ```js JavaScript theme={"system"} // Create the render function const renderMenuSelect = (renderOptions, isFirstRender) => { const { items, canRefine, refine, widgetParams } = renderOptions; if (isFirstRender) { const select = document.createElement("select"); select.addEventListener("change", (event) => { refine(event.target.value); }); widgetParams.container.appendChild(select); } const select = widgetParams.container.querySelector("select"); select.disabled = !canRefine; select.innerHTML = ` ${items .map( (item) => ``, ) .join("")} `; }; // Create the custom widget const customMenuSelect = connectMenu(renderMenuSelect); // Instantiate the custom widget search.addWidgets([ customMenuSelect({ container: document.querySelector("#menu-select"), attribute: "brand", }), ]); ``` # menu Source: https://www.algolia.com/doc/api-reference/widgets/menu/js Shows a menu for refining search results based on a selected facet value. ```ts Signature theme={"system"} menu({ container: string | HTMLElement, attribute: string, // Optional parameters limit?: number, showMore?: boolean, showMoreLimit?: number, sortBy?: string[] | function, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { menu } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { menu } = instantsearch.widgets; // or directly use instantsearch.widgets.menu() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=menu) > > Preview this widget and its behavior. ## About this widget The `menu` widget displays a menu that lets users choose a single value for a specific attribute. > [!NOTE] > > The `menu` widget uses a hierarchical refinement internally, so it can't refine values that include the default separator (`>`). > To support these values, use the [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js) widget instead. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. ## Examples ```js JavaScript icon=code theme={"system"} menu({ container: "#menu", attribute: "categories", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector of the DOM element inside which the widget is inserted. ```js string theme={"system"} menu({ // ... container: "#menu", }); ``` ```js HTMLElement theme={"system"} menu({ // ... container: document.querySelector("#menu"), }); ``` **attribute** `string` *required* The name of the attribute in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```js JavaScript icon=code theme={"system"} menu({ // ... attribute: "categories", }); ``` **limit** `number` (default: 10) How many facet values to retrieve. When you enable the [`showMore`](#param-show-more) feature, this is the number of facet values to display before clicking the "Show more" button. ```js JavaScript icon=code theme={"system"} menu({ // ... limit: 20, }); ``` **showMore** `boolean` Limits the number of results and display a `showMore` button. ```js boolean theme={"system"} menu({ // ... showMore: true, }); ``` **showMoreLimit** `number` (default: 20) How many facet values to retrieve when showing more. ```js JavaScript icon=code theme={"system"} menu({ // ... showMoreLimit: 30, }); ``` **sortBy** `string[] | function` How to sort refinements. Must be one of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` **Default:** `["isRefined", "name:asc"]` It's also possible to give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). If `facetOrdering` is set for this facet in `renderingContent`, and no value for `sortBy` is passed to this widget, `facetOrdering` is used, and the default order as a fallback. ```js string[] theme={"system"} menu({ // ... sortBy: ["count:desc", "name:asc"], }); ``` ```js function theme={"system"} menu({ // ... sortBy(a, b) { return a.name < b.name ? 1 : -1; }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} menu({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `noRefinementRoot`. The root element if there are no refinements. * `list`. The list of results. * `item`. The list items. They contain the link and separator. * `selectedItem`. The selected item in the list. This is the last one, or the root one if there are no refinements. * `link`. The link element of each item. * `label`. The label element of each item. * `count`. The count element of each item. * `showMore`. The "Show more" button. * `disabledShowMore`. The "Show more" button when it's unavailable. ```js JavaScript icon=code theme={"system"} menu({ // ... cssClasses: { root: "MyCustomMenu", list: ["MyCustomMenuList", "MyCustomMenuList--sub-class"], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} menu({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or: combined with results menu({ // ... transformItems(items, { results }) { return items.map((item) => ({ ...item, label: item.isRefined ? `${item.label} (${results.nbPages} pages)` : item.label, })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **item** `string | function` Item template. It exposes: * `count`. The number of occurrences of the facet in the result set. * `isRefined`. Returns `true` if the value is selected. * `label`. The label to display. * `value`. The value used for refining. * `url`. The URL with the selected refinement. * `cssClasses`. An object containing all the computed classes for the item. ```js function theme={"system"} menu({ // ... templates: { item(data, { html }) { const { label, count, url, cssClasses } = data; return html` ${label} ${count.toLocaleString()} `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. menu({ // ... templates: { item: ` {{label}} {{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}} `, }, }); ``` **showMoreText** `string | function` The template for the "Show more" button text. It exposes: * `isShowingMore: boolean`. Whether the list is expanded. ```js function theme={"system"} menu({ // ... templates: { showMoreText(data, { html }) { return html`${data.isShowingMore ? "Show less" : "Show more"}`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. menu({ // ... templates: { showMoreText: ` {{#isShowingMore}} Show less {{/isShowingMore}} {{^isShowingMore}} Show more {{/isShowingMore}} `, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` ## Customize the UI with `connectMenu` If you want to create your own UI of the `menu` widget, you can use connectors. > [!NOTE] > > This connector is also used to build the [`menuSelect`](/doc/api-reference/widgets/menu-select/js) widget. To use `connectMenu`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectMenu } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectMenu } = instantsearch.connectors; // or directly use instantsearch.connectors.connectMenu() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderMenu = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customMenu = connectMenu(renderMenu); // 3. Instantiate search.addWidgets([ customMenu({ // Widget parameters }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderMenu = (renderOptions, isFirstRender) => { const { items, canRefine, refine, sendEvent, createURL, isShowingMore, canToggleShowMore, toggleShowMore, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` > [!NOTE] > > If SEO is important for your search page, ensure that your custom HTML is optimized for search engines: > > * Use `` tags with `href` attributes to allow search engine bots to follow links. > * Use semantic HTML and include [structured data](https://developers.google.com/search/docs/appearance/structured-data) when relevant. > > For more guidance, see the [SEO checklist](/doc/guides/building-search-ui/resources/seo/js). #### Render options **items** `object[]` The elements that can be refined for the current search results. With each item: * `value: string`. The value of the menu item. * `label: string`. The label of the menu item. * `count: number`. The number of results matched after a refinement is applied. * `isRefined: boolean`. Indicates if the refinement is applied. ```js JavaScript icon=code theme={"system"} const renderMenu = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector("#menu").innerHTML = ` `; }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderMenu = (renderOptions, isFirstRender) => { const { items, canRefine, refine } = renderOptions; const container = document.querySelector("#menu"); if (!canRefine) { container.innerHTML = ""; return; } }; ``` **refine** `function` A function to toggle a refinement. ```js JavaScript icon=code theme={"system"} const renderMenu = (renderOptions, isFirstRender) => { const { items, refine } = renderOptions; const container = document.querySelector("#menu"); container.innerHTML = ` `; [...container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.currentTarget.dataset.value); }); }); }; ``` **sendEvent** `(eventType, facetValue) =\> void` The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. * You can learn more about the middleware. * `eventType: 'click'` * `facetValue: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent("click", "Apple"); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['brand:"Apple"'], index: '', }, widgetType: 'ais.menu', } */ ``` **createURL** `function` Generates a URL for the corresponding search state. ```js JavaScript icon=code theme={"system"} const renderMenu = (renderOptions, isFirstRender) => { const { items, createURL } = renderOptions; document.querySelector("#menu").innerHTML = ` `; }; ``` **isShowingMore** `boolean` Returns `true` if the menu is displaying all the menu items. ```js JavaScript icon=code theme={"system"} const renderMenu = (renderOptions, isFirstRender) => { const { items, isShowingMore, toggleShowMore } = renderOptions; const container = document.querySelector("#menu"); if (isFirstRender) { const ul = document.createElement("ul"); const button = document.createElement("button"); button.textContent = "Show more"; button.addEventListener("click", () => { toggleShowMore(); }); container.appendChild(ul); container.appendChild(button); } container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); container.querySelector("button").textContent = isShowingMore ? "Show less" : "Show more"; }; ``` **canToggleShowMore** `boolean` Returns `true` if the "Show more" button can be activated (enough items to display more and not already displaying more than [`limit`](/doc/api-reference/widgets/menu/js#param-limit) items). ```js JavaScript icon=code theme={"system"} const renderMenu = (renderOptions, isFirstRender) => { const { items, canToggleShowMore, toggleShowMore } = renderOptions; const container = document.querySelector("#menu"); if (isFirstRender) { const ul = document.createElement("ul"); const button = document.createElement("button"); button.textContent = "Show more"; button.addEventListener("click", () => { toggleShowMore(); }); container.appendChild(ul); container.appendChild(button); } container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); container.querySelector("button").disabled = !canToggleShowMore; }; ``` **toggleShowMore** `function` Toggles the number of displayed values between [`limit`](/doc/api-reference/widgets/menu/js#param-limit) and [`showMoreLimit`](/doc/api-reference/widgets/menu/js#param-show-more-limit). ```js JavaScript icon=code theme={"system"} const renderMenu = (renderOptions, isFirstRender) => { const { items, toggleShowMore } = renderOptions; const container = document.querySelector("#menu"); if (isFirstRender) { const ul = document.createElement("ul"); const button = document.createElement("button"); button.textContent = "Show more"; button.addEventListener("click", () => { toggleShowMore(); }); container.appendChild(ul); container.appendChild(button); } container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderMenu = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customMenu({ // ... container: document.querySelector("#menu"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customMenu = connectMenu(renderMenu); search.addWidgets([ customMenu({ attribute, // Optional instance params limit, showMoreLimit, sortBy, transformItems, }), ]); ``` #### Instance options **attribute** `string` *required* The name of the attribute for faceting. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```js JavaScript icon=code theme={"system"} customMenu({ attribute: "categories", }); ``` **limit** `number` (default: 10) How many facet values to retrieve. When you enable the [`showMore`](/doc/api-reference/widgets/menu/js#param-show-more) feature, this is the number of facet values to display before clicking the "Show more" button. ```js JavaScript icon=code theme={"system"} customMenu({ // ... limit: 20, }); ``` **showMoreLimit** `number` (default: 10) How many facet values to retrieve when showing more. ```js JavaScript icon=code theme={"system"} customMenu({ // ... showMoreLimit: 20, }); ``` **sortBy** `string[] | function` How to sort refinements. Must be one or more of the following strings: * `"count:asc"` * `"count:desc"` * `"name:asc"` * `"name:desc"` * `"isRefined"` **Default:** `["isRefined", "name:asc"]` It's also possible to give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). If `facetOrdering` is set for this facet in `renderingContent`, and no value for `sortBy` is passed to this widget, `facetOrdering` is used, and the default order as a fallback. ```js string[] theme={"system"} customMenu({ // ... sortBy: ["count:desc", "name:asc"], }); ``` ```js function theme={"system"} customMenu({ // ... sortBy(a, b) { return a.name < b.name ? 1 : -1; }, }); ``` **transformItems** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customMenu({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or, combined with results customMenu({ // ... transformItems(items, { results }) { return items.map((item) => ({ ...item, label: item.isRefined ? `${item.label} (${results.nbPages} pages)` : item.label, })); }, }); ``` ### Full example ```html HTML theme={"system"} ``` ```js JavaScript theme={"system"} // 1. Create a render function const renderMenu = (renderOptions, isFirstRender) => { const { items, refine, createURL, isShowingMore, canToggleShowMore, toggleShowMore, widgetParams, } = renderOptions; if (isFirstRender) { const ul = document.createElement("ul"); const button = document.createElement("button"); button.textContent = "Show more"; button.addEventListener("click", () => { toggleShowMore(); }); widgetParams.container.appendChild(ul); widgetParams.container.appendChild(button); } widgetParams.container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); [...widgetParams.container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.currentTarget.dataset.value); }); }); const button = widgetParams.container.querySelector("button"); button.disabled = !canToggleShowMore; button.textContent = isShowingMore ? "Show less" : "Show more"; }; // 2. Create the custom widget const customMenu = connectMenu(renderMenu); // 3. Instantiate search.addWidgets([ customMenu({ container: document.querySelector("#menu"), attribute: "categories", showMoreLimit: 20, }), ]); ``` # middleware Source: https://www.algolia.com/doc/api-reference/widgets/middleware/js Adds custom logic to your InstantSearch.js apps. ```ts Signature theme={"system"} function middleware({ instantSearchInstance }) { return { onStateChange({ uiState }) {}, subscribe() {}, unsubscribe() {}, }; } const search = instantsearch({ // ... }); search.use(middleware); ``` ## About this widget Middleware is a function that returns an object with [`onStateChange`](#param-on-state-change), [`subscribe`](#param-subscribe), and [`unsubscribe`](#param-unsubscribe) functions. The `middleware` function doesn't perform any actions itself but lets you inject logic into InstantSearch.js, for example, [sending events to Google Analytics](/doc/guides/building-search-ui/going-further/integrate-google-analytics/js). To [send Algolia click and conversion events](/doc/guides/building-search-ui/events/js), use the [`insights`](/doc/api-reference/widgets/insights/js) middleware. ### Requirements * [InstantSearch.js](https://github.com/algolia/instantsearch) v4.8.3 or later. ## Examples ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { return { onStateChange({ uiState }) { // Do something with `uiState` whenever the state changes. }, subscribe() { // Do something when the InstantSearch instance starts. }, unsubscribe() { // Do something when the InstantSearch instance is disposed of. }, }; } // Declare your instantSearch instance normally const search = instantsearch({ // ... }); // Use your middleware function search.use(middleware); ``` ## Options **instantSearchInstance** `object` *required* You have access to [the instance of `instantsearch`](/doc/api-reference/widgets/instantsearch/js) which lets you read values from the instance or call instance methods like `addWidgets`, `setUiState`, and `refresh`. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { return { onStateChange({ uiState }) { // ... }, subscribe() { // ... }, unsubscribe() { // ... }, }; } ``` ## Hooks **onStateChange** `(\{ uiState \}) =\> void` *required* The function is called with [`uiState`](/doc/api-reference/widgets/ui-state/js) whenever the state changes. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { return { // ... onStateChange({ uiState }) { const indexName = ""; const { query } = uiState[indexName]; const title = document.querySelector(""); title.innerText = query ? `Query: ${query}` : `No query`; }, }; } ``` **subscribe** `() =\> void` *required* This function is called when the InstantSearch instance starts (when `search.start()` is called). This is where you can add event listeners, subscribe to an API, and run any side effects. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { let subscription; let listener; return { // ... subscribe() { subscription = someAPI.subscribe(); listener = (event) => { // do something }; document .querySelector("") .addEventListener("click", listener); }, }; } ``` **unsubscribe** `() =\> void` *required* The function is called when the InstantSearch instance is disposed of. You can clean up anything you initiated in the `subscribe` function. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { let subscription; let listener; return { // ... unsubscribe() { subscription.unsubscribe(); document .querySelector("") .removeEventListener("click", listener); }, }; } ``` # numericMenu Source: https://www.algolia.com/doc/api-reference/widgets/numeric-menu/js Shows a list of numeric filters for refining search results. ```ts Signature theme={"system"} numericMenu({ container: string | HTMLElement, attribute: string, items: object[], // Optional parameters templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { numericMenu } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { numericMenu } = instantsearch.widgets; // or directly use instantsearch.widgets.numericMenu() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=numericMenu) > > Preview this widget and its behavior. ## About this widget The `numericMenu` widget displays a list of numeric filters in a list. Those numeric filters are pre-configured when creating the widget. ### Requirements The value provided to the `attribute` option must be an attribute which is a number 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.), not a string. ## Examples ```js JavaScript icon=code theme={"system"} numericMenu({ container: "#numeric-menu", attribute: "price", items: [ { label: "All" }, { label: "Less than 500$", end: 500 }, { label: "Between 500$ - 1000$", start: 500, end: 1000 }, { label: "More than 1000$", start: 1000 }, ], }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} numericMenu({ // ... container: "#numeric-menu", }); ``` ```js HTMLElement theme={"system"} numericMenu({ // ... container: document.querySelector("#numeric-menu"), }); ``` **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} numericMenu({ // ... attribute: "price", }); ``` **items** `object[]` *required* A list of all the options to display, with: * `label: string`. Label of the option. * `start: number`. The option must be greater than or equal to `start` (lower bound). * `end: number`. The option must be smaller than or equal to `end` (upper bound). ```js JavaScript icon=code theme={"system"} numericMenu({ // ... items: [ { label: "All" }, { label: "Less than 500$", end: 500 }, { label: "Between 500$ - 1000$", start: 500, end: 1000 }, { label: "More than 1000$", start: 1000 }, ], }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} numericMenu({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `noRefinementRoot`. Container class without results. * `list`. The list of results. * `item`. The list items. * `selectedItem`. The selected item in the list. * `label`. The label of each item. * `labelText`. The text element of each item. * `radio`. The radio button of each item. ```js JavaScript icon=code theme={"system"} numericMenu({ // ... cssClasses: { root: "MyCustomNumericMenu", list: ["MyCustomNumericMenuList", "MyCustomNumericMenuList--subclass"], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} numericMenu({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or: combined with results numericMenu({ // ... transformItems(items, { results }) { return items.map((item) => ({ ...item, label: item.isRefined && results ? `${item.label} (${results.nbHits} hits)` : item.label, })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **item** `string | function` The template for each item. It exposes: * `attribute: string`. The name of the attribute. * `label: string`. The label for the option. * `value: string`. The encoded URL of the bounds object with a `{start, end}` form. This value can be used verbatim in the webpage and can be read by `refine` directly. If you want to inspect the value, you can do `JSON.parse(window.decodeURI(value))` to get the object. * `isRefined: boolean`. Whether the refinement is selected. * `url: string`. The URL with the applied refinement. * `cssClasses: object`. The CSS classes provided to the widget. ```js function theme={"system"} numericMenu({ // ... templates: { item(data, { html }) { return html` `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. numericMenu({ // ... templates: { item: ` `, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectNumericMenu` If you want to create your own UI of the `numericMenu` widget, you can use connectors. To use `connectNumericMenu`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectNumericMenu } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectNumericMenu } = instantsearch.connectors; // or directly use instantsearch.connectors.connectNumericMenu() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderNumericMenu = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customNumericMenu = connectNumericMenu(renderNumericMenu); // 3. Instantiate search.addWidgets([ customNumericMenu({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderNumericMenu = (renderOptions, isFirstRender) => { const { items, canRefine, refine, sendEvent, createURL, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` The list of available options, with each option: * `label: string`. The label for the option. * `value: string`. The encoded URL of the bounds object with the `{start, end}` form. This value can be used verbatim in the webpage and can be read by `refine` directly. If you want to inspect the value, you can do `JSON.parse(window.decodeURI(value))` to get the object. * `isRefined: boolean`. Whether the refinement is selected. ```js JavaScript icon=code theme={"system"} numericMenu({ // ... templates: { item(data, { html }) { return html` `; }, }, }); ``` **canRefine** `boolean` Whether the search can be refined. This is `true` if there are results or if a range other than "All" is selected. ```js JavaScript icon=code theme={"system"} const renderNumericMenu = (renderOptions, isFirstRender) => { // `canRefine` is only available from v4.45.0 // Use `hasNoResults` in earlier minor versions. const { items, canRefine } = renderOptions; document.querySelector("#numeric-menu").innerHTML = `
      ${items .map( (item) => `
    • `, ) .join("")}
    `; }; ``` **refine** `function` Sets the selected value and triggers a new search. ```js JavaScript icon=code theme={"system"} const renderNumericMenu = (renderOptions, isFirstRender) => { const { items, refine } = renderOptions; const container = document.querySelector("#numeric-menu"); container.innerHTML = `
      ${items .map( (item) => `
    • `, ) .join("")}
    `; [...container.querySelectorAll("input")].forEach((element) => { element.addEventListener("change", (event) => { refine(event.currentTarget.value); }); }); }; ``` **sendEvent** `(eventType, facetValue) =\> void` The function to send `click` events. The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware. * `eventType: 'click'` * `facetValue: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent("click", [10, 20]); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['numerics<=20', 'numerics>=10'], index: '', }, widgetType: 'ais.numericMenu', } */ ``` **createURL** `function` Generates a URL for the next state. ```js JavaScript icon=code theme={"system"} const renderNumericMenu = (renderOptions, isFirstRender) => { const { items, createURL } = renderOptions; document.querySelector("#numeric-menu").innerHTML = ` `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderNumericMenu = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customNumericMenu({ // ... container: document.querySelector("#numeric-menu"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customNumericMenu = connectNumericMenu( renderNumericMenu ); search.addWidgets([ customNumericMenu({ attribute: string, items: object[], // Optional parameters transformItems?: function, }) ]); ``` #### Instance options **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} customNumericMenu({ // ... attribute: "price", }); ``` **items** `object[]` *required* A list of all the options to display, with: * `label: string`. Label of the option. * `start: string`. The option must be greater than or equal to `start` (lower bound). * `end: string`. The option must be smaller than or equal to `end` (upper bound). ```js JavaScript icon=code theme={"system"} customNumericMenu({ // ... items: [ { label: "All" }, { label: "Less than 500$", end: 500 }, { label: "Between 500$ - 1000$", start: 500, end: 1000 }, { label: "More than 1000$", start: 1000 }, ], }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customNumericMenu({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or: combined with results customNumericMenu({ // ... transformItems(items, { results }) { return items.map((item) => ({ ...item, label: item.isRefined && results ? `${item.label} (${results.nbHits} hits)` : item.label, })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderNumericMenu = (renderOptions, isFirstRender) => { // `canRefine` is only available from v4.45.0 // Use `hasNoResults` in earlier minor versions. const { items, canRefine, refine, widgetParams } = renderOptions; widgetParams.container.innerHTML = `
      ${items .map( (item) => `
    • `, ) .join("")}
    `; [...widgetParams.container.querySelectorAll("input")].forEach((element) => { element.addEventListener("change", (event) => { refine(event.currentTarget.value); }); }); }; // Create the custom widget const customNumericMenu = connectNumericMenu(renderNumericMenu); // Instantiate the custom widget search.addWidgets([ customNumericMenu({ container: document.querySelector("#numeric-menu"), attribute: "price", items: [ { label: "All" }, { label: "Less than 500$", end: 500 }, { label: "Between 500$ - 1000$", start: 500, end: 1000 }, { label: "More than 1000$", start: 1000 }, ], }), ]); ``` # pagination Source: https://www.algolia.com/doc/api-reference/widgets/pagination/js Adds controls for navigating search result pages. ```ts Signature theme={"system"} pagination({ container: string | HTMLElement, // Optional parameters showFirst: boolean, showPrevious: boolean, showNext: boolean, showLast: boolean, padding: number, totalPages: number, scrollTo: string | HTMLElement | boolean, templates: object, cssClasses: object, }); ``` ```js Package manager theme={"system"} import { pagination } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { pagination } = instantsearch.widgets; // or directly use instantsearch.widgets.pagination() ``` [See live example]() > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=pagination) > > Preview this widget and its behavior. ## About this widget The `pagination` widget displays a pagination system which lets users change the current page of search results. > [!NOTE] > > Pagination is limited to 1,000 hits per page. > For more information, see [Pagination limitations](/doc/guides/building-search-ui/ui-and-ux-patterns/pagination/js#pagination-limitations). ## Examples ```js JavaScript icon=code theme={"system"} pagination({ container: "#pagination", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} pagination({ container: '#pagination', }); ``` ```js HTMLElement theme={"system"} pagination({ container: document.querySelector("#pagination"), }); ``` **showFirst** `boolean` (default: true) Whether to display the first page link. ```js JavaScript icon=code theme={"system"} pagination({ // ... showFirst: false, }); ``` **showPrevious** `boolean` (default: true) Whether to display the previous page link. ```js JavaScript icon=code theme={"system"} pagination({ // ... showPrevious: false, }); ``` **showNext** `boolean` (default: true) Whether to display the next page link. ```js JavaScript icon=code theme={"system"} pagination({ // ... showNext: false, }); ``` **showLast** `boolean` (default: true) Whether to display the last page link. ```js JavaScript icon=code theme={"system"} pagination({ // ... showLast: false, }); ``` **padding** `number` (default: 3) The number of pages to display on each side of the current page. ```js JavaScript icon=code theme={"system"} pagination({ // ... padding: 2, }); ``` **totalPages** `number` The maximum number of pages to browse. ```js JavaScript icon=code theme={"system"} pagination({ // ... totalPages: 2, }); ``` **scrollTo** `string | HTMLElement | boolean` (default: body) Where to scroll after a click. Set to `false` to turn it off. ```js string theme={"system"} pagination({ // ... scrollTo: 'header', }); ``` ```js HTMLElement theme={"system"} pagination({ // ... scrollTo: document.querySelector(".header"), }); ``` ```js boolean theme={"system"} pagination({ // ... scrollTo: false, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} pagination({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `noRefinementRoot`. The root container without results. * `list`. The list of results. * `item`. The item in the list of results. * `firstPageItem`. The first item. * `lastPageItem`. The last item. * `previousPageItem`. The previous item. * `nextPageItem`. The next item. * `pageItem`. The page items. * `selectedItem`. The selected item. * `disabledItem`. The item when it's unavailable. * `link`. The link elements. ```js JavaScript icon=code theme={"system"} pagination({ // ... cssClasses: { root: "MyCustomPagination", list: ["MyCustomPaginationList", "MyCustomPaginationList--subclass"], }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **first** `string | function` The template for the first page. ```js string theme={"system"} pagination({ // ... templates: { first: '«', }, }); ``` ```js function theme={"system"} pagination({ // ... templates: { first() { return "«"; }, }, }); ``` **previous** `string | function` The template for the previous page. ```js string theme={"system"} pagination({ // ... templates: { previous: '‹', }, }); ``` ```js function theme={"system"} pagination({ // ... templates: { previous() { return "‹"; }, }, }); ``` **next** `string | function` The template for the next page. ```js string theme={"system"} pagination({ // ... templates: { next: '›', }, }); ``` ```js function theme={"system"} pagination({ // ... templates: { next() { return "›"; }, }, }); ``` **last** `string | function` The template for the last page. ```js string theme={"system"} pagination({ // ... templates: { last: '»', }, }); ``` ```js function theme={"system"} pagination({ // ... templates: { last() { return "»"; }, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectPagination` If you want to create your own UI of the `pagination` widget, you can use connectors. To use `connectPagination`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectPagination } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectPagination } = instantsearch.connectors; // or directly use instantsearch.connectors.connectPagination() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderPagination = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customPagination = connectPagination(renderPagination); // 3. Instantiate search.addWidgets([ customPagination({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { pages, currentRefinement, nbHits, nbPages, isFirstPage, isLastPage, canRefine, refine, createURL, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` > [!NOTE] > > If SEO is important for your search page, ensure that your custom HTML is optimized for search engines: > > * Use `` tags with `href` attributes to allow search engine bots to follow links. > * Use semantic HTML and include [structured data](https://developers.google.com/search/docs/appearance/structured-data) when relevant. > > For more guidance, see the [SEO checklist](/doc/guides/building-search-ui/resources/seo/js). #### Rendering options **pages** `number[]` The pages relevant to the current situation and padding. ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { pages } = renderOptions; document.querySelector('#pagination').innerHTML = ` `; }; ``` **currentRefinement** `number` The number of the page currently displayed. ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { pages, currentRefinement } = renderOptions; document.querySelector("#pagination").innerHTML = ` `; }; ``` **nbHits** `number` The number of hits computed for the last query (can be approximate). ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { currentRefinement, nbPages, nbHits } = renderOptions; document.querySelector("#pagination").innerHTML = ` ${currentRefinement + 1} of ${nbPages} page(s) for ${nbHits} hit(s) `; }; ``` **nbPages** `number` The number of pages for the result set. ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { currentRefinement, nbPages, nbHits } = renderOptions; document.querySelector("#pagination").innerHTML = ` ${currentRefinement + 1} of ${nbPages} page(s) for ${nbHits} hit(s) `; }; ``` **isFirstPage** `boolean` Whether the current page is the first page. ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { pages, isFirstPage, isLastPage } = renderOptions; document.querySelector("#pagination").innerHTML = `
      ${ !isFirstPage ? `
    • Previous
    • ` : "" } ${pages .map( (page) => `
    • ${page + 1}
    • `, ) .join("")} ${ !isLastPage ? `
    • Next
    • ` : "" }
    `; }; ``` **isLastPage** `boolean` Whether the current page is the last page. ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { pages, isFirstPage, isLastPage } = renderOptions; document.querySelector("#pagination").innerHTML = `
      ${ !isFirstPage ? `
    • Previous
    • ` : "" } ${pages .map( (page) => `
    • ${page + 1}
    • `, ) .join("")} ${ !isLastPage ? `
    • Next
    • ` : "" }
    `; }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { canRefine } = renderOptions; if (!canRefine) { document.querySelector("#pagination").innerHTML = ""; return; } }; ``` **refine** `function` Sets the current page and triggers a search. ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { pages, currentRefinement, refine } = renderOptions; const container = document.querySelector("#pagination"); container.innerHTML = ` `; [...container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.currentTarget.dataset.value); }); }); }; ``` **createURL** `function` Generates a URL for the next state. The number is the page to generate the URL for. ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { pages, createURL } = renderOptions; document.querySelector("#pagination").innerHTML = ` `; }; ``` **widgetParams** `function` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderPagination = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customPagination({ container: document.querySelector("#pagination"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customPagination = connectPagination(renderPagination); search.addWidgets([customPagination({ totalPages: number, padding: number })]); ``` #### Instance options **totalPages** `number` The total number of pages to browse. ```js JavaScript icon=code theme={"system"} customPagination({ totalPages: 4, }); ``` **padding** `number` (default: 3) The padding of pages to show around the current page ```js JavaScript icon=code theme={"system"} customPagination({ padding: 2, }); ``` ### Full example ```html HTML theme={"system"} ``` ```js JavaScript theme={"system"} // Create the render function const renderPagination = (renderOptions, isFirstRender) => { const { pages, currentRefinement, nbPages, isFirstPage, isLastPage, refine, createURL, } = renderOptions; const container = document.querySelector("#pagination"); container.innerHTML = ` `; [...container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.currentTarget.dataset.value); }); }); }; // Create the custom widget const customPagination = connectPagination(renderPagination); // Instantiate the custom widget search.addWidgets([ customPagination({ container: document.querySelector("#pagination"), }), ]); ``` # panel Source: https://www.algolia.com/doc/api-reference/widgets/panel/js Wraps other widgets in a panel for a consistent layout. ```ts Signature theme={"system"} panel({ // Optional parameters hidden?: function, collapsed?: function, templates?: object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { panel } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { panel } = instantsearch.widgets; // or directly use instantsearch.widgets.panel() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=panel) > > Preview this widget and its behavior. ## About this widget The `panel` widget wraps other widgets in a consistent panel design. It also reacts by adding a CSS class when the widget can no longer refine. For example, when a [`refinementList`](/doc/api-reference/widgets/refinement-list/js) becomes empty because of the current search results. ## Examples ```js JavaScript icon=code theme={"system"} const refinementListWithPanel = panel({ templates: { header: "Brand", }, })(refinementList); refinementListWithPanel({ container: "#refinement-list", attribute: "brand", }); ``` ## Options **hidden** `function` A function that runs on each render and decides, based on the render options, whether to hide the panel. If the function returns `true`, the panel adds the CSS class `noRefinementRoot` and hides the wrapper. This function receives all rendering parameters provided by the inner widget. ```js JavaScript icon=code theme={"system"} panel({ hidden(options) { return options.results.nbHits === 0; }, }); ``` **collapsed** `function` A function that runs on each render and decides, based on the render options, whether to collapse the panel. Providing this option adds the CSS class `collapsibleRoot`. If the function returns `true`, the panel adds the CSS class `collapsedRoot`. This function receives all rendering parameters provided by the inner widget. ```js JavaScript icon=code theme={"system"} panel({ collapsed: ({ state }) => { return state.query.length === 0; }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} panel({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `noRefinementRoot`. The root element when there are no refinements. * `collapsibleRoot`. The root element when you set `collapsed`, which makes the panel collapsible. * `collapsedRoot`. The root element of a collapsed panel. * `collapseButton`. The panel collapse button element. * `collapseIcon`. The panel collapse icon element. * `header`. The panel header element. * `body`. The panel content element. * `footer`. The panel footer element. ```js JavaScript icon=code theme={"system"} panel({ // ... cssClasses: { root: "MyCustomPanel", header: ["MyCustomPanelHeader", "MyCustomPanelHeader--subclass"], }, }); ``` ## Templates You can customize parts of the widget's UI using the Templates API. Every template provides an `html` function you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). Using `html` lets you provide templates as an HTML string. It works directly in the browser without a build step. See [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui) for more information. > [!NOTE] > > The `html` function is available starting from v4.46.0. **header** `string | function` The template used for displaying the header. It exposes: * `results: object`. The complete response from the Algolia API. It contains the `hits` but also metadata about the page, number of hits, etc. * `state: object`. The complete state of the search. * `searchMetadata: object`. Some metadata about the search. It only contains the attribute `isSearchStalled`. * `helper: object`. The instance of the helper. * `createURL: function`. The function to generate a URL from the search state. * `...`. All rendering parameters provided by the inner widget. ```js function theme={"system"} panel({ // ... templates: { header(options, { html }) { if (options.results) { return html`Brand (${options.results.nbHits} results)`; } }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. panel({ // ... templates: { header: "Brand ({{ results.nbHits }} results)", }, }); ``` **footer** `string | function` The template used for displaying the footer. It exposes: * `results: object`. The complete response from the Algolia API. It contains the `hits` but also metadata about the page, number of hits, etc. * `state: object`. The complete state of the search. * `searchMetadata: object`. Some metadata about the search. It only contains the attribute `isSearchStalled`. * `helper: object`. The instance of the helper. * `createURL: function`. The function to generate a URL from the search state. * `...`. All rendering parameters provided by the inner widget. ```js function theme={"system"} panel({ // ... templates: { footer(options, { html }) { if (options.results) { return html`Brand (${options.results.nbHits} results)`; } }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. panel({ // ... templates: { footer: "Brand ({{ results.nbHits }} results)", }, }); ``` **collapseButtonText** `string | function` The template used for displaying the collapse button. It exposes: * `collapsed: boolean`. `true` if the panel shows only its header, `false` if it shows its body. ```js function theme={"system"} panel({ // ... templates: { collapseButtonText(options, { html }) { return html`${options.collapsed ? "+" : "-"}`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. panel({ // ... templates: { collapseButtonText: "{{#collapsed}}+{{/collapsed}}{{^collapsed}}-{{/collapsed}}", }, }); ``` **collapseButtonLabel** `string | function` The text template used for the collapse button's accessible name, set as its `aria-label` attribute. It must return plain text, not HTML. It exposes: * `collapsed: boolean`. `true` if the panel shows only its header, `false` if it shows its body. If you don't set `collapseButtonText`, the label defaults to "Expand panel" for a collapsed panel and "Collapse panel" when it's expanded. If you set `collapseButtonText` without `collapseButtonLabel`, the button doesn't get an `aria-label`, and its accessible name comes from your custom button content. ```js function theme={"system"} panel({ // ... templates: { collapseButtonLabel({ collapsed }) { return collapsed ? "Show brands" : "Hide brands"; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. panel({ // ... templates: { collapseButtonLabel: "{{#collapsed}}Show brands{{/collapsed}}{{^collapsed}}Hide brands{{/collapsed}}", }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    Header
    Panel content
    ``` # poweredBy Source: https://www.algolia.com/doc/api-reference/widgets/powered-by/js Shows the Algolia logo with a link to the Algolia homepage. ```ts Signature theme={"system"} poweredBy({ container: string | HTMLElement, // Optional parameters theme?: string, cssClasses?: object, }); ``` ```js Package manager theme={"system"} import { poweredBy } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { poweredBy } = instantsearch.widgets; // or directly use instantsearch.widgets.poweredBy() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=poweredBy) > > Preview this widget and its behavior. ## About this widget The `poweredBy` widget is used to display an Algolia logo and to redirect to Algolia's homepage. [Algolia requires](https://www.algolia.com/policies/free-services-terms) that you use this widget if you're on a community plan (open source, not-for-profit, or [DocSearch](https://docsearch.algolia.com)). ## Examples ```js JavaScript icon=code theme={"system"} poweredBy({ container: "#powered-by", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} poweredBy({ container: "#powered-by", }); ``` ```js HTMLElement theme={"system"} poweredBy({ container: document.querySelector("#powered-by"), }); ``` **theme** `'light'|'dark'` (default: light) The version of the logo to use, legible on light or dark backgrounds. ```js JavaScript icon=code theme={"system"} poweredBy({ // ... theme: "dark", }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `link`. The link element. * `logo`. The SVG element. ```js JavaScript icon=code theme={"system"} poweredBy({ // ... cssClasses: { root: "MyCustomPoweredBy", link: ["MyCustomPoweredByLink", "MyCustomPoweredByLink--subclass"], }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` ## Customize the UI with `connectPoweredBy` If you want to create your own UI of the `poweredBy` widget, you can use connectors. To use `connectPoweredBy`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectPoweredBy } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectPoweredBy } = instantsearch.connectors; // or directly use instantsearch.connectors.connectPoweredBy() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderPoweredBy = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customPoweredBy = connectPoweredBy(renderPoweredBy); // 3. Instantiate search.addWidgets([ customPoweredBy({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderPoweredBy = (renderOptions, isFirstRender) => { const { url, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **url** `string` The URL to redirect to. ```js JavaScript icon=code theme={"system"} const renderPoweredBy = (renderOptions, isFirstRender) => { const { url } = renderOptions; document.querySelector("#powered-by").innerHTML = ` Powered by Algolia `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderPoweredBy = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customPoweredBy({ container: document.querySelector("#powered-by"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```jsx JavaScript icon=code theme={"system"} const customPoweredBy = connectPoweredBy(renderPoweredBy); search.addWidgets([ customPoweredBy({ url?: string, }), ]); ``` #### Instance options **url** `string` (default: [https://algolia.com](https://algolia.com)) The URL to redirect to. ```js JavaScript icon=code theme={"system"} customPoweredBy({ url: "https://algolia.com/about", }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderPoweredBy = (renderOptions, isFirstRender) => { const { url, widgetParams } = renderOptions; widgetParams.container.innerHTML = ` Powered by Algolia `; }; // Create the custom widget const customPoweredBy = connectPoweredBy(renderPoweredBy); // Instantiate the custom widget search.addWidgets([ customPoweredBy({ container: document.querySelector("#powered-by"), }), ]); ``` # promptSuggestions Source: https://www.algolia.com/doc/api-reference/widgets/prompt-suggestions/js Displays AI-generated prompt suggestions for the current page, built with Algolia Agent Studio. For more information, see [Agent Studio](/doc/guides/algolia-ai/agent-studio). ```ts Signature theme={"system"} promptSuggestions({ container: string | HTMLElement, // Required parameter, either one of agentId?: string, transport?: object, // Optional parameters configurationId?: string, context?: object | function, transformHits?: function, transformItems?: function, onSuggestionClick?: function, templates?: object, cssClasses?: object, translations?: object, }); ``` ## Import ```js Package manager theme={"system"} import { promptSuggestions } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { promptSuggestions } = instantsearch.widgets; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=promptSuggestions) > > Preview this widget and its behavior. ## About this widget Use the `promptSuggestions` widget to display AI-generated prompt suggestions as clickable pills. An Agent Studio agent generates the suggestions from the current search context: the query, the active filters, and a sample of the results. When users click a suggestion, the widget opens the [`chat`](/doc/api-reference/widgets/chat/js) widget on the same index and submits the prompt. That makes it an [entry point](/doc/api-reference/widgets/chat/js#param-disable-trigger-validation) for the chat, so a page with both doesn't also need a [`chatTrigger`](/doc/api-reference/widgets/chat-trigger/js) widget. With an explicit [`context`](#param-context), the widget fetches suggestions as soon as it mounts, so it also works on pages that never trigger a search. Without one, it derives the context from the results, so the first fetch waits for the first search results. In both cases, it fetches again when the query, filters, or results change. If a change produces the same context as the last request, the widget sends nothing and keeps the suggestions already on screen. Suggestions stream in progressively, and a skeleton placeholder shows while the first suggestions load. These suggestions differ from the [follow-up prompt suggestions](/doc/guides/algolia-ai/agent-studio/how-to/agent-configuration#follow-up-prompt-suggestions) that appear after each agent response inside the chat. See also: [Agent Studio](/doc/guides/algolia-ai/agent-studio) > [!NOTE] > > The default click behavior needs a [`chat`](/doc/api-reference/widgets/chat/js) widget on the same index. > Without one, the widget throws instead of rendering pills that lead nowhere. > To use it without a chat, pass [`onSuggestionClick`](#param-on-suggestion-click) and handle the click yourself. ## Examples ```js JavaScript icon=code theme={"system"} promptSuggestions({ container: '#prompt-suggestions', agentId: '8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9', }); chat({ container: '#chat', agentId: '8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9', }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} promptSuggestions({ container: '#prompt-suggestions', }); ``` ```js HTMLElement theme={"system"} promptSuggestions({ container: document.querySelector('#prompt-suggestions'), }); ``` **agentId** `string` The unique identifier of the agent to connect to. You can find the `agentId` in the [Agent Studio dashboard](https://dashboard.algolia.com/generativeAi/agent-studio/agents). Required unless you provide a custom [`transport`](#param-transport). ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... agentId: '8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9', }); ``` **transport** `object` A custom transport object to handle the communication between the widget and your own backend. When set, `agentId` and the search client credentials are ignored. The object accepts the following properties: * `api` (`string`). The endpoint URL to send suggestion requests to. * `headers` (`Record`). Headers to send with each request. * `prepareSendMessagesRequest` (`function`). Transforms the request body before it's sent. Receives the body object and returns `{ body }`. The widget sends a `POST` request with a JSON body of `{ task, kind, input }`, where `task` is the [`configurationId`](#param-configuration-id) if you set one, `kind` is `prompt_suggestions`, and `input` is the page context. It also appends `stream=true` to the endpoint URL. Answer with the suggestions nested under `output`: ```json Response icon=braces theme={"system"} { "output": { "suggestions": [ "Which of these has the longest battery life?", "Compare the two cheapest models" ] } } ``` Send that document as `application/json` to render the suggestions once the request completes, or stream it as `text/plain` to render each suggestion as it arrives. ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... transport: { api: 'https://api.example.com/api/v1/suggestions', headers: { 'X-Api-Version': '2025-01-01', }, }, }); ``` **configurationId** `string` The identifier of the prompt configuration that generates the suggestions. Create prompt configurations in the **Components** section of your agent in the Agent Studio dashboard. For more information, see [Generate prompt suggestions for your pages](/doc/guides/algolia-ai/agent-studio/how-to/prompt-suggestions). When you omit `configurationId`, the widget uses the enabled prompt suggestions configuration of the agent. Set it to target a specific configuration, for example when the agent has several of them. ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... configurationId: 'CONFIGURATION_ID', }); ``` **context** `object | function` Explicit page context to send to the agent instead of the automatically extracted search context. By default, the widget sends the current query, the active filters, and a sample of the results. Set `context` to replace this with your own data, for example the product record on a product detail page. `context` can be a static object or a function that returns an object for each fetch. When you set `context`, the widget fetches suggestions as soon as it mounts, without waiting for search results, and `transformHits` is ignored. Use it on pages that don't trigger a search, such as a product detail page or a page that gates the search on a non-empty query. A `context` function that returns `undefined` falls back to the automatically extracted search context, which waits for results. A `context` that resolves to an empty object sends nothing. A static object doesn't depend on the search state, so the widget fetches once and then keeps those suggestions instead of fetching again. A failed request is the exception: the next search state change retries it. To fetch again, return changing values from a `context` function, or call [`refresh`](#connector-render-state) from the connector. > [!WARNING] > > The widget sends `context` to the agent in plain text. > Don't put secrets, access tokens, or personally identifiable information you don't intend to share with the model in this field. ```js Static object theme={"system"} promptSuggestions({ // ... context: { productName: 'Wireless headphones', category: 'Audio', price: 99, }, }); ``` ```js Function theme={"system"} promptSuggestions({ // ... context: () => ({ currentPage: window.location.pathname, }), }); ``` **transformHits** `function` A function that receives the current results and returns the subset (or reshaped objects) sent to the agent as context. By default, the widget sends the first five hits with internal metadata (attributes prefixed with `_`) removed. This option is ignored when you set [`context`](#param-context). ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... transformHits: (hits) => hits.slice(0, 3).map((hit) => ({ name: hit.name, brand: hit.brand, price: hit.price, })), }); ``` **transformItems** `function` A function that receives the generated suggestions and returns the list to display. The second argument contains the current `query` and `results`. ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... transformItems: (items, { query }) => items.slice(0, 2).map((item) => item.toUpperCase()), }); ``` **onSuggestionClick** `function` A function to override the default click behavior (sending the prompt to the chat widget). It receives the clicked prompt and a helpers object with `sendToChat`, so you can run custom logic (for example, analytics) and then fall through to the default behavior. Setting it is also the only supported way to run the widget without a [`chat`](/doc/api-reference/widgets/chat/js) widget on the same index. In that case, `sendToChat` has nothing to send to and returns `false`. ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... onSuggestionClick: (prompt, { sendToChat }) => { analytics.track('suggestion_clicked', { prompt }); sendToChat(prompt); }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... templates: { // ... }, }); ``` **cssClasses** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `header`. The header element. * `headerTitle`. The header title element. * `suggestion`. Each suggestion pill. * `skeleton`. The skeleton container shown while loading. * `skeletonItem`. Each skeleton placeholder pill. ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... cssClasses: { root: 'MyCustomPromptSuggestions', suggestion: [ 'MyCustomPromptSuggestionsPill', 'MyCustomPromptSuggestionsPill--subclass', ], }, }); ``` **translations** `object` A dictionary of translations to customize the UI text and support internationalization. * `headerTitle`. The title displayed in the header. ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... translations: { headerTitle: 'Ask about these results', }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **layout** `function` A template to replace the default pills layout with custom markup. The template owns the full rendering: the list, the loading state, the error state, and the click handlers. It receives: * `suggestions`. The generated prompt strings. * `isLoading`. Whether the widget is fetching suggestions. * `error`. The `Error` thrown by the latest request, or `undefined`. When a request fails, `suggestions` is empty. The default layout renders nothing in that case, so use this template to show an error state. * `onSuggestionClick`. The click handler for a suggestion. * `isChatBusy`. Whether the chat widget is streaming a response. ```js JavaScript icon=code expandable theme={"system"} promptSuggestions({ // ... templates: { layout( { suggestions, isLoading, error, onSuggestionClick, isChatBusy }, { html }, ) { if (error) { return html`Suggestions are unavailable.`; } if (isLoading) { return html`Generating suggestions...`; } return html`
      ${suggestions.map( (suggestion) => html`
    • `, )}
    `; }, }, }); ``` **header** `function | false` A template to replace the default header. Set it to `false` to hide the header. It receives the `classNames` and `translations` objects. ```js JavaScript icon=code theme={"system"} promptSuggestions({ // ... templates: { header({ translations }, { html }) { return html`

    ${translations.headerTitle}

    `; }, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    Suggestions
    ``` While the first suggestions load, the widget renders skeleton placeholders instead of the pills: ```html HTML icon=code-xml theme={"system"}
    ``` ## Connector render state Use `connectPromptSuggestions` to build a fully custom UI. Its render state exposes: * `suggestions` (`string[]`). The generated prompt strings. * `isLoading` (`boolean`). Whether the widget is fetching suggestions. * `error` (`Error | undefined`). The error thrown by the latest suggestions request. When a request fails, `suggestions` is empty. The error clears when the query, filters, or results change, and when you call `refresh`. * `onSuggestionClick` (`(prompt: string) => void`). The default click handler, which sends the prompt to the chat. It's a no-op when `sendToChat` is `undefined`. * `sendToChat` (`((prompt: string) => boolean) | undefined`). Sends the prompt to the `chat` widget on the same index, with the page context attached as [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context). Returns `true` if the chat received it. It's `undefined` once the connector establishes that no `chat` widget is mounted on the same index. Check for it in a custom UI and render your own click behavior, or hide the suggestions. * `refresh` (`() => void`). Fetches new suggestions for the current search state, even when the context is unchanged. It works from the first render, including on a page that never produces results. It does nothing while a request is in flight, or when it has nothing to send, which is the case before the first results arrive unless you set an explicit [`context`](#param-context). * `isChatBusy` (`boolean`). Whether the chat widget is streaming a response. Use it to set the `disabled` attribute on your suggestion buttons. ```js JavaScript icon=code expandable theme={"system"} import { connectPromptSuggestions } from 'instantsearch.js/es/connectors'; const renderPromptSuggestions = (renderOptions, isFirstRender) => { const { suggestions, isLoading, onSuggestionClick, isChatBusy } = renderOptions; const container = document.querySelector('#prompt-suggestions'); if (isLoading) { container.textContent = 'Generating suggestions...'; return; } container.innerHTML = ''; suggestions.forEach((suggestion) => { const button = document.createElement('button'); button.textContent = suggestion; button.disabled = isChatBusy; button.addEventListener('click', () => onSuggestionClick(suggestion)); container.appendChild(button); }); }; const customPromptSuggestions = connectPromptSuggestions( renderPromptSuggestions, ); search.addWidgets([ customPromptSuggestions({ agentId: '8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9', }), ]); ``` # PromptSuggestions Source: https://www.algolia.com/doc/api-reference/widgets/prompt-suggestions/react Displays AI-generated prompt suggestions for the current page, built with Algolia Agent Studio. For more information, see [Agent Studio](/doc/guides/algolia-ai/agent-studio). ```tsx Signature expandable theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { PromptSuggestions } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=promptSuggestions) > > Preview this widget and its behavior. ## About this widget Use `PromptSuggestions` to display AI-generated prompt suggestions as clickable pills. An Agent Studio agent generates the suggestions from the current search context: the query, the active filters, and a sample of the results. When users click a suggestion, the widget opens the [`Chat`](/doc/api-reference/widgets/chat/react) widget on the same index and submits the prompt. That makes it an [entry point](/doc/api-reference/widgets/chat/react#param-disable-trigger-validation) for the chat, so a page with both doesn't also need a [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) widget. With an explicit [`context`](#param-context), the widget fetches suggestions as soon as it mounts, so it also works on pages that never trigger a search. Without one, it derives the context from the results, so the first fetch waits for the first search results. In both cases, it fetches again when the query, filters, or results change. If a change produces the same context as the last request, the widget sends nothing and keeps the suggestions already on screen. Suggestions stream in progressively, and a skeleton placeholder shows while the first suggestions load. These suggestions differ from the [follow-up prompt suggestions](/doc/guides/algolia-ai/agent-studio/how-to/agent-configuration#follow-up-prompt-suggestions) that appear after each agent response inside the chat. See also: [Agent Studio](/doc/guides/algolia-ai/agent-studio) > [!NOTE] > > The default click behavior needs a [`Chat`](/doc/api-reference/widgets/chat/react) widget on the same index. > Without one, the widget throws instead of rendering pills that lead nowhere. > To use it without a chat, pass [`onSuggestionClick`](#param-on-suggestion-click) and handle the click yourself. ## Examples ```jsx JavaScript icon=code expandable theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Chat, PromptSuggestions } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **agentId** `string` The unique identifier of the agent to connect to. You can find the `agentId` in the [Agent Studio dashboard](https://dashboard.algolia.com/generativeAi/agent-studio/agents). Required unless you provide a custom [`transport`](#param-transport). ```jsx JavaScript icon=code theme={"system"} ``` **transport** `object` A custom transport object to handle the communication between the widget and your own backend. When set, `agentId` and the search client credentials are ignored. The object accepts the following properties: * `api` (`string`). The endpoint URL to send suggestion requests to. * `headers` (`Record`). Headers to send with each request. * `prepareSendMessagesRequest` (`function`). Transforms the request body before it's sent. Receives the body object and returns `{ body }`. The widget sends a `POST` request with a JSON body of `{ task, kind, input }`, where `task` is the [`configurationId`](#param-configuration-id) if you set one, `kind` is `prompt_suggestions`, and `input` is the page context. It also appends `stream=true` to the endpoint URL. Answer with the suggestions nested under `output`: ```json Response icon=braces theme={"system"} { "output": { "suggestions": [ "Which of these has the longest battery life?", "Compare the two cheapest models" ] } } ``` Send that document as `application/json` to render the suggestions once the request completes, or stream it as `text/plain` to render each suggestion as it arrives. ```jsx JavaScript icon=code theme={"system"} ``` **configurationId** `string` The identifier of the prompt configuration that generates the suggestions. Create prompt configurations in the **Components** section of your agent in the Agent Studio dashboard. For more information, see [Generate prompt suggestions for your pages](/doc/guides/algolia-ai/agent-studio/how-to/prompt-suggestions). When you omit `configurationId`, the widget uses the enabled prompt suggestions configuration of the agent. Set it to target a specific configuration, for example when the agent has several of them. ```jsx JavaScript icon=code theme={"system"} ``` **context** `object | (() =\> object | undefined)` Explicit page context to send to the agent instead of the automatically extracted search context. By default, the widget sends the current query, the active filters, and a sample of the results. Set `context` to replace this with your own data, for example the product record on a product detail page. `context` can be a static object or a function that returns an object for each fetch. When you set `context`, the widget fetches suggestions as soon as it mounts, without waiting for search results, and `transformHits` is ignored. Use it on pages that don't trigger a search, such as a product detail page or a page that gates the search on a non-empty query. A `context` function that returns `undefined` falls back to the automatically extracted search context, which waits for results. A `context` that resolves to an empty object sends nothing. A static object doesn't depend on the search state, so the widget fetches once and then keeps those suggestions instead of fetching again. A failed request is the exception: the next search state change retries it. To fetch again, return changing values from a `context` function, or call [`refresh`](#hook) from the Hook. > [!WARNING] > > The widget sends `context` to the agent in plain text. > Don't put secrets, access tokens, or personally identifiable information you don't intend to share with the model in this field. ```jsx Static object theme={"system"} ``` ```jsx Function theme={"system"} ({ currentPage: window.location.pathname, })} /> ``` **transformHits** `(hits: Hit[]) =\> unknown[]` A function that receives the current results and returns the subset (or reshaped objects) sent to the agent as context. By default, the widget sends the first five hits with internal metadata (attributes prefixed with `_`) removed. This prop is ignored when you set [`context`](#param-context). ```jsx JavaScript icon=code theme={"system"} hits.slice(0, 3).map((hit) => ({ name: hit.name, brand: hit.brand, price: hit.price, })) } /> ``` **transformItems** `(items: string[], metadata: \{ query, results \}) =\> string[]` A function that receives the generated suggestions and returns the list to display. The second argument contains the current `query` and `results`. ```jsx JavaScript icon=code theme={"system"} items.slice(0, 2)} /> ``` **onSuggestionClick** `(prompt: string, helpers: \{ sendToChat \}) =\> void` A function to override the default click behavior (sending the prompt to the chat widget). It receives the clicked prompt and a helpers object with `sendToChat`, so you can run custom logic (for example, analytics) and then fall through to the default behavior. Setting it is also the only supported way to run the widget without a [`Chat`](/doc/api-reference/widgets/chat/react) widget on the same index. In that case, `sendToChat` has nothing to send to and returns `false`. ```jsx JavaScript icon=code theme={"system"} { analytics.track("suggestion_clicked", { prompt }); sendToChat(prompt); }} /> ``` **layoutComponent** `(props) =\> JSX.Element` A component to replace the default pills layout with custom markup. The component owns the full rendering: the list, the loading state, the error state, and the click handlers. It receives: * `suggestions`. The generated prompt strings. * `isLoading`. Whether the widget is fetching suggestions. * `error`. The `Error` thrown by the latest request, or `undefined`. When a request fails, `suggestions` is empty. The default layout renders nothing in that case, so use this component to show an error state. * `onSuggestionClick`. The click handler for a suggestion. * `isChatBusy`. Whether the chat widget is streaming a response. ```jsx JavaScript icon=code expandable theme={"system"} { if (error) { return Suggestions are unavailable.; } if (isLoading) { return Generating suggestions...; } return (
      {suggestions.map((suggestion) => (
    • ))}
    ); }} /> ``` **headerComponent** `(props) =\> JSX.Element | false` A component to replace the default header. Set it to `false` to hide the header. It receives the `classNames` and `translations` objects. ```jsx JavaScript icon=code theme={"system"}

    {translations.headerTitle}

    } /> ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `header`. The header element. * `headerTitle`. The header title element. * `suggestion`. Each suggestion pill. * `skeleton`. The skeleton container shown while loading. * `skeletonItem`. Each skeleton placeholder pill. ```jsx JavaScript icon=code theme={"system"} ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `headerTitle`. The title displayed in the header. ```jsx JavaScript icon=code theme={"system"} ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ``` ## Hook Use the `usePromptSuggestions` Hook to build a fully custom UI. It accepts the same connector props (`agentId` or `transport`, `configurationId`, `context`, `transformHits`, `transformItems`) and returns: * `suggestions` (`string[]`). The generated prompt strings. * `isLoading` (`boolean`). Whether the widget is fetching suggestions. * `error` (`Error | undefined`). The error thrown by the latest suggestions request. When a request fails, `suggestions` is empty. The error clears when the query, filters, or results change, and when you call `refresh`. * `onSuggestionClick` (`(prompt: string) => void`). The default click handler, which sends the prompt to the chat. It's a no-op when `sendToChat` is `undefined`. * `sendToChat` (`((prompt: string) => boolean) | undefined`). Sends the prompt to the `Chat` widget on the same index, with the page context attached as [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context). Returns `true` if the chat received it. It's `undefined` once the Hook establishes that no `Chat` widget is mounted on the same index. Check for it in a custom UI and render your own click behavior, or hide the suggestions. * `refresh` (`() => void`). Fetches new suggestions for the current search state, even when the context is unchanged. It works from the first render, including on a page that never produces results. It does nothing while a request is in flight, or when it has nothing to send, which is the case before the first results arrive unless you set an explicit [`context`](#param-context). * `isChatBusy` (`boolean`). Whether the chat widget is streaming a response. Use it to set the `disabled` attribute on your suggestion buttons. ```jsx JavaScript icon=code expandable theme={"system"} import { usePromptSuggestions } from "react-instantsearch"; function CustomPromptSuggestions() { const { suggestions, isLoading, onSuggestionClick, refresh, isChatBusy } = usePromptSuggestions({ agentId: "8f7c4a2d-3b1e-4d5f-9a6c-e2b1f5d0c3e9", }); if (isLoading) { return Generating suggestions...; } return (
    {suggestions.map((suggestion) => ( ))}
    ); } ``` # queryRuleContext Source: https://www.algolia.com/doc/api-reference/widgets/query-rule-context/js Applies rule contexts based on applied filters for triggering context-dependent rules. ```ts Signature theme={"system"} queryRuleContext({ trackedFilters: object, // Optional parameters transformRuleContexts?: function, }); ``` ## Import ```js Package manager theme={"system"} import { queryRuleContext } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { queryRuleContext } = instantsearch.widgets; // or directly use instantsearch.widgets.queryRuleContext() ``` ## About this widget The `queryRuleContext` widget lets you apply [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts) based on filters to trigger context-dependent [rules](/doc/guides/managing-results/rules/rules-overview). Rules offer a custom experience based on contexts. You might want to customize the users' experience based on the filters of the search (for example, they're visiting the "Mobile" category, they selected the "Thriller" genre, etc.) This widget lets you map these filters to their associated rule contexts, so you can trigger context-based rules on refinement. ## Examples ```js JavaScript icon=code theme={"system"} queryRuleContext({ trackedFilters: { genre: () => ["Comedy", "Thriller"], rating: (values) => values, }, }); ``` ## Options **trackedFilters** `object` *required* The filters to track to trigger rule contexts. Each filter is a function which name is the attribute you want to track. They receive their current refinements as arguments. You can either compute the filters you want to track based on those, or return static values. When the tracked values are refined, it toggles the associated rule contexts. The added rule contexts follow the format `ais-{attribute}-{value}` (for example `ais-genre-Thriller`). If the context of your rule follows another format, you can specify it using the [`transformRuleContexts`](/doc/api-reference/widgets/query-rule-context/js#param-transform-rule-contexts) option. Values are escaped so that they only consist of alphanumeric characters, hyphens, and underscores. ```js JavaScript icon=code theme={"system"} queryRuleContext({ trackedFilters: { genre: () => ["Comedy", "Thriller"], // this tracks two static genre values, rating: (values) => values, // this tracks all the rating values }, }); ``` **transformRuleContexts** `function` A function to apply to the rule contexts before sending them to Algolia. This is useful to rename rule contexts that follow a different naming convention. ```js JavaScript icon=code theme={"system"} queryRuleContext({ // ... transformRuleContexts(ruleContexts) { return ruleContexts.map((ruleContext) => ruleContext.replace("ais-", "custom-"), ); }, }); ``` ## Customize the UI with `connectQueryRules` If you want to create your own UI of the `queryRuleContext` widget, you can use connectors. > [!NOTE] > > This connector is also used to build the [`queryRuleCustomData`](/doc/api-reference/widgets/query-rule-custom-data/js) widget. To use `connectQueryRules`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectQueryRules } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectQueryRules } = instantsearch.connectors; // or directly use instantsearch.connectors.connectQueryRules() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderQueryRuleContext = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customQueryRuleContext = connectQueryRules(renderQueryRuleContext); // 3. Instantiate search.addWidgets([ customQueryRuleContext({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderQueryRuleContext = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Render options **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderQueryRuleContext = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customQueryRuleContext({ container: document.querySelector("#queryRuleContext"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customQueryRuleContext = connectQueryRules(renderQueryRuleContext); search.addWidgets([ customQueryRuleContext({ // Optional parameters trackedFilters, transformRuleContexts, }), ]); ``` #### Instance options **trackedFilters** `object` The filters to track to trigger Rule contexts. Each filter is a function which name is the attribute you want to track. They receive their current refinements as arguments. You can either compute the filters you want to track based on those, or return static values. When the tracked values are refined, it toggles the associated rule contexts. The added rule contexts follow the format `ais-{attribute}-{value}` (for example `ais-genre-Thriller`). If the context of your rule follows another format, you can specify it using the [`transformRuleContexts`](/doc/api-reference/widgets/query-rule-context/js#param-transform-rule-contexts) option. Values are escaped so that they only consist of alphanumeric characters, hyphens, and underscores. ```js JavaScript icon=code theme={"system"} customQueryRuleContext({ trackedFilters: { genre: () => ["Comedy", "Thriller"], // this tracks two static genre values, rating: (values) => values, // this tracks all the rating values }, }); ``` **transformRuleContexts** `function` A function to apply to the rule contexts before sending them to Algolia. This is useful to rename rule contexts that follow a different naming convention. ```js JavaScript icon=code theme={"system"} customQueryRuleContext({ // ... transformRuleContexts(ruleContexts) { return ruleContexts.map((ruleContext) => ruleContext.replace("ais-", "custom-"), ); }, }); ``` ### Full example ```js JavaScript icon=code theme={"system"} // Create the render function const renderQueryRuleContext = (renderOptions, isFirstRender) => { // We don't provide anything to render for this widget. }; // Create the custom widget const customQueryRuleContext = connectQueryRules(renderQueryRuleContext); // Instantiate the custom widget search.addWidgets([ customQueryRuleContext({ trackedFilters: { genre: () => ["Comedy", "Thriller"], }, }), ]); ``` # queryRuleCustomData Source: https://www.algolia.com/doc/api-reference/widgets/query-rule-custom-data/js Shows custom data from applied index rules. ```ts Signature theme={"system"} queryRuleCustomData({ container: string | HTMLElement, // Optional parameters templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { queryRuleCustomData } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { queryRuleCustomData } = instantsearch.widgets; // or directly use instantsearch.widgets.widgets() ``` ## About this widget The `queryRuleCustomData` widget displays custom data from [index rules](/doc/guides/managing-results/rules/detecting-intent). You can use this widget to display banners or recommendations returned by rules when they match search parameters. ## Examples ```js JavaScript icon=code theme={"system"} queryRuleCustomData({ container: "#queryRuleCustomData", templates: { default({ items }, { html }) { return html` ${items .map((item) => { const { title, banner, link } = item; if (!banner) { return; } return `

    ${title}

    ${title}
    `; }) .join("")} `; }, }, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} queryRuleCustomData({ container: "#queryRuleCustomData", }); ``` ```js HTMLElement theme={"system"} queryRuleCustomData({ container: document.querySelector("#queryRuleCustomData"), }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} queryRuleCustomData({ // ... templates: { // ... }, }); ``` **cssClasses** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. ```js JavaScript icon=code theme={"system"} queryRuleCustomData({ // ... cssClasses: { root: "MyCustomQueryRuleCustomData", }, }); ``` **transformItems** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} queryRuleCustomData({ // ... transformItems(items) { return items.filter((item) => typeof item.banner !== "undefined"); }, }); // or, combined with results queryRuleCustomData({ // ... transformItems(items, { results }) { return items.map((item) => ({ ...item, visible: results.page === 0, })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **default** `function` The template to use for the custom data. It exposes the items returned by the rules. The following example assumes a rule returned this custom data. ```json JSON icon=braces theme={"system"} { "title": "This is an image", "banner": "image.png", "link": "https://website.com/" } ``` ```js function theme={"system"} queryRuleCustomData({ // ... templates: { default({ items }, { html }) { return html` ${items .map((item) => { const { title, banner, link } = item; if (!banner) { return null; } return `

    ${title}

    ${title}
    `; }) .join("")} `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. queryRuleCustomData({ // ... templates: { default: ` {{#items}} {{#banner}}

    {{title}}

    {{title}}
    {{/banner}} {{/items}} `, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectQueryRules` If you want to create your own UI of the `queryRuleCustomData` widget, you can use connectors. > [!NOTE] > > This connector is also used to build the [`queryRuleContext`](/doc/api-reference/widgets/query-rule-context/js) widget. To use `connectQueryRules`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Import with a package manager theme={"system"} import { connectQueryRules } from "instantsearch.js/es/connectors"; ``` ```js Import with a CDN theme={"system"} const { connectQueryRules } = instantsearch.connectors; // or directly use instantsearch.connectors.connectQueryRules() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderQueryRuleCustomData = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customQueryRuleCustomData = connectQueryRules(renderQueryRuleCustomData); // 3. Instantiate search.addWidgets([ customQueryRuleCustomData({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderQueryRuleCustomData = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Render options **items** `object[]` The items that matched the Rule. ```js JavaScript icon=code theme={"system"} const renderQueryRuleCustomData = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector("#queryRuleCustomData").innerHTML = `
      ${items.map((item) => `
    • ${item.title}
    • `).join("")}
    `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderQueryRuleCustomData = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customQueryRuleCustomData({ container: document.querySelector("#queryRuleCustomData"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customQueryRuleCustomData = connectQueryRules(renderQueryRuleCustomData); search.addWidgets([ customQueryRuleCustomData({ // Optional parameters transformItems, }), ]); ``` #### Instance options **transformItems** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customQueryRuleCustomData({ transformItems(items) { return items.filter((item) => typeof item.banner !== "undefined"); }, }); // or, combined with results customQueryRuleCustomData({ transformItems(items, { results }) { return items.map((item) => ({ ...item, visible: results.page === 0, })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderQueryRuleCustomData = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; if (isFirstRender) { return; } widgetParams.container.innerHTML = `
      ${items.map((item) => `
    • ${item.title}
    • `).join("")}
    `; }; // Create the custom widget const customQueryRuleCustomData = connectQueryRules(renderQueryRuleCustomData); // Instantiate the custom widget search.addWidgets([ customQueryRuleCustomData({ container: document.querySelector("#queryRuleCustomData"), }), ]); ``` # rangeInput Source: https://www.algolia.com/doc/api-reference/widgets/range-input/js Lets users enter a numeric range for refining search results. ```ts Signature theme={"system"} rangeInput({ container: string | HTMLElement, attribute: string, // Optional parameters min?: number, max?: number, precision?: number, templates?: object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { rangeInput } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { rangeInput } = instantsearch.widgets; // or directly use instantsearch.widgets.rangeInput() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=rangeInput) > > Preview this widget and its behavior. ## About this widget The `rangeInput` widget allows a user to select a numeric range using a minimum and maximum input. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. The values of the attribute must be numbers, not strings. ## Examples ```js JavaScript icon=code theme={"system"} rangeInput({ container: "#range-input", attribute: "price", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} rangeInput({ // ... container: "#range-input", }); ``` ```js HTMLElement theme={"system"} rangeInput({ // ... container: document.querySelector("#range-input"), }); ``` **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} rangeInput({ // ... attribute: "price", }); ``` **min** `number` The minimum value for the input. When not provided, the minimum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} rangeInput({ // ... min: 10, }); ``` **max** `number` The maximum value for the input. When not provided, the maximum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} rangeInput({ // ... max: 500, }); ``` **precision** `number` (default: 0) The number of digits after the decimal point to use. ```js JavaScript icon=code theme={"system"} rangeInput({ // ... precision: 2, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} rangeInput({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `form`. The form element. * `label`. The label elements. * `input`. The input elements. * `inputMin`. The minimum input element. * `inputMax`. The maximum input element. * `separator`. The separator element. * `submit`. The submit button. ```js JavaScript icon=code theme={"system"} rangeInput({ // ... cssClasses: { root: "MyCustomRangeInput", form: ["MyCustomRangeInputForm", "MyCustomRangeInputForm--subclass"], }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **separatorText** `string | function` The template for the separator, between the minimum and maximum inputs. ```js function theme={"system"} rangeInput({ // ... templates: { separatorText(_, { html }) { return html`to`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. rangeInput({ // ... templates: { separatorText: "to", }, }); ``` **submitText** `string | function` The template for the submit button. ```js function theme={"system"} rangeInput({ // ... templates: { submitText(_, { html }) { return html`Go`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. rangeInput({ // ... templates: { submitText: "Go", }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    to
    ``` ## Customize the UI with `connectRange` If you want to create your own UI of the `rangeInput` widget, you can use connectors. > [!NOTE] > > This connector is also used to build the [`rangeSlider`](/doc/api-reference/widgets/range-slider/js) widget. To use `connectRange`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectRange } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectRange } = instantsearch.connectors; // or directly use instantsearch.connectors.connectRange() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderRangeInput = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customRangeInput = connectRange(renderRangeInput); // 3. Instantiate search.addWidgets([ customRangeInput({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderRangeInput = (renderOptions, isFirstRender) => { const { start, range, canRefine, refine, sendEvent, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **start** `number[]` The current value for the refinement, with `start[0]` as the minimum value and `start[1]` as the maximum value. ```js JavaScript icon=code theme={"system"} const renderRangeInput = (renderOptions, isFirstRender) => { const { start } = renderOptions; const [min, max] = start; document.querySelector("#range-input").innerHTML = `
    to
    `; }; ``` **range** `object` The current available value for the range. ```js JavaScript icon=code theme={"system"} const renderRangeInput = (renderOptions, isFirstRender) => { const { range } = renderOptions; document.querySelector("#range-input").innerHTML = `
    to
    `; }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderRangeInput = (renderOptions, isFirstRender) => { const { range, canRefine } = renderOptions; if (!canRefine) { document.querySelector("#range-input").innerHTML = ""; return; } }; ``` **refine** `function` Sets a range to filter the results on. Both values are optional, and default to the higher and lower bounds. You can use `undefined` to remove a previously set bound or to set an infinite bound. ```js JavaScript icon=code theme={"system"} const renderRangeInput = (renderOptions, isFirstRender) => { const { start, refine } = renderOptions; const [min, max] = start; const container = document.querySelector("#range-input"); if (isFirstRender) { const form = document.createElement("form"); form.addEventListener("submit", (event) => { event.preventDefault(); const [minInput, maxInput] = event.target.elements; const rawMinInputValue = parseFloat(minInput.value); const rawMaxInputValue = parseFloat(maxInput.value); refine([ Number.isFinite(rawMinInputValue) ? rawMinInputValue : undefined, Number.isFinite(rawMaxInputValue) ? rawMaxInputValue : undefined, ]); }); container.appendChild(form); } container.querySelector("form").innerHTML = ` to `; }; ``` **sendEvent** `(eventType, facetValue) =\> void` The function to send `click` events. The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware. * `eventType: 'click'` * `facetValue: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent("click", [10, 30]); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['price>=10', 'price<=30'], index: '', }, widgetType: 'ais.range', } */ ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderRangeInput = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customRangeInput({ container: document.querySelector("#range-input"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customRangeInput = connectRange(renderRangeInput); search.addWidgets([ customRangeInput({ attribute: string, // Optional parameters min: number, max: number, precision: number, }), ]); ``` #### Instance options **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} customRangeInput({ attribute: "price", }); ``` **min** `number` The minimum value for the input. When not provided, the minimum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} customRangeInput({ // ... min: 10, }); ``` **max** `number` The maximum value for the input. When not provided, the maximum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} customRangeInput({ // ... max: 500, }); ``` **precision** `number` (default: 0) The number of digits after the decimal point to use. ```js JavaScript icon=code theme={"system"} customRangeInput({ // ... precision: 2, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderRangeInput = (renderOptions, isFirstRender) => { const { start, range, refine, widgetParams } = renderOptions; const [min, max] = start; if (isFirstRender) { const form = document.createElement("form"); form.addEventListener("submit", (event) => { event.preventDefault(); const rawMinInputValue = parseFloat(event.target.elements.min.value); const rawMaxInputValue = parseFloat(event.target.elements.max.value); refine([ Number.isFinite(rawMinInputValue) ? rawMinInputValue : undefined, Number.isFinite(rawMaxInputValue) ? rawMaxInputValue : undefined, ]); }); widgetParams.container.appendChild(form); return; } widgetParams.container.querySelector("form").innerHTML = ` to `; }; // Create the custom widget const customRangeInput = connectRange(renderRangeInput); // Instantiate the custom widget search.addWidgets([ customRangeInput({ container: document.querySelector("#range-input"), attribute: "price", }), ]); ``` # rangeSlider Source: https://www.algolia.com/doc/api-reference/widgets/range-slider/js Lets users refine search results by a numeric range using a slider. ```ts Signature theme={"system"} rangeSlider({ container: string | HTMLElement, attribute: string, // Optional parameters min?: number, max?: number, precision?: number, step?: number, pips?: boolean, tooltips?: boolean | object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { rangeSlider } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { rangeSlider } = instantsearch.widgets; // or directly use instantsearch.widgets.rangeSlider() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=rangeSlider) > > Preview this widget and its behavior. ## About this widget The `rangeSlider` widget provides a user-friendly way to filter the results, based on a single numeric range. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. The values of the attribute must be numbers, not strings. ## Examples ```js JavaScript icon=code theme={"system"} rangeSlider({ container: "#range-slider", attribute: "price", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} rangeSlider({ // ... container: "#range-slider", }); ``` ```js HTMLElement theme={"system"} rangeSlider({ // ... container: document.querySelector("#range-slider"), }); ``` **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} rangeSlider({ // ... attribute: "price", }); ``` **min** `number` The minimum value for the input. When not provided, the minimum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} rangeSlider({ // ... min: 10, }); ``` **max** `number` The maximum value for the input. When not provided, the maximum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} rangeSlider({ // ... max: 500, }); ``` **precision** `number` (default: 0) The number of digits after the decimal point to use. Use a negative value to round values to powers of 10. For example, a precision of -2 would round a number to the nearest hundred, while a precision of -3 would round it to the nearest thousand. ```js JavaScript icon=code theme={"system"} // Round values to 2 digits after the decimal point rangeSlider({ // ... precision: 2, }); // Round values to the nearest hundred rangeSlider({ // ... precision: -2, }); ``` **step** `number` The number of steps between each handle move. ```js JavaScript icon=code theme={"system"} rangeSlider({ // ... step: 5, }); ``` **pips** `boolean` Whether to show slider pips. ```js JavaScript icon=code theme={"system"} rangeSlider({ // ... pips: false, }); ``` **tooltips** `boolean | object` Whether to show tooltips. The default tooltips show the raw value. You can also provide an object with a `format` function. ```js boolean theme={"system"} rangeSlider({ // ... tooltips: false, }); ``` ```js object theme={"system"} rangeSlider({ // ... tooltips: { format(value) { return `$${Math.round(value).toLocaleString()}`; }, }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `handle`. The handle elements. * `lowerHandle`. The handle for the minimum value. * `upperHandle`. The handle for the maximum value. * `tooltip`. The tooltip elements. ```js JavaScript icon=code theme={"system"} rangeSlider({ // ... cssClasses: { root: "MyCustomRangeSlider", handle: [ "MyCustomRangeSliderHandle", "MyCustomRangeSliderHandle--subclass", ], }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    $750
    $5,000
    1
    5,000
    ``` ## Customize the UI with `connectRange` If you want to create your own UI of the `rangeSlider` widget, you can use connectors. > [!NOTE] > > This connector is also used to build the [`rangeInput`](/doc/api-reference/widgets/range-input/js) widget. To use `connectRange`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectRange } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectRange } = instantsearch.connectors; // or directly use instantsearch.connectors.connectRange() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderRangeSlider = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customRangeSlider = connectRange(renderRangeSlider); // 3. Instantiate search.addWidgets([ customRangeSlider({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderRangeSlider = (renderOptions, isFirstRender) => { const { start, range, canRefine, refine, sendEvent, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **start** `number[]` The current value for the refinement, with `start[0]` as the minimum value and `start[1]` as the maximum value. ```js JavaScript icon=code theme={"system"} const renderRangeSlider = (renderOptions, isFirstRender) => { const { start } = renderOptions; document.querySelector("#range-slider").innerHTML = ` `; }; ``` **range** `object` The current available value for the range. ```js JavaScript icon=code theme={"system"} const renderRangeSlider = (renderOptions, isFirstRender) => { const { start, range } = renderOptions; document.querySelector("#range-slider").innerHTML = ` `; }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderRangeSlider = (renderOptions, isFirstRender) => { const { canRefine } = renderOptions; if (!canRefine) { document.querySelector("#range-slider").innerHTML = ""; return; } }; ``` **refine** `function` Sets a range to filter the results on. Both values are optional, and default to the higher and lower bounds. You can use `undefined` to remove a previously set bound or to set an infinite bound. ```js JavaScript icon=code theme={"system"} const renderRangeSlider = (renderOptions, isFirstRender) => { const { start, range, refine } = renderOptions; const container = document.querySelector("#range-slider"); if (isFirstRender) { const input = document.createElement("input"); input.type = "range"; input.addEventListener("change", (event) => { refine([parseFloat(event.currentTarget.value)]); }); container.appendChild(input); return; } const input = container.querySelector("input"); input.min = range.min; input.max = range.max; input.value = Number.isFinite(start[0]) ? start[0] : "0"; }; ``` **sendEvent** `(eventType, facetValue) =\> void` The function to send `click` events. The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware. * `eventType: 'click'` * `facetValue: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent("click", [10, 30]); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['price>=10', 'price<=30'], index: '', }, widgetType: 'ais.range', } */ ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderRangeSlider = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customRangeSlider({ container: document.querySelector("#range-slider"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customRangeSlider = connectRange(renderRangeSlider); search.addWidgets([ customRangeSlider({ attribute: string, // Optional parameters: min:number, max:number, precision:number, }), ]); ``` #### Instance options **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} customRangeSlider({ attribute: "price", }); ``` **min** `number` The minimum value for the input. When not provided, the minimum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} customRangeSlider({ // ... min: 10, }); ``` **max** `number` The maximum value for the input. When not provided, the maximum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} customRangeSlider({ // ... max: 500, }); ``` **precision** `number` (default: 0) The number of digits after the decimal point to use. Use a negative value to round values to powers of 10. For example, a precision of -2 would round a number to the nearest hundred, while a precision of -3 would round it to the nearest thousand. ```js JavaScript icon=code theme={"system"} // Round values to 2 digits after the decimal point customRangeSlider({ // ... precision: 2, }); // Round values to the nearest hundred customRangeSlider({ // ... precision: -2, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderRangeSlider = (renderOptions, isFirstRender) => { const { start, range, refine, widgetParams } = renderOptions; if (isFirstRender) { const input = document.createElement("input"); input.type = "range"; input.addEventListener("change", (event) => { refine([parseFloat(event.currentTarget.value)]); }); widgetParams.container.appendChild(input); return; } const input = widgetParams.container.querySelector("input"); input.min = range.min; input.max = range.max; input.value = Number.isFinite(start[0]) ? start[0] : "0"; }; // Create the custom widget const customRangeSlider = connectRange(renderRangeSlider); // Instantiate the custom widget search.addWidgets([ customRangeSlider({ container: document.querySelector("#range-slider"), attribute: "price", }), ]); ``` # ratingMenu Source: https://www.algolia.com/doc/api-reference/widgets/rating-menu/js Lets users refine search results by selecting star ratings. ```ts Signature theme={"system"} ratingMenu({ container: string | HTMLElement, attribute: string, // Optional parameters max?: number, templates?: object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { ratingMenu } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { ratingMenu } = instantsearch.widgets; // or directly use instantsearch.widgets.ratingMenu() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=ratingMenu) > > Preview this widget and its behavior. ## About this widget The `ratingMenu` widget lets users refine search results by clicking on stars. The stars are based on the selected attribute. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. **Attribute values must be integers, not strings or floats.** The widget only returns exact numerical matches. For example, if a user selects "4 \[stars] & Up", only return records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) with values such as 4 or 5 are returned— not those with values like 4.5 or 4.7. If your attribute is a float, index a new attribute with the rounded integer value to use in this widget. ## Examples ```js JavaScript icon=code theme={"system"} ratingMenu({ container: "#rating-menu", attribute: "rating", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} ratingMenu({ // ... container: "#rating-menu", }); ``` ```js HTMLElement theme={"system"} ratingMenu({ // ... container: document.querySelector("#rating-menu"), }); ``` **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} ratingMenu({ // ... attribute: "rating", }); ``` **max** `number` (default: 5) The maximum value for the rating. This value is exclusive, which means the number of stars will be the provided value, minus one. ```js JavaScript icon=code theme={"system"} ratingMenu({ // ... max: 4, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} ratingMenu({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `noRefinementRoot`. Container class without results. * `list`. The list of results. * `item`. The list items. * `selectedItem`. The selected item in the list. * `disabledItem`. The list item when it's unavailable. * `starIcon`. The default class of each star (when using the default template). * `fullStarIcon`. The class of each full star (when using the default template). * `emptyStarIcon`. The class of each empty star (when using the default template). * `label`. The label of each item. * `count`. The count of each item. ```js JavaScript icon=code theme={"system"} ratingMenu({ // ... cssClasses: { root: "MyCustomRatingMenu", list: ["MyCustomRatingMenuList", "MyCustomRatingMenuList--subclass"], }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **item** `string | function` The template used for displaying each item, with: * `count: number`. The number of results that match this refinement. * `isRefined: boolean`. Whether the refinement is selected. * `name: string`. The name corresponding to the number of stars. * `value: string`. The number of stars with a string form. * `url: string`. The value of the URL with the applied refinement. * `labels: object`. The value of the other templates. * `cssClasses: object`. The CSS classes provided to the widget. * `stars: boolean[]`. The list of stars to generate with: * `true`. Represents a filled star * `false`. Represents an empty star ```js function theme={"system"} ratingMenu({ // ... templates: { item(data, { html }) { return html` ${data.stars.map( (isFilled) => html` `, )} & Up ${data.count} `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. ratingMenu({ // ... templates: { item: ` {{#count}} {{/count}} {{^count}} {{/count}} `, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` ## Customize the UI with `connectRatingMenu` If you want to create your own UI of the `ratingMenu` widget, you can use connectors. To use `connectRatingMenu`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectRatingMenu } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectRatingMenu } = instantsearch.connectors; // or directly use instantsearch.connectors.connectRatingMenu() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderRatingMenu = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customRatingMenu = connectRatingMenu(renderRatingMenu); // 3. Instantiate search.addWidgets([ customRatingMenu({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderRatingMenu = (renderOptions, isFirstRender) => { const { items, canRefine, refine, sendEvent, createURL } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Render options **items** `object[]` This list of stars to display, with: * `count: number`. The number of results that match this refinement. * `isRefined: boolean`. Whether the refinement is selected. * `name: string`. The name corresponding to the number of stars. * `value: string`. The number of stars with a string form. * `stars: boolean[]`. The list of stars to generate with: * `true`. Represents a filled star * `false`. Represents an empty star ```js JavaScript icon=code theme={"system"} const renderRatingMenu = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector("#rating-menu").innerHTML = ` `; }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderRatingMenu = (renderOptions, isFirstRender) => { const { canRefine } = renderOptions; if (!canRefine) { document.querySelector("#rating-menu").innerHTML = ""; return; } }; ``` **refine** `function` Selects a rating to filter the results. Takes the value of an item as parameter. ```js JavaScript icon=code theme={"system"} const renderRatingMenu = (renderOptions, isFirstRender) => { const { items, refine } = renderOptions; const container = document.querySelector("#rating-menu"); if (isFirstRender) { container.appendChild(document.createElement("ul")); return; } container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.stars.map((isFilled) => (isFilled ? "★" : "☆")).join("")} & Up ${item.count}
  • `, ) .join(""); [...container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.currentTarget.dataset.value); }); }); }; ``` **sendEvent** `(eventType, facetValue) =\> void` The function to send `click` events. The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware. * `eventType: 'click'` * `facetValue: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent("click", 3); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['rates>=3'], index: '', }, widgetType: 'ais.ratingMenu', } */ ``` **createURL** `function` Generates a URL for the next state. Takes the value of an item as parameter. ```js JavaScript icon=code theme={"system"} const renderRatingMenu = (renderOptions, isFirstRender) => { const { items, createURL } = renderOptions; document.querySelector("#rating-menu").innerHTML = ` `; }; ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customRatingMenu = connectRatingMenu(renderRatingMenu); search.addWidgets([ customRatingMenu({ attribute, // Optional parameters max, }), ]); ``` #### Instance options **attribute** `string` *required* The name of the attribute in the record. ```js JavaScript icon=code theme={"system"} customRatingMenu({ attribute: "rating", }); ``` **max** `number` (default: 5) The maximum value for the rating. ```js JavaScript icon=code theme={"system"} customRatingMenu({ // ... max: 4, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderRatingMenu = (renderOptions, isFirstRender) => { const { items, refine, createURL, widgetParams } = renderOptions; if (isFirstRender) { widgetParams.container.appendChild(document.createElement("ul")); return; } widgetParams.container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.stars.map((isFilled) => (isFilled ? "★" : "☆")).join("")} & Up ${item.count}
  • `, ) .join(""); [...widgetParams.container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.currentTarget.dataset.value); }); }); }; // Create the custom widget const customRatingMenu = connectRatingMenu(renderRatingMenu); // Instantiate the custom widget search.addWidgets([ customRatingMenu({ container: document.querySelector("#rating-menu"), attribute: "rating", }), ]); ``` # React InstantSearch Source: https://www.algolia.com/doc/api-reference/widgets/react Overview of React InstantSearch widgets. To get started with React InstantSearch, see [What is InstantSearch?](/doc/guides/building-search-ui/what-is-instantsearch/react) ## Basics | | | | - | - | | [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) | The root wrapper component for all widgets and Hooks. | | [`useInstantSearch`](/doc/api-reference/widgets/use-instantsearch/react) | React Hook to access the InstantSearch API. | | [`Index`](/doc/api-reference/widgets/index-widget/react) | The provider component for an Algolia index. | | [`SearchBox`](/doc/api-reference/widgets/search-box/react) | A widget to display a search box. | | [`Configure`](/doc/api-reference/widgets/configure/react) | A widget to forward search parameters to Algolia. | | [`Autocomplete`](/doc/api-reference/widgets/autocomplete/react) | A widget that shows query results as you type in a search box. | | [`Chat`](/doc/api-reference/widgets/chat/react) | A widget to display a chat interface that interacts with a [generative AI assistant](/doc/guides/algolia-ai/agent-studio). | | [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) | A widget to display a button that opens the [`Chat`](/doc/api-reference/widgets/chat/react) widget's overlay. | | [`Feeds`](/doc/api-reference/widgets/feeds/react) | A component to display multiple independent result sets from a [multifeed composition](/doc/guides/compositions/multifeed-compositions). | | [`useVoiceSearch`](/doc/api-reference/widgets/voice-search/react) | React Hook to use a voice search connector. | | [`insights`](/doc/api-reference/widgets/insights/react) | A middleware to help set the user token for insights purposes, and send events from built-in and custom widgets. | | [`middleware`](/doc/api-reference/widgets/middleware/react) | With the middleware API, you can inject logic into InstantSearch.js. | | [`renderState`](/doc/api-reference/widgets/render-state/react) | The `renderState` provides access to all the data to render the widgets, including the methods to refine the search. | | [`useConnector`](/doc/api-reference/widgets/connector/react) | React Hook to use an InstantSearch connector. | ## Results | | | | - | - | | [`Hits`](/doc/api-reference/widgets/hits/react) | A widget to display a list of search results. | | [`InfiniteHits`](/doc/api-reference/widgets/infinite-hits/react) | A widget to display an infinite list of results. | | [`Highlight`](/doc/api-reference/widgets/highlight/react) | Highlighting visually emphasizes matching terms in your search results to help them stand out. | | [`Snippet`](/doc/api-reference/widgets/snippet/react) | A widget that displays attributes in your search results in a shorter form (a snippet). | ## Recommendations | | | | - | - | | [`FrequentlyBoughtTogether`](/doc/api-reference/widgets/frequently-bought-together/react) | A widget to display a list of frequently bought together items. | | [`RelatedProducts`](/doc/api-reference/widgets/related-products/react) | A widget to display a list of related products and related content. | | [`TrendingItems`](/doc/api-reference/widgets/trending-items/react) | A widget to display a list of trending items. | | [`TrendingFacets`](/doc/api-reference/widgets/trending-facets/react) | A widget to display a list of trending facets. | | [`LookingSimilar`](/doc/api-reference/widgets/looking-similar/react) | A widget to display a list of visually similar products. | ## Refinements | | | | - | - | | [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) | A widget to display multi-select facets. | | [`ColorRefinementList`](/doc/api-reference/widgets/color-refinement-list/react) | A widget that filters results based on color facet values. It helps users quickly visualize the kind of color that products have. Only available in React InstantSearch v6. | | [`DynamicWidgets`](/doc/api-reference/widgets/dynamic-widgets/react) | A widget to conditionally render other widgets based on the index settings. | | [`HierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/react) | Widget that displays a hierarchical menu. | | [`useRange`](/doc/api-reference/widgets/range-slider/react) | React Hook to filter search results within a numeric range. | | [`Menu`](/doc/api-reference/widgets/menu/react) | A widget to display a menu to let users choose a single value. | | [`CurrentRefinements`](/doc/api-reference/widgets/current-refinements/react) | A widget to display the list of active refinements in the search. | | [`RangeInput`](/doc/api-reference/widgets/range-input/react) | A widget to select a numerical range using minimum and maximum inputs. | | [`MenuSelect`](/doc/api-reference/widgets/menu-select/react) | A widget to use a menu select connector. | | [`ToggleRefinement`](/doc/api-reference/widgets/toggle-refinement/react) | A widget to provide an on/off filter. | | [`useNumericMenu`](/doc/api-reference/widgets/numeric-menu/react) | React Hook that shows a list of numeric filters that are pre-configured when creating the widget. | | [`useRatingMenu`](/doc/api-reference/widgets/rating-menu/react) | React Hook to use a rating menu connector. | | [`ClearRefinements`](/doc/api-reference/widgets/clear-refinements/react) | A widget to reset the active refinements of the search. | ## Pagination | | | | - | - | | [`Pagination`](/doc/api-reference/widgets/pagination/react) | A widget to display a pagination to browse pages. | | [`HitsPerPage`](/doc/api-reference/widgets/hits-per-page/react) | A widget to display a menu of options to change the number of results per page. | ## Metadata | | | | - | - | | [`Breadcrumb`](/doc/api-reference/widgets/breadcrumb/react) | A widget to display navigation links to see where the current page is in relation to the facet's hierarchy. | | [`Stats`](/doc/api-reference/widgets/stats/react) | A widget that displays the total number of matching hits and the time it took to get them (time spent in the Algolia server). | | [`PoweredBy`](/doc/api-reference/widgets/powered-by/react) | A widget to display the Algolia logo to redirect to the Algolia website. | | [`StateResults`](/doc/api-reference/widgets/state-results/react) | A widget for accessing the state and results of InstantSearch. | | [`QueryRulesCustomData`](/doc/api-reference/widgets/query-rule-custom-data/react) | A widget to display custom data from rules. | | [`QueryRuleContext`](/doc/api-reference/widgets/query-rule-context/react) | A widget to set Rule contexts without rendering anything. | | [`useQueryRules`](/doc/api-reference/widgets/query-rules/react) | React Hook to set custom Rule contexts and display custom data from Rules. | ## Sorting | | | | - | - | | [`SortBy`](/doc/api-reference/widgets/sort-by/react) | A widget to sort by specified indices. | | [`useRelevantSort`](/doc/api-reference/widgets/relevant-sort/react) | React Hook to use a relevant sort connector. | ## Geo search | | | | - | - | | [`useGeoSearch`](/doc/api-reference/widgets/geo-search/react) | A React Hook that displays search results on a Google Map. | ## Routing | | | | - | - | | [`simple`](/doc/api-reference/widgets/simple-state-mapping/react) | A state mapping used by default routing. | | [`history`](/doc/api-reference/widgets/history-router/react) | A router used by default routing. | | [`uiState`](/doc/api-reference/widgets/ui-state/react) | An object that represents a search UI state. | | [`createInstantSearchRouterNext`](/doc/api-reference/widgets/instantsearch-next/react) | The function that creates a Next.js-compatible InstantSearch router. | ## Server | | | | - | - | | [`InstantSearchSSRProvider`](/doc/api-reference/widgets/ssr-provider/react) | The provider component that forwards the server state to `InstantSearch`. | | [`getServerState`](/doc/api-reference/widgets/server-state/react) | The function that retrieves the server state to pass to `InstantSearchSSRProvider`. | # refinementList Source: https://www.algolia.com/doc/api-reference/widgets/refinement-list/js Shows a list of facets for refining search results. ```ts Signature theme={"system"} refinementList({ container: string | HTMLElement, attribute: string, // Optional parameters operator?: string, limit?: number, showMore?: boolean, showMoreLimit?: number, showMoreButtonLabel?: string, searchable?: boolean, searchablePlaceholder?: string, searchableIsAlwaysActive?: boolean, searchableEscapeFacetValues?: boolean, sortBy?: string[] | function, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { refinementList } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { refinementList } = instantsearch.widgets; // or directly use instantsearch.widgets.refinementList() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=refinementList) > > Preview this widget and its behavior. ## About this widget The `refinementList` widget is one of the most common widgets in a search UI. With this widget, users can filter the dataset based on facets. The widget only displays the most relevant facet values for the current search context. The sort option only affects the facets that are returned by the engine, not which facets are returned. This widget includes a "search for facet values" feature, enabling users to search through the [values of a specific facet attribute](/doc/guides/managing-results/refine-results/faceting#search-for-facet-values). This helps you find uncommon facet values. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. If you are using the [`searchable`](#param-searchable) prop, you also need to make the attribute searchable using the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the `searchable` modifier of [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) with the API. ### Disappearing facet values With many facet values, the available options can change depending on the user's query. The refinement widget displays the most common facet values for a given query. A user's chosen value can vanish if they alter the query. This occurs because only the most common facet values are displayed when there are many options. A previously selected value might not appear if it's uncommon for the new query. To also show less common values, adjust the maximum number of values with the [`configure`](/doc/api-reference/widgets/configure/js) widget. It doesn't change how many items are shown: the limits you set with [`limit`](/doc/api-reference/widgets/refinement-list/js#param-limit) and [`showMoreLimit`](/doc/api-reference/widgets/refinement-list/js#param-show-more-limit) still apply. ```js JavaScript icon=code theme={"system"} search.addWidgets([ // ... configure({ maxValuesPerFacet: 1000, }), ]); ``` ## Examples ```js JavaScript icon=code theme={"system"} refinementList({ container: "#refinement-list", attribute: "brand", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector of the DOM element inside which the widget is inserted. ```js string theme={"system"} refinementList({ // ... container: "#refinement-list", }); ``` ```js HTMLElement theme={"system"} refinementList({ // ... container: document.querySelector("#refinement-list"), }); ``` **attribute** `string` *required* The name of the attribute in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```js JavaScript icon=code theme={"system"} refinementList({ // ... attribute: "categories", }); ``` **operator** `string` (default: or) How to apply refinements. * `"or"`: apply an `OR` between all selected values. * `"and"`: apply an `AND` between all selected values. Use [filters or facet filters](/doc/guides/managing-results/refine-results/filtering/in-depth/filters-and-facetfilters) for more complex result refinement. ```js JavaScript icon=code theme={"system"} refinementList({ // ... operator: "and", }); ``` **limit** `number` (default: 10) How many facet values to retrieve. When you enable the [`showMore`](#param-show-more) feature, this is the number of facet values to display before clicking the "Show more" button. ```js JavaScript icon=code theme={"system"} refinementList({ //... limit: 5, }); ``` **showMore** `boolean` (default: false) Whether to display a button that expands the number of items. ```js JavaScript icon=code theme={"system"} refinementList({ // ... showMore: true, }); ``` **showMoreLimit** `number` The maximum number of displayed items (only used when [`showMore`](#param-show-more) is set to `true`). ```js JavaScript icon=code theme={"system"} refinementList({ //... showMoreLimit: 20, }); ``` **showMoreButtonLabel** `string` The static accessible name (`aria-label`) for the **Show more** and **Show less** toggle buttons. This option is only used when [`showMore`](#param-show-more) is `true`. Use a label that works for both expanded and collapsed states because this value doesn't change as the button expands or collapses. ```js JavaScript icon=code theme={"system"} refinementList({ //... showMoreButtonLabel: "Show more or show less brands", }); ``` **searchable** `boolean` (default: false) Whether to add a search input to let users search for more facet values. To make this feature work, you need to make the attribute searchable using the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the `searchable` modifier of [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) with the API. > [!NOTE] > > In some situations, refined facet values might not be present in the data > returned by Algolia. ```js JavaScript icon=code theme={"system"} refinementList({ // ... searchable: true, }); ``` **searchablePlaceholder** `string` (default: Search...) The value of the search input's placeholder. ```js JavaScript icon=code theme={"system"} refinementList({ // ... searchablePlaceholder: "Search our products", }); ``` **searchableIsAlwaysActive** `boolean` (default: true) When `false`, disables the search input if there are fewer items to display than the [`limit`](#param-limit) option. Otherwise, the search input is always usable. ```js JavaScript icon=code theme={"system"} refinementList({ // ... searchableIsAlwaysActive: false, }); ``` **searchableEscapeFacetValues** `boolean` (default: true) When `true`, escapes the facet values that are returned from Algolia. In this case, the surrounding tags are always `mark`. ```js JavaScript icon=code theme={"system"} refinementList({ // ... searchableEscapeFacetValues: false, }); ``` **sortBy** `string[] | function` (default: Uses facetOrdering if set, \['isRefined','count:desc','name:asc']) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` It's also possible to give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). If `facetOrdering` is set for this facet in `renderingContent`, and no value for `sortBy` is passed to this widget, `facetOrdering` is used, and the default order as a fallback. > [!NOTE] > > In some situations, refined facet values might not be present in the data > returned by Algolia. ```js string[] theme={"system"} refinementList({ // ... sortBy: ['count:desc', 'name:asc'], }); ``` ```js function theme={"system"} refinementList({ // ... sortBy(a, b) { return a.name < b.name ? 1 : -1; }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} refinementList({ // ... templates: { // ... }, }); ``` **cssClasses** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `noRefinementRoot`. The root element if there are no refinements. * `noResults`. The root element if there are no results. * `list`. The list of results. * `item`. The list items. They contain the link and separator. * `selectedItem`. Each selected item in the list. * `label`. Each label element (when using the default template). * `checkbox`. Each checkbox element (when using the default template). * `labelText`. Each label text element. * `showMore`. The "Show more" element. * `disabledShowMore`. The "Show more" element when it's unavailable. * `count`. Each count element (when using the default template). * `searchableRoot`. The root element of the search box. * `searchableForm`. The form element of the search box. * `searchableInput`. The input element of the search box. * `searchableSubmit`. The reset button element of the search box. * `searchableSubmitIcon`. The reset button icon of the search box. * `searchableReset`. The loading indicator element of the search box. * `searchableResetIcon`. The loading indicator icon of the search box. * `searchableLoadingIndicator`. The submit button element of the search box. * `searchableLoadingIcon`. The submit button icon of the search box. ```js JavaScript icon=code theme={"system"} refinementList({ // ... cssClasses: { root: "MyCustomRefinementList", list: [ "MyCustomRefinementListList", "MyCustomRefinementListList--subclass", ], }, }); ``` **transformItems** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} refinementList({ // ... transformItems(items) { return items.map((item) => ({ ...item, highlighted: item.highlighted.toUpperCase(), })); }, }); // or, combined with results refinementList({ // ... transformItems(items, { results }) { return results.page === 0 ? items.slice(0, 5) : items; }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **item** `string | function` The template used for an item. It exposes: * `count`. The number of occurrences of the facet in the result set. * `isRefined`. Returns `true` if the value is selected. * `label`. The label to display. * `value`. The value used for refining. * `highlighted`. The highlighted label (when using search for facet values). This value is displayed in the default template. * `url`. The URL with the selected refinement. * `cssClasses`. An object containing all the computed classes for the item. ```js function theme={"system"} refinementList({ // ... templates: { item(item, { html }) { const { url, label, count, isRefined } = item; return html` ${label} (${count}) `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. refinementList({ // ... templates: { item: ` {{label}} ({{count}}) `, }, }); ``` **showMoreText** `string | function` The template for the "Show more" button text. It exposes: * `isShowingMore: boolean`. Whether the list is expanded. ```js function theme={"system"} refinementList({ // ... templates: { showMoreText(data, { html }) { return html`${data.isShowingMore ? 'Show less' : 'Show more'}`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. refinementList({ // ... templates: { showMoreText: ` {{#isShowingMore}} Show less {{/isShowingMore}} {{^isShowingMore}} Show more {{/isShowingMore}} `, }, }); ``` **searchableNoResults** `string | function` The template used for when there are no results. ```js function theme={"system"} refinementList({ // ... templates: { searchableNoResults(data, { html }) { return html`No results`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. refinementList({ // ... templates: { searchableNoResults: "No results", }, }); ``` **searchableSubmit** `string | function` The template used for displaying the submit button. ```js function theme={"system"} refinementList({ // ... templates: { searchableSubmit(data, { html }) { return html`submit`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. refinementList({ // ... templates: { searchableSubmit: "submit", }, }); ``` **searchableReset** `string | function` The template used for displaying the reset button. ```js function theme={"system"} refinementList({ // ... templates: { searchableReset(data, { html }) { return html`reset`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. refinementList({ // ... templates: { searchableReset: "reset", }, }); ``` **searchableLoadingIndicator** `string | function` The template used for displaying the loading indicator. ```js function theme={"system"} refinementList({ // ... templates: { searchableLoadingIndicator(data, { html }) { return html`loading`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. refinementList({ // ... templates: { searchableLoadingIndicator: "loading", }, }); ``` ## Customize the UI with `connectRefinementList` If you want to create your own UI of the `refinementList` widget, you can use connectors. To use `connectRefinementList`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectRefinementList } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectRefinementList } = instantsearch.connectors; // or directly use instantsearch.connectors.connectRefinementList() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderRefinementList = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customRefinementList = connectRefinementList( renderRefinementList ); // 3. Instantiate search.addWidgets([ customRefinementList({ // instance params }) ]); ``` [See code examples](#full-example) ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items, canRefine, refine, sendEvent, createURL, isFromSearch, searchForItems, hasExhaustiveItems, isShowingMore, canToggleShowMore, toggleShowMore, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` The list of refinement values returned from the Algolia API. Each object has the following properties: * `value: string`: the value of the item. * `label: string`: the label of the item. * `count: number`: the number of results that match the item. * `isRefined: boolean`: indicates if the refinement is applied. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector('#refinement-list').innerHTML = ` `; }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items, canRefine, refine } = renderOptions; const container = document.querySelector("#refinement-list"); if (!canRefine) { container.innerHTML = ""; return; } }; ``` **refine** `function` A function to toggle a refinement. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items, refine } = renderOptions; const container = document.querySelector("#refinement-list"); container.innerHTML = ` `; [...container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.currentTarget.dataset.value); }); }); }; ``` **sendEvent** `(eventType, facetValue) =\> void` The function to send `click` events. The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware. * `eventType: 'click'` * `facetValue: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent('click', 'Apple'); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['brand:"Apple"'], index: '', }, widgetType: 'ais.refinementList', } */ ``` **createURL** `function` Generates a URL for the corresponding search state. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items, createURL } = renderOptions; document.querySelector("#refinement-list").innerHTML = ` `; }; ``` **isFromSearch** `boolean` Whether the `items` prop contains facet values from the global search or from the search inside the items. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items, isFromSearch, refine, searchForItems } = renderOptions; const container = document.querySelector("#refinement-list"); if (isFirstRender) { const ul = document.createElement("ul"); const input = document.createElement("input"); input.addEventListener("input", (event) => { searchForItems(event.currentTarget.value); }); container.appendChild(input); container.appendChild(ul); } const input = container.querySelector("input"); if (!isFromSearch && input.value) { input.value = ""; } container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); [...container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.currentTarget.dataset.value); }); }); }; ``` **searchForItems** `function` A function to trigger a search inside item values. To make this feature work, you need to make the attribute searchable using the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the `searchable` modifier of [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) with the API. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items, searchForItems } = renderOptions; const container = document.querySelector("#refinement-list"); if (isFirstRender) { const ul = document.createElement("ul"); const input = document.createElement("input"); input.addEventListener("input", (event) => { searchForItems(event.currentTarget.value); }); container.appendChild(input); container.appendChild(ul); } container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); }; ``` **hasExhaustiveItems** `boolean` Whether the results are complete. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector("#refinement-list").innerHTML = `
      ${items .map( (item) => `
    • ${item.label} (${hasExhaustiveItems ? "~" : ""}${item.count})
    • `, ) .join("")}
    `; }; ``` **isShowingMore** `boolean` Returns `true` if the menu is displaying all the menu items. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items, isShowingMore, toggleShowMore } = renderOptions; const container = document.querySelector("#refinement-list"); if (isFirstRender) { const ul = document.createElement("ul"); const button = document.createElement("button"); button.textContent = "Show more"; button.addEventListener("click", () => { toggleShowMore(); }); container.appendChild(ul); container.appendChild(button); } container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); container.querySelector("button").textContent = isShowingMore ? "Show less" : "Show more"; }; ``` **canToggleShowMore** `boolean` Returns `true` if the "Show more" button can be activated (if enough items to display and not already displaying more than [`limit`](/doc/api-reference/widgets/refinement-list/js#param-limit) items). ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items, canToggleShowMore, toggleShowMore } = renderOptions; const container = document.querySelector("#refinement-list"); if (isFirstRender) { const ul = document.createElement("ul"); const button = document.createElement("button"); button.textContent = "Show more"; button.addEventListener("click", () => { toggleShowMore(); }); container.appendChild(ul); container.appendChild(button); } container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); container.querySelector("button").disabled = !canToggleShowMore; }; ``` **toggleShowMore** `function` Toggles the number of displayed values between [`limit`](/doc/api-reference/widgets/refinement-list/js#param-limit) and [`showMoreLimit`](/doc/api-reference/widgets/refinement-list/js#param-show-more-limit). ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { items, toggleShowMore } = renderOptions; const container = document.querySelector("#refinement-list"); if (isFirstRender) { const ul = document.createElement("ul"); const button = document.createElement("button"); button.textContent = "Show more"; button.addEventListener("click", () => { toggleShowMore(); }); container.appendChild(ul); container.appendChild(button); } container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderRefinementList = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customRefinementList({ // ... container: document.querySelector("#refinement-list"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customRefinementList = connectRefinementList( renderRefinementList ); search.addWidgets([ customRefinementList({ attribute: string, // Optional parameters operator: string, limit: number, showMoreLimit: number, escapeFacetValues: boolean, sortBy: string[]|function, transformItems: function, }) ]); ``` #### Instance options **attribute** `string` *required* The name of the attribute in the records. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```js JavaScript icon=code theme={"system"} customRefinementList({ attribute: "brand", }); ``` **operator** `string ('or'|'and')` (default: or) How to apply refinements. * `"or"`: apply an `OR` between all selected values. * `"and"`: apply an `AND` between all selected values. ```js JavaScript icon=code theme={"system"} customRefinementList({ // ... operator: "and", }); ``` **limit** `number` (default: 10) How many facet values to retrieve. When [isShowingMore](/doc/api-reference/widgets/refinement-list/js#param-is-showing-more) is `false`, this is the number of facet values displayed before clicking the "Show more" button. ```js JavaScript icon=code theme={"system"} customRefinementList({ // ... limit: 5, }); ``` **showMoreLimit** `number` The maximum number of items to display if the widget is showing more items. Needs to be bigger than the [`limit`](/doc/api-reference/widgets/refinement-list/js#param-limit) parameter. ```js JavaScript icon=code theme={"system"} customRefinementList({ // ... showMoreLimit: 20, }); ``` **escapeFacetValues** `boolean` (default: true) When activate, escapes the facet values that are returned from Algolia. In this case, the surrounding tags are always [`mark`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark). ```js JavaScript icon=code theme={"system"} customRefinementList({ // ... escapeFacetValues: false, }); ``` **sortBy** `string[] | function` (default: Uses facetOrdering if set, \['isRefined','count:desc','name:asc']) How to sort refinements. Must be one or more of the following strings: * `"count:asc"` * `"count:desc"` * `"name:asc"` * `"name:desc"` * `"isRefined"` It's also possible to give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). If `facetOrdering` is set for this facet in `renderingContent`, and no value for `sortBy` is passed to this widget, `facetOrdering` is used, and the default order as a fallback. > [!NOTE] > > In some situations, refined facet values might not be present in the data > returned by Algolia. ```js string[] theme={"system"} customRefinementList({ // ... sortBy: ['count:desc', 'name:asc'], }); ``` ```js function theme={"system"} customRefinementList({ // ... sortBy(a, b) { return a.name < b.name ? 1 : -1; }, }); ``` **transformItems** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customRefinementList({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or, combined with results customRefinementList({ // ... transformItems(items, { results }) { return results.page === 0 ? items.slice(0, 5) : items; }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // 1. Create a render function const renderRefinementList = (renderOptions, isFirstRender) => { const { items, isFromSearch, refine, createURL, isShowingMore, canToggleShowMore, searchForItems, toggleShowMore, widgetParams, } = renderOptions; if (isFirstRender) { const input = document.createElement("input"); const ul = document.createElement("ul"); const button = document.createElement("button"); button.textContent = "Show more"; input.addEventListener("input", (event) => { searchForItems(event.currentTarget.value); }); button.addEventListener("click", () => { toggleShowMore(); }); widgetParams.container.appendChild(input); widgetParams.container.appendChild(ul); widgetParams.container.appendChild(button); } const input = widgetParams.container.querySelector("input"); if (!isFromSearch && input.value) { input.value = ""; } widgetParams.container.querySelector("ul").innerHTML = items .map( (item) => `
  • ${item.label} (${item.count})
  • `, ) .join(""); [...widgetParams.container.querySelectorAll("a")].forEach((element) => { element.addEventListener("click", (event) => { event.preventDefault(); refine(event.currentTarget.dataset.value); }); }); const button = widgetParams.container.querySelector("button"); button.disabled = !canToggleShowMore; button.textContent = isShowingMore ? "Show less" : "Show more"; }; // 2. Create the custom widget const customRefinementList = connectRefinementList(renderRefinementList); // 3. Instantiate search.addWidgets([ customRefinementList({ container: document.querySelector("#refinement-list"), attribute: "brand", showMoreLimit: 20, }), ]); ``` # relatedProducts Source: https://www.algolia.com/doc/api-reference/widgets/related-products/js Shows related product and content recommendations based on a provided item. > [!NOTE] > > If you're using the deprecated [`recommend-js`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js) UI library, > see [`relatedProducts`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js/relatedProducts). ```ts Signature theme={"system"} relatedProducts({ container: string | HTMLElement, objectIDs: string[], // Optional parameters limit?: number, threshold?: number, queryParameters?: object, fallbackParameters?: object, escapeHTML?: boolean, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { relatedProducts } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { relatedProducts } = instantsearch.widgets; // or directly use instantsearch.widgets.relatedProducts() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=relatedProducts) > > Preview this widget and its behavior. ## About this widget Use the `relatedProducts` widget to display a list of related products and related content. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) ## Examples ```js JavaScript icon=code theme={"system"} relatedProducts({ container: "#relatedProducts", objectIDs: ["5723537"], templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, }, }); ``` ### With a carousel layout Display the `relatedProducts` widget as a [scrollable carousel](#param-layout). ```js JavaScript icon=code theme={"system"} import { carousel } from "instantsearch.js/es/templates"; // or const { carousel } = instantsearch.templates; relatedProducts({ container: "#relatedProducts", objectIDs: ["5723537"], templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, layout: carousel(), }, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} relatedProducts({ // ... container: '#relatedProducts', }); ``` ```js HTMLElement theme={"system"} relatedProducts({ container: document.querySelector("#relatedProducts"), }); ``` **objectIDs** `string[]` *required* The list of object IDs for which to get recommendations. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```js JavaScript icon=code theme={"system"} relatedProducts({ // ... objectIDs: ["1", "2", "3"], }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} relatedProducts({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} relatedProducts({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send, except `page`, `hitsPerPage`, `offset`, and `length`. ```js JavaScript icon=code theme={"system"} relatedProducts({ // ... queryParameters: { filters: "category:Book", }, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as an additional 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.) set when there aren't enough recommendations. ```js JavaScript icon=code theme={"system"} relatedProducts({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} relatedProducts({ // ... escapeHTML: false, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} relatedProducts({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The widget's root element. * `emptyRoot`. The container element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of recommendations. * `item`. The list item. ```js JavaScript icon=code theme={"system"} relatedProducts({ // ... cssClasses: { root: "MyCustomRelatedProducts", list: ["MyCustomRelatedProducts", "MyCustomRelatedProducts--subclass"], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} relatedProducts({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **empty** `string | function` The template to use when there are no recommendations. ```js function theme={"system"} relatedProducts({ // ... templates: { empty(_, { html }) { return html`

    No recommendations.

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. relatedProducts({ // ... templates: { empty: "No recommendations.", }, }); ``` **header** `string | function` The template to use for the recommendations header. This template receives the recommendations as well as the `cssClasses` object. ```js function theme={"system"} relatedProducts({ // ... templates: { header({ cssClasses, items }, { html }) { return html`

    Recommendations (${items.length})

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. relatedProducts({ // ... templates: { header: "

    Recommendations ({{items.length}})

    ", }, }); ``` **item** `string | function` The template to use for each recommendation. This template receives an object containing a single record. ```js function theme={"system"} relatedProducts({ // ... templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. relatedProducts({ // ... templates: { item: `

    {{ name }}

    {{ description }}

    `, }, }); ``` **layout** `function` The template to use to wrap all items. InstantSearch.js provides an out-of-the-box carousel layout template with the following options: `cssClasses`: * `root`. The carousel's root element. * `list`. The list of recommendations. * `item`. The list item. * `navigation`. The navigation controls. * `navigationPrevious`. The "Previous" navigation control. * `navigationNext`. The "Next" navigation control. `templates`: * `previous`. The content of the "Previous" navigation control. * `next`. The content of the "Next" navigation control. ```js Carousel theme={"system"} import { carousel } from 'instantsearch.js/es/templates'; // or const { carousel } = instantsearch.templates; relatedProducts({ // ... templates: { layout: carousel({ cssClasses: { root: 'MyCustomCarousel', }, templates: { previous({ html }) { return html`

    Previous

    `; }, next({ html }) { return html`

    Next

    `; }, }, }), }, }); ``` ```js Custom theme={"system"} relatedProducts({ // ... templates: { layout({ items }, { html }) { return html`
      ${items.map((item) => html`
    • ${item.objectID}

    • `)}
    `; }, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Related Products

    1. ...
    2. ...
    3. ...
    ``` ## Customize the UI with `connectRelatedProducts` If you want to create your own UI of the `relatedProducts` widget, you can use connectors. To use `connectRelatedProducts`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectRelatedProducts } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectRelatedProducts } = instantsearch.connectors; // or directly use instantsearch.connectors.connectRelatedProducts() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderRelatedProducts = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customRelatedProducts = connectRelatedProducts(renderRelatedProducts); // 3. Instantiate search.addWidgets([ customRelatedProducts({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderRelatedProducts = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` The matched recommendations from the Algolia API. ```js JavaScript icon=code theme={"system"} const renderRelatedProducts = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector('#relatedProducts').innerHTML = `
      ${items .map(item => `
    • ${item.name}
    • `) .join('')}
    `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderRelatedProducts = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = '...'; }; // ... search.addWidgets([ customRelatedProducts({ // ... container: document.querySelector('#relatedProducts'), }) ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customRelatedProducts = connectRelatedProducts( renderRelatedProducts ); search.addWidgets([ customRelatedProducts({ objectIDs: string[], // Optional parameters limit: number, threshold: number, queryParameters: object, fallbackParameters: object, escapeHTML: boolean, transformItems: function, }) ]); ``` #### Instance options **objectIDs** `string[]` *required* The list of object IDs to get recommendations for. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). > For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```js JavaScript icon=code theme={"system"} customRelatedProducts({ // ... objectIDs: ["1", "2", "3"], }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} customRelatedProducts({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} customRelatedProducts({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```js JavaScript icon=code theme={"system"} customRelatedProducts({ // ... queryParameters: { filters: "category:Book", }, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as additional filters when there aren't enough recommendations. ```js JavaScript icon=code theme={"system"} customRelatedProducts({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} customRelatedProducts({ // ... escapeHTML: false, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customRelatedProducts({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderRelatedProducts = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; widgetParams.container.innerHTML = `
      ${items .map(item => `
    • ${item.name}
    • `) .join('')}
    `; }; // Create the custom widget const customRelatedProducts = connectRelatedProducts( renderRelatedProducts ); // Instantiate the custom widget search.addWidgets([ customRelatedProducts({ container: document.querySelector('#relatedProducts'), objectIDs: ['5723537'], }) ]); ``` # relevantSort Source: https://www.algolia.com/doc/api-reference/widgets/relevant-sort/js Lets users switch between search modes, exhaustive or relevant sorting. ```ts Signature theme={"system"} relevantSort({ container: string | HTMLElement, // Optional parameters templates?: object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { relevantSort } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { relevantSort } = instantsearch.widgets; // or directly use instantsearch.widgets.relevantSort() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=js) > > Preview this widget and its behavior. ## About this widget Virtual indices let you use [relevant sort](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort), a sorting mechanism that favors relevancy over the attribute you're sorting on. The `relevantSort` widget displays the current search mode when searching in a virtual replica index, and allows users to switch between relevant and regular sorting, which is more exhaustive but can return less relevant results. ## Examples ```js JavaScript icon=code theme={"system"} relevantSort({ container: "#relevant-sort", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} relevantSort({ container: "#relevant-sort", }); ``` ```js HTMLElement theme={"system"} relevantSort({ container: document.querySelector("#relevant-sort"), }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} relevantSort({ // ... templates: { // ... }, }); ``` **cssClasses** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `text`. The text element for explanation. * `button`. The toggle button element. ```js JavaScript icon=code theme={"system"} relevantSort({ // ... cssClasses: { root: "MyCustomRelevantSort", button: [ "MyCustomRelevantSort-button", "MyCustomRelevantSort-button--subclass", ], }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **button** `string | function` The template used for displaying the toggle button. ```js string theme={"system"} relevantSort({ // ... templates: { button: "Toggle", }, }); ``` ```js function theme={"system"} relevantSort({ // ... templates: { button({ isRelevantSorted }) { return isRelevantSorted ? "See all results" : "See relevant results"; }, }, }); ``` **text** `string | function` The template used for displaying extra information. ```js string theme={"system"} relevantSort({ // ... templates: { text: "Click the button", }, }); ``` ```js function theme={"system"} searchBox({ // ... templates: { text({ isRelevantSorted }) { return ( "Click the button to " + (isRelevantSorted ? "see all results." : "see relevant results.") ); }, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectRelevantSort` If you want to create your own UI of the `relevantSort` widget, you can use connectors. To use `connectRelevantSort`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectRelevantSort } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectRelevantSort } = instantsearch.connectors; // or directly use instantsearch.connectors.connectRelevantSort() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderRelevantSort = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customRelevantSort = connectRelevantSort(renderRelevantSort); // 3. Instantiate search.addWidgets([ customRelevantSort({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderRelevantSort = (renderOptions, isFirstRender) => { const { isVirtualReplica, isRelevantSorted, canRefine, refine, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Render options **isVirtualReplica** `boolean` Indicates whether the index is a virtual replica. ```js JavaScript icon=code theme={"system"} const renderRelevantSort = (renderOptions, isFirstRender) => { const { isVirtualReplica } = renderOptions; const container = document.querySelector("#relevant-sort"); container.innerHTML = "This index is a " + (isVirtualReplica ? "virtual replica" : "standard replica"); }; ``` **isRelevantSorted** `boolean` Indicates whether the search result is relevantly sorted out. ```js JavaScript icon=code theme={"system"} const renderRelevantSort = (renderOptions, isFirstRender) => { const { isRelevantSorted } = renderOptions; const container = document.querySelector("#relevant-sort"); container.innerHTML = "Showing " + (isRelevantSorted ? "relevant results." : "all results."); }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const renderRelevantSort = (renderOptions, isFirstRender) => { const { canRefine } = renderOptions; if (!canRefine) { document.querySelector("#relevant-sort").innerHTML = ""; return; } }; ``` **refine** `function` Sets `relevancyStrictness`. ```js JavaScript icon=code theme={"system"} const isRelevantSortedRef = { current: undefined }; const renderRelevantSort = (renderOptions, isFirstRender) => { const { refine, isRelevantSorted } = renderOptions; isRelevantSortedRef.current = isRelevantSorted; const container = document.querySelector("#relevant-sort"); if (isFirstRender) { const button = document.createElement("button"); button.setAttribute("type", "button"); button.innerText = "Toggle"; button.addEventListener("click", () => { // `0`: show all results // `undefined`: fall back to the value from the index config. refine(isRelevantSortedRef.current ? 0 : undefined); }); container.appendChild(button); } }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderRelevantSort = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customRelevantSort({ container: document.querySelector("#relevant-sort"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customRelevantSort = connectRelevantSort( renderRelevantSort ); search.addWidgets([ customRelevantSort({ container, ]); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create a render function const isRelevantSortedRef = { current: undefined }; const renderRelevantSort = (renderOptions, isFirstRender) => { const { isVirtualReplica, isRelevantSorted, refine, widgetParams } = renderOptions; isRelevantSortedRef.current = isRelevantSorted; if (isFirstRender) { const button = document.createElement("button"); button.addEventListener("click", () => { refine(isRelevantSortedRef.current ? 0 : undefined); }); widgetParams.container.appendChild(button); } const button = widgetParams.container.querySelector("button"); button.textContent = isRelevantSortedRef.current ? "See all results" : "See relevant results"; }; // Create custom widget const customRelevantSort = connectRelevantSort(renderRelevantSort); // Instantiate custom widget search.addWidgets([ customRelevantSort({ container: document.querySelector("#relevant-sort"), }), ]); ``` # renderState Source: https://www.algolia.com/doc/api-reference/widgets/render-state/js Object with state data about all widgets. ```ts Signature theme={"system"} search.renderState: object; ``` ## About this widget The `renderState` property provides all the data and functions from the widgets. It lets you access the render state of any widget from anywhere, so you can create custom widgets or refine the search outside the InstantSearch.js lifecycle. > [!NOTE] > > The `renderState` property is available starting in InstantSearch.js v4.9. ## Examples You can access the render state of the `searchBox` widget. ```js JavaScript icon=code theme={"system"} const indexName = ""; const search = instantsearch({ indexName, // ... }); search.addWidgets([ searchBox({ container: "", }), ]); search.start(); console.log(search.renderState[indexName].searchBox); /* { query: string; refine: Function; clear: Function; isSearchStalled: boolean; widgetParams: object; } */ ``` ## Working with virtual widgets To access the `renderState` of widgets, you must add them to the InstantSearch instance. If you don't want to add a widget to the UI, but want to get access to its `renderState`, you can add it as a virtual, or renderless widget. ```html HTML theme={"system"} ``` ```js JavaScript theme={"system"} const renderer = () => null; const virtualPagination = connectPagination(renderer); search.addWidgets([virtualPagination()]); // Add a click listener to the button document.querySelector("#next-page-button").addEventListener("click", () => { // Get the `renderState` of the `pagination` widget const paginationRenderState = search.renderState[indexName].pagination; // Get the current page const currentPage = paginationRenderState.currentRefinement; // Call `refine` to move to the next page paginationRenderState.refine(currentPage + 1); }); ``` ## Type definition ```ts TypeScript icon=code theme={"system"} type RenderState = { [indexId: string]: IndexRenderState; } type IndexRenderState = Partial<{ searchBox: SearchBoxState; autocomplete: AutocompleteState; breadcrumb: BreadcrumbState; clearRefinements: ClearRefinementsState; configure: ConfigureState; currentRefinements: CurrentRefinementsState; hierarchicalMenu: { [attribute: string]: HierarchicalMenuState; } hits: HitsState; infiniteHits: InfiniteHitsState; analytics: AnalyticsState; places: PlacesState; poweredBy: PoweredByState; range: { [attribute: string]: RangeState; ratingMenu: { [attribute: string]: RatingMenuState; }; numericMenu: { [attribute: string]: NumericMenuState; }; voiceSearch: VoiceSearchState; geoSearch: GeoSearchState; queryRules: QueryRulesState; hitsPerPage: HitsPerPageState; pagination: PaginationState; refinementList: { [attribute: string]: RefinementListState; }; answers: AnswersState; }>; ``` # resultCard Source: https://www.algolia.com/doc/api-reference/widgets/result-card/js Displays an AI Overview card above the results with an Agent Studio recommendation based on the query and the top hits. For more information, see [Agent Studio](/doc/guides/algolia-ai/agent-studio). ```ts Signature theme={"system"} resultCard({ container: string | HTMLElement, agentId: string, // Optional parameters, at most one of transport?: object, requestOptions?: object, // Optional parameters templates?: object, cssClasses?: object, translations?: object, }); ``` ## Import ```js Package manager theme={"system"} import { resultCard } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { resultCard } = instantsearch.widgets; ``` ## About this widget Use the `resultCard` widget to display an "AI Overview" card above your results. The widget sends the query, the active filters, and the first five hits to an Agent Studio agent as [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context), and asks the agent which of these results it recommends and why. The answer streams into the card, and a skeleton placeholder shows until the first text arrives. The card only shows when the search meets both conditions: * A [rule](/doc/guides/managing-results/rules/rules-overview) that matches the search sets `renderingContent.widgets.resultCard.enabled` to `true` in the search response. For more information, see [Activate the card with a rule](#activate-the-card-with-a-rule). * The query has at least two words. The widget sends a new request only when the query, the filters, or the first five hits change. Going to another page of results doesn't send a new request. Users can collapse the card to its header, which hides the answer, the follow-up suggestions, and the **Continue in chat** button. The answer keeps generating in the meantime, and the card stays collapsed for new searches until users restore it. The card clips long answers and shows a **Show more** button to reveal the full text. If the answer completes without any text, the card doesn't render. When a [`chat`](/doc/api-reference/widgets/chat/js) widget with the same `agentId` is on the same index, the card shows a **Continue in chat** button and up to two follow-up suggestions from the agent. Both hand the card's conversation to the chat and open it. A suggestion is also sent to the chat as the next message. If the chat is generating a response, the handoff doesn't happen. That makes the widget an [entry point](/doc/api-reference/widgets/chat/js#param-disable-trigger-validation) for the chat, so a page with both doesn't also need a [`chatTrigger`](/doc/api-reference/widgets/chat-trigger/js) widget. Without a matching `chat` widget, the card hides these actions and, in development, logs a warning. ## Activate the card with a rule The widget adds the [rule context](/doc/api-reference/api-parameters/ruleContexts) `agent-studio-result-card-AGENT_ID` to every search, where `_` replaces any character in the agent ID other than letters, digits, `_`, and `-`. Use this context as a rule condition to limit the rule to searches that include the widget. To show the card, add a rule whose consequence sets [`renderingContent`](/doc/api-reference/api-parameters/renderingContent) in its search parameters: ```json JSON icon=braces theme={"system"} { "renderingContent": { "widgets": { "resultCard": { "enabled": true } } } } ``` ## Examples ```js JavaScript icon=code theme={"system"} resultCard({ container: '#result-card', agentId: 'AGENT_ID', }); chat({ container: '#chat', agentId: 'AGENT_ID', }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} resultCard({ container: '#result-card', }); ``` ```js HTMLElement theme={"system"} resultCard({ container: document.querySelector('#result-card'), }); ``` **agentId** `string` *required* The unique identifier of the agent to connect to. You can find the `agentId` in the [Agent Studio dashboard](https://dashboard.algolia.com/generativeAi/agent-studio/agents). It's required even with a custom [`transport`](#param-transport), because the widget derives its rule context from it. The card only hands its conversation to a `chat` widget with the same `agentId`. ```js JavaScript icon=code theme={"system"} resultCard({ // ... agentId: 'AGENT_ID', }); ``` **transport** `object` A custom transport object to send the card's requests to your own backend instead of Agent Studio. It has the same shape as the [`transport`](/doc/api-reference/widgets/chat/js#param-transport) option of the `chat` widget. You can't use it together with [`requestOptions`](#param-request-options). ```js JavaScript icon=code theme={"system"} resultCard({ // ... transport: { api: 'https://chatapi.example.com/api/v1/chat', }, }); ``` **requestOptions** `object` Request options to send with the built-in Agent Studio completion requests. You can't use it together with [`transport`](#param-transport). The object accepts the following properties: * `queryParameters` (`Record`). Query parameters to append to each completion request. * `headers` (`Record | Headers`). Headers to send with each completion request. ```js JavaScript icon=code theme={"system"} resultCard({ // ... requestOptions: { headers: { 'X-Session-Id': 'SESSION_ID', }, }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} resultCard({ // ... templates: { // ... }, }); ``` **cssClasses** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `header`. The header element. * `headerTitle`. The header title element. * `continueButton`. The **Continue in chat** button. * `minimizeButton`. The button that minimizes or maximizes the card. * `body`. The element that holds the answer and clips it. * `loader`. The skeleton placeholder shown while the answer loads. * `message`. Each answer message. * `suggestions`. The follow-up suggestions container. * `expandButton`. The button that shows or clips a long answer. ```js JavaScript icon=code theme={"system"} resultCard({ // ... cssClasses: { root: 'MyCustomResultCard', body: ['MyCustomResultCardBody', 'MyCustomResultCardBody--subclass'], }, }); ``` **translations** `object` A dictionary of translations to customize the UI text and support internationalization. * `headerTitle`. The title displayed in the header. Defaults to `"AI Overview"`. * `continueInChatText`. The text of the button that hands the conversation to the chat. Defaults to `"Continue in chat"`. * `minimizeLabel`. The accessible label of the button that minimizes the card. Defaults to `"Minimize"`. * `maximizeLabel`. The accessible label of the button that maximizes a minimized card. Defaults to `"Maximize"`. * `expandText`. The text of the button that shows a clipped answer in full. Defaults to `"Show more"`. * `collapseText`. The text of the button that clips an expanded answer. Defaults to `"Show less"`. * `retryText`. The text of the retry button shown when the request fails. Defaults to `"Retry"`. ```js JavaScript icon=code theme={"system"} resultCard({ // ... translations: { headerTitle: 'Our pick', continueInChatText: 'Ask a follow-up question', }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **layout** `function` A template to replace the default card with custom markup. The template owns the full rendering, including every status and the retry and handoff actions. It receives the same data as the [connector render state](#connector-render-state). ```js JavaScript icon=code expandable theme={"system"} resultCard({ // ... templates: { layout({ status, messages, error, retry }, { html }) { if (status === 'hidden') { return html``; } if (status === 'failed') { return html`

    ${error ? error.message : 'Something went wrong.'}

    `; } const answer = messages .filter((message) => message.role === 'assistant') .flatMap((message) => message.parts) .filter((part) => part.type === 'text') .map((part) => part.text) .join(''); return html`

    ${answer || 'Loading...'}

    `; }, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ... AI Overview
    ...
    ``` While the answer loads, the body holds a skeleton placeholder instead of the message: ```html HTML icon=code-xml theme={"system"}
    ``` When users collapse the card, the `ais-ResultCard-minimizeButton` button has `aria-expanded="false"` and the `Maximize` label, and the body, the expand button, the **Continue in chat** button, and the suggestions don't render. When users expand a long answer, or when the request fails, the body has the `ais-ResultCard-body--expanded` class. When the answer is complete and isn't clipped, the follow-up suggestions render after the body in a `div` with the `ais-ChatPromptSuggestions ais-ResultCard-suggestions` classes. ## Connector render state Use `connectResultCard` to build a fully custom UI. It accepts the same `agentId`, `transport`, and `requestOptions` options as the widget. Its render state exposes: * `status` (`'hidden' | 'loading' | 'streaming' | 'complete' | 'failed'`). The card status. It's `hidden` when no rule enables the card or when the query is shorter than two words. It's `loading` while the widget waits to send the request and until the response starts streaming. * `query` (`string`). The query the answer is about. * `messages` (`UIMessage[]`). The card's conversation: the user message, then the assistant answer as it streams. * `error` (`Error | undefined`). The error of the latest failed request. It's only set when `status` is `failed`. * `suggestions` (`string[] | undefined`). Follow-up suggestions sent by the agent with the answer. * `retry` (`() => void`). Sends the request again. * `canContinueInChat` (`boolean`). Whether a `chat` widget with the same `agentId` is on the same index. * `continueInChat` (`(message?: string) => void`). Hands the conversation to the `chat` widget and opens it. When you pass `message`, the chat sends it as the next message. * `expanded` (`boolean`). Whether a long answer shows in full instead of clipped. * `setExpanded` (`(expanded: boolean) => void`). Shows or clips a long answer. * `sendEvent` (`function`). Sends an event to the [`insights`](/doc/api-reference/widgets/insights/js) middleware. The user message in `messages` contains the full question that the widget sends to the agent. To display the query, use `query` instead. ```js JavaScript icon=code expandable theme={"system"} import { connectResultCard } from 'instantsearch.js/es/connectors'; const renderResultCard = (renderOptions, isFirstRender) => { const { status, messages, canContinueInChat, continueInChat } = renderOptions; const container = document.querySelector('#result-card'); if (isFirstRender) { return; } if (status === 'hidden') { container.innerHTML = ''; return; } const answer = messages .filter((message) => message.role === 'assistant') .flatMap((message) => message.parts) .filter((part) => part.type === 'text') .map((part) => part.text) .join(''); container.innerHTML = ''; const text = document.createElement('p'); text.textContent = answer || 'Loading...'; container.appendChild(text); if (status === 'complete' && canContinueInChat) { const continueButton = document.createElement('button'); continueButton.textContent = 'Continue in chat'; continueButton.addEventListener('click', () => continueInChat()); container.appendChild(continueButton); } }; const customResultCard = connectResultCard(renderResultCard); search.addWidgets([ customResultCard({ agentId: 'AGENT_ID', }), ]); ``` # ReverseHighlight Source: https://www.algolia.com/doc/api-reference/widgets/reverse-highlight/js Visually highlights the non-matching parts in search results. ```ts Signature theme={"system"} components.ReverseHighlight({ attribute: string, hit: object, // Optional parameters highlightedTagName?: string, nonHighlightedTagName?: string, separator?: string, cssClasses?: object, }); ``` ## About this widget The `reverseHighlight` function returns any attribute from a hit into its highlighted form, when relevant. The function leverages the [highlighting feature of Algolia](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/js) and is designed to work with the [`hits`](/doc/api-reference/widgets/hits/js) or [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widget. ## Examples ```js JavaScript icon=code theme={"system"} hits({ container: "#hits", templates: { item(hit, { html, components }) { return html`

    ${components.ReverseHighlight({ attribute: "name", hit })}

    ${hit.description}

    `; }, }, }); ``` ## Options **attribute** `string` *required* The attribute of the record to highlight. You can give a dot-separated value for deeply nested objects, like `actor.name`. ```js JavaScript icon=code theme={"system"} components.ReverseHighlight({ // ... attribute: "actor.name", }); ``` **hit** `object` *required* The original `hit` object provided to the function. The value is already bound to the function inside a string template, so you don't have to provide it. For this to work, the provided object must have a `_highlightResult[attribute].value` property. ```js JavaScript icon=code theme={"system"} components.ReverseHighlight({ // ... hit: item, }); ``` **highlightedTagName** `string` (default: mark) The name of the HTML element to wrap the highlighted parts of the string. ```js JavaScript icon=code theme={"system"} components.ReverseHighlight({ // ... highlightedTagName: "em", }); ``` **nonHighlightedTagName** `string` (default: span) The HTML element that wraps the non-highlighted parts of the string. ```js JavaScript icon=code theme={"system"} components.ReverseHighlight({ // ... nonHighlightedTagName: "div", }); ``` **separator** `string` The character between each item when the attribute to highlight is an array. ```js JavaScript icon=code theme={"system"} components.ReverseHighlight({ // ... separator: " - ", }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The component's root element. * `highlighted`. The highlighted parts. * `nonHighlighted`. The non-highlighted parts. * `separator`. The separator elements between highlighted parts. ```js JavaScript icon=code theme={"system"} components.ReverseHighlight({ // ... cssClasses: { root: "MyCustomReverseHighlight", highlighted: [ "MyCustomReverseHighlightPart", "MyCustomReverseHighlightPart--subclass", ], }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"} This is the highlighted text ``` # ReverseSnippet Source: https://www.algolia.com/doc/api-reference/widgets/reverse-snippet/js Turns an attribute from a search result into a shortened snippet. ```ts Signature theme={"system"} components.ReverseSnippet({ attribute: string, hit: object, // Optional parameters highlightedTagName?: string, nonHighlightedTagName?: string, separator?: string, cssClasses?: object, }); ``` ## About this widget The `reverseSnippet` function returns any attribute from a hit into its snippeted form, when relevant. This function leverages the [snippeting feature of Algolia](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) and is designed to work with the [`hits`](/doc/api-reference/widgets/hits/js) or [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widget. The `attribute` must be set in [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet), either inside the Algolia dashboard or at runtime: ```js JavaScript icon=code theme={"system"} search.addWidgets([ configure({ attributesToSnippet: ["description"], }), ]); ``` ## Examples ```js JavaScript icon=code theme={"system"} hits({ container: "#hits", templates: { item(hit, { html, components }) { return html`

    ${hit.name}

    ${components.ReverseSnippet({ attribute: "description", hit })}

    `; }, }, }); ``` ## Options **attribute** `string` *required* The attribute of the record to snippet. You can give a dot-separated value for deeply nested objects, like `description.full`. ```js JavaScript icon=code theme={"system"} components.ReverseSnippet({ // ... attribute: "description.full", }); ``` **hit** `object` *required* Original `hit` object provided to the function. The value is already bound to the function inside a string template, so you don't have to provide it. For this to work, the provided object must have a `_snippetResult[attribute].value` property. ```js JavaScript icon=code theme={"system"} components.ReverseSnippet({ // ... hit: item, }); ``` **highlightedTagName** `string` (default: mark) The name of the HTML element to wrap the snippeted parts of the string. ```js JavaScript icon=code theme={"system"} components.ReverseSnippet({ // ... highlightedTagName: "em", }); ``` **nonHighlightedTagName** `string` (default: span) The HTML element that wraps the non-highlighted parts of the string. ```js JavaScript icon=code theme={"system"} components.ReverseSnippet({ // ... nonHighlightedTagName: "div", }); ``` **separator** `string` (default: , ) The character between each item when the attribute to highlight is an array. ```js JavaScript icon=code theme={"system"} components.ReverseSnippet({ // ... separator: " - ", }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The component's root element. * `highlighted`. The highlighted parts. * `nonHighlighted`. The non-highlighted parts. * `separator`. The separator elements between highlighted parts. ```js JavaScript icon=code theme={"system"} components.ReverseSnippet({ // ... cssClasses: { root: "MyCustomReverseSnippet", highlighted: [ "MyCustomReverseSnippetPart", "MyCustomReverseSnippetPart--subclass", ], }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"} This is the highlighted text ``` # searchBox Source: https://www.algolia.com/doc/api-reference/widgets/search-box/js Lets users perform a search using text input. ```ts Signature theme={"system"} searchBox({ container: string | HTMLElement, // Optional parameters placeholder?: string, autofocus?: boolean, ignoreCompositionEvents?: boolean, searchAsYouType?: boolean, showReset?: boolean, showSubmit?: boolean, showLoadingIndicator?: boolean, aiMode?: boolean, queryHook?: function, templates?: object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { searchBox } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { searchBox } = instantsearch.widgets; // or directly use instantsearch.widgets.searchBox() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=searchBox) > > Preview this widget and its behavior. ## About this widget The `searchBox` widget is used to let users perform a text-based 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.). This usually is the main entry point to start the search in an InstantSearch context. It's usually placed at the top of a search experience, so that users can start searching right away. ## Examples ```js JavaScript icon=code theme={"system"} searchBox({ container: "#searchbox", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} searchBox({ container: "#searchbox", }); ``` ```js HTMLElement theme={"system"} searchBox({ container: document.querySelector("#searchbox"), }); ``` **placeholder** `string` The placeholder text of the input. ```js JavaScript icon=code theme={"system"} searchBox({ // ... placeholder: "Search for products", }); ``` **autofocus** `boolean` (default: false) Whether to focus the input when the page loads. ```js JavaScript icon=code theme={"system"} searchBox({ // ... autofocus: true, }); ``` **ignoreCompositionEvents** `boolean` (default: false) Whether to update the search state in the middle of a composition session. This is useful when users need to search using non-latin characters. ```js JavaScript icon=code theme={"system"} searchBox({ // ... ignoreCompositionEvents: true, }); ``` **searchAsYouType** `boolean` (default: true) If `false`, triggers the search only on submit. ```js JavaScript icon=code theme={"system"} searchBox({ // ... searchAsYouType: false, }); ``` **showReset** `boolean` (default: true) Whether to show the reset button. ```js JavaScript icon=code theme={"system"} searchBox({ // ... showReset: false, }); ``` **showSubmit** `boolean` (default: true) Whether to show the submit button. ```js JavaScript icon=code theme={"system"} searchBox({ // ... showSubmit: false, }); ``` **showLoadingIndicator** `boolean` (default: true) Whether to show the loading indicator (replaces the submit button if the search is stalled). ```js JavaScript icon=code theme={"system"} searchBox({ // ... showLoadingIndicator: false, }); ``` **aiMode** `boolean` (default: false) Whether to show an AI Mode button in the search box. When users click this button, it opens the [Chat](/doc/api-reference/widgets/chat/js) widget and sends the current query. To use this option, add a `chat()` widget on the same index. For an end-to-end walkthrough, see [Build an AI-powered search experience](/doc/guides/building-search-ui/going-further/chat-customization/ai-search-experience/js). ```js JavaScript icon=code theme={"system"} searchBox({ // ... aiMode: true, }); ``` **queryHook** `function` A function that's called just before the search is triggered. It takes two parameters: * `query: string`: the current query string * `search: function`: a function to trigger the search. If the `search` method isn't called, no search is made to Algolia and the UI doesn't refresh. If the `search` method is called, the widget is rendered. This can be useful if you need to: * Debounce the number of searches done from the `searchBox`. You can find more information in [the guide on slow network](/doc/guides/building-search-ui/going-further/improve-performance/js). * Programmatically alter the query. ```js JavaScript icon=code theme={"system"} searchBox({ // ... queryHook(query, search) { search(query); }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} searchBox({ // ... templates: { // ... }, }); ``` **cssClasses** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`: the root element of the widget. * `form`: the form element. * `input`: the input element. * `reset`: the reset button element. * `resetIcon`: the reset button icon. * `loadingIndicator`: the loading indicator element. * `loadingIcon`: the loading indicator icon. * `submit`: the submit button element. * `submitIcon`: the submit button icon. * `aiModeButton`: the AI mode button. * `aiModeIcon`: the AI mode icon. * `aiModeLabel`: the AI mode label. ```js JavaScript icon=code theme={"system"} searchBox({ // ... cssClasses: { root: "MyCustomSearchBox", form: ["MyCustomSearchBoxForm", "MyCustomSearchBoxForm--subclass"], }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **submit** `string | function` The template used for displaying the submit button. ```js function theme={"system"} searchBox({ // ... templates: { submit({ cssClasses }, { html }) { return html`submit`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. searchBox({ // ... templates: { submit: "submit", }, }); ``` **reset** `string | function` The template used for displaying the reset button. ```js function theme={"system"} searchBox({ // ... templates: { reset({ cssClasses }, { html }) { return html`reset`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. searchBox({ // ... templates: { reset: "reset", }, }); ``` **loadingIndicator** `string | function` The template used for displaying the loading indicator. ```js function theme={"system"} searchBox({ // ... templates: { loadingIndicator({ cssClasses }, { html }) { return html`loading`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. searchBox({ // ... templates: { loadingIndicator: "loading", }, }); ``` **aiMode** `string | function` The template used for displaying the AI Mode button. ```js function theme={"system"} searchBox({ // ... templates: { aiMode({ cssClasses }, { html }) { return html`Ask AI`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. searchBox({ // ... templates: { aiMode: "Ask AI", }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` ## Customize the UI with `connectSearchBox` If you want to create your own UI of the `searchBox` widget, you can use connectors. To use `connectSearchBox`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectSearchBox } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectSearchBox } = instantsearch.connectors; // or directly use instantsearch.connectors.connectSearchBox() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderSearchBox = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customSearchBox = connectSearchBox(renderSearchBox); // 3. Instantiate search.addWidgets([ customSearchBox({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderSearchBox = (renderOptions, isFirstRender) => { const { query, refine, clear, isSearchStalled, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Render options **query** `string` The query from the current search. ```js JavaScript icon=code theme={"system"} const renderSearchBox = (renderOptions, isFirstRender) => { const { query, refine } = renderOptions; const container = document.querySelector("#searchbox"); if (isFirstRender) { const input = document.createElement("input"); input.addEventListener("input", (event) => { refine(event.target.value); }); container.appendChild(input); } container.querySelector("input").value = query; }; ``` **refine** `function` Sets a new query and triggers a new search. ```js JavaScript icon=code theme={"system"} const renderSearchBox = (renderOptions, isFirstRender) => { const { query, refine } = renderOptions; const container = document.querySelector("#searchbox"); if (isFirstRender) { const input = document.createElement("input"); input.addEventListener("input", (event) => { refine(event.target.value); }); container.appendChild(input); } container.querySelector("input").value = query; }; ``` **clear** `function` Removes the query and triggers a new search. ```js JavaScript icon=code theme={"system"} const renderSearchBox = (renderOptions, isFirstRender) => { const { query, refine, clear } = renderOptions; const container = document.querySelector("#searchbox"); if (isFirstRender) { const input = document.createElement("input"); const button = document.createElement("button"); button.textContent = "X"; input.addEventListener("input", (event) => { refine(event.target.value); }); button.addEventListener("click", () => { clear(); }); container.appendChild(input); container.appendChild(button); } container.querySelector("input").value = query; }; ``` **isSearchStalled** `boolean` Returns `true` if the search results take more than a certain time to come back from Algolia servers. This can be configured on the [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) constructor with the attribute `stalledSearchDelay`. ```js JavaScript icon=code theme={"system"} const renderSearchBox = (renderOptions, isFirstRender) => { const { query, refine, isSearchStalled } = renderOptions; const container = document.querySelector("#searchbox"); if (isFirstRender) { const input = document.createElement("input"); const loadingIndicator = document.createElement("span"); loadingIndicator.textContent = "Loading..."; input.addEventListener("input", (event) => { refine(event.target.value); }); container.appendChild(input); container.appendChild(loadingIndicator); } container.querySelector("input").value = query; container.querySelector("span").hidden = !isSearchStalled; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderRangeInput = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customRangeInput({ container: document.querySelector("#searchbox"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customSearchBox = connectSearchBox(renderSearchBox); search.addWidgets([ customSearchBox({ // Optional parameters queryHook: function, }), ]); ``` #### Instance options **queryHook** `function` A function that's called just before the search is triggered. It takes two parameters * `query: string`: the current query string * `search: function`: a function to trigger the search. If the `search` method isn't called, no search is made to Algolia and the UI doesn't refresh. If the `search` method is called, the widget is rendered. This can be useful if you need to: * debounce the number of searches done from the `searchBox`. You can find more information in [the guide on slow network](/doc/guides/building-search-ui/going-further/improve-performance/js). * programmatically alter the query. ```js JavaScript icon=code theme={"system"} customSearchBox({ queryHook(query, search) { search(query); }, }); ``` ### Full example ```html HTML theme={"system"} ``` ```js JavaScript theme={"system"} // Create a render function const renderSearchBox = (renderOptions, isFirstRender) => { const { query, refine, clear, isSearchStalled, widgetParams } = renderOptions; if (isFirstRender) { const input = document.createElement("input"); const loadingIndicator = document.createElement("span"); loadingIndicator.textContent = "Loading..."; const button = document.createElement("button"); button.textContent = "X"; input.addEventListener("input", (event) => { refine(event.target.value); }); button.addEventListener("click", () => { clear(); }); widgetParams.container.appendChild(input); widgetParams.container.appendChild(loadingIndicator); widgetParams.container.appendChild(button); } widgetParams.container.querySelector("input").value = query; widgetParams.container.querySelector("span").hidden = !isSearchStalled; }; // Create custom widget const customSearchBox = connectSearchBox(renderSearchBox); // Instantiate custom widget search.addWidgets([ customSearchBox({ container: document.querySelector("#searchbox"), }), ]); ``` # SearchBox Source: https://www.algolia.com/doc/api-reference/widgets/search-box/react Lets users perform a search using text input. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} JSX.Element} resetIconComponent={() => JSX.Element} loadingIconComponent={() => JSX.Element} aiModeIconComponent={() => JSX.Element} classNames={object} translations={object} ...props={ComponentProps<'div'>} /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { SearchBox } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=searchBox) > > Preview this widget and its behavior. ## About this widget `SearchBox` is a widget to let users perform a text-based 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.). The search box usually is the main entry point to start the search on an InstantSearch page. You typically place it at the top of a search experience so that users can start searching right away. > [!TIP] > > You can also create your own UI with [`useSearchBox`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, SearchBox } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **placeholder** `string` The placeholder text of the input. ```jsx JavaScript icon=code theme={"system"} ; ``` **queryHook** `(query: string, search: (value: string) =\> void) =\> void` Function called every time the query changes. It takes two parameters: * `query`: The current query. * `search`: The function to trigger the search. This prop can be useful if you need to: * Debounce searches to regulate requests. * Programmatically alter the query before sending it to Algolia. When using this prop, you're responsible for triggering the search with `search`. If you don't call this function, no search is triggered to Algolia. ```jsx JavaScript theme={"system"} const queryHook = (query, search) => { search(query); }; function Search() { return ; } ``` ```tsx TypeScript theme={"system"} import type { SearchBoxProps } from "react-instantsearch"; const queryHook: SearchBoxProps["queryHook"] = (query, search) => { search(query); }; function Search() { return ; } ``` **ignoreCompositionEvents** `boolean` (default: false) Whether to update the search state in the middle of a composition session. This is useful when users need to search using non-latin characters. ```jsx JavaScript icon=code theme={"system"} ; ``` **searchAsYouType** `boolean` (default: true) Whether to make a search on every change to the query. If `false`, new searches are only triggered by clicking the search button or by pressing the `Enter` key while focusing the search box. ```jsx JavaScript icon=code theme={"system"} ; ``` **aiMode** `boolean` (default: false) Whether to show an AI Mode button in the search box. When users click this button, it opens the [Chat](/doc/api-reference/widgets/chat/react) widget and sends the current query. To use this prop, add a `Chat` widget on the same index. For an end-to-end walkthrough, see [Build an AI-powered search experience](/doc/guides/building-search-ui/going-further/chat-customization/ai-search-experience/react). ```jsx JavaScript icon=code theme={"system"} ; ``` **autoFocus** `boolean` (default: false) Whether to focus the input when the page loads. ```jsx JavaScript icon=code theme={"system"} ; ``` **onSubmit** `(event: React.FormEvent\) =\> void` A callback to run when submitting the form of the search box. ```jsx JavaScript icon=code theme={"system"} { // Code to run when the form submits }} />; ``` **submitIconComponent** `(props: IconProps) =\> JSX.Element` A component to replace the icon in the submit button. The component receives the passed [`classNames`](/doc/api-reference/widgets/search-box/react#param-class-names) prop. ```jsx JavaScript icon=code theme={"system"} (
    Submit
    )} />; ``` **resetIconComponent** `(props: IconProps) =\> JSX.Element` A component to replace the icon in the reset button. The component receives the passed [`classNames`](/doc/api-reference/widgets/search-box/react#param-class-names) prop. ```jsx JavaScript icon=code theme={"system"} (
    Reset
    )} />; ``` **loadingIconComponent** `(props: IconProps) =\> JSX.Element` A component to replace the loading icon. The component receives the passed [`classNames`](/doc/api-reference/widgets/search-box/react#param-class-names) prop. ```jsx JavaScript icon=code theme={"system"} (
    Loading
    )} />; ``` **aiModeIconComponent** `(props: IconProps) =\> JSX.Element` A component to replace the icon in the AI Mode button. The component receives the passed [`classNames`](/doc/api-reference/widgets/search-box/react#param-class-names) prop. ```jsx JavaScript icon=code theme={"system"} (
    AI
    )} />; ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `form`. The form element. * `input`. The input element. * `submit`. The submit button. * `reset`. The reset button. * `loadingIndicator`. The loading indicator element. * `submitIcon`. The submit icon. * `resetIcon`. The reset icon. * `loadingIcon`. The loading icon. * `aiModeButton`. The AI mode button. * `aiModeIcon`. The AI mode icon. * `aiModeLabel`. The AI mode label. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `submitButtonTitle`. The submit button's title. * `resetButtonTitle`. The reset button's title. * `aiModeButtonTitle`. The AI mode button's title. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `SearchBox` widget with `useSearchBox`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useSearchBox` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useSearchBox } from "react-instantsearch"; function CustomSearchBox(props) { const { query, refine, clear } = useSearchBox(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **queryHook** `(query: string, hook: (value: string) =\> void) =\> void` Function called every time the query changes. See [`queryHook`](/doc/api-reference/widgets/search-box/react#param-query-hook) for detail. ```jsx JavaScript theme={"system"} const queryHook = (query, search) => { search(query); }; function SearchBox() { const searchBoxApi = useSearchBox({ // ... queryHook, }); return <>{/*Your JSX*/}; } ``` ```tsx TypeScript theme={"system"} import type { UseSearchBoxProps } from "react-instantsearch"; const queryHook: UseSearchBoxProps["queryHook"] = (query, search) => { search(query); }; function SearchBox() { const searchBoxApi = useSearchBox({ // ... queryHook, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **query** `string` The query from the last search. **refine** `(value: string) =\> void` Sets a new query and searches. **clear** `() =\> void` Clears the query and searches. **isSearchStalled** `boolean` *deprecated* > Use `status` from [`useInstantSearch`](/doc/api-reference/widgets/use-instantsearch/react) instead. Whether the search results take more than a certain time to come back from Algolia servers. This can be configured on [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) with the `stalledSearchDelay` props which defaults to 200 ms. ### Example ```jsx JavaScript theme={"system"} import React, { useState, useRef } from "react"; import { useInstantSearch, useSearchBox } from "react-instantsearch"; function CustomSearchBox(props) { const { query, refine } = useSearchBox(props); const { status } = useInstantSearch(); const [inputValue, setInputValue] = useState(query); const inputRef = useRef(null); const isSearchStalled = status === "stalled"; function setQuery(newQuery) { setInputValue(newQuery); refine(newQuery); } return (
    { event.preventDefault(); event.stopPropagation(); if (inputRef.current) { inputRef.current.blur(); } }} onReset={(event) => { event.preventDefault(); event.stopPropagation(); setQuery(""); if (inputRef.current) { inputRef.current.focus(); } }} > { setQuery(event.currentTarget.value); }} autoFocus />
    ); } ``` ```tsx TypeScript theme={"system"} import React, { useState, useRef } from "react"; import { useInstantSearch, useSearchBox, UseSearchBoxProps, } from "react-instantsearch"; function CustomSearchBox(props: UseSearchBoxProps) { const { query, refine } = useSearchBox(props); const { status } = useInstantSearch(); const [inputValue, setInputValue] = useState(query); const inputRef = useRef(null); const isSearchStalled = status === "stalled"; function setQuery(newQuery: string) { setInputValue(newQuery); refine(newQuery); } return (
    { event.preventDefault(); event.stopPropagation(); if (inputRef.current) { inputRef.current.blur(); } }} onReset={(event) => { event.preventDefault(); event.stopPropagation(); setQuery(""); if (inputRef.current) { inputRef.current.focus(); } }} > { setQuery(event.currentTarget.value); }} autoFocus />
    ); } ``` # simple Source: https://www.algolia.com/doc/api-reference/widgets/simple-state-mapping/js Default mapping between InstantSearch state and the URL. > [!NOTE] > > You are viewing the documentation for **InstantSearch.js v4**. > To upgrade from v3, see the [migration guide](/doc/guides/building-search-ui/upgrade-guides/js#upgrade-from-v3-to-v4). > Looking for the v3 version of this page? > [View the v3 docs](https://algolia.com/old-docs/deprecated/instantsearch/js/v3/api-reference/simple-state-mapping). ```js Signature theme={"system"} const routing = { stateMapping: simple(), }; ``` ## Import ```js Package manager theme={"system"} import { simple } from "instantsearch.js/es/lib/stateMappings"; ``` ```js CDN theme={"system"} const { simple } = instantsearch.stateMappings; // or directly use instantsearch.stateMappings.simple() ``` ## About this widget This `simple` state mapping is the default for the `instantsearch` object's [routing](/doc/api-reference/widgets/instantsearch/js#param-routing) option. The router provides an API that lets you customize some of its behaviors. To get a sense of what you can do with the API, see [Routing URLs](/doc/guides/building-search-ui/going-further/routing-urls/js). The only transformation applied by the function is the omission of `configure`. ```js JavaScript icon=code theme={"system"} simple().stateToRoute({ instant_search: { query: "Apple", page: 5, configure: { hitsPerPage: 4, }, }, }); // gives as output: // { // instant_search: { // query: 'Apple', // page: 5, // }, // } ``` ## Examples ```js JavaScript icon=code theme={"system"} instantsearch({ // ... routing: { stateMapping: simple(), }, }); ``` # singleIndex Source: https://www.algolia.com/doc/api-reference/widgets/single-index-state-mapping/js Mapping between search state and browser URL for backwards compatibility with InstantSearch v3. ```ts Signature theme={"system"} singleIndex( indexName: string, ); ``` ## Import ```js Package manager theme={"system"} import { singleIndex } from "instantsearch.js/es/lib/stateMappings"; ``` ```js CDN theme={"system"} const { singleIndex } = instantsearch.stateMappings; // or directly use instantsearch.stateMappings.singleIndex() ``` ## About this widget The `singleIndex` state mapping can be applied to the `instantsearch` object's [routing](/doc/api-reference/widgets/instantsearch/js#param-routing) option. It enables backward compatibility with version 3.x. The mapping applies the following transformations: * Omits the `configure` widget * Uses the given index for the top level ```js JavaScript icon=code theme={"system"} singleIndex("instant_search").stateToRoute({ instant_search: { query: "Apple", page: 5, configure: { hitsPerPage: 4, }, }, }); // { // query: 'Apple', // page: 5, // } ``` ## Examples ```js JavaScript icon=code theme={"system"} instantsearch({ // ... routing: { stateMapping: singleIndex("instant_search"), }, }); ``` ## Options **indexName** `string` *required* The name of the index to get the state from, usually the same value provided to [`instantsearch`](/doc/api-reference/widgets/instantsearch/js). ```js JavaScript icon=code theme={"system"} singleIndex("instant_search"); ``` # Snippet Source: https://www.algolia.com/doc/api-reference/widgets/snippet/js Shortens attributes from hits for display in search results. ```ts Signature theme={"system"} components.Snippet({ attribute: string, hit: object, // Optional parameters highlightedTagName?: string, nonHighlightedTagName?: string, separator?: string, cssClasses?: object, }); ``` ## About this widget The `snippet` function returns attributes in your search results in a shorter form (a snippet). Snippeted attributes are also [highlighted](/doc/api-reference/widgets/highlight/js). This function uses Algolia's [snippeting feature](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/js#snippeting) in combination with the [`hits`](/doc/api-reference/widgets/hits/js) or [`infiniteHits`](/doc/api-reference/widgets/infinite-hits/js) widgets. To determine which [attributes](#param-attribute) should be snippeted, set them from [the Algolia dashboard](https://dashboard.algolia.com/explorer/configuration/snippeting), [the CLI](/doc/tools/cli/commands/objects/browse#highlighting-and-snippeting), or with the API (using the [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet) parameter): ```js JavaScript icon=code theme={"system"} search.addWidgets([ configure({ attributesToSnippet: ["description"], }), ]); ``` With `attributesToSnippet`, you can also set the snippet's size to a specific number of words (it defaults to 10). For example, `attributesToSnippet: ['description:5']`. ## Examples ```js JavaScript icon=code theme={"system"} hits({ container: "#hits", templates: { item(hit, { html, components }) { return html`

    ${hit.name}

    ${components.Snippet({ attribute: "description", hit })}

    `; }, }, }); ``` ## Options **attribute** `string` *required* The attribute of the record to snippet. For deeply nested objects, specify a dot-separated value like `actor.bio`. ```js JavaScript icon=code theme={"system"} components.Snippet({ // ... attribute: "description.full", }); ``` **hit** `object` *required* Original `hit` object provided to the function. The value is already bound to the function inside a string template, so you don't have to provide it. For this to work, the provided object must have a `_snippetResult[attribute].value` property. ```js JavaScript icon=code theme={"system"} components.Snippet({ // ... hit: item, }); ``` **highlightedTagName** `string` (default: mark) The name of the HTML element to wrap the snippeted parts of the string. ```js JavaScript icon=code theme={"system"} components.Snippet({ // ... highlightedTagName: "em", }); ``` **nonHighlightedTagName** `string` (default: span) The HTML element that wraps the non-highlighted parts of the string. ```js JavaScript icon=code theme={"system"} components.Snippet({ // ... nonHighlightedTagName: "div", }); ``` **separator** `string` (default: , ) The character between each item when the attribute to highlight is an array. ```js JavaScript icon=code theme={"system"} components.Snippet({ // ... separator: " - ", }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The component's root element. * `highlighted`. The highlighted parts. * `nonHighlighted`. The non-highlighted parts. * `separator`. The separator elements between highlighted parts. ```js JavaScript icon=code theme={"system"} components.Snippet({ // ... cssClasses: { root: "MyCustomSnippet", highlighted: ["MyCustomSnippetPart", "MyCustomSnippetPart--subclass"], }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"} This is the highlighted text ``` # sortBy Source: https://www.algolia.com/doc/api-reference/widgets/sort-by/js Shows a list of indices for alternative sorting or ranking of search results. ```ts Signature theme={"system"} sortBy({ container: string | HTMLElement, items: object[], // Optional parameters cssClasses?: object, transformItems?: function, }); ``` ```js Package manager theme={"system"} import { sortBy } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { sortBy } = instantsearch.widgets; // or directly use instantsearch.widgets.sortBy() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=sortBy) > > Preview this widget and its behavior. ## About this widget This widget displays a list of indices, allowing a user to change the way hits are sorting (with [replica indices](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas)). Another common use case for this widget is to let users switch between different indices. For this widget to work, you must define all indices that you pass down as options as replicas of the main 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.). ## Examples ```js JavaScript icon=code theme={"system"} sortBy({ container: "#sort-by", items: [ { label: "Featured", value: "instant_search" }, { label: "Price (asc)", value: "instant_search_price_asc" }, { label: "Price (desc)", value: "instant_search_price_desc" }, ], }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} sortBy({ // ... container: "#sort-by", }); ``` ```js HTMLElement theme={"system"} sortBy({ // ... container: document.querySelector("#sort-by"), }); ``` **items** `object[]` *required* The list of indices to search in, with each item: * `label: string`. The label of the index to display. * `value: string`. The name of the index to target. ```js JavaScript icon=code theme={"system"} sortBy({ // ... items: [ { label: "Featured", value: "instant_search" }, { label: "Price (asc)", value: "instant_search_price_asc" }, { label: "Price (desc)", value: "instant_search_price_desc" }, ], }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `select`. The`select` element. * `option`. The `option` elements of the `select`. ```js JavaScript icon=code theme={"system"} sortBy({ // ... cssClasses: { root: "MyCustomSortBy", select: ["MyCustomSortBySelect", "MyCustomSortBySelect--subclass"], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} sortBy({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or, combined with results sortBy({ // ... transformItems(items, { results }) { return results?.nbPages < 4 ? items.filter((item) => item.value === "instant_search") : items; }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectSortBy` If you want to create your own UI of the `sortBy` widget, you can use connectors. To use `connectSortBy`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectSortBy } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectSortBy } = instantsearch.connectors; // or directly use instantsearch.connectors.connectSortBy() ``` Then it's a 3-step process: ```jsx JavaScript icon=code theme={"system"} // 1. Create a render function const renderSortBy = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customSortBy = connectSortBy(renderSortBy); // 3. Instantiate search.addWidgets([ customSortBy({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderSortBy = (renderOptions, isFirstRender) => { const { options, currentRefinement, canRefine, refine, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **options** `object[]` The list of items the widget can display, with each item: * `label: string`. The label of the index to display. * `value: string`. The name of the index to target. ```js JavaScript icon=code theme={"system"} const renderSortBy = (renderOptions, isFirstRender) => { const { options } = renderOptions; document.querySelector("#sort-by").innerHTML = ` `; }; ``` **currentRefinement** `string` The currently selected index. ```js JavaScript icon=code theme={"system"} const renderSortBy = (renderOptions, isFirstRender) => { const { options, currentRefinement } = renderOptions; document.querySelector("#sort-by").innerHTML = ` `; }; ``` **canRefine** `boolean` Whether the search can be refined. ```js JavaScript icon=code theme={"system"} const renderSortBy = (renderOptions, isFirstRender) => { // `canRefine` is only available from v4.45.0 // Use `hasNoResults` in earlier minor versions. const { options, currentRefinement, canRefine } = renderOptions; document.querySelector("#sort-by").innerHTML = ` `; }; ``` **refine** `function` Switches indices and triggers a new search. ```js JavaScript icon=code theme={"system"} const renderSortBy = (renderOptions, isFirstRender) => { const { options, currentRefinement, refine } = renderOptions; const container = document.querySelector("#sort-by"); if (isFirstRender) { const select = document.createElement("select"); select.addEventListener("change", (event) => { refine(event.target.value); }); container.appendChild(select); } container.querySelector("select").innerHTML = ` ${options .map( (option) => ` `, ) .join("")} `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderSortBy = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customSortBy({ container: document.querySelector("#sort-by"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customSortBy = connectSortBy( renderSortBy ); search.addWidgets([ customSortBy({ items: object[], // Optional parameters transformItems: function, }) ]); ``` #### Instance options **items** `object[]` *required* The list of indices to search in, with each item: * `label: string`. The label of the index to display. * `value: string`. The name of the index to target. ```js JavaScript icon=code theme={"system"} customSortBy({ items: [ { label: "Featured", value: "instant_search" }, { label: "Price (asc)", value: "instant_search_price_asc" }, { label: "Price (desc)", value: "instant_search_price_desc" }, ], }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customSortBy({ transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, }); // or, combined with results customSortBy({ // ... transformItems(items, { results }) { return results?.nbPages < 4 ? items.filter((item) => item.value === "instant_search") : items; }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderSortBy = (renderOptions, isFirstRender) => { const { options, currentRefinement, refine, widgetParams, // `canRefine` is only available from v4.45.0 // Use `hasNoResults` in earlier minor versions. canRefine, } = renderOptions; if (isFirstRender) { const select = document.createElement("select"); select.addEventListener("change", (event) => { refine(event.target.value); }); widgetParams.container.appendChild(select); } const select = widgetParams.container.querySelector("select"); select.disabled = !canRefine; select.innerHTML = ` ${options .map( (option) => ` `, ) .join("")} `; }; // Create the custom widget const customSortBy = connectSortBy(renderSortBy); // Instantiate the custom widget search.addWidgets([ customSortBy({ container: document.querySelector("#sort-by"), items: [ { label: "Featured", value: "instant_search" }, { label: "Price (asc)", value: "instant_search_price_asc" }, { label: "Price (desc)", value: "instant_search_price_desc" }, ], }), ]); ``` # stats Source: https://www.algolia.com/doc/api-reference/widgets/stats/js Shows data about the performed search. ```ts Signature theme={"system"} stats({ container: string | HTMLElement, // Optional parameters templates?: object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { stats } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { stats } = instantsearch.widgets; // or directly use instantsearch.widgets.stats() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=stats) > > Preview this widget and its behavior. ## About this widget The `stats` widget displays the total number of matching hits and the time it took to get them (time spent in the Algolia server). ## Examples ```js JavaScript icon=code theme={"system"} stats({ container: "#stats", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} stats({ container: "#stats", }); ``` ```js HTMLElement theme={"system"} stats({ container: document.querySelector("#stats"), }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} stats({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `text`. The text element. ```js JavaScript icon=code theme={"system"} stats({ // ... cssClasses: { root: "MyCustomStats", text: ["MyCustomStatsText", "MyCustomStatsText--subclass"], }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **text** `string | function` The template to use to customize the text. It exposes: * `hitsPerPage: number`. The maximum number of hits returned per page. * `nbPages: number`. The number of pages returned. Calculation is based on the total number of hits (`nbHits`) divided by the number of hits per page (`hitsPerPage`), rounded up to the nearest integer. * `nbHits: number`. The number of hits matched by 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.). * `areHitsSorted: boolean`. True if the index is currently using relevant sort and displaying only sorted hits. * `nbSortedHits: number`. The number of sorted hits matched by the query (when using relevant sort). * `page: number`. The index of the current page (zero-based). * `processingTimeMS: number`. The time the server took to process the search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.), in milliseconds. This doesn't include network time. * `query: string`. The query text. - `hasManyResults: boolean`. Indicates whether the search has more than one result. - `hasNoResults: boolean`. `true` when the search doesn't return any results. - `hasOneResult: boolean`. Indicates whether the search has one result. - `areHitsSorted: boolean`. Indicates whether Relevant sort is applied to the result. - `nbSortedHits: number`. The number of sorted hits from Relevant sort. - `hasManySortedResults: boolean`. Indicates whether the search has more than one sorted result. - `hasNoSortedResults: boolean`. `true` when the search doesn't return any sorted results. - `hasOneSortedResults: boolean`. Indicates whether the search has one sorted result. - `cssClasses: object`. Same option as the `cssClasses` parameter provided to the widget. ```js function theme={"system"} stats({ // ... templates: { text(data, { html }) { let count = ""; if (data.hasManyResults) { count += `${data.nbHits} results`; } else if (data.hasOneResult) { count += `1 result`; } else { count += `no result`; } return html`${count} found in ${data.processingTimeMS}ms`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. stats({ // ... templates: { text: ` {{#areHitsSorted}} {{#hasNoSortedResults}}No relevant results{{/hasNoSortedResults}} {{#hasOneSortedResults}}1 relevant result{{/hasOneSortedResults}} {{#hasManySortedResults}}{{#helpers.formatNumber}}{{nbSortedHits}}{{/helpers.formatNumber}} relevant results{{/hasManySortedResults}} sorted out of {{#helpers.formatNumber}}{{nbHits}}{{/helpers.formatNumber}} {{/areHitsSorted}} {{^areHitsSorted}} {{#hasNoResults}}No results{{/hasNoResults}} {{#hasOneResult}}1 result{{/hasOneResult}} {{#hasManyResults}}{{#helpers.formatNumber}}{{nbHits}}{{/helpers.formatNumber}} results{{/hasManyResults}} {{/areHitsSorted}} found in {{processingTimeMS}}ms `, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    7,435 relevant results sorted out of 20,337 found in 1ms.
    20,337 results found in 1ms.
    ``` ## Customize the UI with `connectStats` If you want to create your own UI of the `stats` widget, you can use connectors. To use `connectStats`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectStats } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectStats } = instantsearch.connectors; // or directly use instantsearch.connectors.connectStats() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderStats = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customStats = connectStats(renderStats); // 3. Instantiate search.addWidgets([ customStats({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { hitsPerPage, nbHits, areHitsSorted, nbSortedHits, nbPages, page, processingTimeMS, query, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **hitsPerPage** `number` The maximum number of hits returned per page. ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { hitsPerPage } = renderOptions; document.querySelector("#stats").innerHTML = `

    ${hitsPerPage} hits per page

    `; }; ``` **nbHits** `number` The number of hits matched by the query. ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { nbHits } = renderOptions; document.querySelector("#stats").innerHTML = `

    ${nbHits} hits

    `; }; ``` **areHitsSorted** `boolean` Indicates whether relevant sort is applied to the result. ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { areHitsSorted } = renderOptions; document.querySelector("#stats").innerHTML = `

    You're seeing ${areHitsSorted ? "relevant" : "all"} results.

    `; }; ``` **nbSortedHits** `number` The number of sorted hits from relevant sort. ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { nbSortedHits, nbHits } = renderOptions; document.querySelector("#stats").innerHTML = `

    ${nbSortedHits} relevant hit(s) sorted out of ${nbHits}

    `; }; ``` **nbPages** `number` The number of returned pages. The calculation is based on the total number of hits (`nbHits`) divided by the number of hits per page (`hitsPerPage`), rounded up to the nearest integer. ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { nbPages } = renderOptions; document.querySelector("#stats").innerHTML = `

    ${nbPages} pages

    `; }; ``` **page** `number` The position of the current page (zero-based). ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { page, nbPages } = renderOptions; document.querySelector("#stats").innerHTML = `

    ${page + 1}/${nbPages} pages

    `; }; ``` **processingTimeMS** `number` The time the server took to process the request, in milliseconds. This doesn't include network time. ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { processingTimeMS } = renderOptions; document.querySelector("#stats").innerHTML = `

    Results found in ${processingTimeMS}ms

    `; }; ``` **query** `string` The query sent to the server. ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { query } = renderOptions; document.querySelector("#stats").innerHTML = ` ${query} `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderStats = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customStats({ container: document.querySelector("#stats"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customStats = connectStats(renderStats); search.addWidgets([customStats()]); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderStats = (renderOptions, isFirstRender) => { const { nbHits, areHitsSorted, nbSortedHits, processingTimeMS, query, widgetParams, } = renderOptions; if (isFirstRender) { return; } let count = ""; if (areHitsSorted) { if (nbSortedHits > 1) { count = `${nbSortedHits} relevant results`; } else if (nbSortedHits === 1) { count = "1 relevant result"; } else { count = "No relevant result"; } count += ` sorted out of ${nbHits}`; } else { if (nbHits > 1) { count += `${nbHits} results`; } else if (nbHits === 1) { count += "1 result"; } else { count += "no result"; } } widgetParams.container.innerHTML = ` ${count} found in ${processingTimeMS}ms ${query ? `for ${query}` : ""} `; }; // Create the custom widget const customStats = connectStats(renderStats); // Instantiate the custom widget search.addWidgets([ customStats({ container: document.querySelector("#stats"), }), ]); ``` # toggleRefinement Source: https://www.algolia.com/doc/api-reference/widgets/toggle-refinement/js Lets users toggle a specific filter. ```ts Signature theme={"system"} toggleRefinement({ container: string | HTMLElement, attribute: string, // Optional parameters on?: boolean | number | string, off?: boolean | number | string, templates?: object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { toggleRefinement } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { toggleRefinement } = instantsearch.widgets; // or directly use instantsearch.widgets.toggleRefinement() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=toggleRefinement) > > Preview this widget and its behavior. ## About this widget The `toggleRefinement` widget provides an on/off filtering feature based on an attribute value. ### Requirements Ensure that the attribute provided is already declared as an [attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting). ## Examples ```js JavaScript icon=code theme={"system"} toggleRefinement({ container: "#toggle-refinement", attribute: "free_shipping", templates: { labelText({ count }, { html }) { return html`Free shipping (${count.toLocaleString()})`; }, }, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} toggleRefinement({ // ... container: "#toggle-refinement", }); ``` ```js HTMLElement theme={"system"} toggleRefinement({ // ... container: document.querySelector("#toggle-refinement"), }); ``` **attribute** `string` *required* The name of the attribute on which apply the refinement. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```js JavaScript icon=code theme={"system"} toggleRefinement({ // ... attribute: "free_shipping", }); ``` **on** `boolean | number | string` (default: true) The value of the refinement to apply on the attribute when checked. ```js boolean theme={"system"} toggleRefinement({ // ... on: false, }); ``` ```js number theme={"system"} toggleRefinement({ // ... on: 10, }); ``` ```js string theme={"system"} toggleRefinement({ // ... on: "apple", }); ``` **off** `boolean | number | string` The value of the refinement to apply on the attribute when unchecked. ```js boolean theme={"system"} toggleRefinement({ // ... off: false, }); ``` ```js number theme={"system"} toggleRefinement({ // ... off: 10, }); ``` ```js string theme={"system"} toggleRefinement({ // ... off: "apple", }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} toggleRefinement({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `label`. The label of the toggle. * `checkbox`. The checkbox element of the toggle. * `labelText`. The label text of the toggle. ```js JavaScript icon=code theme={"system"} toggleRefinement({ // ... cssClasses: { root: "MyCustomToggleRefinement", label: [ "MyCustomToggleRefinementLabel", "MyCustomToggleRefinementLabel--subclass", ], }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **labelText** `string | function` The template to use to customize the label. It exposes: * `isRefined: boolean`. Indicates whether the checkbox is checked. * `count: number`. Shows the number of results after the refinement was applied. * `onFacetValue: object`. Describes the value for the attribute. It contains `count` (useful to get the raw value of the count). * `offFacetValue: object`. Describes the value for the attribute. It contains `count` (useful to get the raw value of the count). ```js function theme={"system"} toggleRefinement({ // ... templates: { labelText({ count }, { html }) { return html`Free shipping (${count.toLocaleString()})`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. toggleRefinement({ // ... templates: { labelText: "Free shipping ({{ count }})", }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` ## Customize the UI with `connectToggleRefinement` If you want to create your own UI of the `toggleRefinement` widget, you can use connectors. To use `connectToggleRefinement`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectToggleRefinement } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectToggleRefinement } = instantsearch.connectors; // or directly use instantsearch.connectors.connectToggleRefinement() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderToggleRefinement = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customToggleRefinement = connectToggleRefinement(renderToggleRefinement); // 3. Instantiate search.addWidgets([ customToggleRefinement({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderToggleRefinement = (renderOptions, isFirstRender) => { const { value, canRefine, refine, sendEvent, createURL, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **value** `object` The current refinement, with: * `isRefined: boolean`. Indicates whether the checkbox is checked. * `count: number`. Shows the number of results after the refinement was applied. * `onFacetValue: object`. Describes the value for the attribute. It contains `count` (useful to get the raw value of the count). * `offFacetValue: object`. Describes the value for the attribute. It contains `count` (useful to get the raw value of the count). ```js JavaScript icon=code theme={"system"} const renderToggleRefinement = (renderOptions, isFirstRender) => { const { value } = renderOptions; document.querySelector("#toggle-refinement").innerHTML = ` `; }; ``` **canRefine** `boolean` *required* Indicates if search state can be refined. ```js JavaScript icon=code theme={"system"} const { canRefine } = renderOptions; if (!canRefine) { document.querySelector("#toggle-refinement").innerHTML = ""; return; } ``` **refine** `function` Updates to the next state by applying the toggle refinement. ```js JavaScript icon=code theme={"system"} const renderToggleRefinement = (renderOptions, isFirstRender) => { const { value, refine } = renderOptions; const container = document.querySelector("#toggle-refinement"); if (isFirstRender) { const label = document.createElement("label"); const input = document.createElement("input"); input.type = "checkbox"; input.addEventListener("change", (event) => { refine({ isRefined: !event.target.checked }); }); label.appendChild(input); label.appendChild(document.createTextNode("Free shipping")); container.appendChild(label); } container.querySelector("input").checked = value.isRefined; }; ``` **sendEvent** `(eventType, facetValue) =\> void` The function to send `click` events. The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/js) middleware. * `eventType: 'click'` * `facetValue: string` ```js JavaScript icon=code theme={"system"} // For example, sendEvent("click", true); /* A payload like the following will be sent to the `insights` middleware. { eventType: 'click', insightsMethod: 'clickedFilters', payload: { eventName: 'Filter Applied', filters: ['isShippingFree:true'], index: '', }, widgetType: 'ais.toggleRefinement', } */ ``` **createURL** `function` Generates a URL for the next state. ```js JavaScript icon=code theme={"system"} const renderToggleRefinement = (renderOptions, isFirstRender) => { const { createURL } = renderOptions; document.querySelector("#toggle-refinement").innerHTML = ` Link to the next state `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderToggleRefinement = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; // ... search.addWidgets([ customToggleRefinement({ container: document.querySelector("#toggle-refinement"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```tsx TSX theme={"system"} const customToggleRefinement = connectToggleRefinement(renderToggleRefinement); search.addWidgets([ customToggleRefinement({ attribute: string, // Optional parameters on: boolean | number | string, off: boolean | number | string, }), ]); ``` #### Instance options **attribute** `string` *required* The name of the attribute on which to apply the refinement. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```js JavaScript icon=code theme={"system"} customToggleRefinement({ attribute: "free_shipping", }); ``` **on** `boolean | number | string` (default: true) The value of the refinement to apply on the attribute when checked. ```js boolean theme={"system"} customToggleRefinement({ // ... on: false, }); ``` ```js number theme={"system"} customToggleRefinement({ // ... on: 10, }); ``` ```js string theme={"system"} customToggleRefinement({ // ... on: "apple", }); ``` **off** `boolean | number | string` The value of the refinement to apply on the attribute when unchecked. ```js boolean theme={"system"} customToggleRefinement({ // ... off: false, }); ``` ```js number theme={"system"} customToggleRefinement({ // ... off: 10, }); ``` ```js string theme={"system"} customToggleRefinement({ // ... off: "apple", }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderToggleRefinement = (renderOptions, isFirstRender) => { const { value, refine, widgetParams } = renderOptions; if (isFirstRender) { const label = document.createElement("label"); const input = document.createElement("input"); input.type = "checkbox"; const span = document.createElement("span"); input.addEventListener("change", (event) => { refine({ isRefined: !event.target.checked }); }); label.appendChild(input); label.appendChild(document.createTextNode("Free shipping")); label.appendChild(span); widgetParams.container.appendChild(label); } widgetParams.container.querySelector("input").checked = value.isRefined; widgetParams.container.querySelector("span").innerHTML = value.count !== null ? ` (${value.count})` : ""; }; // Create the custom widget const customToggleRefinement = connectToggleRefinement(renderToggleRefinement); // Instantiate the custom widget search.addWidgets([ customToggleRefinement({ container: document.querySelector("#toggle-refinement"), attribute: "free_shipping", }), ]); ``` # trendingFacets Source: https://www.algolia.com/doc/api-reference/widgets/trending-facets/js Shows a list of trending facet values. > [!NOTE] > > If you're using the deprecated [`recommend-js`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js) UI library, > see [`trendingFacets`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js/trendingFacets). ```ts Signature theme={"system"} trendingFacets({ container: string | HTMLElement, facetName: string, // Optional parameters limit?: number, threshold?: number, fallbackParameters?: object, escapeHTML?: boolean, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { trendingFacets } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { trendingFacets } = instantsearch.widgets; // or directly use instantsearch.widgets.trendingFacets() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=trendingFacets) > > Preview this widget and its behavior. ## About this widget Use the `trendingFacets` widget to display a list of trending facet values. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) ## Examples ```js JavaScript icon=code theme={"system"} trendingFacets({ container: "#trendingFacets", facetName: "brand", templates: { item(item, { html }) { return html`${item.facetValue}`; }, }, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} trendingFacets({ // ... container: '#trendingFacets', }); ``` ```js HTMLElement theme={"system"} trendingFacets({ container: document.querySelector("#trendingFacets"), }); ``` **facetName** `string` *required* The facet attribute to get trending values for. ```js JavaScript icon=code theme={"system"} trendingFacets({ // ... facetName: "brand", }); ``` **limit** `number` The number of trending facet values to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} trendingFacets({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} trendingFacets({ // ... threshold: 80, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send when there aren't enough recommendations. ```js JavaScript icon=code theme={"system"} trendingFacets({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} trendingFacets({ // ... escapeHTML: false, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} trendingFacets({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The widget's root element. * `emptyRoot`. The container element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of trending facet values. * `item`. The list item. ```js JavaScript icon=code theme={"system"} trendingFacets({ // ... cssClasses: { root: "MyCustomTrendingFacets", list: ["MyCustomTrendingFacets", "MyCustomTrendingFacets--subclass"], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} trendingFacets({ // ... transformItems(items) { return items.map((item) => ({ ...item, facetValue: item.facetValue.toUpperCase(), })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **empty** `string | function` The template to use when there are no trending facet values. ```js function theme={"system"} trendingFacets({ // ... templates: { empty(_, { html }) { return html`

    No trending facets.

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. trendingFacets({ // ... templates: { empty: "No trending facets.", }, }); ``` **header** `string | function` The template to use for the widget header. This template receives the items as well as the `cssClasses` object. ```js function theme={"system"} trendingFacets({ // ... templates: { header({ cssClasses, items }, { html }) { return html`

    Trending (${items.length})

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. trendingFacets({ // ... templates: { header: "

    Trending ({{items.length}})

    ", }, }); ``` **item** `string | function` The template to use for each trending facet value. This template receives an object with `facetName`, `facetValue`, and `_score`. ```js function theme={"system"} trendingFacets({ // ... templates: { item(item, { html }) { return html`${item.facetValue}`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. trendingFacets({ // ... templates: { item: `{{ facetValue }}`, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Trending

    1. ...
    2. ...
    3. ...
    ``` ## Customize the UI with `connectTrendingFacets` If you want to create your own UI of the `trendingFacets` widget, you can use connectors. To use `connectTrendingFacets`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectTrendingFacets } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectTrendingFacets } = instantsearch.connectors; // or directly use instantsearch.connectors.connectTrendingFacets() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderTrendingFacets = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customTrendingFacets = connectTrendingFacets(renderTrendingFacets); // 3. Instantiate search.addWidgets([ customTrendingFacets({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderTrendingFacets = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` The trending facet values from the Algolia Recommend API. Each item has `facetName`, `facetValue`, and `_score` properties. ```js JavaScript icon=code theme={"system"} const renderTrendingFacets = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector('#trendingFacets').innerHTML = `
      ${items .map(item => `
    • ${item.facetValue}
    • `) .join('')}
    `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderTrendingFacets = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = '...'; }; // ... search.addWidgets([ customTrendingFacets({ // ... container: document.querySelector('#trendingFacets'), }) ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customTrendingFacets = connectTrendingFacets( renderTrendingFacets ); search.addWidgets([ customTrendingFacets({ // Required parameter facetName: string, // Optional parameters limit: number, threshold: number, fallbackParameters: object, escapeHTML: boolean, transformItems: function, }) ]); ``` #### Instance options **facetName** `string` *required* The facet attribute to get trending values for. ```js JavaScript icon=code theme={"system"} customTrendingFacets({ // ... facetName: "brand", }); ``` **limit** `number` The number of trending facet values to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} customTrendingFacets({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} customTrendingFacets({ // ... threshold: 80, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send when there aren't enough recommendations. ```js JavaScript icon=code theme={"system"} customTrendingFacets({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} customTrendingFacets({ // ... escapeHTML: false, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customTrendingFacets({ // ... transformItems(items) { return items.map((item) => ({ ...item, facetValue: item.facetValue.toUpperCase(), })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderTrendingFacets = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; widgetParams.container.innerHTML = `
      ${items .map(item => `
    • ${item.facetValue}
    • `) .join('')}
    `; }; // Create the custom widget const customTrendingFacets = connectTrendingFacets( renderTrendingFacets ); // Instantiate the custom widget search.addWidgets([ customTrendingFacets({ container: document.querySelector('#trendingFacets'), facetName: 'brand', }) ]); ``` # trendingItems Source: https://www.algolia.com/doc/api-reference/widgets/trending-items/js Shows a list of trending items. > [!NOTE] > > If you're using the deprecated [`recommend-js`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js) UI library, > see [`trendingItems`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js/trendingItems). ```ts Signature theme={"system"} trendingItems({ container: string | HTMLElement, // Optional parameters facetName?: string, facetValue?: string, limit?: number, threshold?: number, queryParameters?: object, fallbackParameters?: object, escapeHTML?: boolean, templates?: object, cssClasses?: object, transformItems?: function, }); ``` ## Import ```js Package manager theme={"system"} import { trendingItems } from 'instantsearch.js/es/widgets'; ``` ```js CDN theme={"system"} const { trendingItems } = instantsearch.widgets; // or directly use instantsearch.widgets.trendingItems() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=trendingItems) > > Preview this widget and its behavior. ## About this widget Use the `trendingItems` widget to display a list of trending items. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) ## Examples ```js JavaScript icon=code theme={"system"} trendingItems({ container: "#trendingItems", facetName: "category", facetValue: "Book", templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, }, }); ``` ### With a carousel layout Display the `trendingItems` widget as a [scrollable carousel](#param-layout). ```js JavaScript icon=code theme={"system"} import { carousel } from "instantsearch.js/es/templates"; // or const { carousel } = instantsearch.templates; trendingItems({ container: "#trendingItems", facetName: "category", facetValue: "Book", templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, layout: carousel(), }, }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} trendingItems({ // ... container: '#trendingItems', }); ``` ```js HTMLElement theme={"system"} trendingItems({ container: document.querySelector("#trendingItems"), }); ``` **facetName** `string` The facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) attribute to get recommendations for. Use this parameter with [`facetValue`](/doc/api-reference/widgets/trending-items/js#param-facet-value). ```js JavaScript icon=code theme={"system"} trendingItems({ // ... facetName: "category", facetValue: "Book", }); ``` **facetValue** `string` The value for the targeted facet name. This parameter should be used along with [`facetName`](/doc/api-reference/widgets/trending-items/js#param-facet-name). ```js JavaScript icon=code theme={"system"} trendingItems({ // ... facetName: "category", facetValue: "Book", }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} trendingItems({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} trendingItems({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```js JavaScript icon=code theme={"system"} trendingItems({ // ... queryParameters: { filters: "category:Book", }, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send when there aren't enough recommendations. ```js JavaScript icon=code theme={"system"} trendingItems({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} trendingItems({ // ... escapeHTML: false, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} trendingItems({ // ... templates: { // ... }, }); ``` **cssClasses** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The widget's root element. * `emptyRoot`. The container element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of recommendations. * `item`. The list item. ```js JavaScript icon=code theme={"system"} trendingItems({ // ... cssClasses: { root: "MyCustomTrendingItems", list: ["MyCustomTrendingItems", "MyCustomTrendingItems--subclass"], }, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} trendingItems({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **empty** `string | function` The template to use when there are no recommendations. ```js function theme={"system"} trendingItems({ // ... templates: { empty(_, { html }) { return html`

    No recommendations.

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. trendingItems({ // ... templates: { empty: "No recommendations.", }, }); ``` **header** `string | function` The template to use for the recommendations header. This template receives the recommendations as well as the `cssClasses` object. ```js function theme={"system"} trendingItems({ // ... templates: { header({ cssClasses, items }, { html }) { return html`

    Recommendations (${items.length})

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. trendingItems({ // ... templates: { header: "

    Recommendations ({{items.length}})

    ", }, }); ``` **item** `string | function` The template to use for each recommendation. This template receives an object containing a single record. ```js function theme={"system"} trendingItems({ // ... templates: { item(recommendation, { html }) { return html`

    ${recommendation.name}

    ${recommendation.description}

    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. trendingItems({ // ... templates: { item: `

    {{ name }}

    {{ description }}

    `, }, }); ``` **layout** `function` The template to use to wrap all items. InstantSearch.js provides an out-of-the-box carousel layout template with the following options: `cssClasses`: * `root`. The carousel's root element. * `list`. The list of recommendations. * `item`. The list item. * `navigation`. The navigation controls. * `navigationPrevious`. The "Previous" navigation control. * `navigationNext`. The "Next" navigation control. `templates`: * `previous`. The content of the "Previous" navigation control. * `next`. The content of the "Next" navigation control. ```js Carousel theme={"system"} import { carousel } from 'instantsearch.js/es/templates'; // or const { carousel } = instantsearch.templates; trendingItems({ // ... templates: { layout: carousel({ cssClasses: { root: 'MyCustomCarousel', }, templates: { previous({ html }) { return html`

    Previous

    `; }, next({ html }) { return html`

    Next

    `; }, }, }), }, }); ``` ```js Custom theme={"system"} trendingItems({ // ... templates: { layout({ items }, { html }) { return html`
      ${items.map((item) => html`
    • ${item.objectID}

    • `)}
    `; }, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Trending Items

    1. ...
    2. ...
    3. ...
    ``` ## Customize the UI with `connectTrendingItems` If you want to create your own UI of the `trendingItems` widget, you can use connectors. To use `connectTrendingItems`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectTrendingItems } from 'instantsearch.js/es/connectors'; ``` ```js CDN theme={"system"} const { connectTrendingItems } = instantsearch.connectors; // or directly use instantsearch.connectors.connectTrendingItems() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderTrendingItems = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customTrendingItems = connectTrendingItems(renderTrendingItems); // 3. Instantiate search.addWidgets([ customTrendingItems({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderTrendingItems = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Rendering options **items** `object[]` The matched recommendations from the Algolia API. ```js JavaScript icon=code theme={"system"} const renderTrendingItems = (renderOptions, isFirstRender) => { const { items } = renderOptions; document.querySelector('#trendingItems').innerHTML = `
      ${items .map(item => `
    • ${item.name}
    • `) .join('')}
    `; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderTrendingItems = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = '...'; }; // ... search.addWidgets([ customTrendingItems({ // ... container: document.querySelector('#trendingItems'), }) ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customTrendingItems = connectTrendingItems( renderTrendingItems ); search.addWidgets([ customTrendingItems({ // Optional parameters facetName: string, facetValue: string, limit: number, threshold: number, queryParameters: object, fallbackParameters: object, escapeHTML: boolean, transformItems: function, }) ]); ``` #### Instance options **facetName** `string` The facet attribute to get recommendations for. This parameter should be used along with [`facetValue`](/doc/api-reference/widgets/trending-items/js#param-facet-value). ```js JavaScript icon=code theme={"system"} customTrendingItems({ // ... facetName: "category", facetValue: "Book", }); ``` **facetValue** `string` The value for the targeted facet name. This parameter should be used along with [`facetName`](/doc/api-reference/widgets/trending-items/js#param-facet-name). ```js JavaScript icon=code theme={"system"} customTrendingItems({ // ... facetName: "category", facetValue: "Book", }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```js JavaScript icon=code theme={"system"} customTrendingItems({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```js JavaScript icon=code theme={"system"} customTrendingItems({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```js JavaScript icon=code theme={"system"} customTrendingItems({ // ... queryParameters: { filters: "category:Book", }, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send when there aren't enough recommendations. ```js JavaScript icon=code theme={"system"} customTrendingItems({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Escapes HTML entities from recommendations string values. ```js JavaScript icon=code theme={"system"} customTrendingItems({ // ... escapeHTML: false, }); ``` **transformItems** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```js JavaScript icon=code theme={"system"} customTrendingItems({ // ... transformItems(items) { return items.map((item) => ({ ...item, name: item.name.toUpperCase(), })); }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create the render function const renderTrendingItems = (renderOptions, isFirstRender) => { const { items, widgetParams } = renderOptions; widgetParams.container.innerHTML = `
      ${items .map(item => `
    • ${item.name}
    • `) .join('')}
    `; }; // Create the custom widget const customTrendingItems = connectTrendingItems( renderTrendingItems ); // Instantiate the custom widget search.addWidgets([ customTrendingItems({ container: document.querySelector('#trendingItems'), }) ]); ``` # uiState Source: https://www.algolia.com/doc/api-reference/widgets/ui-state/js Object with the state of your search UI. > [!NOTE] > > You are viewing the documentation for **InstantSearch.js v4**. > To upgrade from v3, see the [migration guide](/doc/guides/building-search-ui/upgrade-guides/js#upgrade-from-v3-to-v4). > Looking for the v3 version of this page? > [View the v3 docs](https://algolia.com/old-docs/deprecated/instantsearch/js/v3/api-reference/ui-state). ## About this widget The `uiState` object represents the state of the search UI and can be saved and returned to later. It's updated whenever the user interacts with the UI and can be used to: * Store the current search query and parameters * Pass data to widgets * Determine which results to show when a user visits a new page ([routing](/doc/api-reference/widgets/instantsearch/js#param-routing)) It doesn't directly store search results: just the state that leads to those results. ### Example This is an example of an InstantSearch `uiState` object. It represents what the user has searched for, how they've refined the results, and how the results are displayed. Replace `INDEX_NAME` with the name of your Algolia 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.). ```js JavaScript icon=code theme={"system"} const uiState = { INDEX_NAME: { query: "Hill Valley", refinementList: { colors: ["white", "black"], }, configure: { distinct: true, }, menu: { category: "Decoration", }, hierarchicalMenu: { categories: ["Decoration > Clocks"], }, numericMenu: { price: "100:500", }, ratingMenu: { rating: 4, }, range: { ageInYears: "2:10", }, toggle: { freeShipping: true, }, geoSearch: { boundingBox: "47.3165,4.9665,47.3424,5.0201", }, sortBy: "most_popular_index", page: 2, hitsPerPage: 20, }, }; ``` ### Custom widgets To have your custom widgets interact with the `uiState` object, you need to implement the `getWidgetUiState` and `getWidgetSearchParameters` methods in the connector. To learn more, see [Custom widgets](/doc/guides/building-search-ui/widgets/create-your-own-widgets/js#interact-with-routing). # useInstantSearch Source: https://www.algolia.com/doc/api-reference/widgets/use-instantsearch/react React Hook for accessing state and results of your React InstantSearch apps. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} const { indexUiState, setIndexUiState, uiState, setUiState, indexRenderState, renderState, results, scopedResults refresh, status, error, addMiddlewares, } = useInstantSearch({ catchError }) ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { useInstantSearch } from "react-instantsearch"; ``` ## About this Hook A React Hook that lets you access the InstantSearch API and interact with its state. Use `useInstantSearch` to access and update UI state, retrieve search results, refresh the search, and add middleware. Call this Hook from a component inside [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react). ## Examples ```jsx indexUiState theme={"system"} import { InstantSearch, RefinementList, useInstantSearch, } from "react-instantsearch"; function Discounts() { const { indexUiState, setIndexUiState } = useInstantSearch(); const disabled = indexUiState.refinementList?.discounts?.includes("-50% off"); return ( ); } function App({ searchClient }) { return ( ); } ``` ```jsx uiState theme={"system"} import { InstantSearch, Hits, SearchBox, useInstantSearch, } from "react-instantsearch"; function App({ searchClient }) { return ( }> ); } function NoResultsBoundary({ children, fallback }) { const { results } = useInstantSearch(); if (!results.__isArtificial && results.nbHits === 0) { return fallback; } return children; } function NoResults() { const { setUiState } = useInstantSearch(); return (

    No results.

    ); } ``` ```jsx indexRenderState theme={"system"} import { useInstantSearch } from "react-instantsearch"; function Refine() { const { indexRenderState } = useInstantSearch(); return ( ); } ``` ```jsx renderState theme={"system"} import { useInstantSearch } from "react-instantsearch"; function Refine() { const { renderState } = useInstantSearch(); return ( ); } ``` ```jsx results theme={"system"} import { useInstantSearch } from "react-instantsearch"; function Stats() { const { results } = useInstantSearch(); return (
    {results.nbHits} results found for the query "{results.query}".
    ); } ``` ```jsx scopedResults theme={"system"} import { useInstantSearch } from "react-instantsearch"; function Stats() { const { scopedResults } = useInstantSearch(); const totalNbHits = scopedResults.reduce( (acc, { results }) => acc + results.nbHits, 0, ); return
    {totalNbHits} results found across all indices.
    ; } ``` ```jsx refresh theme={"system"} import { useInstantSearch } from "react-instantsearch"; function Refresh() { const { refresh } = useInstantSearch(); return ; } ``` ```jsx middleware theme={"system"} import { createInsightsMiddleware } from "instantsearch.js/es/middlewares"; import { useInstantSearch } from "react-instantsearch"; function InsightsMiddleware(props) { const { addMiddlewares } = useInstantSearch(); // The Insights middleware is added in a layout effect because it mounts // a widget. Widgets must be added in layout effects to minimize // the number of network requests. useLayoutEffect(() => { const middleware = createInsightsMiddleware(props); return addMiddlewares(middleware); }, [addMiddlewares, props]); return null; } ``` ```jsx status theme={"system"} import { useInstantSearch } from "react-instantsearch"; function Status() { const { status } = useInstantSearch(); if (status === "loading" || status === "stalled") { return <>Loading...; } } ``` ```jsx error theme={"system"} import { useInstantSearch } from "react-instantsearch"; function Error() { const { error } = useInstantSearch({ catchError: true }); if (error) { return <>Search error: {error.message}; } } ``` ## Parameters **catchError** `boolean` (default: false) If `true`, InstantSearch catches errors during the search lifecycle and exposes them through the [`error`](#param-error) property. If you handle these errors elsewhere, set `catchError` to `false`. ## Returns **indexUiState** `IndexUiState` The [`uiState`](/doc/api-reference/widgets/ui-state/react) of the parent 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.). **setIndexUiState** `(indexUiState: IndexUiState) =\> void | ((prevIndexUiState: IndexUiState) =\> IndexUiState) =\> void` Updates the [`uiState`](/doc/api-reference/widgets/ui-state/react) of the parent index.. Accepts a new index UI state object or a function that receives the current index UI state and returns the next index UI state. > [!NOTE] > > Mount the widget that manages the UI state you update. > For example, mount a [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) widget, > or an equivalent [virtual widget](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#build-virtual-widgets-with-hooks), > for changes to `refinementList` to take effect. ```jsx Function theme={"system"} setIndexUiState((prevIndexUiState) => ({ ...prevIndexUiState, refinementList: { ...prevIndexUiState.refinementList, discounts: ["-50% off", "free shipping"], }, })); ``` ```jsx Object theme={"system"} setIndexUiState({ refinementList: { discounts: ["-50% off", "free shipping"], }, }); ``` **results** `SearchResults` The search results. **uiState** `UiState` The [`uiState`](/doc/api-reference/widgets/ui-state/react) of the search interface. Each property maps an index identifier to that index's UI state. By default, the identifier is the index name. **setUiState** `(uiState: UiState) =\> void | ((prevUiState: UiState) =\> UiState) =\> void` Function to update the [`uiState`](/doc/api-reference/widgets/ui-state/react) of the search. This function either takes a new `uiState` object, or a function that exposes the current `uiState` object and returns a new one. > [!NOTE] > > Mount the widget that manages the UI state you update. > For example, mount a [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) widget, > or an equivalent [virtual widget](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#build-virtual-widgets-with-hooks), > for changes to `refinementList` to take effect. ```tsx Function theme={"system"} const indexName = 'instant_search'; setIndexUiState((prevUiState) => ({ ...prevUiState, [indexName]: { ...prevUiState[indexName].refinementList, refinementList: { ...prevUiState[indexName].refinementList, discounts: ['-50% off', 'free shipping'], }, }, })); ``` ```jsx Object theme={"system"} const indexName = "instant_search"; setIndexUiState({ [indexName]: { refinementList: { discounts: ["-50% off", "free shipping"], }, }, }); ``` **scopedResults** `ScopedResult[]` An array with the results of the index, as well as the results of all adjacent [`Index`](/doc/api-reference/widgets/index-widget/react) widgets. ```ts TypeScript icon=code theme={"system"} type ScopedResult = { indexId: string; results: SearchResults; }; ``` **indexRenderState** `IndexRenderState` The [`renderState`](/doc/api-reference/widgets/render-state/react) of the parent index. **renderState** `RenderState` The [`renderState`](/doc/api-reference/widgets/render-state/react) of the search. **refresh** `() =\> void` Clears the search client's cache and performs a new search. This is useful to update the results once an indexing operation has finished. **addMiddlewares** `(...middlewares: Middleware[]) =\> () =\> void` Adds one or more [`middleware`](/doc/api-reference/widgets/middleware/js) functions to InstantSearch. Returns a cleanup function that removes them. ```jsx JavaScript icon=code theme={"system"} import { myMiddleware } from "./myMiddleware"; function MyMiddleware() { const { addMiddlewares } = useInstantSearch(); useEffect(() => { return addMiddlewares(myMiddleware); }, [addMiddlewares]); return null; } ``` **status** `idle | loading | stalled | error` The status of the search happening. Possible values are: * `'idle'`: no search request is in progress. * `'loading'`: a search request is in progress. Use this status to provide immediate feedback after an action. For loading indicators, use `'stalled'` instead. * `'stalled'`: the search has exceeded [`stalledSearchDelay`](/doc/api-reference/widgets/instantsearch/react#param-stalled-search-delay). * `'error'`: the search failed. The `error` property contains the search error. **error** `Error | undefined` The error that occurred during the search. This is only valid when `status` is `'error'`. # voiceSearch Source: https://www.algolia.com/doc/api-reference/widgets/voice-search/js Lets users submit search queries using voice input. ```ts Signature theme={"system"} voiceSearch({ container: string | HTMLElement, // Optional parameters searchAsYouSpeak?: boolean, language?: string, additionalQueryParameters?: object, templates?: object, cssClasses?: object, }); ``` ## Import ```js Package manager theme={"system"} import { voiceSearch } from "instantsearch.js/es/widgets"; ``` ```js CDN theme={"system"} const { voiceSearch } = instantsearch.widgets; // or directly use instantsearch.widgets.voiceSearch() ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=js\&widget=voiceSearch) > > Preview this widget and its behavior. ## About this widget The `voiceSearch` widget lets users perform a voice-based 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.). It uses the [Web Speech API](https://w3c.github.io/speech-api), which only Chrome (from version 25) has implemented so far. This means the `voiceSearch` widget only works on desktop Chrome and Android Chrome. It doesn't work on iOS Chrome, which uses the iOS WebKit. ## Examples ```js JavaScript icon=code theme={"system"} voiceSearch({ container: "#voicesearch", }); ``` ## Options **container** `string | HTMLElement` *required* The CSS Selector or `HTMLElement` to insert the widget into. ```js string theme={"system"} voiceSearch({ container: "#voicesearch", }); ``` ```js HTMLElement theme={"system"} voiceSearch({ container: document.querySelector("#voicesearch"), }); ``` **searchAsYouSpeak** `boolean` (default: false) Whether to trigger the search as you speak. If `false`, search is triggered only after speech is finished. If `true`, search is triggered whenever the engine delivers an interim transcript. ```js JavaScript icon=code theme={"system"} voiceSearch({ // ... searchAsYouSpeak: true, }); ``` **language** `string` (default: all languages) The language you want your `voiceSearch` widget to recognize. The default (`all languages`) can result in false positives. For example, an English word you pronounce might be recognized as a French word, which can cause irrelevant results. Make sure to give a [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag), like `en-US` or `fr-FR`. This language is automatically forwarded to the [`queryLanguages`](/doc/api-reference/api-parameters/queryLanguages) query parameter. ```js JavaScript icon=code theme={"system"} voiceSearch({ // ... language: "en-US", }); ``` **additionalQueryParameters** `function` A function that receives the current query and returns the list of [search parameters](/doc/api-reference/search-api-parameters) you want to enable for voice search. By default, the following query parameters are set: * [`ignorePlurals`](/doc/api-reference/api-parameters/ignorePlurals) to `true` * [`removeStopWords`](/doc/api-reference/api-parameters/removeStopWords) to `true` * [`optionalWords`](/doc/api-reference/api-parameters/optionalWords) to the current query To turn off this behavior, override the return value of `additionalQueryParameters`. ```js JavaScript icon=code theme={"system"} voiceSearch({ // ... additionalQueryParameters(query) { return { ignorePlurals: false, }; }, }); ``` **templates** `object` The [templates](#templates) to use for the widget. ```js JavaScript icon=code theme={"system"} voiceSearch({ // ... templates: { // ... }, }); ``` **cssClasses** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#style-your-widgets): * `root`. The root element of the widget. * `button`. The button element. * `status`. The status element. ```js JavaScript icon=code theme={"system"} voiceSearch({ // ... cssClasses: { root: "MyCustomVoiceSearch", button: [ "MyCustomVoiceSearchButton", "MyCustomVoiceSearchButton--subclass", ], status: [ "MyCustomVoiceSearchStatus", "MyCustomVoiceSearchStatus--subclass", ], }, }); ``` ## Templates You can customize parts of a widget's UI using the Templates API. Each template includes an `html` function, which you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This function safely renders templates as HTML strings and works directly in the browser—no build step required. For details, see [Templating your UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#templating-your-ui). > [!NOTE] > > The `html` function is available in InstantSearch.js version 4.46.0 or later. **buttonText** `string | function` The template used for displaying the button. ```js function theme={"system"} voiceSearch({ // ... templates: { buttonText( { isListening, status, errorCode, transcript, isSpeechFinal, isBrowserSupported, }, { html }, ) { return html`${isListening ? "Stop" : "Start"}`; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. voiceSearch({ // ... templates: { buttonText: "🎙", }, }); ``` **status** `string | function` The template used for displaying the status. ```js function theme={"system"} voiceSearch({ // ... templates: { status( { isListening, status, errorCode, transcript, isSpeechFinal, isBrowserSupported, }, { html }, ) { const className = isListening ? "listening" : "not-listening"; return html`
    ${transcript ? transcript : ""}
    `; }, }, }); ``` ```js string theme={"system"} // String-based templates are deprecated, use functions instead. voiceSearch({ // ... templates: { status: `

    status: {{status}}

    errorCode: {{errorCode}}

    isListening: {{isListening}}

    transcript: {{transcript}}

    isSpeechFinal: {{isSpeechFinal}}

    isBrowserSupported: {{isBrowserSupported}}

    `, }, }); ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ...
    ``` ## Customize the UI with `connectVoiceSearch` If you want to create your own UI of the `voiceSearch` widget, you can use connectors. To use `connectVoiceSearch`, you can import it with the declaration relevant to how you installed InstantSearch.js. ```js Package manager theme={"system"} import { connectVoiceSearch } from "instantsearch.js/es/connectors"; ``` ```js CDN theme={"system"} const { connectVoiceSearch } = instantsearch.connectors; // or directly use instantsearch.connectors.connectVoiceSearch() ``` Then it's a 3-step process: ```js JavaScript icon=code theme={"system"} // 1. Create a render function const renderVoiceSearch = (renderOptions, isFirstRender) => { // Rendering logic }; // 2. Create the custom widget const customVoiceSearch = connectVoiceSearch(renderVoiceSearch); // 3. Instantiate search.addWidgets([ customVoiceSearch({ // instance params }), ]); ``` ### Create a render function This rendering function is called before the first search (`init` lifecycle step) and each time results come back from Algolia (`render` lifecycle step). ```js JavaScript icon=code theme={"system"} const renderVoiceSearch = (renderOptions, isFirstRender) => { const { isBrowserSupported, isListening, toggleListening, voiceListeningState, widgetParams, } = renderOptions; if (isFirstRender) { // Do some initial rendering and bind events } // Render the widget }; ``` #### Render options **isBrowserSupported** `boolean` `true` if user's browser supports voice search. ```js JavaScript icon=code theme={"system"} const renderVoiceSearch = (renderOptions, isFirstRender) => { const { isBrowserSupported } = renderOptions; const container = document.querySelector("#voicesearch"); if (isFirstRender && !isBrowserSupported) { const message = document.createElement("p"); message.innerText = "This browser is not supported."; container.appendChild(message); } }; ``` **toggleListening** `function` Starts listening to user's speech, or stops it if already listening. ```js JavaScript icon=code theme={"system"} const renderVoiceSearch = (renderOptions, isFirstRender) => { const { toggleListening } = renderOptions; const container = document.querySelector("#voicesearch"); if (isFirstRender) { const button = document.createElement("button"); button.textContent = "Toggle"; button.addEventListener("click", (event) => { toggleListening(); }); container.appendChild(button); } }; ``` **isListening** `boolean` `true` if listening to user's speech. ```js JavaScript icon=code theme={"system"} const renderVoiceSearch = (renderOptions, isFirstRender) => { const { isListening, toggleListening } = renderOptions; const container = document.querySelector("#voicesearch"); if (isFirstRender) { const button = document.createElement("button"); button.textContent = "Toggle"; button.addEventListener("click", (event) => { toggleListening(); }); container.appendChild(button); } container.querySelector("button").textContent = isListening ? "Stop" : "Start"; }; ``` **voiceListeningState** `boolean` An object containing the following states regarding speech recognition: * `status: string`. Current status (`initial`|`askingPermission`| `waiting`|`recognizing`|`finished`|`error`). * `transcript: string`. Currently recognized transcript. * `isSpeechFinal: boolean`. `true` if speech recognition is finished. * `errorCode: string | undefined`. An error code (if any). Refer to the [spec](https://w3c.github.io/speech-api/#speechreco-error) for more information. ```js JavaScript icon=code theme={"system"} const renderVoiceSearch = (renderOptions, isFirstRender) => { const { voiceListeningState, toggleListening } = renderOptions; const { status, transcript, isSpeechFinal, errorCode } = voiceListeningState; const container = document.querySelector("#voicesearch"); if (isFirstRender) { const state = document.createElement("div"); state.innerHTML = `

    status :

    transcript :

    isSpeechFinal :

    errorCode :

    `; container.appendChild(state); const button = document.createElement("button"); button.textContent = "Toggle"; button.addEventListener("click", (event) => { toggleListening(); }); container.appendChild(button); } container.querySelector(".status").innerText = status; container.querySelector(".transcript").innerText = transcript; container.querySelector(".is-speech-final").innerText = isSpeechFinal; container.querySelector(".error-code").innerText = errorCode || ""; }; ``` **widgetParams** `object` All original widget options forwarded to the render function. ```js JavaScript icon=code theme={"system"} const renderVoiceSearch = (renderOptions, isFirstRender) => { const { widgetParams } = renderOptions; widgetParams.container.innerHTML = "..."; }; const customVoiceSearch = connectVoiceSearch(renderVoiceSearch); search.addWidgets([ customVoiceSearch({ container: document.querySelector("#voicesearch"), }), ]); ``` ### Create and instantiate the custom widget First, create your custom widgets using a rendering function. Then, instantiate them with parameters. You can pass two kinds of parameters: * **Instance parameters**. Predefined options that configure Algolia's behavior. * **Custom parameters**. Parameters you define to make the widget reusable and adaptable. Inside the `renderFunction`, both instance and custom parameters are accessible through `connector.widgetParams`. ```js JavaScript icon=code theme={"system"} const customVoiceSearch = connectVoiceSearch( renderVoiceSearch ); search.addWidgets([ customVoiceSearch({ // Optional parameters searchAsYouSpeak?: boolean, }) ]); ``` #### Instance options **searchAsYouSpeak** `boolean` (default: false) Whether to trigger the search as you speak. If `false`, search is triggered only after speech is finished. If `true`, search is triggered as many times as the engine delivers an interim transcript. ```js JavaScript icon=code theme={"system"} customVoiceSearch({ searchAsYouSpeak: true, }); ``` **language** `string` (default: all languages) The language you want your `voiceSearch` widget to recognize. The default (`all languages`) can result in false positives. For example, an English word you pronounce might be recognized as a French word, which can cause irrelevant results. Make sure to give a [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag), like `en-US` or `fr-FR`. This language is automatically forwarded to the [`queryLanguages`](/doc/api-reference/api-parameters/queryLanguages) query parameter. ```js JavaScript icon=code theme={"system"} customVoiceSearch({ // ... language: "en-US", }); ``` **additionalQueryParameters** `function` A function that receives the current query and returns the list of [search parameters](/doc/api-reference/search-api-parameters) you want to enable for voice search. By default, the following query parameters are set: * [`ignorePlurals`](/doc/api-reference/api-parameters/ignorePlurals) to `true` * [`removeStopWords`](/doc/api-reference/api-parameters/removeStopWords) to `true` * [`optionalWords`](/doc/api-reference/api-parameters/optionalWords) to the current query To turn off this behavior, override the return value of `additionalQueryParameters`. ```js JavaScript icon=code theme={"system"} customVoiceSearch({ // ... additionalQueryParameters(query) { return { ignorePlurals: false, }; }, }); ``` ### Full example ```html HTML theme={"system"}
    ``` ```js JavaScript theme={"system"} // Create a render function const renderVoiceSearch = (renderOptions, isFirstRender) => { const { isListening, toggleListening, voiceListeningState } = renderOptions; const container = document.querySelector("#voicesearch"); if (isFirstRender) { const button = document.createElement("button"); button.addEventListener("click", (event) => { toggleListening(); }); container.appendChild(button); const state = document.createElement("pre"); container.appendChild(state); } container.querySelector("button").textContent = isListening ? "Stop" : "Start"; container.querySelector("pre").textContent = JSON.stringify( voiceListeningState, null, 2, ); }; // create custom widget const customVoiceSearch = connectVoiceSearch(renderVoiceSearch); // instantiate custom widget search.addWidgets([ customVoiceSearch({ container: document.querySelector("#voicesearch"), }), ]); ``` # Framework integrations Source: https://www.algolia.com/doc/framework-integration Integrate Algolia with web frameworks. * [**Django**](/doc/framework-integration/django/setup) * [**Laravel**](/doc/framework-integration/laravel/getting-started/introduction-to-scout-extended) * [**Ruby on Rails**](/doc/framework-integration/rails/getting-started/setup) * [**Symfony**](/doc/framework-integration/symfony/getting-started/algolia-searchbundle) # Set up Algolia for Django Source: https://www.algolia.com/doc/framework-integration/django/setup Integrate Algolia search into your Django project. This package lets you integrate the Algolia Search API into your [Django](https://www.djangoproject.com/) project. It's based on the [algoliasearch-client-python](https://github.com/algolia/algoliasearch-client-python) package. For an example, see [`algoliasearch-django-example`](https://github.com/algolia/algoliasearch-django-example). * Compatible with **Python 3.8** or later. * Supports **Django 4 and 5**. ## Install ```sh Command line icon=square-terminal theme={"system"} pip install --upgrade 'algoliasearch-django>=4.0,<5.0' ``` ## Configure Django settings for Algolia In your Django settings, add `algoliasearch_django` to `INSTALLED_APPS` and add these two settings: ```python Python icon=code theme={"system"} ALGOLIA = {"APPLICATION_ID": "ALGOLIA_APPLICATION_ID", "API_KEY": "ALGOLIA_API_KEY"} ``` You can also add these optional settings: * `INDEX_PREFIX` A prefix for all indices. Use it to separate different indices, like `site1_Products` and `site2_Products`. * `INDEX_SUFFIX` A suffix for all indices. Use it to differentiate development and production environments, such as `Location_dev` and `Location_prod`. * `AUTO_INDEXING` If true (default), models are automatically synced with Algolia. * `RAISE_EXCEPTIONS` Raise exceptions for network errors instead of logging them (defaults to the value of `settings.DEBUG`). ## Define your index Create an `index.py` in each app with the models you want to index. Inside this file, call `algoliasearch.register()` for each model: ```python Python icon=code theme={"system"} # index.py import algoliasearch_django as algoliasearch from .models import YourModel algoliasearch.register(YourModel) ``` Algolia indexes all model fields by default. You can 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.) by creating a subclass of `AlgoliaIndex` and using the `register` decorator: ```python Python icon=code theme={"system"} # index.py from algoliasearch_django import AlgoliaIndex from algoliasearch_django.decorators import register from .models import YourModel @register(YourModel) class YourModelIndex(AlgoliaIndex): fields = ("name", "date") geo_field = "location" settings = {"searchableAttributes": ["name"]} index_name = "my_index" ``` ## Connection errors If you get one of these errors: `Impossible to connect`, `Unable to connect`, or `Unreachable hosts`: * Ensure you're using the correct application ID and API key. You can find your credentials on your [Algolia dashboard](https://dashboard.algolia.com/account/api-keys/all). * Check for recent changes in your code. * Check the status of your data center provider. > [!NOTE] > > If you're using Firebase, you can only access Algolia from a paid Firebase tier. If you can't solve the problem yourself, contact the [Algolia support](https://support.algolia.com/hc/en-us/requests/new) team and provide them with the following information: * The name of your [framework integration](/doc/framework-integration) (Django) and its version number * A code snippet to reproduce the issue * Error message or stack trace (if applicable) * The name of the Algolia index that's causing problems * The exact [UTC time](https://wikipedia.org/wiki/Coordinated_Universal_Time) of the event * If you can't connect to the Algolia API from your browser, send the output from [`community.algolia.com/diag`](https://community.algolia.com/diag/). * If you can't connect to the Algolia API from your servers, send the output from the following command (run on any affected server): ```sh Command line icon=square-terminal theme={"system"} curl -sL https://algolia.com/downloads/diag.sh > ./diag.sh && sudo ./diag.sh ALGOLIA_APPLICATION_ID ``` > [!TIP] > > Replace `ALGOLIA_APPLICATION_ID` with *your* Algolia application ID. ## Handle indexing errors Any `Record at the position XX objectID=XX is too big` errors during indexing are because you exceeded the size limit for records. Trim oversized records to fit the [record size limit](https://support.algolia.com/hc/en-us/articles/4406981897617-Is-there-a-size-limit-for-my-index-records-), then retry indexing. # Multiple models in one index Source: https://www.algolia.com/doc/framework-integration/laravel/advanced-use-cases/multiple-models-in-one-index How to implement aggregators to handle multiple models in one index using Laravel Scout with Algolia Scout Extended. To search in multiple models at the same time, use aggregators. Aggregators let you index multiple models in a single index. They listen for updates in your models to automatically keep everything in sync. ## Create an aggregator To create a new aggregator, use the `scout:make-aggregator` Artisan command. This command creates a new aggregator class in the `app/Search` directory: ```sh Command line icon=square-terminal theme={"system"} php artisan scout:make-aggregator News ``` After generating your aggregator, fill in the `$models` property of the class to identify the models to aggregate: ```php PHP icon=code theme={"system"} namespace App\Search; use Algolia\ScoutExtended\Searchable\Aggregator; class News extends Aggregator { /** * The names of the models that should be aggregated. * * @var string[] */ protected $models = [ \App\Models\Event::class, \App\Models\Article::class, ]; } ``` To register an Aggregator, use the `bootSearchable` method on the aggregator you want to register. For this, use the `boot` method of one of your service providers. The following example registers the aggregator in the `App\AppServiceProvider`: ```php PHP icon=code theme={"system"} namespace App\Providers; use App\Search\News; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { News::bootSearchable(); } } ``` When you use a model in multiple aggregators, by default Scout Extended registers one observer per model per aggregator. This can result in twice the amount of indexing jobs when you update a model. To ensure a single observer gets registered you have to boot all your aggregators using the `Aggregators::bootSearchables` method, passing your aggregator classes as argument: ```php PHP icon=code theme={"system"} namespace App\Providers; use App\Search\Article; use App\Search\PremiumArticle; use Algolia\ScoutExtended\Searchable\Aggregators; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { Aggregators::bootSearchables([ Article::class, PremiumArticle::class, ]); } } ``` ## Conditionally sync an aggregator For `Searchable` models, you can use the [`shouldBeSearchable`](/doc/framework-integration/laravel/indexing/import-into-algolia#conditional-model-sync) method to conditionally change which results you want to index. For aggregators, you can define a `shouldBeSearchable` method that calls the `shouldBeSearchable` method of the aggregated model. ```php PHP icon=code theme={"system"} namespace App\Search; use Algolia\ScoutExtended\Searchable\Aggregator; use Laravel\Scout\Searchable; class News extends Aggregator { /** * The names of the models that should be aggregated. * * @var string[] */ protected $models = [ \App\Models\Event::class, \App\Models\Article::class, ]; public function shouldBeSearchable() { // Check if the class uses the Searchable trait before calling shouldBeSearchable if (array_key_exists(Searchable::class, class_uses($this->model))) { return $this->model->shouldBeSearchable(); } } } ``` You can change the logic of your aggregator's `shouldBeSearchable` method with any condition that doesn't depend on its grouped models. ## Search an aggregator An aggregator is a standard `Searchable` class. You can search models on the aggregator using the `search` method: ```php PHP icon=code theme={"system"} $models = App\Search\News::search('Star Trek')->get(); echo get_class($models[0]); // "App\Models\Article" echo get_class($models[1]); // "App\Models\Comment" ``` > [!TIP] > > Be careful, the `$models` array may contain different types of models instances. If you want to get the raw results from Algolia, use the `raw` method. Be aware that each result may contain a different structure when using this method: ```php PHP icon=code theme={"system"} $results = App\Search\News::search('Star Trek')->raw(); ``` ```json JSON icon=braces theme={"system"} { "hits": [ { "id": 1, "title": "Article title", "slug": "article-title", "content": "Article content", "objectID": "App\\Models\\Article::1" }, { "id": 1, "content": "Comment content", "objectID": "App\\Models\\Comment::1" } ] } ``` To ensure that each result have a similar structure, you might need to implement the method `toSearchableArray` on the each `Searchable` classes or directly on the aggregator class. ## Eager loading relationships Sometimes you may want to eager load a relationship of an aggregated model. To eager load relationships, use the property `$relations` of your aggregator class. For example: ```php PHP icon=code theme={"system"} class News extends Aggregator { /** * The names of the models that should be aggregated. * * @var string[] */ protected $models = [ \App\Models\Event::class, \App\Models\Article::class, ]; /** * Map of model relations to load. * * @var string[] */ protected $relations = [ \App\Models\Event::class => ['user'], \App\Models\Article::class => ['photo', 'user'], ]; } ``` ## Turning off syncing per model By default, the aggregator creates one aggregated index (in this case, "news"). But, Laravel Scout also still indexes the `Article` and `Event` models in their own "articles" and "events" indices. To stop this, turn off indexing for these models globally. In your `AppServiceProvider`, add the following: ```php PHP icon=code theme={"system"} Laravel\Scout\ModelObserver::disableSyncingFor(Article::class); Laravel\Scout\ModelObserver::disableSyncingFor(Event::class); ``` You can't use the [`shouldBeSearchable`](/doc/framework-integration/laravel/indexing/import-into-algolia#conditional-model-sync) method in this case because the method doesn't know about the destination index. > [!NOTE] > > The `scout:import command` doesn't take calls to `disableSearchSyncing` into account. > Disabling search syncing only applies to updates to models made by the application, and not to bulk import operations through the `scout:import` command. # Split large records Source: https://www.algolia.com/doc/framework-integration/laravel/advanced-use-cases/split-large-records How to split records in Laravel Scout with Algolia Scout Extended. Algolia records should be 10 kB or less. Create smaller records by splitting big records into logical chunks such as paragraphs or sentences. To split an attribute, your `Searchable` class must implement a `split${Attribute}` method. For example, if you want to split the `body` attribute, the method name should be `splitBody`. ## Split directly on the `Searchable` class The most basic way to split a record is by doing it directly on the `Searchable` class: ```php PHP icon=code theme={"system"} namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; class Article extends Model { use Searchable; /** * Splits the given value. * * @param string $value * @return array */ public function splitBody($value) { return explode('. ', $value); } } ``` > [!NOTE] > > If all split methods for a specific model return an empty array, no records are sent to Algolia. ## Split using a splitter Sometimes you need to isolate the splitting logic into a dedicated class. ```php PHP icon=code theme={"system"} namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\Scout\Searchable; use Algolia\ScoutExtended\Splitters\HtmlSplitter; class Article extends Model { use Searchable; /** * Splits the given value. * * @param string $value * @return string */ public function splitBody($value) { return HtmlSplitter::class; // You can also return an instance instead of the class name. } } ``` ## Write a splitter One of the primary benefits of creating a splitter class is the ability to type-hint any dependencies your splitter may need in its constructor. Laravel automatically resolves and injects these dependencies into your splitter instance. To write a splitter, create a new class that implements the `Algolia\ScoutExtended\Contracts\SplitterContract`. The `split` method should split the given `$value` as needed: ```php PHP icon=code theme={"system"} namespace App\Search\Splitters; use App\Contracts\SplitterServiceContract; use Algolia\ScoutExtended\Contracts\SplitterContract; class CustomSplitter implements SplitterContract { /** * @var \App\Contracts\SplitterServiceContract */ protected $service; /** * Creates a new instance of the class. * * @param \App\Contracts\SplitterServiceContract $service * * @return void */ public function __construct(SplitterServiceContract $service) { $this->service = $service; } /** * Splits the given value. * * @param object $searchable * @param mixed $value * * @return array */ public function split($searchable, $value): array { $values = $this->service->split($searchable->articleType, $value); return $values; } } ``` ## Distinct The *distinct* feature lets you force Algolia to return distinct results based on one attribute defined in [`attributeForDistinct`](/doc/api-reference/api-parameters/attributeForDistinct). Using this attribute, you can limit the number of returned records that contain the same value in that attribute. To use *distinct*, configure the `attributeForDistinct` in your `config/scout-articles.php` configuration file: ```php PHP icon=code theme={"system"} // ... /* |-------------------------------------------------------------------------- | Distinct |-------------------------------------------------------------------------- | | Using this attribute, you can limit the number of returned records that contain the same | value in that attribute. For example, if the distinct attribute is the series_name and | several hits (Episodes) have the same value for series_name (Laravel From Scratch). | | Supported(distinct): Boolean | Supported(attributeForDistinct): Null, String | Example(attributeForDistinct): 'slug' */ 'distinct' => true, 'attributeForDistinct' => 'slug', // ... ``` # Introduction to Scout Extended Source: https://www.algolia.com/doc/framework-integration/laravel/getting-started/introduction-to-scout-extended Scout Extended provides the perfect starting point to integrate Algolia into your Laravel application. It's made to simplify the usage of Algolia within the [Laravel Framework](https://laravel.com). Scout Extended is built on top of the latest release of [Laravel Scout](https://laravel.com/docs/scout), the official Laravel search package. Using Scout Extended, you are free to use all Laravel Scout's features, and the full power of Algolia at the same time. Scout Extended is 100% open source, so you're free to dig through the source to see how it works behind the scenes. See something that needs improving? You can [make a pull request on GitHub](https://github.com/algolia/scout-extended). > [!NOTE] > > Scout Extended adds functionalities to [Laravel Scout](https://github.com/laravel/scout). > For this reason, it's encouraged to read the [Scout documentation](https://laravel.com/docs/scout) first. ## Documentation Laravel has great [documentation](https://laravel.com/docs/scout) to go along with its release of Scout. They go into great detail on how to use the Scout interface. These resources should be enough to help you get started and implement Algolia in the best possible way within the Laravel framework. One of the goals of the documentation on this site is to complement Laravel's documentation, by anticipating questions you might have after reading Laravel's documentation. On top of that, this documentation details the features Scout Extended adds, such as [aggregators](/doc/framework-integration/laravel/advanced-use-cases/multiple-models-in-one-index) and [zero downtime reimports](/doc/framework-integration/laravel/production/zero-downtime-reimports). # Development and testing Source: https://www.algolia.com/doc/framework-integration/rails/advanced/dev-and-testing Good practices and advanced usage of the Algolia Search gem. ## Connection errors Are you getting "Impossible to connect", "Unable to connect", or "Unreachable hosts" errors? First, make sure the issue isn't at your end: * Ensure you're using the correct application ID and API key. Find these credentials on your [Algolia dashboard](https://dashboard.algolia.com/account/api-keys/all). * Check for recent changes in your code. * Check the status of your data center provider. > [!NOTE] > > If you're using Firebase, you can only access Algolia from a paid Firebase tier. If you can't solve the problem yourself, contact the [Algolia support](https://support.algolia.com/hc/en-us/requests/new) team and provide them with the following information: * The name of the used gem and its version number * A code snippet to reproduce the issue * Error message or stack trace (if applicable) * The name of the Algolia 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's causing problems * The exact [UTC time](https://wikipedia.org/wiki/Coordinated_Universal_Time) of the event * If you can't connect to the Algolia API from your browser, send the output from [community.algolia.com/diag/](https://community.algolia.com/diag/). * If you can't connect to the Algolia API from your servers, send the output from the following command (run on any affected server): ```sh Command line icon=square-terminal theme={"system"} curl -sL https://algolia.com/downloads/diag.sh > ./diag.sh && sudo ./diag.sh ALGOLIA_APPLICATION_ID ``` > [!TIP] > > Replace `ALGOLIA_APPLICATION_ID` with *your* Algolia application ID. ## Raise or log exceptions By default, this gem raises exceptions if something goes wrong. ## Turn off indexing Use the following code snippet to turn off indexing based on the current Rails environment. This can be helpful during testing or development. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch disable_indexing: Rails.env.test? do # Algolia configuration end end class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch disable_indexing: Proc.new { Rails.env.test? || more_complex_condition } do # Algolia configuration end end ``` ## Wait for indexing tasks Indexing operations are asynchronous by default. During your tests, you might create models and then confirm if they were indexed in Algolia. To test this, it's best to make your indexing synchronous. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch synchronous: Rails.env.test? do # Algolia configuration end end ``` > [!TIP] > > Only use this option for testing. ## Run tests for the Algolia Search gem If you want to [contribute to this gem](https://github.com/algolia/algoliasearch-rails), you can run the tests locally or let the CI run them when opening your pull requests. Before you run the tests, set the `ALGOLIA_APPLICATION_ID` and `ALGOLIA_API_KEY` environment variables. Since the tests create and delete indices, make sure the index names won't affect your production. # Advanced usage Source: https://www.algolia.com/doc/framework-integration/rails/advanced/using-api-client Access the Algolia API client or model indices directly. Sometimes you need to access the Algolia API client directly. ## Access the Algolia API client The client is available as a singleton and uses the Algolia credentials you added to your [Rails configuration](/doc/framework-integration/rails/getting-started/setup#configure). For example, to get 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.) settings: ```ruby Ruby icon=code theme={"system"} key = AlgoliaSearch.client.get_settings("index_name") ``` ## Access model indices To access the index used by a model, use the `index_name` class method and pass it as a variable to the API client. This method follows your naming configuration, such as per-environment settings or custom names. ```ruby Ruby icon=code theme={"system"} index_name = Contact.index_name # client..get_settings(index_name), ... ``` # Set up Algolia for Ruby on Rails Source: https://www.algolia.com/doc/framework-integration/rails/getting-started/setup Integrate Algolia with your Ruby on Rails app. Check the [`algoliasearch-rails-example`](https://github.com/algolia/algoliasearch-rails-example) repository on GitHub for a sample Rails app with Autocomplete and InstantSearch results pages. ## Compatibility This gem supports: * Ruby versions 2.5 and later * Ruby on Rails versions 6 and 7. This gem is compatible with: * [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord), * [Mongoid](https://github.com/mongoid/mongoid) * [Sequel](https://github.com/jeremyevans/sequel). ## Install Install the gem directly: ```sh Command line icon=square-terminal theme={"system"} gem install algoliasearch-rails ``` Or add it to your `Gemfile`: ```rb theme={"system"} gem "algoliasearch-rails" ``` And run: ```sh Command line icon=square-terminal theme={"system"} bundle install ``` ## Configure To configure the gem, create a new file `config/initializers/algoliasearch.rb`. Add your Algolia credentials, the application ID and the API key. You can find both in the [Algolia dashboard](https://dashboard.algolia.com/account/api-keys). ```ruby Ruby icon=code theme={"system"} AlgoliaSearch.configuration = { application_id: "ALGOLIA_APPLICATION_ID", api_key: "ALGOLIA_API_KEY" } ``` ### API client with custom settings To customize settings such as timeout for the API client, you can make the Algolia gem use your instance of an API client. You can do this by setting the `AlgoliaSearch.client` option to your initialized client: ```ruby Ruby icon=code theme={"system"} # your options opts = {} AlgoliaSearch.client = Algolia::SearchClient.create("ALGOLIA_APPLICATION_ID", "ALGOLIA_API_KEY", opts) ``` ### The `algoliasearch` block To send your model to Algolia, include the `AlgoliaSearch` module and the `algoliasearch` block. This block sets up 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.) and options. If you want to use the default options, declare an empty block. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do # Use all default configuration end end ``` ### Example configuration To see a full configuration example, see the [repository](https://github.com/algolia/hn-search/blob/e27760e09840a6fa3efc592649fceb89237e4c2f/app/models/item.rb) for the [HN Search app](https://hn.algolia.com/). ## Method name and aliases All methods injected by the `AlgoliaSearch` module have the prefix `algolia_` and are aliased to short names if they aren't already defined by Rails: ```ruby Ruby icon=code theme={"system"} # Both calls do the same Contact.algolia_reindex! Contact.reindex! # Both calls to the same Contact.algolia_search("jon doe") Contact.search("jon doe") ``` # Index settings Source: https://www.algolia.com/doc/framework-integration/rails/index-configuration/index-settings Customize index names and settings. ## Index names Index names are derived from the class names. To customize the index names, pass a string to the `index_name` option. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch(index_name: "MyCustomName") do attribute :first_name, :last_name, :email end end ``` ## Production and staging indices To avoid changing your production index, you can append the current Rails environment to the index name with the `per_environment` option. This creates separate indices with names following the pattern `MODEL_ENVIRONMENT`. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch(per_environment: true) do attribute :first_name, :last_name, :email end end ``` To make it even harder to overwrite your production data, use different API keys in development and production. You can restrict access of your development API key to indices with names ending with `_development`. For more information, see [API keys restrictions](/doc/guides/security/api-keys#api-key-restrictions). ## Basic relevance settings To set a baseline for your index, define which attributes should be searchable and define a custom ranking. ```ruby Ruby icon=code theme={"system"} class Product < ActiveRecord::Base include AlgoliaSearch algoliasearch do # List of attributes used to build an Algolia record attributes :title, :subtitle, :description, :likes_count, :thumbnail_url, :release_date searchableAttributes ["title", "subtitle", "unordered(description)"] customRanking ["desc(likes_count)"] end end ``` For more information, see: * [Searchable attributes](/doc/guides/managing-results/must-do/searchable-attributes) * [Custom ranking](/doc/guides/managing-results/must-do/custom-ranking) ## Faceting and filtering Add all attributes you want to use for [filtering](/doc/framework-integration/rails/searching/search#facets) as facets. ```ruby Ruby icon=code theme={"system"} class Product < ActiveRecord::Base include AlgoliaSearch algoliasearch do # List of attributes used to build an Algolia record attributes :title, :subtitle, :likes_count, :ratings, :categories, :features, :sizes # ... Other settings removed for brevity attributesForFaceting ["searchable(categories)", "features", "sizes"] numericAttributesForFiltering ["likes_count", "equalOnly(ratings)"] end end ``` For more information, see: * [Faceting](/doc/guides/managing-results/refine-results/faceting) * [Filtering](/doc/guides/managing-results/refine-results/filtering) ## Synonyms You can define [regular synonyms](/doc/guides/managing-results/optimize-search-results/adding-synonyms#regular-synonyms) where all words are considered equivalent. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do attributes :first_name, :email synonyms( [ [ "bob", "bobby" \ "robert" ] ] ) end end ``` To define [other types of synonyms](/doc/guides/managing-results/optimize-search-results/adding-synonyms), directly call the [`saveSynonyms`](/doc/libraries/sdk/v1/methods/save-synonyms) method on the API client. ## Sync your settings with Algolia By default, your settings will be synced with Algolia. ## Turn off automatic syncing If you turn off automatic syncing, make sure to manually send updates to Algolia whenever you change your settings. ```ruby Ruby icon=code theme={"system"} class Musician < ActiveRecord::Base include AlgoliaSearch algoliasearch(check_settings: false) do searchableAttributes ["name", "band"] end end ``` ## Apply all settings To send settings updates for all indices, use the `set_all_settings` rake command. It sends updates to your primary indices, your replicas, and any indices you added with `add_index`. Consider adding this command to your deployment pipeline, especially if you turned off automatic settings updates. ```sh Command line icon=square-terminal theme={"system"} rake algoliasearch:set_all_settings ``` # Replicas and multiple indices Source: https://www.algolia.com/doc/framework-integration/rails/index-configuration/replicas-multiple-indices Learn how to use replicas and multiple indices with the Algolia gem. ## Replica indices Algolia uses one ranking strategy 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.). If you want to provide more ranking or sorting strategies, [add replica indices](/doc/guides/managing-results/refine-results/sorting/in-depth/exhaustive-sort#backend-implementation) with the `add_replica` method. To inherit the settings from the primary index, add `inherit:true`. ```ruby Ruby icon=code theme={"system"} class Book < ActiveRecord::Base attr_protected include AlgoliaSearch algoliasearch(per_environment: true) do searchableAttributes [:name, :author, :editor] # Define a replica index to search by `author` only add_replica("Book_by_author", per_environment: true) do searchableAttributes [:author] end # define a replica index with custom ordering but same settings than the main block add_replica("Book_custom_order", inherit: true, per_environment: true) do customRanking ["asc(rank)"] end end end ``` ## Share a single index If you want to share an index for multiple models, make sure that your object IDs are unique. For example, you can prepend the model class name to the record's ID: ```ruby Ruby icon=code theme={"system"} class Student < ActiveRecord::Base attr_protected include AlgoliaSearch algoliasearch(index_name: "people", id: :algolia_id) do # [...] end private def algolia_id # ensure the teacher & student IDs are not conflicting "student_#{id}" end end class Teacher < ActiveRecord::Base attr_protected include AlgoliaSearch algoliasearch(index_name: "people", id: :algolia_id) do # [...] end private def algolia_id # ensure the teacher & student IDs are not conflicting "teacher_#{id}" end end ``` > [!WARNING] > > To reindex a model that's part of a shared index, > you must use `Model.reindex!` instead of `Model.reindex`. > If you use `reindex`, the resulting index would only contain records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) for the current model. > For more information, see [Zero-downtime indexing](/doc/framework-integration/rails/indexing/indexing#regular-reindexing) ## Target multiple indices To index records in several indices, use the `add_index` method. ```ruby Ruby icon=code theme={"system"} class Book < ActiveRecord::Base attr_protected include AlgoliaSearch PUBLIC_INDEX_NAME = "Book_#{Rails.env}" SECURED_INDEX_NAME = "SecuredBook_#{Rails.env}" # store all books in index 'SECURED_INDEX_NAME' algoliasearch(index_name: SECURED_INDEX_NAME) do searchableAttributes [:name, :author] # convert security to tags tags do [released ? "public" : "private", premium ? "premium" : "standard"] end # store all 'public' (released and not premium) books in index 'PUBLIC_INDEX_NAME' add_index(PUBLIC_INDEX_NAME, if: :public?) do searchableAttributes [:name, :author] end end private def public? released && !premium end end ``` # Indexing Source: https://www.algolia.com/doc/framework-integration/rails/indexing/indexing Manage Algolia indices with Rails models. When indexing your data, the request to Algolia is **synchronous**, but the indexing operation on Algolia's servers is **asynchronous**. If you want to call the Algolia API asynchronously, use [background job managers](/doc/framework-integration/rails/indexing/queues). When testing, it's best to [wait for indexing tasks](/doc/framework-integration/rails/advanced/dev-and-testing#wait-for-indexing-tasks). ## Manual indexing with `rake` The rake command `algoliasearch:reindex` looks for all models in your app and indexes them. ```sh Command line icon=square-terminal theme={"system"} $ rake algoliasearch:reindex Reindexing 1 models: Contact. Contact Reindexing 500 records... ``` ## Regular reindexing To reindex all objects in place, use the `reindex!` class method. This method sends all entries to the Algolia 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.). Any record with the same `objectID` is replaced, and new ones are added. This method doesn't delete records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) from your index. To delete them, [clear your index](/doc/libraries/sdk/v1/methods/clear-objects) before reindexing. ```ruby Ruby icon=code theme={"system"} Contact.clear_index! Contact.reindex! ``` Clearing your index deletes all records, but preserves your settings, rules, and synonyms. This method doesn't increase the number of records beyond the new objects you want to index, but your index will be briefly empty, which makes your search unavailable. ## Zero-downtime reindexing To reindex all your records without downtime, including deleted objects, use the `reindex` class method. ```ruby Ruby icon=code theme={"system"} Contact.reindex ``` This method indexes all your records to a temporary index `INDEX_NAME.tmp`. After everything is indexed, the temporary index overwrites your production index. This method guarantees that your index is never empty, but it temporarily doubles the number of records in 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.). If you [restrict your API key to specific indices](/doc/guides/security/api-keys#api-key-restrictions), allow access to both `INDEX_NAME` and `INDEX_NAME.tmp`. If you've changed settings, rules, or synonyms in the Algolia dashboard, this method deletes or resets them, because it recreates your index configuration as defined in your Rails project. To prevent this, turn off [`check_settings`](/doc/framework-integration/rails/index-configuration/index-settings#sync-your-settings-with-algolia). ## Indexing subsets To index a subset of your records, use model scoping. In this case, it's better **not** to use zero-downtime indexing, since it would replace the whole index with just the filtered records. Use regular reindexing with `reindex!`. ```ruby Ruby icon=code theme={"system"} Contact.where("updated_at > ?", 10.minutes.ago).reindex! ``` To index a list of objects directly, use `index_objects`. ```ruby Ruby icon=code theme={"system"} objects = Contact.limit(5) Contact.index_objects(objects) ``` ## Index single instances To index a single instance, use the `index!` instance method. To remove a model from the Algolia index, use `remove_from_index!` ```ruby Ruby icon=code theme={"system"} c = Contact.create!(params[:contact]) # Add to Algolia c.index! # Remove from Algolia c.remove_from_index! ``` ## Automatic updates To keep Algolia indices synced with your Rails models, this gem uses these Rails callbacks: * `after_validation` * `before_save` * `after_commit` If you're bypassing these callbacks in your app, Algolia won't update your changes. Each time you save or delete a record, it'll be indexed or removed from the index. To turn off automatic index updates, change the following options: ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch(auto_index: false, auto_remove: false) do attribute :first_name, :last_name, :email end end ``` ### Temporarily turn off auto-indexing To temporarily turn off auto-indexing, use the `without_auto_index` scope. This can be helpful to achieve better performance if you're making many changes. For example, if you delete all contacts from the index and then create 10,000 new contacts, your app would make 10,001 API requests. ```ruby Ruby icon=code theme={"system"} Contact.delete_all 1.upto(10000) { Contact.create!(attributes) } ``` If you turn off automatic indexing and call `reindex!` after, your app makes fewer API requests, since the `reindex!` method batches the requests automatically. ```ruby Ruby icon=code theme={"system"} Contact.delete_all Contact.without_auto_index do # Auto-indexing is turned off inside this block 1.upto(10000) { Contact.create!(attributes) } end # Uses batch operations Contact.reindex! ``` ### Indexing only if attributes have changed For database-stored attributes, Rails provides a `will_save_change_to_ATTRIBUTE?` method to detect changes. Add this method for the all dynamic attributes you defined. Otherwise, Algolia updates every model because it won't know whether a dynamic attribute was updated. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do attributes :first_name, :email attribute :full_name end def full_name "#{first_name} #{last_name}" end def will_save_change_to_fullname? will_save_change_to_first_name? || will_save_name_to_last_name? end end ``` In Rails versions older than 5.1, this method was called `ATTRIBUTE_changed?`. The Algolia gem checks for both method names. ### `tags` and `geoloc` helpers The [`tags` or `geoloc` helpers](/doc/framework-integration/rails/indexing/serializing-models#helpers) map to the `_tags` and `_geoloc` attributes. That's why you need to use double underscores: * `will_save_change_to__tags` * `will_save_change_to__geoloc` ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do attributes :first_name, :email geoloc :latitude, :longitude end def will_save_change_to__geoloc? will_save_change_to_latitude? || will_save_change_to_longitude? end end ``` ### Single `will_save_change_to` method If Algolia finds an `algolia_dirty?` method, it calls this method instead of all the `will_save_change_to_ATTRIBUTE?` methods for this model. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do attributes :first_name, :email attribute :full_name end def full_name "#{first_name} #{last_name}" end def algolia_dirty? # Return true if the model should be reindexed end end ``` ## Conditional indexing To control the indexing of a record, add constraints with the `:if` or `:unless` options. ```ruby Ruby icon=code theme={"system"} class Post < ActiveRecord::Base include AlgoliaSearch algoliasearch(if: :published?, unless: :deleted?) do end def published? # [...] end def deleted? # [...] end end ``` If you add these constraints, [`saveObjects`](/doc/libraries/sdk/v1/methods/save-objects) and [`deleteObjects`](/doc/libraries/sdk/v1/methods/delete-objects) methods are called to keep the index in sync with your database. Since the gem is stateless and can't know whether the object doesn't match your constraints anymore or if it never did, these operations are always performed. To prevent this, add an `ATTRIBUTE_changed?` method. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch(if: :published) do end def published # true or false end def published_changed? # return true only if you know that the 'published' state changed end end ``` ## Indexing errors Any `Record at the position XX objectID=XX is too big` errors during indexing are because you've exceeded the [size limit for records](https://support.algolia.com/hc/en-us/articles/4406981897617-Is-there-a-size-limit-for-my-index-records-). Reduce the size of your records and try again. # Queues and background jobs Source: https://www.algolia.com/doc/framework-integration/rails/indexing/queues Keep Algolia up to date asynchronously with ActiveJob, Sidekiq or DelayedJob. To perform [auto-indexing](/doc/framework-integration/rails/indexing/indexing#automatic-updates) in the background, use queues. ## Background jobs with ActiveJob By default, ActiveJob queues are used, but you can define your own queueing mechanism: ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch enqueue: true do attribute :first_name, :last_name, :email end end ``` If you perform updates and deletions in the background, you can commit a record to your database before running the job. If you load a record before the job runs, the call to `ActiveRecord#find` fails with a `RecordNotFound` error. To prevent this, you can bypass loading the record from the ActiveRecord and directly communicate with the index: ```ruby Ruby icon=code theme={"system"} class MySidekiqWorker def perform(id, remove) if remove # the record has likely already been removed from your database so we cannot # use ActiveRecord#find to load it task = client.delete_objects(Contact.index_name, [id]) client.wait_for_task(task.task_id) else # the record should be present c = Contact.find(id) c.index! end end end ``` ## Background jobs with Sidekiq You can process jobs in the background with [Sidekiq](https://github.com/mperham/sidekiq): ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch enqueue: :trigger_sidekiq_worker do attribute :first_name, :last_name, :email end def self.trigger_sidekiq_worker(record, remove) MySidekiqWorker.perform_async(record.id, remove) end end class MySidekiqWorker def perform(id, remove) if remove # the record has likely already been removed from your database so we cannot # use ActiveRecord#find to load it task = client.delete_objects(Contact.index_name, [id]) client.wait_for_task(task.task_id) else # the record should be present c = Contact.find(id) c.index! end end end ``` ### Background jobs with delayed\_job You can process jobs in the background with [delayed\_job](https://github.com/collectiveidea/delayed_job): ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch enqueue: :trigger_delayed_job do attribute :first_name, :last_name, :email end def self.trigger_delayed_job(record, remove) if remove record.delay.remove_from_index! else record.delay.index! end end end ``` # Serialization Source: https://www.algolia.com/doc/framework-integration/rails/indexing/serializing-models Convert Rails models to Algolia records. By default, all model attributes are indexed. To control, which attributes to index, use the `attribute` method. ## The `attribute` method The `attribute` method defines which model attributes to index. * If you pass one or more symbols, it calls the methods with the same name. * If you pass it a block, you can only use one symbol to define the attribute name. The block defines the value. To keep your code as readable as possible: * `attributes` is an alias for `attribute` * You can call both methods multiple times For example: ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do # Sending 3 model attributes attributes :first_name, :last_name, :email # Sending a dynamic attribute defined by instance method attribute :full_name # Aliasing birth_date attribute to dob with shorthand block syntax attribute(:dob) { birth_date } # Nesting the company information for organization-sake attribute(:company) do {name: company_name, website: company_website} end end def full_name "#{first_name} #{last_name}" end end ``` If you're using dynamic attributes, you need to handle updates to those attributes manually. For more information, see [Automatic updates](/doc/framework-integration/rails/indexing/indexing). ## The `add_attribute` method You can use the `add_attribute` and `add_attributes` methods, if you want to add new attributes to your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do # Since no +attribute+ were defined, all model attributes will be sent. # Plus the full_name attribute add_attribute(:full_name) do "#{first_name} #{last_name}" end end end ``` ## Custom object IDs Every Algolia record needs an `objectID`. By default, this gem uses Rails `id` as the Algolia `objectID`. To use a custom ID, use the `id` option. Each `objectID` must be unique 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.), or your records might be overwritten or deleted by other models. The `id` can be a model attribute or you can define your own attribute. ```ruby Ruby icon=code theme={"system"} class UniqUser < ActiveRecord::Base include AlgoliaSearch algoliasearch(id: :uniq_email) do end end ``` ```ruby Ruby icon=code theme={"system"} class UniqUser < ActiveRecord::Base include AlgoliaSearch algoliasearch(id: :algolia_id) do end def algolia_id "user_#{id}" end end ``` ## Helpers The `algoliasearch-rails` gem comes with helpers for special Algolia attributes. To detect changes for these helpers, see [`tags` and `geoloc` helpers](/doc/framework-integration/rails/indexing/indexing#tags-and-geoloc-helpers). ### Coordinates for location search Adding coordinates to your records lets you [search by location](/doc/api-reference/api-parameters/aroundLatLng). ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do geoloc :latitude, :longitude end end ``` ### Tags The `_tags` attribute is required for [filtering by tags](/doc/api-reference/api-parameters/tagFilters). You use any attribute for filtering if you [configure it in the settings](/doc/framework-integration/rails/index-configuration/index-settings). ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do tags ["trusted"] end end ``` To create tags dynamically, use a block: ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do tags do [ first_name.blank? || last_name.blank? ? "partial" : "full", has_valid_email? ? "valid_email" : "invalid_email" ] end end end ``` ## Sanitize HTML To remove all HTML tags from your attributes, use the `sanitize` options. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch(sanitize: true) do attributes :name, :email, :company end end ``` ## UTF-8 encoding To make all your attributes UTF-8 encoded, use the `force_utf8_encoding` option. ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch(force_utf8_encoding: true) do attributes :name, :email, :company end end ``` ## Serialization with ActiveModelSerializers You can use [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers/tree/v0.10.6/docs) to extract all logic for creating your records into a separate class. In the `algoliasearch` block, specify what class to use with `use_serializer`. If specified, all `attribute(s)` methods are ignored. You can still use `add_attribute(s)` and the `tags` or `geoloc` helpers. ```ruby Ruby icon=code theme={"system"} # post_serializer.rb class PostSerializer < ActiveModel::Serializer attributes :id, :title, :text has_one :author has_many :comments end # author_serializer.rb class AuthorSerializer < ActiveModel::Serializer attributes :id, :name end # comment_serializer.rb class CommentSerializer < ActiveModel::Serializer attributes :text has_one :author end # post.rb class Post has_one :author has_many :comments algoliasearch do use_serializer PostSerializer # Also combine the serialize result with # `add_attribute`, `tags` or `geoloc` if necessary end end ``` # Relationships Source: https://www.algolia.com/doc/framework-integration/rails/indexing/working-with-relationships Reindex models if a relationship was updated. To represent relationships and hierarchical structures in your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.), you can embed nested objects within your attributes. These nested objects can be arrays, hashes, or a combination of both, provided they can be converted into JSON. ```ruby Ruby icon=code theme={"system"} class Profile < ActiveRecord::Base include AlgoliaSearch belongs_to :user has_many :specializations algoliasearch do attribute :user do # restrict the nested "user" object to its `name` + `email` { name: user.name, email: user.email } end attribute :public_specializations do # build an array of public specialization (include only `title` and `another_attr`) specializations.select { |s| s.public? }.map do |s| { title: s.title, another_attr: s.another_attr } end end end end ``` ## Update changes from nested relations with ActiveRecord To update changes with nested relations using ActiveRecord, use `touch` and `after_touch`. ```ruby Ruby icon=code theme={"system"} # app/models/app.rb class App < ApplicationRecord include AlgoliaSearch belongs_to :author, class_name: :User after_touch :index! algoliasearch do attribute :title attribute :author do author.as_json end end end # app/models/user.rb class User < ApplicationRecord # If your association uses belongs_to # - use `touch: true` # - do not define an `after_save` hook has_many :apps, foreign_key: :author_id after_save { apps.each(&:touch) } end ``` ## Update changes from nested relations with Sequel With Sequel, you can use the touch plugin to propagate the changes: ```ruby Ruby icon=code theme={"system"} # app/models/app.rb class App < Sequel::Model include AlgoliaSearch many_to_one :author, class: :User plugin :timestamps plugin :touch algoliasearch do attribute :title attribute :author do author.to_hash end end end # app/models/user.rb class User < Sequel::Model one_to_many :apps, key: :author_id plugin :timestamps # Can't use the associations since it won't trigger the after_save plugin :touch # Define the associations that need to be touched here # Less performant, but allows for the after_save hook to trigger def touch_associations apps.map(&:touch) end def touch super touch_associations end end ``` # Search Source: https://www.algolia.com/doc/framework-integration/rails/searching/search Build a search experience for your Rails app. To create a search experience for your app, you can choose between frontend or backend search. ## Frontend search Use Algolia's UI libraries, InstantSearch and Autocomplete, for client-side searching. Search requests are made from the browser or app, and results are returned without going through your server. This ensures rapid searching. For more information, see: * [What is InstantSearch?](/doc/guides/building-search-ui/what-is-instantsearch/js) * [What is Autocomplete?](/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete) * [Example Rails app with Autocomplete and InstantSearch.js](https://github.com/algolia/algoliasearch-rails-example) ## Backend search Sometimes, [backend search](/doc/guides/building-search-ui/going-further/backend-search/js#why-backend-search) can be more suitable. The search request goes from your server to Algolia. Algolia sends the response back to your server, where you can process it. ### Backend search response With backend search, you'll get [ORM-compliant](https://guides.rubyonrails.org/active_record_basics.html#object-relational-mapping) Rails model objects. ```ruby Ruby icon=code theme={"system"} hits = Contact.search("jon doe") p(hits) ``` The [`search`](/doc/libraries/sdk/v1/methods/search) method accepts a query and [search parameters](/doc/api-reference/search-api-parameters): ```ruby Ruby icon=code theme={"system"} # Query and search parameters p(Contact.search("jon doe", {hitsPerPage: 5, page: 2})) ``` If you always pass the same parameters, set them as [index settings](/doc/framework-integration/rails/index-configuration/index-settings). ```ruby Ruby icon=code theme={"system"} class Contact < ActiveRecord::Base include AlgoliaSearch algoliasearch do # Default search parameters stored in the index settings minWordSizefor1Typo 4 minWordSizefor2Typos 8 hitsPerPage 42 end end ``` Every ORM object of the response contains the [highlighted text](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/js#highlighting) for the search query. This corresponds to the `highlight_result` attribute in Algolia's raw response. ```ruby Ruby icon=code theme={"system"} hits[0].highlight_result["first_name"]["value"] ``` #### Raw search response To access attributes from the Algolia search response, use the `raw_answer` object: ```ruby Ruby icon=code theme={"system"} hits = Contact.search("jon doe") p(hits.raw_answer.nb_hits) p(hits.raw_answer.nb_pages) ``` If you don't need models, you can avoid loading objects from the database by retrieving Algolia's raw JSON response: ```ruby Ruby icon=code theme={"system"} json_answer = Contact.raw_search("jon doe") p(json_answer) p(json_answer[:hits]) p(json_answer[:facets]) ``` #### Search in specific indices To search in specific indices, specify 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.) with the `index` key: ```ruby Ruby icon=code theme={"system"} Book.search("new america", index: "Book_by_editor") Book.raw_search("new america", index: "Book_by_editor") ``` To search in a replica index, use the `replica` key: ```ruby Ruby icon=code theme={"system"} Book.search("new america", replica: "Book_by_editor") Book.raw_search("new america", replica: "Book_by_editor") ``` ### Backend pagination To add pagination server-side, use one of the following pagination backends: * [`will_paginate`](https://github.com/mislav/will_paginate) * [`kaminari`](https://github.com/amatsuda/kaminari) * [`pagy`](https://github.com/ddnexus/pagy) To use `:will_paginate`, `:kaminari`, or `:pagy`, pass one of them as `:pagination_backend` option to your global configuration: ```ruby Ruby icon=code theme={"system"} AlgoliaSearch.configuration = { application_id: "ALGOLIA_APPLICATION_ID", api_key: "ALGOLIA_API_KEY", pagination_backend: :will_paginate } ``` In your controller: ```ruby Ruby icon=code theme={"system"} @results = MyModel.search("america", hitsPerPage: 10) # when using Pagy pagy, @results = MyModel.search("america", hitsPerPage: 10) ``` In your views: ```ruby ERB icon=code theme={"system"} # If using will_paginate <%%= will_paginate @results %> # If using kaminari <%%= paginate @results %> ``` ### Facets To retrieve facets, you must first [configure them](/doc/framework-integration/rails/index-configuration/index-settings#faceting-and-filtering). To retrieve them, use the `facets` method. ```ruby Ruby icon=code theme={"system"} hits = Contact.search("jon doe", {facets: "*"}) # ORM-compliant array of objects p(hits) # Extra method added to retrieve facets p(hits.facets) # Facet values+count of facet 'company' p(hits.facets[:company]) # Facet values+count of facet 'zip_code' p(hits.facets[:zip_code]) ``` Or perform a raw search and access the JSON response attributes: ```ruby Ruby icon=code theme={"system"} raw_json = Contact.raw_search("jon doe", {facets: "*"}) p(raw_json[:facets]) ``` ### Search for facet values If you have more facet values than what can fit in your UI, it can be helpful to let users [search for them](/doc/guides/managing-results/refine-results/faceting#search-for-facet-values). If you want to support searching for facet values, configure the facet as [`searchable`](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting). ```ruby Ruby icon=code theme={"system"} # Array of {value, highlighted, count} Product.search_for_facet_values("category", "Headphones") ``` This method accepts search parameters, such as filters. ```ruby Ruby icon=code theme={"system"} # Only sends back the categories containing red Apple products (and only counts those) Product.search_for_facet_values( "category", "phone", { query: "red", filters: "brand:Apple" # Array of phone categories linked to red Apple products } ) ``` # Direct API access Source: https://www.algolia.com/doc/framework-integration/symfony/advanced-use-cases/direct-api-access How to access the API client directly using Algolia SearchBundle. In some cases, you might want to access the Algolia client directly to perform advanced operations, like managing API keys and managing indices. The bundle exposes two ways to obtain the `Algolia\AlgoliaSearch\Api\SearchClient`: * **Preferred**: [inject it](/doc/framework-integration/symfony/getting-started/quick-start) by type-hinting the constructor. Symfony autowires the client for you. * **From the container**: retrieve it with the public `search.client` service ID. For example, `$container->get('search.client')`. For example: ```php PHP icon=code theme={"system"} use Algolia\AlgoliaSearch\Api\SearchClient; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; class TestController extends AbstractController { public function listApiKeysAction(SearchClient $client): JsonResponse { return new JsonResponse($client->listApiKeys()); } } ``` # Multiple models in one index Source: https://www.algolia.com/doc/framework-integration/symfony/advanced-use-cases/multiple-models-in-one-index How to implement aggregators to handle multiple models in one index using Algolia SearchBundle. To search in multiple entities at the same time, use aggregators. Aggregators let you index multiple entities in a single index. ## Define aggregators To create a new aggregator, add a new class to your entities directory. The `getEntities` method of this class should return all the entity classes you want to index: ```php PHP icon=code theme={"system"} namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Algolia\SearchBundle\Entity\Aggregator; #[ORM\Entity] class News extends Aggregator { /** * Returns the entities class names that should be aggregated. * * @return string[] */ public static function getEntities() { return [ Post::class, Comment::class, ]; } } ``` If you're using a NoSQL database like MongoDB, use the `Algolia\SearchBundle\Document\Aggregator` class instead. Finally, add the new aggregator class name to the `config/packages/algolia_search.yaml` file: ```yaml YAML icon=code theme={"system"} algolia_search: indices: - name: news class: App\Entity\News ``` ## Search An aggregator is a standard entity class. To start searching entities on the aggregator, use the `search` method of your [SearchService](/doc/framework-integration/symfony/getting-started/quick-start): ```php PHP icon=code theme={"system"} $this->searchService->index($objectManager, $post); $this->searchService->index($objectManager, $comment); $results = $this->searchService->search($objectManager, News::class, 'query'); // $results[0] contains a \App\Entity\Post. // $results[1] contains a \App\Entity\Comment. ``` > [!TIP] > > Be careful, the `$result` array may contain different types of entities instances. To get the raw results from Algolia, use the [`rawSearch`](/doc/framework-integration/symfony/searching/server-side-search#raw-search) method. However, each result might have a different structure: ```php PHP icon=code theme={"system"} $results = $this->searchService->rawSearch(News::class, 'query'); ``` ```json JSON icon=braces theme={"system"} { "hits": [ { "id": 1, "title": "Post title", "slug": "post-title", "content": "Post content by query", "objectID": "App\\Entity\\Post::1" }, { "id": 1, "content": "Comment content by query", "objectID": "App\\Entity\\Comment::1" } ] } ``` To ensure that each result has a similar structure, implement the [`normalize`](/doc/framework-integration/symfony/indexing/configure-searchable-data#normalize) method on each entity or override it on the aggregator class. ## Conditional indexing Conditional indexing on aggregators works just like a normal entity, using the `index_if` key. Use `$this->entity` to have access to the current entity being aggregated. Here is an example using the method approach: ```yaml YAML icon=code theme={"system"} algolia_search: indices: - name: news class: App\Entity\News index_if: isPublished ``` ```php PHP icon=code theme={"system"} #[ORM\Entity] class News extends Aggregator { // ... public function isPublished() { // Index only published posts. if ($this->entity instanceof Post) { return $this->entity->isPublished(); } // If it's not a post, index anyway. return true; } } ``` For more information, see [Conditional indexing](/doc/framework-integration/symfony/indexing/index-data-into-algolia#conditional-indexing). # Troubleshooting Source: https://www.algolia.com/doc/framework-integration/symfony/advanced-use-cases/troubleshooting Troubleshooting your Algolia Symfony implementation. ## No `serializer` service found The bundle requires `symfony/serializer`, which is installed as a Composer dependency. However, the `serializer` service alias is only registered when `framework-bundle` enables it. If your app doesn't enable the framework-bundle serializer, add the following to `config/packages/framework.yaml`: ```yaml YAML icon=code theme={"system"} framework: serializer: enabled: true ``` In most cases, it's better to let the framework-bundle register the serializer for you rather than defining the service by hand. ## The `Groups` attribute wasn't taken into account If your `#[Groups(['searchable'])]` attributes aren't being applied at index time, check these in order: 1. **The bundle's per-index serializer-groups flag is on.** This is the most common cause:it defaults to `false`. Set it in your `algolia_search` configuration: ```yaml YAML icon=code theme={"system"} algolia_search: indices: - name: posts class: App\Entity\Post enable_serializer_groups: true ``` Without this flag, the bundle normalizes entities without passing the `searchable` group to the Symfony serializer, so `#[Groups]` filtering never activates. 2. **Symfony attribute serialization is enabled.** This is the default on Symfony 7 and later. If your app explicitly turns it off, turn it on in `config/packages/framework.yaml`: ```yaml YAML icon=code theme={"system"} framework: serializer: enable_attributes: true ``` 3. **The `#[Groups]` attribute uses the current namespace.** Import `Symfony\Component\Serializer\Attribute\Groups`, not the legacy `Annotation\Groups`. # Algolia SearchBundle Source: https://www.algolia.com/doc/framework-integration/symfony/getting-started/algolia-searchbundle Algolia SearchBundle is the best way to get started with Algolia in your Symfony application. This bundle integrates Algolia Search into your Symfony application (using Doctrine). It lets you index your data, keep it in sync, and search through it. ### Compatibility Algolia/SearchBundle 8.1 is compatible with Symfony 7 and 8, and requires PHP >= 8.2 to work (PHP >= 8.4 when using Symfony 8). For older versions of Symfony, use one of the following versions of `Algolia/SearchBundle`: | Symfony version | Algolia/SearchBundle version | PHP version | | - | - | - | | 4.x | ^5.0 | ^7.2 or ^8.0 | | 5.x | ^5.0 | ^7.2 or ^8.0 | | | ^6.0 | >= 8.0.2 | | 6.x | ^6.0 | >= 8.0.2 | | 7.x | ^7.0 or ^8.0 | >= 8.2 | | 8.x | ^8.0 | >= 8.4 | > [!NOTE] > > Since version 8.1, the bundle depends on version 4 of the [Algolia PHP client](https://github.com/algolia/algoliasearch-client-php). > If you are upgrading from an 8.0.x release, see the [upgrade guide](/doc/framework-integration/symfony/getting-started/upgrade-guide) for namespace and request-option changes. # Install the AlgoliaSearchBundle Source: https://www.algolia.com/doc/framework-integration/symfony/getting-started/installation Install the AlgoliaSearchBundle to integrate Algolia into your Symfony app. ## Require the dependency (with Composer) ```sh Command line icon=square-terminal theme={"system"} composer require algolia/search-bundle ``` ## Register the bundle The bundle is registered automatically. If it doesn't register, add the following entry to `config/bundles.php`: ```php PHP icon=code theme={"system"} return [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], // ... Other bundles ... Algolia\SearchBundle\AlgoliaSearchBundle::class => ['all' => true], ]; ``` ## Algolia credentials You need to provide the Algolia application ID (A unique alphanumeric string that identifies an Algolia application.) and Admin API key (An alphanumeric string that controls access to the Algolia APIs. It defines what actions are allowed, such as searching an index or adding new records.). By default, they're loaded from the `ALGOLIA_APP_ID` and `ALGOLIA_API_KEY` environment variables. If you use a `.env` file, you can set them there. ```dotenv .env icon=lock-keyhole theme={"system"} ALGOLIA_APP_ID=# Your Algolia application ID ALGOLIA_API_KEY=# Your Algolia API key ``` # Access the Algolia search service Source: https://www.algolia.com/doc/framework-integration/symfony/getting-started/quick-start Integrate the Algolia SearchBundle in your Symfony app to index, search, and manage data. Use the `SearchService` to index, search, and manage data with Algolia. ## Inject the SearchService using type hints To use search in a controller or service, inject the `SearchService` into the constructor. Symfony's container autowires the concrete implementation for you. ```php PHP icon=code theme={"system"} namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Algolia\SearchBundle\SearchService; class ExampleController extends AbstractController { protected $searchService; public function __construct(SearchService $searchService) { $this->searchService = $searchService; } } ``` # Upgrade the Algolia SearchBundle Source: https://www.algolia.com/doc/framework-integration/symfony/getting-started/upgrade-guide Upgrade from SearchBundle 8.0.x to 8.1.x. Version 8.1 of the Algolia SearchBundle is built on [version 4 of the Algolia PHP API client](/doc/libraries/sdk/upgrade/php). This guide covers the breaking changes between 8.0.x and 8.1.x and how to update your code. ## Update your dependencies SearchBundle 8.1 requires: * PHP 8.2 or later * Symfony 7 or 8 * `algolia/algoliasearch-client-php` v4 Bump the bundle constraint in your `composer.json` and update: ```sh Command line icon=square-terminal theme={"system"} composer require algolia/search-bundle:^8.1 composer update algolia/search-bundle algolia/algoliasearch-client-php ``` Composer installs a compatible v4 of the PHP client as a transitive dependency. ## `SearchClient` namespace `SearchClient` has moved to a new namespace. If you inject or type-hint `SearchClient` in your own code, update the fully qualified class name. ```diff PHP icon=code theme={"system"} - use Algolia\AlgoliaSearch\SearchClient; + use Algolia\AlgoliaSearch\Api\SearchClient; ``` If you wire the client by its fully qualified name in a service definition, update the service ID: ```diff YAML icon=braces theme={"system"} services: App\Service\MyService: arguments: - $client: '@Algolia\AlgoliaSearch\SearchClient' + $client: '@Algolia\AlgoliaSearch\Api\SearchClient' ``` Automatically provided constructors need the same change: ```diff PHP icon=code theme={"system"} - public function __construct(\Algolia\AlgoliaSearch\SearchClient $client) {} + public function __construct(\Algolia\AlgoliaSearch\Api\SearchClient $client) {} ``` ## Request options format In the v3 client, custom HTTP headers could be passed as top-level keys in `$requestOptions`. The v4 client silently ignores flat top-level keys: headers must be nested under `headers`. ```diff PHP icon=code theme={"system"} $this->searchService->index($em, $entity, [ - 'X-Forwarded-For' => '0.0.0.0', + 'headers' => [ + 'X-Forwarded-For' => '0.0.0.0', + ], ]); ``` This applies to every `SearchService` method that accepts `$requestOptions`: `index()`, `remove()`, `clear()`, `delete()`, `search()`, `rawSearch()`, and `count()`. These top-level HTTP option keys are the ones the v4 client extracts from `$requestOptions`: | Key | Type | Purpose | | - | - | - | | `headers` | associative array | Custom HTTP headers | | `queryParameters` | associative array | Extra URL query parameters | | `body` | associative array | Extra request-body parameters | | `readTimeout` | int (seconds) | Read timeout | | `writeTimeout` | int (seconds) | Write timeout | | `connectTimeout` | int (seconds) | Connection timeout | For `search()`, `rawSearch()`, and `count()`, any other top-level keys are forwarded to the engine as search parameters (for example `filters`, `hitsPerPage`, `attributesToRetrieve`). The HTTP option keys above are the ones that change between 8.0 and 8.1. ## Responses you can wait for `SearchServiceResponse::wait()` no longer accepts parameters. In 8.0, you could pass request options (for example, a custom `readTimeout`) to `wait()`. In 8.1, waiting is handled internally by the client's `waitForTask()` method, and `wait()` doesn't take any arguments. ```diff PHP icon=code theme={"system"} $response = $this->searchService->index($em, $entities); - $response->wait(['readTimeout' => 30]); + $response->wait(); ``` The response classes returned by the bundle now live in `Algolia\SearchBundle\Responses\`: | Class | Returned from | | - | - | | `Algolia\SearchBundle\Responses\SearchServiceResponse` | `index()` and `remove()` on the live service | | `Algolia\SearchBundle\Responses\EngineResponse` | `clear()` and `delete()` on the live service when the index exists | | `Algolia\SearchBundle\Responses\NullResponse` | All four write methods on the test service: `clear()` and `delete()` when the target index doesn't exist | All three classes expose `wait()`, so the calling pattern is the same regardless of which one you receive. If you type-hint against response classes from the PHP client, replace them with the bundle's response classes. ## Autowiring alias The bundle's autowiring alias now targets the new client namespace: ```yaml YAML icon=braces theme={"system"} # src/Resources/config/services.yaml Algolia\AlgoliaSearch\Api\SearchClient: alias: search.client Algolia\SearchBundle\SearchService: alias: search.service ``` If your own service definitions alias or decorate `Algolia\AlgoliaSearch\SearchClient`, update them to reference `Algolia\AlgoliaSearch\Api\SearchClient`. ## Summary of namespace changes | 8.0.x | 8.1.x | | - | - | | `Algolia\AlgoliaSearch\SearchClient` | `Algolia\AlgoliaSearch\Api\SearchClient` | | `Algolia\AlgoliaSearch\Response\IndexingResponse` | `Algolia\SearchBundle\Responses\EngineResponse` | | `Algolia\AlgoliaSearch\Response\NullResponse` | `Algolia\SearchBundle\Responses\NullResponse` | | `Algolia\AlgoliaSearch\Support\UserAgent` | `Algolia\AlgoliaSearch\Support\AlgoliaAgent` | After updating your code, run your test suite and index a few records in a staging environment to confirm that request options and responses you can wait for behave as expected. # Index configuration Source: https://www.algolia.com/doc/framework-integration/symfony/indexing/configure-index Configure your index to optimize the search experience with Algolia SearchBundle. ## Create configuration file `algolia_search.yaml` The configuration for Algolia lives in `config/packages/algolia_search.yaml`. Here, you can define what to index and change other settings, such as the index name prefix and the batch size for automatic indexing. This guide uses the [Symfony demo](https://github.com/symfony/demo) app as an example, using posts and comments. ### Configuration examples The following example lets Algolia store your posts in the index `posts` and your comments in the index `comments`. The other settings use their default values. ```yaml YAML icon=code theme={"system"} algolia_search: indices: - name: posts class: App\Entity\Post - name: comments class: App\Entity\Comment ``` The following example lets Algolia index your posts and comments. The index names are combined from a prefix and the names `posts` or `comments`. The prefix is read from the environment variable `SEARCH_PREFIX`. This configuration example also turns off real-time sync and opts posts into serializer groups. ```yaml YAML icon=code theme={"system"} algolia_search: # Use a prefix for index names based on environment variables prefix: '%env(SEARCH_PREFIX)%' # Turn off real-time sync doctrineSubscribedEvents: [] indices: - name: posts class: App\Entity\Post enable_serializer_groups: true - name: comments class: App\Entity\Comment ``` ## Multi-environment setup To avoid working with production data while developing your app, use different configurations for each environment. ### Prefix To use different indices for production and development, set up different index name prefixes. Do this in configuration files or environment variables. #### Set up different prefixes in configuration files Create a configuration file inside the `dev/` directory and override your default configuration. The configuration for your production environment uses the `app_prod_` prefix: ```yaml YAML icon=code theme={"system"} # config/packages/algolia_search.yaml algolia_search: prefix: app_prod_ ``` The configuration for your development environment would store the indices with the `app_dev_` prefix: ```yaml YAML icon=code theme={"system"} # config/packages/dev/algolia_search.yaml algolia_search: prefix: app_dev_ ``` #### Set up different prefixes in environment variables In your configuration file, set the prefix as environment variable. ```yaml YAML icon=code theme={"system"} algolia_search: prefix: '%env(SEARCH_PREFIX)%' ``` Then, define the `SEARCH_PREFIX` variable in your `.env` file or your server configuration. The following example uses the `APP_ENV` environment variable to create a search prefix: ```dotenv .env icon=lock-keyhole theme={"system"} SEARCH_PREFIX=app1_${APP_ENV}_ ``` ### Bypass calls to Algolia To prevent requests to Algolia while developing your app, you can use one of the following approaches: * [Unsubscribe from Doctrine events](/doc/framework-integration/symfony/indexing/index-data-into-algolia#automatic-indexing-with-doctrine-events) to avoid indexing calls when you update your data. * Override `search.service` with the built-in `Algolia\SearchBundle\Services\NullSearchService` in your test environment: ```yaml YAML icon=code theme={"system"} # config/services_test.yaml services: search.service: class: Algolia\SearchBundle\Services\NullSearchService ``` ## Index settings To configure index settings, you can use the JSON files in `config/settings/algolia_search/{$index_name}-settings.json`. The `SettingsManager` class backs up settings from the engine and pushes them back with the following commands: ```sh Command line icon=square-terminal theme={"system"} php bin/console search:settings:backup --indices=posts,comments php bin/console search:settings:push --indices=posts,comments ``` The `--indices` option takes a comma-separated list of index names (without prefix). Without options, the command processes **all indices**. ### Settings directory By default, the bundle looks for settings files under `config/settings/algolia_search/`. You can change the settings directory in your configuration file. The bundle appends the value directly to `kernel.project_dir`, so it must start with a leading slash: ```yaml YAML icon=code theme={"system"} algolia_search: settingsDirectory: /search-settings ``` # Configure searchable data Source: https://www.algolia.com/doc/framework-integration/symfony/indexing/configure-searchable-data The Algolia SearchBundle helps you transform your model into Algolia records. Learn how to customize this process. ## Indexing data You have to define the entities that should be indexed to Algolia. You can do this by adding the entities to your configuration file, under the `algolia_search.indices` key. Each entry under the `indices` key must contain the following attributes: * `name` is the canonical name of the index in Algolia * `class` is the full class reference of the entity to index For example, to index all posts: ```yaml YAML icon=code theme={"system"} algolia_search: indices: - name: posts class: App\Entity\Post ``` #### `enable_serializer_groups` Before sending your data to Algolia, each entity is converted to an array using the Symfony built-in serializer. This option lets you define which attributes to index using the `#[Groups(['searchable'])]` attribute. For more information, see [Normalizers](#normalizers). Example: ```yaml YAML icon=code theme={"system"} algolia_search: indices: - name: posts class: App\Entity\Post enable_serializer_groups: true ``` For more information about sending data to Algolia, see [Index data into Algolia](/doc/framework-integration/symfony/indexing/index-data-into-algolia). #### Batching By default, calls to Algolia for indexing or removing data are batched per 500 items. You can modify the batch size in your configuration. ```yaml YAML icon=code theme={"system"} algolia_search: batchSize: 250 ``` The import command also uses this parameter to retrieve data with Doctrine. If you run out of memory while importing your data, use a smaller `batchSize` value. #### JMS serializer The bundle also provides basic support for the JMS serializer. Note that not all features are supported (like the `#[Groups]` attribute). In your configuration, pass the name of the JMS serializer service (`jms_serializer` by default). ```yaml YAML icon=code theme={"system"} algolia_search: serializer: jms_serializer indices: - name: posts class: App\Entity\Post ``` ## Normalizers By default all entities are converted to an array with the built-in [Symfony normalizers](https://symfony.com/doc/current/components/serializer.html#normalizers) (for example `GetSetMethodNormalizer`, `DateTimeNormalizer`, or `ObjectNormalizer`) which should be enough for simple use cases. For more control over what you send to Algolia, or to avoid [circular references](https://symfony.com/doc/current/components/serializer.html#handling-circular-references), write your own normalizer. Symfony uses the first normalizer in the array to support your entity or format. You can [change the order](#normalize) in your service declaration. > [!NOTE] > > The normalizer is called with *searchableArray* format. You have many choices on how to customize your records: * [Use attributes in the entity](#attributes) * [Write a custom method in the entity](#normalize) * [Write a custom normalizer class](#custom-normalizers) > [!NOTE] > > The following features are only supported with the [default Symfony serializer](https://symfony.com/doc/current/components/serializer.html), not with [JMS serializer](https://jmsyst.com/libs/serializer). ### Attributes The simplest way to choose which fields to index is to mark them with the `#[Groups]` attribute. This feature relies on the built-in `ObjectNormalizer` and its serializer-group support. > [!NOTE] > > Attributes require [`enable_serializer_groups` to be set to `true`](#enable_serializer_groups) in the configuration. ```php PHP icon=code theme={"system"} title; } #[Groups(['searchable'])] public function getSlug(): ?string { return $this->slug; } #[Groups(['searchable'])] public function getCommentCount(): ?int { return count($this->comments); } } ``` ### Normalize Another option is to implement a dedicated method that returns the entity as an array. This feature relies on the `CustomNormalizer` that ships with the serializer component. Implement the `Symfony\Component\Serializer\Normalizer\NormalizableInterface` interface and write your `normalize` method. Example based on a simplified version of [this Post entity](https://gist.github.com/julienbourdeau/3d17304951028cf370ed5fe95d104911): ```php PHP icon=code theme={"system"} $this->getTitle(), 'content' => $this->getContent(), 'comment_count' => $this->getComments()->count(), 'tags' => array_unique(array_map(function ($tag) { return $tag->getName(); }, $this->getTags()->toArray())), // Reuse the $serializer 'author' => $serializer->normalize($this->getAuthor(), $format, $context), 'published_at' => $serializer->normalize($this->getPublishedAt(), $format, $context), ]; } } ``` #### Handle multiple formats In case you are already using this method for something else, like encoding entities into JSON for instance, you may want to use a different format for both use cases. You can rely on the format to return different arrays. ```php PHP icon=code theme={"system"} public function normalize(NormalizerInterface $serializer, $format = null, array $context = []): array { if (\Algolia\SearchBundle\Searchable::NORMALIZATION_FORMAT === $format) { return [ 'title' => $this->getTitle(), 'content' => $this->getContent(), 'author' => $this->getAuthor()->getFullName(), ]; } // Or if it's not for search return ['title' => $this->getTitle()]; } ``` ### Custom normalizers You can create a custom normalizer for any entity by implementing the `Symfony\Component\Serializer\Normalizer\NormalizerInterface` interface. The following snippet shows a simple `UserNormalizer`. ```php PHP icon=code theme={"system"} $object->getId(), 'username' => $object->getUsername(), ]; } public function supportsNormalization($data, ?string $format = null, array $context = []): bool { return $data instanceof User; // Or if you want to use it only for indexing: // return $data instanceof User && Searchable::NORMALIZATION_FORMAT === $format; } public function getSupportedTypes(?string $format): array { return [User::class => true]; } } ``` Tag the normalizer to add it to the default serializer. In your service declaration, add the following. ```yaml YAML icon=code theme={"system"} services: user_normalizer: class: App\Serializer\Normalizer\UserNormalizer tags: - { name: serializer.normalizer } ``` ```xml XML icon=code-xml theme={"system"} ``` With this configuration, the serializer uses `UserNormalizer` whenever it encounters a `User`: either as the top-level indexed entity or as a nested property on another entity being indexed. ## Order of normalizers Because Symfony uses the first normalizer that supports your entity or format, the order is important. The `ObjectNormalizer` is registered with a priority of -1000 and should always be the last fallback. Most of Symfony's built-in normalizers sit in the negative range below the `CustomNormalizer` (registered at -800), though a few (such as the `UnwrappingDenormalizer`) run at higher priorities. To keep your normalizers ahead of the bundle's `CustomNormalizer`, register them above -800. This also puts them ahead of the lower-priority Symfony fallback normalizers, such as `ObjectNormalizer`. The default priority is 0. You can change the priority in your service definition. ```yaml YAML icon=code theme={"system"} services: serializer.normalizer.datetime: class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer tags: - { name: serializer.normalizer, priority: -100 } ``` ```xml XML icon=code-xml theme={"system"} ``` # Send data to Algolia Source: https://www.algolia.com/doc/framework-integration/symfony/indexing/index-data-into-algolia Send data to Algolia with the Algolia SearchBundle. ## Before you begin After configuring what entities to index, you can send data to Algolia. This guide uses [this Post entity](https://gist.github.com/julienbourdeau/3d17304951028cf370ed5fe95d104911) and the following configuration: ```yaml YAML icon=code theme={"system"} algolia_search: indices: - name: posts class: App\Entity\Post - name: comments class: App\Entity\Comment ``` ## Manual indexing ### With the CLI Once your `indices` configuration is ready, you can use the built-in console command to import all existing data: ```sh Command line icon=square-terminal theme={"system"} # Import all indices php bin/console search:import # Choose what indices to reindex by passing the index name php bin/console search:import --indices=posts,comments ``` Before reindexing everything, you may want to [clear the index](#manual-deletion). #### Atomic reindexing For a zero-downtime rebuild, pass the `--atomic` flag. The bundle creates a temporary index, copies your existing settings, synonyms, and rules to it, indexes all entities into it, and then atomically moves it over the source index: ```sh Command line icon=square-terminal theme={"system"} php bin/console search:import --atomic --indices=posts ``` Use this flag when the application is serving live traffic and you need the existing records and settings to remain available until the new index is fully populated. ### Programmatically To index any entities in your code, use the [`SearchService`](/doc/framework-integration/symfony/getting-started/quick-start). Pass it the `ObjectManager` associated with your records and the records to index. Records can be a single entity, an array of entities or even an array of different entities as long as they're using the same `ObjectManager`. ```php PHP icon=code theme={"system"} $searchService->index($entityManager, $post); $searchService->index($entityManager, $posts); $searchService->index($entityManager, $postsAndComments); ``` You can pass a third optional argument to the `index` method, the `$requestOptions` array or object. With PHP client v4, HTTP-level options (headers, query parameters, body extras, timeouts) must be nested under their own key: ```php PHP icon=code theme={"system"} $searchService->index($entityManager, $anyObject, [ 'headers' => [ 'X-Forwarded-For' => '0.0.0.0', ], ]); ``` The `index` method, along with all the methods used to manage your data and indices in the `SearchService`, is *waitable*. By chaining the function [`wait()`](/doc/libraries/sdk/methods/search/wait-for-task) to your operation, you're saying explicitly that you want to wait for the engine to finish processing your task before moving on. ```php PHP icon=code theme={"system"} $searchService->index($entityManager, $anyObject)->wait(); ``` ## Manual deletion ### With the CLI To completely clear your indices (before reindexing for example), use the `search:clear` command. ```sh Command line icon=square-terminal theme={"system"} # Clear all indices php bin/console search:clear # Choose what indices to clear by passing the index name php bin/console search:clear --indices=posts,comments ``` ### Programmatically The same way you [index data](#manual-indexing), you can use the `remove` method to delete entries from the Algolia 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.). ```php PHP icon=code theme={"system"} $searchService->remove($entityManager, $post); $searchService->remove($entityManager, $posts); $searchService->remove($entityManager, $postsAndComments); ``` Here as well, you can pass `$requestOptions`: ```php PHP icon=code theme={"system"} $searchService->remove($entityManager, $anyObject, $requestOptions); ``` And wait for the engine to process your deletion completely: ```php PHP icon=code theme={"system"} $searchService->remove($entityManager, $anyObject)->wait(); ``` ## Automatic indexing with Doctrine events The bundle listens to three Doctrine events: `postPersist`, `postUpdate`, and `preRemove`. Every time data is inserted, updated, or deleted using Doctrine, your Algolia index stays in sync. To unsubscribe from all three events, set `doctrineSubscribedEvents` to an empty array. This is useful when you index through a queue (such as RabbitMQ) or want to skip Algolia calls in development. ```yaml YAML icon=code theme={"system"} # Unsubscribe from all Doctrine events algolia_search: doctrineSubscribedEvents: [] ``` > [!NOTE] > > The three events are subscribed together or not at all. > Passing a subset (for example, `['postPersist']`) doesn't have any effect: the bundle still listens to all three. ## Conditional indexing Most of the time, there are some of your items that you don't want to index. For instance, you may want to only index a post if it's published. In your configuration, you can specify when a post should be indexed with the `index_if` key. Because Algolia relies on the [PropertyAccess component](https://symfony.com/doc/current/components/property_access.html) you can pass a method name, a class property name, or a nested key in a property array. The property must evaluate to true to index the entity and false to bypass indexing. If you're updating an entity using Doctrine and this property evaluates to false, the entity will be removed. **Example with a method or a property** ```yaml YAML icon=code theme={"system"} algolia_search: indices: - name: posts class: App\Entity\Post index_if: isPublished ``` In this case, `isPublished` could be a method or a class property. With a method: ```php PHP icon=code theme={"system"} class Post { public function isPublished() { return !is_null($this->published_at); } } ``` With a property: ```php PHP icon=code theme={"system"} class Post { public $isPublished = true; } ``` **Example with an array** ```yaml YAML icon=code theme={"system"} algolia_search: indices: - name: posts class: App\Entity\Post index_if: config.indexable ``` In this case, the bundle will read this value. ```php PHP icon=code theme={"system"} class Post { public $config = ['indexable' => false]; } ``` ## Connection errors Are you getting "Impossible to connect", "Unable to connect", or "Unreachable hosts" errors? First, make sure the issue isn't at your end: * Ensure you're using the correct application ID and API key. Find these credentials on your [Algolia dashboard](https://dashboard.algolia.com/account/api-keys/all). * Check for recent changes in your code. * Check the status of your data center provider. > [!NOTE] > > If you're using Firebase, you can only access Algolia from a paid Firebase tier. If you can't solve the problem yourself, contact the [Algolia support](https://support.algolia.com/hc/en-us/requests/new) team and provide them with the following information: * The name of your Symfony integration and its version number * A code snippet to reproduce the issue * Error message or stack trace (if applicable) * The name of the Algolia index that's causing problems * The exact [UTC time](https://wikipedia.org/wiki/Coordinated_Universal_Time) of the event * If you can't connect to the Algolia API from your servers, send the output from the following command (run on any affected server): ```sh Command line icon=square-terminal theme={"system"} curl -sL https://algolia.com/downloads/diag.sh > ./diag.sh && sudo ./diag.sh ALGOLIA_APPLICATION_ID ``` > [!TIP] > > Replace `ALGOLIA_APPLICATION_ID` with *your* Algolia application ID. ## Indexing errors Any `Record at the position XX objectID=XX is too big` errors during indexing are because you've exceeded the [size limit for records](https://support.algolia.com/hc/en-us/articles/4406981897617-Is-there-a-size-limit-for-my-index-records-). Reduce the size of your records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) and try again. # Client-side search Source: https://www.algolia.com/doc/framework-integration/symfony/searching/client-side-search How to implement client-side search. Searching directly from the frontend significantly improves search response time, and reduces load and traffic on your servers. ## Generate search keys For client-side implementations, always use a **Search-only** API key (An alphanumeric string that controls access to the Algolia APIs. It defines what actions are allowed, such as searching an index or adding new records.). Your Admin API key is sensitive. Never share it with anyone. It must remain confidential. To generate a search key for a given 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.), use the `SearchClient`'s [`generateSecuredApiKey`](/doc/libraries/sdk/methods/search/generate-secured-api-key) method. > [!TIP] > > Store your [search-only API key](/doc/guides/security/api-keys#search-only-api-key) in your environment variables, > or generate a new one using the Clients' [`addApiKey`](/doc/libraries/sdk/methods/search/add-api-key) method. ```php PHP icon=code theme={"system"} $searchOnlyAPIKey = getenv('ALGOLIA_SEARCH_API_KEY'); $validUntil = time() + 3600; // Generate a new secured API key $searchKey = \Algolia\AlgoliaSearch\Api\SearchClient::generateSecuredApiKey( $searchOnlyAPIKey, [ 'restrictIndices' => $this->searchService->searchableAs(Post::class), 'validUntil' => $validUntil ] ); ``` Define the indices you want to access with this API key, and change the `validUntil` parameter to set a validity period. ## Integration with InstantSearch Now that you know how to generate your secured API keys, you can use them to create your frontend view with InstantSearch. To learn more, see [What is InstantSearch?](/doc/guides/building-search-ui/what-is-instantsearch/js). # Server-side search Source: https://www.algolia.com/doc/framework-integration/symfony/searching/server-side-search How to search on your server using Algolia SearchBundle. ## Basic search You can search for documents in your Algolia 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 a single line of code. The `search` method queries Algolia for matching results and returns an array of Doctrine-managed objects (ORM entities or ODM documents). The objects are loaded from your database through the Doctrine Manager you pass in. > [!TIP] > > Both `SearchService` and `Doctrine\Persistence\ManagerRegistry` must be [injected into your class](/doc/framework-integration/symfony/getting-started/quick-start), > so that you can use `$this->searchService` and `$this->managerRegistry`. ```php PHP icon=code theme={"system"} $em = $this->managerRegistry->getManagerForClass(Post::class); $posts = $this->searchService->search($em, Post::class, 'query'); ``` The `search` method only retrieves a list of IDs from the engine and uses them to hydrate the corresponding Doctrine objects from your database. You can only pass parameters to modify what to search, not modify the type of response. If you want to modify the attributes to retrieve or retrieve data like `facets`, `facets_stats`, or `_rankingInfo`, use the [`rawSearch`](#raw-search) method. ## Request options The search and write methods in `SearchService` take a `$requestOptions` array as their last argument. For `search()`, `rawSearch()`, and `count()`, any top-level keys that aren't HTTP options become search parameters sent to Algolia, for example, to change the page, filter the results, or choose what attributes to retrieve. You can find all possibilities in the [list of search API parameters](/doc/api-reference/search-api-parameters). For write methods (`index()`, `remove()`, `clear()`, `delete()`), `$requestOptions` is passed directly to the PHP client, so only supported request-option keys (such as `headers`, `queryParameters`, and timeouts) apply. ```php PHP icon=code theme={"system"} $em = $this->managerRegistry->getManagerForClass(Post::class); $posts = $this->searchService->search($em, Post::class, 'query', [ 'page' => 0, 'hitsPerPage' => 10, 'filters' => 'comment_count>10', ]); ``` ## Raw search If you want to get the raw results from Algolia, use the `rawSearch` method. This is the method you'll need to use if you want to retrieve the highlighted snippets or ranking information for instance. It takes a `$requestOptions` array as an optional last parameter. ```php PHP icon=code theme={"system"} $posts = $this->searchService->rawSearch(Post::class, 'query'); $posts = $this->searchService->rawSearch(Post::class, 'query', [ 'page' => 0, 'hitsPerPage' => 10, 'filters' => 'comment_count>10' // ... ]); ``` The difference with the `search` method is that you won't retrieve Doctrine-managed objects, but a PHP associative array containing the raw engine response. ## Count Use this method when you only need the number of results for a query. `count` still triggers a search request. ```php PHP icon=code theme={"system"} $posts = $this->searchService->count(Post::class, 'query'); ``` ## Clear The `clear` method deletes the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) in an index without affecting its settings. It takes the class name associated with an index and, optionally, `$requestOptions` as the last parameter. You can [wait for this method to finish](/doc/libraries/sdk/methods/search/wait-for-task). ```php PHP icon=code theme={"system"} $posts = $this->searchService->clear(Post::class); $posts = $this->searchService->clear(Post::class, [ // ..any request options ]); $posts = $this->searchService->clear(Post::class)->wait(); ``` ## Delete Delete an index and all its settings, including links to its replicas. It takes the class name associated with an index and, optionally, `$requestOptions` as the last parameter. You can [wait for this method to finish](/doc/libraries/sdk/methods/search/wait-for-task) as well. ```php PHP icon=code theme={"system"} $posts = $this->searchService->delete(Post::class); $posts = $this->searchService->delete(Post::class, [ // ..any request options ]); $posts = $this->searchService->delete(Post::class)->wait(); ``` # Libraries and tools Source: https://www.algolia.com/doc/libraries Explore Algolia libraries, integrations, and resources for building and scaling search and discovery experiences. ## Ready-made solutions Use pre-built solutions with data ingestion, a search interface, and default settings. > [card] [DocSearch](https://docsearch.algolia.com/docs/who-can-apply) > > Add search to docs sites with built-in UI and indexing. > [card] [Ecommerce integrations](/doc/integration) > > Connect Algolia to Shopify, Magento, Salesforce, and more. ## Build with Algolia components Combine prebuilt components for more freedom than ready-made solutions. Write less code than a custom build. ### Data ingestion > [card] [Crawler](/doc/tools/crawler/getting-started/overview) > > Index content from websites. > [card] [Connectors](/doc/integration#data-connectors) > > Sync data from databases and services with Algolia. > [card] [Netlify plugin](/doc/tools/crawler/netlify-plugin/quick-start) > > Index Netlify sites with little setup. ### UI and retrieval > [card] [SiteSearch](https://sitesearch.algolia.com/) > > Pre-built search UI for content-heavy websites. > [card] [InstantSearch](/doc/ui-libraries) > > UI libraries for building search interfaces across platforms. > [card] [Autocomplete](/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete) > > JavaScript library for building autocomplete features. > [card] [Agent Studio](/doc/guides/algolia-ai/agent-studio) > > Build conversational agents. ### Event tracking > [card] [Tealium](/doc/guides/sending-events/connectors/tealium) > > Collect click and conversion events with Tealium. > [card] [Google Tag Manager](/doc/guides/sending-events/connectors/google-tag-manager) > > Collect click and conversion events with Google Tag Manager. > [card] [Segment](/doc/guides/sending-events/connectors/segment) > > Collect click and conversion events with Segment. ### Relevance and merchandising > [card] [Dashboard](https://dashboard.algolia.com/dashboard/) > > Manage indices, settings, and relevance. > [card] [Merchandising Studio](https://dashboard.algolia.com/merchandising/studio) > > Promote and curate results with a no-code interface. ## Build your own Use APIs and libraries for full control over ingestion, relevance, and UI. > [card] [API clients](/doc/libraries/sdk) > > Official SDKs for popular programming languages. > [card] [API parameters](/doc/api-reference/api-parameters) > > Configure ranking, filtering, faceting, and more. > [card] [HTTP API reference](/doc/rest-api) > > REST API docs for all Algolia services. > [card] [Terraform provider](https://registry.terraform.io/providers/algolia/algolia/latest/docs) > > Manage Algolia resources with Terraform. ## More resources > [card] [Code Exchange](https://www.algolia.com/developers/code-exchange/) > > Examples and integrations from Algolia and the community. > [card] [UI design kit](/doc/guides/building-search-ui/resources/ui-kit/js) > > Design resources for common search UI. # API clients Source: https://www.algolia.com/doc/libraries/sdk The API client libraries let you use Algolia's APIs in the programming language of your choice. This page lists the latest major versions of Algolia's API clients, labeled *current*. To see the previous major versions, labeled *legacy*, use the version selector on the top left, or see [Install API clients (legacy)](/doc/libraries/sdk/v1/install). * [**C#**](/doc/libraries/sdk/install#csharp) * [**Dart**](/doc/libraries/sdk/install#dart) * [**Go**](/doc/libraries/sdk/install#go) * [**Java**](/doc/libraries/sdk/install#java) * [**JavaScript**](/doc/libraries/sdk/install#javascript) * [**Kotlin**](/doc/libraries/sdk/install#kotlin) * [**PHP**](/doc/libraries/sdk/install#php) * [**Python**](/doc/libraries/sdk/install#python) * [**Ruby**](/doc/libraries/sdk/install#ruby) * [**Scala**](/doc/libraries/sdk/install#scala) * [**Swift**](/doc/libraries/sdk/install#swift) # Add search with the Algolia Wizard Source: https://www.algolia.com/doc/tools/algolia-wizard Use the Algolia Wizard to index your app's data and add a search UI with a guided, AI-assisted setup. This is a **beta feature** according to [Algolia's Terms of Service ("Beta Services")](https://www.algolia.com/policies/terms/). The wizard identifies data to index, generates an ingestion script to send that data to Algolia, and adds a basic search UI. The wizard asks you to approve commands that can change your project, install software, or send records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) to Algolia. ## When to use the Algolia Wizard Use the wizard to go from an app with no search to searching your own data, typically in less than 10 minutes. The wizard indexes one of your app's data types and adds a search UI you can test with your records. The generated code is yours to review, change, and extend. The wizard gives you a starting point. To extend the integration: * **Keep the index up to date.** The generated ingestion script runs on demand, so schedule it, extend it to send only changed records, or replace it with a [connector](/doc/guides/sending-and-managing-data/send-and-update-your-data/connectors) that Algolia runs for you. * **Build on the generated search UI.** The wizard uses [InstantSearch](/doc/ui-libraries), so you can add widgets such as filters, pagination, and highlighting from the same library. * **Use your own coding agent for later changes.** Connect your coding agent to [DevEx MCP](/doc/guides/model-context-protocol/devex-mcp) to help it generate API client code that matches your project. ## Before you begin Before you run the wizard, make sure you have: * An existing app in a Git repository with at least one commit. Start from a clean working tree, with no uncommitted changes or untracked files, so you can identify and undo the wizard's changes. The wizard changes files in your project directory but doesn't create a branch, commit, or push. * Node.js 24 or later. * An Algolia account with an active application and permission to manage [API keys](/doc/guides/security/api-keys) for that application. * If you want to index existing data, you need a data source such as a file, database, or API. > [!NOTE] > > Imported JSON files must contain an array of objects. Imported CSV and TSV files must include a header row. ## Run the wizard In the root directory of your app, run: ```sh Command line icon=square-terminal theme={"system"} npx @algolia/wizard@latest ``` > [!TIP] > > For the best layout, use a terminal with at least 100 columns by 30 rows. > Press `Tab` to show or hide the log panel and `Esc` to exit. **1. Sign in to Algolia** In the browser window the wizard opens, sign in to Algolia or create an account. **2. Choose an application** Choose the Algolia application to work in. If a `.env` or `.env.local` file in your project defines a variable ending in `ALGOLIA_APP_ID` or `ALGOLIA_APPLICATION_ID`, the wizard marks that application as detected. **3. Consent to AI-generated changes** The wizard asks for this consent once per project and saves your answer. To reset consent, run the wizard with `--reset-on-run`. This also deletes this project's saved session and removes the Algolia Wizard API keys stored for all projects. During the run, the wizard asks you to approve commands that can change your project, install software, or send data, unless an earlier approval already covers the action. It shows the full command, its working directory, and a brief explanation, and you approve or reject it. Commands that only read, check, test, or build your project run without a prompt. An approval also covers later commands that take the same action with the same side effects. Installing a different package requires a new approval. The wizard always asks for approval before running scripts in `.algolia-wizard/`. **4. Confirm your app's languages** Confirm the languages the wizard detected, or list them yourself. **5. Choose the type of data to index** Select the type of data to index, or name a different type for the wizard to find. The wizard refers to types of app data as *entities*. For example, a `Product` entity might have `name`, `description`, `category`, and `price` properties. Each item becomes a record in the index. **6. Choose an index** Select an existing Algolia index or create one. > [!WARNING] > > Use a non-production Algolia index while evaluating the wizard. > If you approve the wizard's ingestion step, it sends records to the selected index. **7. Choose data to index** Choose existing project data, a JSON, CSV, or TSV file, or generated sample data. **8. Review and run the ingestion script** Review the ingestion script, then approve the exact command that runs it. If the script changes after approval, the wizard asks for approval again. Running the script sends records to the selected index, and the wizard reports how many records it indexed. **9. Confirm your frontend framework** Confirm the frontend framework the wizard detected, or select one from the list. **10. Check the search UI for errors** Review the wizard's type-check and lint results for the generated search UI. If your project lacks packages needed for these checks, the wizard offers to install them. When the workflow finishes, the wizard summarizes what it did, suggests next steps, and asks you to review the generated code. If the wizard stops, restart it in the same directory. Choose whether to resume the session or start a new one. ## Review the generated changes Before committing, review the files the wizard created or changed. Use `git status` to find new and modified files and `git diff` to inspect changes to tracked files. Open new files separately to review their contents. The generated search UI enables Insights and sends view and click events. It sends a conversion event only if your app already has a matching action, such as adding a result to a cart. Otherwise, the generated code leaves a `TODO` comment in the search UI component where you can add one. For more information, see [Click and conversion events](/doc/guides/sending-events). ### Inspect project files * Review the ingestion script in `.algolia-wizard/` at the root of your project. The wizard doesn't add this directory to `.gitignore`, so you can review and commit its contents. * Check any imported data copied into `.algolia-wizard/` or generated sample data written to `.algolia-wizard/data/`. If the script uses sample data, find the `TODO` comment in the ingestion script that marks where to replace it with your real data source. * Review changes to the file that lists the packages your project uses, such as `package.json`, `requirements.txt`, `Gemfile`, `go.mod`, or `composer.json`. The wizard adds the Algolia client library for your language and uses your project's package manager to install the packages needed for InstantSearch. * Confirm that the wizard added the search UI in an appropriate location and followed your project's conventions. It creates a component and adds the code to display it in an existing file. If your frontend has no `package.json`, it adds scripts to your HTML or template instead. These load InstantSearch.js and the Algolia client from a content delivery network (CDN). * If your app already has a search box for the same content, the wizard replaces it with the Algolia search UI and removes the code it replaced. It keeps filters, navigation, and search controls for other content. ### Check API keys and credentials The wizard creates or reuses two API keys, both restricted to the selected index: a write key for the ingestion script and a search-only key for the UI. The write key can add and delete records and change index settings, so treat it as a private credential. Find them on the [**API Keys** page of the Algolia dashboard](https://dashboard.algolia.com/account/api-keys), as "Algolia Wizard write key for INDEX\_NAME index" and "Algolia Wizard search-only key for INDEX\_NAME index". Review any credentials written to project files before committing. * If the ingestion script loads credentials from an existing `.env` file, confirm that `ALGOLIA_APP_ID` and `ALGOLIA_INDEX_NAME` match the selected application and index. The wizard restricts `ALGOLIA_WRITE_KEY` to that index, but the key can still add and delete records and change index settings. Confirm that Git doesn't track the `.env` file and that `.gitignore` excludes it. * Confirm that the search UI configuration file exports only `ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME`. If your UI loads scripts from a CDN, check the values that the configuration script sets on the browser's `window` object. * Confirm that the search-only key grants search access only to the intended index. * Check the indexed records for personal information, private fields, and other data you don't intend to make searchable. > [!WARNING] > > Only the search-only API key should appear in client-side code. > Never commit or expose `ALGOLIA_WRITE_KEY`, an Algolia Admin API key, a database password, or other private credentials. ### Test your search 1. Confirm that the ingestion script used the intended data source and that the selected index contains the expected number of records. 2. Check that the attributes displayed in the search UI exist in the indexed records. 3. Start your app using your usual local development workflow. 4. Search for terms that match your indexed content. Check that the results are relevant and that the search UI works as expected. After reviewing the generated files and credentials, commit the changes. ## Update your index To refresh the index after changing the source data, run the ingestion command that the wizard showed after the ingestion step. The script reads `ALGOLIA_APP_ID`, `ALGOLIA_WRITE_KEY`, and `ALGOLIA_INDEX_NAME` from the environment. If the wizard wrote them to a `.env` file, the script loads them from there. Otherwise, set them before running the command. ## Command-line options Add these options after `npx @algolia/wizard@latest` to change how the wizard runs. For example, use `npx @algolia/wizard@latest --help` to list the available options. | Option | Description | | - | - | | `--reset-on-run` | Delete this project's saved wizard state, including AI consent and any saved session. It also removes Algolia Wizard API keys stored in your keychain for all projects. Later runs create new keys. | | `-h`, `--help` | Show the options and exit. | ## Troubleshoot the wizard Use the error messages and logs to investigate problems with a wizard run. ### Error messages | Message | What to do | | - | - | | "You have loaded an old version of the Wizard." | Run `npx @algolia/wizard@latest`. | | "This Algolia account has no applications." | Create an application in the Algolia dashboard or with `npx @algolia/cli@latest application create`, then rerun the wizard. | | "None of the applications on this Algolia account are usable." | Each application is paused or your account lacks permission to manage its API keys. Activate an application or ask an administrator for permission to manage its API keys. | | "implement requires a git repository with at least one commit." | In your app's root directory, initialize a Git repository, stage the project files, and create a commit. Then rerun the wizard. | | "Records were not indexed" | You declined the command, or the script failed or timed out. The wizard shows the command and working directory. Fix the cause and run the command yourself. The script needs `ALGOLIA_APP_ID`, `ALGOLIA_WRITE_KEY`, and `ALGOLIA_INDEX_NAME` in its environment. | | "Could not provision a search-only Algolia API key" | The search UI configuration file contains the placeholder `` and a `TODO` comment. Create a search-only API key in the Algolia dashboard and replace the placeholder. | | "Unable to validate the implementation after 3 attempts" | Review the reported issue and fix it. To undo the wizard's changes, restore modified tracked files and review any new files before removing them. | | "Recommended size is 100x30" | Enlarge the terminal window. The wizard works in a smaller terminal with a compact layout. | | "The AI service had trouble responding." | Run the wizard again. It offers to resume the interrupted session. | ### Wizard state and logs The wizard stores its progress, settings, and logs outside your project, in `~/.algolia/PROJECT_SLUG/`. Here, `PROJECT_SLUG` is your project's full path with path separators replaced by hyphens. The directory contains: * `config.json`: your AI consent and a record of completed runs. * `state-default.json`: the current session, if a run stopped before it finished. The wizard offers to resume it the next time you run it in the same directory with the same application. * `wizard.log`: a detailed log of the run. Set `WIZARD_LOG` to change the log path. Set `LOG_LEVEL` to change the amount of detail in the log. Set `WIZARD_HOME` to store wizard data outside `~/.algolia`. ## Data use and privacy The wizard sends project content for AI processing and sends analytics and diagnostic data to Algolia. ### AI processing To analyze your project and generate code, the wizard sends project content to Anthropic Claude models through a server hosted by Algolia. This content can include source code, configuration, file paths, directory listings, search results, command outputs, entity names, and entity attributes. ### Environment variables and command output The wizard masks the values in files named `.env` or `.env.*` before sending their content to an Anthropic Claude model. It still sends variable names, comments, and other non-secret text. It also masks the Algolia API write key in command output. ### Analytics and telemetry The wizard sends usage analytics and diagnostic data to Algolia, as described in the [privacy policy](https://www.algolia.com/policies/privacy/). Besides standard usage data, this includes file paths in your project, the names of the data types and properties you select, and model usage for each step. The analytics and diagnostic data doesn't include prompts, file contents, or records. # Get started with the Algolia CLI Source: https://www.algolia.com/doc/tools/cli/get-started Manage your Algolia resources directly from the command line. ![](https://mintcdn.com/algolia/TTlnKswkCK2YkCpC/doc/tools/cli/hero.png?fit=max\&auto=format\&n=TTlnKswkCK2YkCpC\&q=85\&s=1a7d78a91ad359e0ab76a0d3e2318c30) The Algolia CLI lets you work with Algolia's APIs from your terminal. It's great for interactive commands, scripts, and continuous integration workflows. ## Before you begin You'll need an Algolia account. If you don't have one yet, create one from the CLI while you [sign in](#sign-in). ## Install the Algolia CLI You can install the CLI with your favorite package manager, or download binaries from the [Releases](https://github.com/algolia/cli/releases) page in the GitHub repository. The CLI runs on macOS, Linux, and Windows, on both AMD64 and ARM64 platforms. > [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 > > The Algolia CLI is available on [Homebrew](https://brew.sh). > To install it, run: > > ```sh icon=square-terminal theme={"system"} > brew install algolia > ``` > > Or download the latest archive for your Mac: `algolia_*_macOS_*.tar.gz`. > Unpack it by running `tar xf algolia_*_macOS_*.tar.gz`. > [tab] Linux > > The Algolia CLI is available as DEB and RPM packages. > Download the appropriate `*.deb` and `*.rpm` file for your platform. > To install it, run: > > ```sh icon=square-terminal theme={"system"} > # DEB-based distributions > sudo dpkg -i /path/to/algolia_*.deb > # RPM-based distributions > sudo rpm -i /path/to/algolia_*.rpm > ``` > > Or download the latest archive for Linux: `algolia_*_linux_*.tar.gz`. > Unpack it by running `tar xf algolia_*_linux_*.tar.gz`. > [tab] Windows > > The Algolia CLI is available for the [Chocolatey](https://chocolatey.org) package manager. > To install it, run: > > ```powershell icon="square-terminal" theme={"system"} > choco install algolia > ``` > > Or download the latest archive: `algolia_*_windows_*.zip`. > Then [unpack it](https://support.microsoft.com/en-us/windows/zip-and-unzip-files-8d28fa72-f2f9-712f-67df-f80cf89fd4e5). > If you're using the Windows Subsystem for Linux (WSL), > follow the instructions for [Linux](#linux). ## Sign in Authenticate the CLI with your Algolia account. This opens your browser so you can sign in or create a new account: ```sh icon=square-terminal theme={"system"} algolia auth login ``` If you're running the CLI in an environment without a browser, such as an SSH session or container, run `algolia auth login --no-browser`. The CLI prints an authorization URL and waits for you to complete the sign-in process. To check your authentication status, run `algolia auth status`. ## Select an application Set the application for your commands: ```sh icon=square-terminal theme={"system"} algolia application list # list your applications algolia application select # pick the current one interactively ``` `algolia application select` creates a local profile and an API key for the application if needed, so you don't have to pass `--application-id` or `--api-key` with every command. ## Index and search your first records With an application selected, import some records and search them: ```sh icon=square-terminal theme={"system"} algolia objects import MOVIES -F movies.ndjson # import newline-delimited JSON records algolia search MOVIES --query "toy story" # search the index ``` ## Explore other commands Type `algolia` to list the available commands. Add `--help` to any command to get more information about it and its options. For example, to list all indices in your Algolia application, run: ```sh icon=square-terminal theme={"system"} algolia indices list ``` > [!NOTE] > > If you're running the CLI in a CI pipeline or script, or if you prefer to manage credentials yourself, use the `--application-id` and `--api-key` options or environment variables instead of signing in. > For more information, see [Authentication](/doc/tools/cli/authentication). ## Next steps * [Set up command completion](/doc/tools/cli/completion). Enable shell completion for Bash, Zsh, Fish, or PowerShell. * [Authenticate CLI commands](/doc/tools/cli/authentication). Sign in with OAuth, or use environment variables and command-line flags for your credentials. * [Use the CLI in automation](/doc/tools/cli/automation). Tips for running the CLI in scripts, CI, and agent workflows with structured output, exit codes, and dry runs. * [Opt out of telemetry collection](/doc/tools/cli/telemetry). Learn what the CLI reports and how to turn off telemetry and update checks. * [Usage examples for the Algolia CLI](/doc/tools/cli/examples). Follow recipes for importing, exporting, comparing, and copying Algolia data. # Algolia Crawler Source: https://www.algolia.com/doc/tools/crawler/getting-started/overview The Algolia Crawler extracts content from your site and makes it searchable. Given a set of start URLs, the Crawler: 1. Visits these pages and extracts data that's relevant for search. 2. Discovers other pages through links (and extracts *their* data). 3. Sends your data to your [Algolia indices](/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/prepare-data-in-depth#algolia-index). You can run the Crawler on a schedule to keep your Algolia indices up-to-date. ## Why you should use the Crawler The Crawler simplifies uploading your data to Algolia and keeping your indices up-to-date. Compared to using [the API clients or other methods](/doc/libraries) to index your data, using the Crawler has these benefits: * You don't have to write and maintain code for extracting content, transforming it to indices, and scheduling periodic updates. * It helps you extract data from unstructured content (such as HTML and PDF files). * It can index your web pages when it's difficult to access the *sources*, for example, due to restricted access, or if you want to index different resources managed by different teams using different tools. ## Get started To add, verify, and crawl your site, see [Create a new crawler](/doc/tools/crawler/getting-started/create-crawler). If you want to use the Crawler to index a technical documentation site, consider [DocSearch](https://docsearch.algolia.com), which also comes with a search UI. If you use Netlify to host your website, use the [Netlify Crawler plugin](/doc/tools/crawler/netlify-plugin/quick-start). # ais-autocomplete Source: https://www.algolia.com/doc/api-reference/widgets/autocomplete/vue Provides logic for creating an autocomplete component in Vue. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisAutocomplete } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisAutocomplete, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=vue) > > Preview this widget and its behavior. ## About this widget The `ais-autocomplete` widget provides the logic to create a connected component that provides access to all indices of your InstantSearch instance. * To configure the number of hits to show, use the [`ais-hits-per-page`](/doc/api-reference/widgets/hits-per-page/vue) widget or the [`ais-configure`](/doc/api-reference/widgets/configure/vue) widget. * To retrieve results from several indices, use the [`ais-index`](/doc/api-reference/widgets/index-widget/vue) widget. The Autocomplete UI library lets you build a full-featured, accessible search experience. See also: [Integrate Autocomplete with Vue InstantSearch](/doc/ui-libraries/autocomplete/integrations/with-vue-instantsearch) ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **escapeHTML** `boolean` (default: true) Whether to escape HTML entities from hits string values. ```vue Vue icon=code theme={"system"} ``` **requires-search** `boolean` (default: true) Whether the widget makes InstantSearch require a main search request. By default (`true`), the widget contributes to the main search. Set it to `false` to opt the widget out of the main search: if it's the only widget on the page, InstantSearch doesn't send a main search request. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-Autocomplete`. The container of the widget. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. If you use this slot, the other slots don't change the output, because the default slot surrounds them. **Scope** * `currentRefinement: string`. The current value of the query. * `indices: object[]`. The list of indices. * `refine: (string) => void`. The function to change the query. The indices contain their hits and results, and the main 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 first position. You can use [the highlighting feature](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#highlight-and-snippet-your-search-results) through the [`highlight`](/doc/api-reference/widgets/highlight/vue) widget (see below). Each index includes: * `indexName: string`. The name of the index. * `indexId: string`. The id of the index. * `hits: object[]`. The resolved hits from the index matching the query. * `results: object`. The full results object from the Algolia API. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. The `view` event is automatically sent when the connector renders hits. You can learn more about the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. * `eventType: 'click' | 'conversion'` * `hit: Hit | Hit[]` * `eventName: string` ```vue Vue icon=code theme={"system"} ``` ## Prebuilt autocomplete component The `ais-experimental-autocomplete` component renders a complete autocomplete menu: a search box, a panel with results from one or more indices, recent searches, Query Suggestions, prompt suggestions, and a detached layout for small screens. It uses the same markup and most of the same options as the [InstantSearch.js `autocomplete` widget](/doc/api-reference/widgets/autocomplete/js), so see that page for details about each option. The JavaScript `container` and `templates` options don't apply: Vue renders the component in place, and slots replace templates. Use `class-names` for `cssClasses` and `get-search-page-url` for `getSearchPageURL`. > [!NOTE] > > The component is experimental: its name and API may change in a later version. ```js Vue icon=code theme={"system"} import { AisExperimentalAutocomplete } from "vue-instantsearch"; // Use "vue-instantsearch/vue3/es" for Vue 3 ``` ```vue Vue icon=code expandable theme={"system"} ``` The component runs autocomplete searches separately from the main search. It starts searching when users focus the search box, or on page load if `autofocus` is `true`. The component reads its props only when initialized. Changing them later doesn't have any effect. ### Props | Prop | Type | Default | | - | - | - | | `indices` | `object[]` | | | `feeds` | `object[]` | | | `show-query-suggestions` | `object` | | | `show-prompt-suggestions` | `object` | | | `show-recent` | `boolean \| object` | `false` | | `search-parameters` | `object` | `{ hitsPerPage: 5 }` | | `transform-items` | `function` | | | `on-select` | `function` | | | `get-search-page-url` | `function` | | | `placeholder` | `string` | | | `autofocus` | `boolean` | `false` | | `detached-media-query` | `string` | `(max-width: 680px)` | | `translations` | `object` | | | `ai-mode` | `boolean` | `false` | | `requires-search` | `boolean` | `true` | | `escapeHTML` | `boolean` | `true` | | `class-names` | `object` | `{}` | * `indices` and `feeds` are mutually exclusive. Setting both throws an error. `feeds` requires a composition-based InstantSearch instance with `compositionID` set. * `show-prompt-suggestions` and `ai-mode` send the query to an [`ais-chat`](/doc/api-reference/widgets/chat/vue) widget in the same index. * Write `escapeHTML` in camelCase. The kebab-case form `escape-html` doesn't bind. * `class-names` uses the keys of the InstantSearch.js `cssClasses` option, such as `root`, `panel`, and `panelOpen`, not `ais-` class names. ### Slots | Slot | Scope | Replaces | | - | - | - | | `item` | `item`, `indexName`, `onSelect`, `onApply` | Each item of an index in `indices` or `feeds` | | `header` | `items`, `indexName` | The header of an index in `indices` or `feeds` | | `noResults` | `indexName` | The content of an index in `indices` or `feeds` without results | | `querySuggestionItem` | `item`, `indexName`, `onSelect`, `onApply` | Each Query Suggestions item | | `querySuggestionHeader` | `items`, `indexName` | The Query Suggestions header | | `promptSuggestionItem` | `item`, `indexName`, `onSelect`, `onApply` | Each prompt suggestion | | `promptSuggestionHeader` | `items`, `indexName` | The prompt suggestions header | | `recentItem` | `item`, `onSelect`, `onApply`, `onRemoveRecentSearch` | Each recent search | | `recentHeader` | `items` | The recent searches header | | `panel` | `elements`, `indices` | The panel content. `elements` holds the rendered sections, keyed by `recent`, `suggestions`, `promptSuggestions`, and index name. | # Breadcrumb Source: https://www.algolia.com/doc/api-reference/widgets/breadcrumb/react Shows a navigation path based on hierarchical facets. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { Breadcrumb } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=breadcrumb) > > Preview this widget and its behavior. ## About this widget `Breadcrumb` is a widget that displays navigation links to see where the current page is in relation to the facet's hierarchy. It reduces the number of actions a user needs to take to get to a higher-level page and makes the app or website's sections and pages more discoverable. It's commonly used for websites with lot of data, organized into categories with subcategories. > [!TIP] > > You can also create your own UI with [`useBreadcrumb`](#hook). ### Requirements The objects to use in the breadcrumb must follow this structure: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories.lvl0": "products", "categories.lvl1": "products > fruits" }, { "objectID": "8976987", "name": "orange", "categories.lvl0": "products", "categories.lvl1": "products > fruits" } ] ``` It's also possible to provide more than one path for each level: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories.lvl0": ["products", "goods"], "categories.lvl1": ["products > fruits", "goods > to eat"] } ] ``` To create a hierarchical menu: 1. Decide on an appropriate [facet hierarchy](/doc/guides/managing-results/refine-results/faceting#hierarchical-facets) 2. Determine your [`attributes`](#param-attributes) for faceting from 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) 3. Display the UI with the hierarchical menu widget. For more information, see [Facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/react). By default, the separator is `>` (with spaces), but you can use a different one by using the [separator](#param-separator) option. If there is also a [`HierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/react) on the page, it must follow the same configuration. ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Breadcrumb } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **attributes** `string[]` *required* An array of attributes to generate the breadcrumb. ```jsx JavaScript icon=code theme={"system"} ; ``` **rootPath** `string` The path to use if the first level isn't the root level. Make sure to also include the root path in your [UI state](/doc/api-reference/widgets/ui-state/react), for example, by setting [`initialUiState`](/doc/api-reference/widgets/instantsearch/react#param-initial-ui-state) or calling [`setUiState`](/doc/api-reference/widgets/use-instantsearch/react#param-set-ui-state). ```jsx JavaScript icon=code theme={"system"} ; ``` **separator** `string` (default: > ) The level separator used in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { BreadcrumbProps } from "react-instantsearch"; const transformItems: BreadcrumbProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `noRefinementRoot`. The root element when there are no refinements. * `list`. The list element. * `item`. Each item element. * `selectedItem`. The selected item element. * `separator`. The separator between items. * `link`. The link of each item. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `rootElementText`. The text for the breadcrumb's starting point (for example, "Home page"). ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch lets you create your own UI for the `Breadcrumb` widget with `useBreadcrumb`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useBreadcrumb` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useBreadcrumb } from "react-instantsearch"; function CustomBreadcrumb(props) { const { items, canRefine, refine, createURL } = useBreadcrumb(props); return <>{/* Your JSX */}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **attributes** `string[]` *required* An array of attributes to generate the breadcrumb. ```js JavaScript icon=code theme={"system"} const breadcrumbApi = useBreadcrumb({ attributes: [ "hierarchicalCategories.lvl0", "hierarchicalCategories.lvl1", "hierarchicalCategories.lvl2", ], }); ``` **rootPath** `string` The path to use if the first level isn't the root level. Make sure to also include the root path in your [UI state](/doc/api-reference/widgets/ui-state/react), for example, by setting [`initialUiState`](/doc/api-reference/widgets/instantsearch/react#param-initial-ui-state) or calling [`setUiState`](/doc/api-reference/widgets/use-instantsearch/react#param-set-ui-state). ```js JavaScript icon=code theme={"system"} const breadcrumbApi = useBreadcrumb({ // ... rootPath: "Audio", }); ``` **separator** `string` (default: > ) The level separator used in the records. ```js JavaScript icon=code theme={"system"} const breadcrumbApi = useBreadcrumb({ // ... separator: " / ", }); ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Breadcrumb() { const breadcrumbApi = useBreadcrumb({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseBreadcrumbProps } from "react-instantsearch"; const transformItems: UseBreadcrumbProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Breadcrumb() { const breadcrumbApi = useBreadcrumb({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs **items** `BreadcrumbItem[]` The list of available options, with each option: * `label: string`. The label of the category or subcategory. * `value: string`. The value of breadcrumb item. ```ts TypeScript icon=code theme={"system"} type BreadcrumbItem = { value: string; label: string; }; ``` **canRefine** `boolean` Indicates if search state can be refined. **refine** `(value: string | null) =\> string` Sets the path of the hierarchical filter and triggers a new search. **createURL** `(value: string | null) =\> string` Generates a URL of the next state of a clicked item. The special value `null` is used for the root item of the breadcrumb and returns an empty URL. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useBreadcrumb } from "react-instantsearch"; function CustomBreadcrumb(props) { const { items, refine, createURL } = useBreadcrumb(props); function createOnClick(value) { return function onClick(event) { if (!isModifierClick(event)) { event.preventDefault(); refine(value); } }; } return (
    • Home
    • {items.map((item, index) => { const isLast = index === items.length - 1;
    • {isLast ? ( item.label ) : ( {item.label} )}
    • ); })}
    ); } function isModifierClick(event) { const isMiddleClick = event.button === 1; return Boolean( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey ); } ``` ```tsx TypeScript theme={"system"} import React from 'react'; import { useBreadcrumb, UseBreadcrumbProps } from 'react-instantsearch'; type BreadcrumbItem = ReturnType['items'][number]; function CustomBreadcrumb(props: UseBreadcrumbProps) { const { items, refine, createURL } = useBreadcrumb(props); function createOnClick(value: BreadcrumbItem['value']) { return function onClick( event: React.MouseEvent, ) { if (!isModifierClick(event)) { event.preventDefault(); refine(value); } }; } return (
    • Home
    • {items.map((item, index) => { const isLast = index === items.length - 1; return (
    • {isLast ? ( item.label ) : ( {item.label} )}
    • ); })}
    ); } function isModifierClick(event: React.MouseEvent) { const isMiddleClick = event.button === 1; return Boolean( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey ); } ``` # ais-breadcrumb Source: https://www.algolia.com/doc/api-reference/widgets/breadcrumb/vue Shows a navigation path based on hierarchical facets. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisBreadcrumb } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisBreadcrumb > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=breadcrumb) > > Preview this widget and its behavior. ## About this widget The `ais-breadcrumb` widget is a secondary navigation scheme that lets users see where the current page is in relation to the facet's hierarchy. It reduces the number of actions a user needs to take to get to a higher-level page and makes the app or website's sections and pages more discoverable. It's commonly used for websites with lots of data, organized into categories with subcategories. ### Requirements Breadcrumbs objects must have this structure: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories.lvl0": "products", "categories.lvl1": "products > fruits" }, { "objectID": "8976987", "name": "orange", "categories.lvl0": "products", "categories.lvl1": "products > fruits" } ] ``` It's also possible to provide more than one path for each level: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories.lvl0": ["products", "goods"], "categories.lvl1": ["products > fruits", "goods > to eat"] } ] ``` To create a hierarchical menu: 1. Decide on an appropriate [facet hierarchy](/doc/guides/managing-results/refine-results/faceting#hierarchical-facets) 2. Determine your [`attributes`](#param-attributes) for faceting from 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) 3. Display the UI with the hierarchical menu widget. For more information, see [Facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/vue). By default, the separator is `>` (with spaces), but you can use a different one by using the [`separator`](#param-separator) option. If there is also a [`ais-hierarchical-menu`](/doc/api-reference/widgets/hierarchical-menu/vue) on the page, it must follow the same configuration. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **attributes** `string[]` *required* An array of attributes to generate the breadcrumb. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **root-path** `string` The path to use if the first level isn't the root level. Make sure to also include the root path in your [UI state](/doc/api-reference/widgets/ui-state/vue), for example, by setting [`initial-ui-state`](/doc/api-reference/widgets/instantsearch/vue#param-initial-ui-state). ```vue Vue icon=code theme={"system"} ``` **separator** `string` (default: > ) The level separator used in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-Breadcrumb`. The root element of the widget. * `ais-Breadcrumb--noRefinement`. The root element of the widget with no refinement. * `ais-Breadcrumb-list`. The list of all breadcrumb items. * `ais-Breadcrumb-item`. The breadcrumb navigation item. * `ais-Breadcrumb-item--selected`. The selected breadcrumb item. * `ais-Breadcrumb-separator`. The separator of each breadcrumb item. * `ais-Breadcrumb-link`. The clickable breadcrumb element. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. Note that when you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]` * `refine: (value: string) => void` * `createURL: (value: string) => string` Where each item is an `object` containing: * `label`. The label of the category or subcategory. * `value`. The value of breadcrumb item. ```vue Vue icon=code theme={"system"} ``` **rootLabel** The slot to override the root label. ```vue Vue icon=code theme={"system"} ``` **separator** The slot to override the separator. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` > [!NOTE] > > If SEO is important for your search page, ensure that your custom HTML is optimized for search engines: > > * Use `` tags with `href` attributes to allow search engine bots to follow links. > * Use semantic HTML and include [structured data](https://developers.google.com/search/docs/appearance/structured-data) when relevant. > > For more guidance, see the [SEO checklist](/doc/guides/building-search-ui/resources/seo/js). # ais-chat-trigger Source: https://www.algolia.com/doc/api-reference/widgets/chat-trigger/vue Renders a button that opens the ais-chat widget's overlay, built with Algolia Agent Studio. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisChatTrigger } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisChatTrigger, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget The `ais-chat-trigger` widget renders a button that opens and closes the [`ais-chat`](/doc/api-reference/widgets/chat/vue) widget. By default, the button floats in the bottom-right corner of the viewport. Add `ais-chat-trigger` when using the overlay or side-panel layout. The [inline layout](/doc/api-reference/widgets/chat/vue#param-layout) doesn't need a trigger. See also: [Agent Studio](/doc/guides/algolia-ai/agent-studio) ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **floating** `boolean` (default: true) By default, the trigger is a floating button. Set `floating` to `false` to render an inline button that flows with surrounding content. ```vue Vue icon=code theme={"system"} ``` **toggle-icon-component** `object | (props: \{ isOpen: boolean \}) =\> VNode` A Vue component or render function to replace the default icon. It receives an `isOpen` Boolean prop so it can show different icons when the chat opens or closes. ```vue Vue icon=code expandable theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-ChatToggleButton`. The widget's root element (the button). ```vue Vue icon=code theme={"system"} ``` **translations** `object` A dictionary of translations to customize the button's accessible label, which screen readers announce. * `openLabel` (`string`). The accessible label when the chat isn't open. The default is `Open chat`. * `closeLabel` (`string`). The accessible label when the chat is open. The default is `Close chat`. ```vue Vue icon=code theme={"system"} ``` ## Events **click** `() =\> void` The widget emits a `click` event after opening or closing the chat. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` When the chat is open, the button also gets the `ais-ChatToggleButton--open` class, and its `aria-label` becomes `Close chat`. # ais-chat Source: https://www.algolia.com/doc/api-reference/widgets/chat/vue Displays a chat interface to interact with a generative AI assistant built with Algolia Agent Studio. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisChat, AisChatTrigger } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisChat, > AisChatTrigger, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget Use `ais-chat` to add an AI chat interface to your Vue InstantSearch app. Connect it to an Algolia Agent Studio agent with `agent-id`, or provide a custom `transport` or `chat` instance. See also: [Agent Studio](/doc/guides/algolia-ai/agent-studio) > [!NOTE] > > With the default `overlay` and the `sidePanel` [layouts](#param-layout), `ais-chat` renders the chat panel but doesn't provide a way to open it. > Add an entry point to the same `ais-instant-search` instance, such as the [`ais-chat-trigger`](/doc/api-reference/widgets/chat-trigger/vue), [`ais-compare-bar`](/doc/api-reference/widgets/compare-bar/vue), or [`ais-result-card`](/doc/api-reference/widgets/result-card/vue) widget, or use the `inline` layout. > Otherwise, the widget logs a development warning. > To silence it, set [`disable-trigger-validation`](#param-disable-trigger-validation) to `true`. ## Examples ```vue Vue icon=code expandable theme={"system"} ``` ## Props **agent-id** `string` The unique identifier of the agent to connect to. You can find the agent ID in the [Agent Studio dashboard](https://dashboard.algolia.com/generativeAi/agent-studio/agents). ```vue Vue icon=code theme={"system"} ``` **feedback** `boolean` Whether to enable feedback (thumbs up/down) on assistant messages. Only available when using `agent-id`. ```vue Vue icon=code theme={"system"} ``` **request-options** `object` Request options to send with the built-in Agent Studio completion requests. Use this option only with `agent-id`. It isn't available with a custom `transport` or custom `chat` instance. The object accepts the following properties: * `queryParameters` (`Record`). Query parameters to append to each completion request. * `headers` (`Record | Headers`). Headers to send with each completion request. InstantSearch always keeps `compatibilityMode=ai-sdk-5`, even if you set `compatibilityMode` in `queryParameters`. Custom headers can't override the required Algolia identity headers (`x-algolia-application-id`, `x-algolia-api-key`) or the `x-algolia-agent` header that identifies InstantSearch. ```vue Vue icon=code theme={"system"} ``` **transport** `HttpChatTransportInitOptions` A custom transport object to handle the communication between the chat widget and the agent. The API endpoint must be compatible with [Vercel AI SDK 5](https://ai-sdk.dev/docs/ai-sdk-ui/transport#custom-transport-configuration). ```vue Vue icon=code theme={"system"} ``` **chat** `Chat` A preconfigured chat instance. If you pass `chat`, configure its transport and message persistence on the instance. ```vue Vue icon=code theme={"system"} ``` **get-search-page-u-r-l** `(nextUiState: object) =\> string` A function to return the URL of the main search page with the `nextUiState`. The search tool's **View all** link uses it to send users to the main search page. ```vue Vue icon=code theme={"system"} ``` **tools** `Record\` An object defining the client-side tools that the agent can use to interact with your application. The object's keys are tool names that must match the tools defined in the [Agent Studio dashboard](/doc/guides/algolia-ai/agent-studio/how-to/dashboard#client-side-tools). The widget has built-in renderers for the search, recommend, grouped results, and compare products tools. To customize the default rendering, use the tool name constants from `instantsearch.js/es/lib/chat` as keys: * `SearchIndexToolType` (`'algolia_search_index'`). Displays search results from an Algolia index in a carousel. * `RecommendToolType` (`'algolia_recommend'`). Displays recommendations in a carousel. * `GroupedResultsToolType` (`'algolia_grouped_results'`). Displays grouped results from the tool's streaming input in carousels. * `CompareProductsToolType` (`'algolia_compare_products'`). Displays a comparison table of products. To customize only the appearance of each result, use the [`item`](#param-item) slot instead. To replace a whole tool layout, override the tool's `layoutComponent` with a Vue component that accepts a `context` prop. Overriding a built-in tool's `layoutComponent` keeps that tool's `streamInput` and `shouldRender` options unless you pass your own. ```vue Vue icon=code expandable theme={"system"} ``` Each tool is an object with the following properties: * `layoutComponent`. A Vue component or render function that renders the tool call in the chat. It receives a single `context` prop holding the tool's own data alongside the shared chat state: * `context.message`. The tool call message. It contains `input` (parameters from the agent) and `output` (the result you provide with `addToolResult`). * `context.records`. The records the chat's tools have fetched during the conversation, keyed by `objectID`. Read a record with `context.records.get(objectID)`. * `context.indexUiState`. The current InstantSearch UI state. * `context.setIndexUiState`. Updates the InstantSearch UI state. * `context.applyFilters`. Applies filters to the InstantSearch UI state from the tool call. * `context.addToolResult`. Sends the tool's output back to the agent. Call this at least once before the next message. * `context.onClose`. Dismisses the tool's UI in the chat. * `context.sendEvent`. Sends `click` or `conversion` events related to the tool call. * `onToolCall`. Optional handler invoked when the agent calls the tool. It receives `input`, `addToolResult`, `signal`, `toolCallId`, `toolName`, and `dynamic`. It can be `async`. When it throws or rejects, the chat settles the tool call as failed. * `cancelOutput`. Optional function that returns the output to report when the chat starts a new message while the tool call is waiting for a result. * `timeout`. Optional number of milliseconds for `onToolCall` to submit a result, `20000` by default. Set it to `false` to wait indefinitely. * `retryOnError`. Optional boolean, `false` by default. When `true`, a failed tool call shows a **Retry** button. * `streamInput`. Optional boolean. When `true`, the chat hides the default loader while the tool's input streams from the agent. * `shouldRender`. Optional function that decides whether this tool call renders at all. * `matchesToolName`. Optional function that claims the tool names your agent's server derives from this tool. For more details about each property, see the [React `Chat` reference](/doc/api-reference/widgets/chat/react#param-tools). **context** `Record\ | () =\> Record\` Extra context to send with each user message (for example, the current page or selected locale). The widget sends this context with every message, but doesn't show it in the chat UI. `context` can be a static object or a function that returns an object at send time. The widget attaches it to the latest user message as `metadata.turnContext`, following the Agent Studio [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context) contract. The context must be a flat object that maps strings to strings (up to 32 keys, 4,096 bytes total). > [!WARNING] > > The agent can read the values you send in `context`. > Don't include secrets, access tokens, or personal information unless you intend to share them with the model. ```vue Vue icon=code theme={"system"} ``` **initial-messages** `UIMessage[]` Messages to pre-populate the chat with when it initializes. The widget adds these messages without triggering an AI response. `initial-messages` only applies when the chat doesn't have any existing messages. When you enable `resume`, the widget ignores `initial-messages`. ```vue Vue icon=code theme={"system"} ``` **initial-user-message** `string` A message to send automatically when the chat initializes. The widget only sends `initial-user-message` when the chat doesn't have any existing messages. It sends it after applying `initial-messages`. When you enable `resume`, the widget doesn't send this message. ```vue Vue icon=code theme={"system"} ``` **resume** `boolean` (default: false) Whether to resume an ongoing chat generation stream when the widget mounts. Use this when restoring a chat session after a page reload to continue receiving an in-flight assistant response. ```vue Vue icon=code theme={"system"} ``` **persistence** `boolean | \{ messages?: boolean; open?: boolean \}` (default: true) Whether to persist and restore the conversation and the open state of the chat panel from `sessionStorage`. When `true` (the default), the widget saves both the messages and whether the panel is open, and restores them when the widget re-mounts. Set it to `false` to keep everything in memory only. To control each policy separately, pass an object with the `messages` and `open` booleans. The widget only persists what you set to `true`. With a custom `chat` instance, the instance owns message persistence, so only the `open` option is available. When the open state isn't persisted, the prompt gets focus when the chat opens. ```vue Vue icon=code theme={"system"} ``` **disable-trigger-validation** `boolean` (default: false) Whether to skip the validation that requires a way to open the chat. By default, `ais-chat` logs a development warning unless the chat has an entry point, such as an `ais-chat-trigger`, `ais-compare-bar`, or `ais-result-card` widget. The `inline` [layout](#param-layout) skips this validation automatically. Set this to `true` when you open the chat programmatically, for example with the [`setOpen`](#methods) method. ```vue Vue icon=code theme={"system"} ``` **requires-search** `boolean` (default: true) By default, `ais-chat` requires a main search request. Set `requires-search` to `false` to avoid this request when no other widget requires one. ```vue Vue icon=code theme={"system"} ``` **on-finish** `(options: \{ message, messages, isAbort, isDisconnect, isError \}) =\> void` A callback called when the assistant response has finished streaming, including when the stream aborts, disconnects, or fails. The widget also accepts the `on-error`, `on-data`, and `on-tool-call` callbacks, which it forwards to the chat instance it creates. ```vue Vue icon=code theme={"system"} ``` **title** `string` The title to display in the chat header. ```vue Vue icon=code theme={"system"} ``` **layout** `'overlay' | 'inline' | 'sidePanel'` (default: 'overlay') The built-in layout of the chat widget: * `overlay`. A floating overlay that opens from an entry point, such as `ais-chat-trigger`. * `inline`. The chat renders in the page flow and is always visible. It doesn't require an entry point. * `sidePanel`. A side panel on the right side of the page. It requires an entry point. By default, it adjusts `document.body` to make room for the panel. To adjust another element, set `parentElement` to a CSS selector with [`layout-props`](#param-layout-props). For a custom layout, use the [`layout`](#param-other-slots) slot or the `layout-component` prop. A custom layout component with a `$$inlineLayout` property set to `true` skips the trigger validation, like the `inline` layout. ```vue Vue icon=code theme={"system"} ``` **layout-props** `object` Extra props for the built-in [`layout`](#param-layout), such as `{ parentElement: '#main-content' }` for `sidePanel`. **show-reasoning** `boolean` (default: true) Whether to display the reasoning an agent sends. Set it to `false` to hide reasoning in this widget, including a custom [`reasoning`](#param-reasoning) slot. This prop can't make an agent send reasoning. Whether reasoning reaches the browser depends on the agent's own [`sendReasoning`](/doc/guides/algolia-ai/agent-studio/how-to/agent-configuration) setting. ```vue Vue icon=code theme={"system"} ``` **loader-position** `string` (default: messages-end) Where the loader appears while a chat turn is in progress. * `messages-end`. Renders the loader in its own row after the last message. * `message-inline`. Renders the loader inside the streaming assistant message, after the content received so far. ```vue Vue icon=code theme={"system"} ``` **should-show-loader** `(context) =\> boolean` Controls when the loader appears. The function receives the loader context, including `status`, `phase`, `message`, `messages`, `tools`, and `defaultValue`, which indicates whether the widget would show the loader by default. ```vue Vue icon=code theme={"system"} ``` **loader-show-delay** `number` (default: 250) The delay, in milliseconds, before the loader reappears after it hid earlier in the same turn. The first loader in a turn isn't delayed. **loader-min-duration** `number` (default: 200) The shortest time, in milliseconds, that the loader remains visible while the turn is running. **header-props** `object` Props forwarded to the chat header, merged over the ones the widget sets, such as `onClose` and `maximized`. **messages-props** `object` Props forwarded to the messages section of the widget. Use `assistantMessageProps` and `userMessageProps` to customize each role separately, for example with `textComponent` or `parseMarkdown`. For details, see the [React `Chat` reference](/doc/api-reference/widgets/chat/react#param-messages-props). ```vue Vue icon=code theme={"system"} ``` **prompt-props** `object` Props forwarded to the prompt, merged over the ones the widget sets. For example, set `autoFocus` to control whether the prompt gets focus when the chat opens. ```vue Vue icon=code theme={"system"} ``` **class-names** `Partial\` The CSS classes to add to the widget's elements. Unlike other Vue InstantSearch widgets, this object uses the same structure as the React `Chat` widget's `classNames`, such as `root`, `container`, `header`, `messages`, `message`, and `prompt`. For the full list, see the [React `Chat` reference](/doc/api-reference/widgets/chat/react#param-class-names). ```vue Vue icon=code theme={"system"} ``` **translations** `Partial\` A dictionary of translations to customize the UI text, grouped by `header`, `messages`, `message`, and `prompt`. For the full list, see the [React `Chat` reference](/doc/api-reference/widgets/chat/react#param-translations). ```vue Vue icon=code theme={"system"} ``` **component props** `object | (props) =\> VNode` Each customizable part of the chat UI accepts a Vue component or a render function as a `*-component` prop. The parts that also have a [slot](#customize-the-ui) receive the same props as the slot's scope. A slot takes precedence over the matching prop. * `item-component`. Each result in the tool carousels and comparison table. * `layout-component`, `header-component`, `prompt-component`, `suggestions-component`, `empty-component`, `loader-component`, `actions-component`, `messages-error-component`, `reasoning-component`, `prompt-header-component`, `prompt-footer-component`. The same parts as the matching slots. * `header-title-icon-component`, `header-close-icon-component`, `header-minimize-icon-component`, `header-maximize-icon-component`. Header icons. The maximize icon receives `maximized`. * `assistant-message-leading-component`, `assistant-message-footer-component`. Assistant message leading element, such as an avatar, and footer. * `user-message-leading-component`, `user-message-footer-component`. User message leading element, such as an avatar, and footer. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **item** The slot to override the DOM output of each result in the search, recommend, and grouped results carousels, and in the compare products table. **Scope** * `item: object`. A single record with all its attributes. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. ```vue Vue icon=code theme={"system"} ``` **empty** The slot to customize the welcome screen shown before the first message. **Scope** * `context.sendMessage`. Sends a message to the agent. Call it with `{ text }` to submit a starter prompt. * `context.status`. The current chat status. * `context.onClose`. Dismisses the chat. * `context.setInput`. Sets the value of the prompt input without sending it. ```vue Vue icon=code theme={"system"} ``` **header** The slot to override the chat header. **Scope** * `onClose: () => void`. Closes the chat. * `maximized: boolean`. Whether the chat takes up the maximized view. * `onToggleMaximize: () => void`. Toggles the maximized state. * `onClear: () => void`. Clears the conversation. * `canClear: boolean`. Whether there's a conversation to clear. * `translations`, `classNames`. The header translations and CSS classes. ```vue Vue icon=code theme={"system"} ``` **loader** The slot to customize the loader shown while waiting for an AI response. **Scope** * `translations`. The resolved loader translations. * `context`. The loader context, such as `status` and `phase`. * `inline`. Whether the loader renders inside an assistant message instead of in its own row. **error** The slot to customize the error message. **Scope** * `errorMessage`. The raw error message from the API or transport layer. * `onNewConversation`. Clears the conversation and starts a new one. * `context`. The shared chat state. **reasoning** The slot to render the reasoning of an assistant message, once per reasoning part. [`:show-reasoning="false"`](#param-show-reasoning) hides it. **Scope** * `part`. The reasoning part, with the reasoning text in its `text` property. * `partIndex`. The index of the reasoning part in the message's `parts` array. * `isStreaming`. Whether the agent is still streaming this reasoning part. * `message`. The message the reasoning part belongs to. * `context`. The shared chat state. **suggestions** The slot to render [follow-up prompt suggestions](/doc/guides/algolia-ai/agent-studio/how-to/agent-configuration#follow-up-prompt-suggestions). **Scope** * `suggestions: string[]`. The suggestions. * `onSuggestionClick: (suggestion: string) => void`. Sends the suggestion as a message. **other slots** * `layout`. A custom layout for the whole chat. It receives the same props as the built-in layouts. * `prompt`. Replaces the prompt. * `prompt-header`, `prompt-footer`. Content above and below the prompt input, for example a legal notice. * `actions`. Replaces the action buttons of assistant messages. ```vue Vue icon=code theme={"system"} ``` ## Methods To control the chat from your code, access the widget through a [template ref](https://vuejs.org/guide/essentials/template-refs.html): * `setOpen(open: boolean)`. Opens or closes the chat. * `sendMessage(message)`. Sends a message to the agent, for example `{ text: "MESSAGE" }`, and scrolls the conversation to the bottom. * `setInput(input: string)`. Sets the value of the prompt input without sending it. ```vue Vue icon=code expandable theme={"system"} ``` # ClearRefinements Source: https://www.algolia.com/doc/api-reference/widgets/clear-refinements/react Lets users reset all applied search refinements. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { ClearRefinements } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=clearRefinements) > > Preview this widget and its behavior. ## About this widget `ClearRefinements` is a widget that resets the active refinements of the search. You can control which attributes are cleared with the props. > [!TIP] > > You can also create your own UI with [`useClearRefinements`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, ClearRefinements } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **includedAttributes** `string[]` (default: \[]) The attributes to include in the refinements to clear (all by default). Can't be used with [`excludedAttributes`](/doc/api-reference/widgets/clear-refinements/react#param-excluded-attributes). In the example below, only the `categories` attribute is included in the refinements to clear. ```jsx JavaScript icon=code theme={"system"} ; ``` **excludedAttributes** `string[]` (default: \['query']) The attributes to exclude from the refinements to clear. Can't be used with [`includedAttributes`](/doc/api-reference/widgets/clear-refinements/react#param-included-attributes). In the example below, the attribute `brand` is excluded from the refinements to clear. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.filter((item) => item.attribute !== "brand"); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { ClearRefinementsProps } from "react-instantsearch"; const transformItems: ClearRefinementsProps["transformItems"] = (items) => { return items.filter((item) => item.attribute !== "brand"); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `button`. The button element. * `disabledButton`. The button element when there are no refinements to clear. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `resetButtonText`. The text for the reset button. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `ClearRefinements` widget with `useClearRefinements`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useClearRefinements` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useClearRefinements } from "react-instantsearch"; function CustomClearRefinements(props) { const { refine, canRefine, createURL } = useClearRefinements(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **includedAttributes** `string[]` (default: \[]) The attributes to include in the refinements to clear (all by default). Can't be used with [`excludedAttributes`](/doc/api-reference/widgets/clear-refinements/react#param-excluded-attributes). In the example below, only the `categories` attribute is included in the refinements to clear. ```jsx JavaScript icon=code theme={"system"} const clearRefinementsApi = useClearRefinements({ includedAttributes: ["categories"], }); ``` **excludedAttributes** `string[]` (default: \['query']) The attributes to exclude from the refinements to clear. Can't be used with [`includedAttributes`](/doc/api-reference/widgets/clear-refinements/react#param-included-attributes). In the example below, the attribute `brand` is excluded from the refinements to clear. ```jsx JavaScript icon=code theme={"system"} const clearRefinementsApi = useClearRefinements({ excludedAttributes: ["brand"], }); ``` **transformItems** `(items: string[], metadata: \{ results: SearchResults \}) =\> string[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.filter((item) => item !== "brand"); }; function ClearRefinements() { const clearRefinementsApi = useClearRefinements({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseClearRefinementsProps } from "react-instantsearch"; const transformItems: UseClearRefinementsProps["transformItems"] = (items) => { return items.filter((item) => item !== "brand"); }; function ClearRefinements() { const clearRefinementsApi = useClearRefinements({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **refine** `() =\> void` Clears all the currently refined values and triggers a new search. **canRefine** `boolean` Indicates if search state can be refined. **createURL** `() =\> string` Generates a URL of the next state. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useClearRefinements } from "react-instantsearch"; function CustomClearRefinements(props) { const { canRefine, refine } = useClearRefinements(props); return ( ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useClearRefinements, UseClearRefinementsProps, } from "react-instantsearch"; function CustomClearRefinements(props: UseClearRefinementsProps) { const { canRefine, refine } = useClearRefinements(props); return ( ); } ``` # ais-clear-refinements Source: https://www.algolia.com/doc/api-reference/widgets/clear-refinements/vue Lets users reset all applied search refinements. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisClearRefinements } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisClearRefinements > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=clearRefinements) > > Preview this widget and its behavior. ## About this widget The `ais-clear-refinements` widget displays a button that lets users clears every currently applied refinement. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **excluded-attributes** `string[]` (default: \['query']) The attributes to exclude from the refinements to clear. In the example below, the attribute `brand` is excluded from the refinements to clear. This can't be used with [`included-attributes`](#param-included-attributes). ```vue Vue icon=code theme={"system"} ``` **included-attributes** `string[]` (default: \[]) The attributes to include in the refinements to clear (all by default). In the example below, only the `categories` attribute is included in the refinements to clear. This can't be used with [`excluded-attributes`](#param-excluded-attributes). ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-ClearRefinements`. The root container of the widget. * `ais-ClearRefinements-button`. The button of the widget. * `ais-ClearRefinements-button--disabled`. The widget's button when it's unavailable. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `canRefine: boolean`. Are there any refinements? * `refine: () => void`. The function to clear all the refinements. * `createURL: () => string`. The function to return a link for the search without the refinements. ```vue Vue icon=code theme={"system"} ``` **resetLabel** The slot to override the DOM output for the label of the reset button. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # ColorRefinementList Source: https://www.algolia.com/doc/api-reference/widgets/color-refinement-list/react Shows facet values as colors for filtering search results. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). > [!NOTE] > > This widget is [only available in React InstantSearch v6](https://www.algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/color-refinement-list/). > Vote on this [open issue](https://alg.li/0zdqHL) if you want this widget to be available in React InstantSearch, > and help prioritize its development. ## About this widget This widget that filters results based on color facet values. It only displays the most relevant colors for the current search context. > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/react-instantsearch-widget-color-refinement-list?file=/example/index.tsx) > > Run and edit the ColorRefinementList example in CodeSandbox. # ais-color-refinement-list Source: https://www.algolia.com/doc/api-reference/widgets/color-refinement-list/vue Shows facet values as colors for filtering search results. > [!NOTE] > > For now, this widget is [only available in React InstantSearch v6](https://www.algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/color-refinement-list/). > Vote on this [open issue](https://alg.li/0zdqHL) if you want this widget to be > available in Vue InstantSearch, and help prioritize its development. ## About this widget This widget filters results based on color facet values. It only displays the most relevant colors for the current search context. # CompareBar Source: https://www.algolia.com/doc/api-reference/widgets/compare-bar/react Shows selected products and sends the selected records to the chat widget for an agent to compare. For more information, see [Agent Studio](/doc/guides/algolia-ai/agent-studio). ```tsx Signature theme={"system"} ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { CompareBar } from "react-instantsearch"; ``` ## About this widget Use `CompareBar` to let users compare products with an Agent Studio agent. The widget renders a comparison bar with the products users selected for comparison and a **Compare** button. When users select **Compare**, the widget opens the [`Chat`](/doc/api-reference/widgets/chat/react) widget on the same index and sends a comparison request with the selected records attached. The widget clears the selection after sending the request. The chat displays the agent's comparison of the attached records without showing the request as a user message. The widget doesn't include controls for selecting products. Add a **Compare** checkbox to each product in your [`Hits`](/doc/api-reference/widgets/hits/react) component. Use `useInstantSearch().indexRenderState.compare` to read and update the shared selection, as shown in the [example](#examples). To build a custom comparison bar or **Compare** button, use the [`useCompare`](#hook) Hook. All `CompareBar` components and `useCompare` Hooks in the same `InstantSearch` component share the same selection, so the toggles and comparison bar update together. The comparison bar stays hidden while the selection is empty. The comparison bar is an [entry point](/doc/api-reference/widgets/chat/react#param-disable-trigger-validation) for the chat, so you don't need a [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) widget when you use both `CompareBar` and `Chat`. The widget requires a comparison configuration, which controls the comparison goal and instructions. Create it in the Agent Studio dashboard and pass its ID as [`configurationId`](#param-configuration-id). See also: [Compare products in chat](/doc/guides/algolia-ai/agent-studio/how-to/product-comparison) ## Examples ```jsx JavaScript icon=code expandable theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { Chat, CompareBar, Hits, InstantSearch, SearchBox, useInstantSearch, } from "react-instantsearch"; import "instantsearch.css/components/chat.css"; const searchClient = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY"); function Hit({ hit }) { // The selection `CompareBar` owns. It's undefined until the bar mounts. const { indexRenderState } = useInstantSearch(); const { isSelected, toggleItem, canAddItems } = indexRenderState.compare ?? {}; const selected = isSelected?.(hit.objectID) ?? false; return (

    {hit.name}

    ); } function App() { return ( ); } ``` ## Props **configurationId** `string` *required* The ID of the comparison configuration that controls how the agent compares products. Create comparison configurations in the **Components** section of your agent in the Agent Studio dashboard. For more information, see [Compare products in chat](/doc/guides/algolia-ai/agent-studio/how-to/product-comparison). The widget sends `__ALGOLIA_COMPARISON___` as the user message, with `comparison_configuration_id` and `selected_products` as [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context). `selected_products` holds the selected records as a JSON string, with internal attributes such as `_highlightResult` removed. Agent Studio replaces the placeholder with the configured agent instructions, and the chat doesn't show a user message for it. If the configuration doesn't exist or is turned off, the placeholder reaches the agent unchanged, so only set the ID of an enabled configuration. ```jsx JavaScript icon=code theme={"system"} ``` **minItems** `number` (default: 2) The minimum number of products users must select before they can compare. The **Compare** button is unavailable until users select this number of products. The value must be at least `2`. Set `minItems` to the **Minimum products** value in your comparison configuration. ```jsx JavaScript icon=code theme={"system"} ``` **maxItems** `number` (default: 3) The maximum number of products users can select. When the selection reaches this limit, the widget ignores attempts to add more products and shows a hint. The value must be greater than or equal to [`minItems`](#param-min-items). Set `maxItems` to the **Maximum products** value in your comparison configuration. ```jsx JavaScript icon=code theme={"system"} ``` **transformItems** `(items: CompareItem[]) =\> CompareItem[]` A function that receives the selected records and returns the records to display in the comparison bar. The widget sends these transformed records to the agent when users select **Compare**. Use this function to remove long attributes, such as product descriptions. Return one record for each selected record and preserve its `objectID`. The selection itself, including `isSelected` and the limits, uses the original records. ```jsx JavaScript icon=code theme={"system"} items.map(({ description, ...item }) => item) } /> ``` **itemComponent** `(props: \{ item: CompareItem \}) =\> JSX.Element` A component to replace the label of each selected item. It receives the selected record as the `item` prop. By default, the label is the record's `name` or `title`, falling back to its `objectID`. ```jsx JavaScript icon=code theme={"system"} ( <> {item.name} )} /> ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `header`. The header element. * `title`. The title element. * `count`. The selection count element. * `list`. The list of selected items. * `item`. Each selected item. * `itemLabel`. The label of each selected item. * `itemRemove`. The button that removes an item from the selection. * `hint`. The hint shown when users select fewer than `minItems` or reach `maxItems`. * `actions`. The container of the action buttons. * `clearButton`. The button that clears the selection. * `compareButton`. The button that starts the comparison. ```jsx JavaScript icon=code theme={"system"} ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `title` (`string`). The title of the comparison bar. The default is `Compare`. * `countText` (`(count: number, maxItems: number) => string`). The selection count. The default is `${count} of ${maxItems}`. * `compareButtonText` (`(count: number) => string`). The label of the **Compare** button. The default is `Compare ${count}`. * `clearButtonText` (`string`). The label of the **Clear all** button. * `removeItemLabel` (`(itemLabel: string) => string`). The accessible label of each remove button. The default is `Remove ${itemLabel} from comparison`. * `minItemsHint` (`(minItems: number) => string`). The hint shown while the selection is below `minItems`. The default is `Select at least ${minItems} products to compare.` * `limitReachedHint` (`string`). The hint shown when the selection reaches `maxItems`. The default is `Compare limit reached — remove a product to add another.`. ```jsx JavaScript icon=code theme={"system"} `Compare ${count} products`, }} /> ``` ## Hook Use the `useCompare` Hook to build a custom comparison bar or **Compare** button. It accepts the same props as `CompareBar`: `configurationId` (required), `minItems`, `maxItems`, and `transformItems`. It returns: * `items` (`CompareItem[]`). The selected records, in selection order, after `transformItems`. Each item has at least an `objectID`. * `isSelected` (`(objectID: string) => boolean`). Whether the record with this `objectID` is in the selection. * `addItem` (`(item: CompareItem) => void`). Adds a record to the selection. It doesn't add the record if it's already selected or the selection has reached `maxItems`. The label uses `name` or `title`, falling back to `objectID`. Each record must have an `objectID`. * `removeItem` (`(objectID: string) => void`). Removes the record with this `objectID` from the selection. * `toggleItem` (`(item: CompareItem) => void`). Adds the record to the selection when it isn't selected, and removes it otherwise. * `clearItems` (`() => void`). Empties the selection. * `canAddItems` (`boolean`). Whether the selection is below `maxItems`. * `canCompare` (`boolean`). Whether the selection has reached `minItems`. * `minItems` (`number`). The resolved [`minItems`](#param-min-items) value. * `maxItems` (`number`). The resolved [`maxItems`](#param-max-items) value. * `compare` (`() => boolean`). Opens the `Chat` widget on the same index, sends the compare request with the selected records attached, and clears the selection. Returns `true` when it submits the message and `false` when the selection is below `minItems`, the page doesn't have a `Chat` widget, or the chat is busy. Every `useCompare` Hook and `CompareBar` component in the same `InstantSearch` shares the selection. Pass the same `configurationId`, `minItems`, and `maxItems` to each so they use the same configuration and limits. Each `useCompare` call mounts a widget, so for a toggle on every hit, read the selection from `useInstantSearch().indexRenderState.compare` instead, as in the [example](#examples). ```jsx JavaScript icon=code expandable theme={"system"} import { useCompare } from "react-instantsearch"; function CustomCompareBar() { const { items, canCompare, compare, removeItem, clearItems } = useCompare({ minItems: 2, maxItems: 3, configurationId: "CONFIGURATION_ID", }); if (items.length === 0) { return null; } return (
      {items.map((item) => (
    • {item.name}
    • ))}
    ); } ``` # ais-compare-bar Source: https://www.algolia.com/doc/api-reference/widgets/compare-bar/vue Shows selected products and sends the selected records to the chat widget for an agent to compare. For more information, see [Agent Studio](/doc/guides/algolia-ai/agent-studio). ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisCompareBar } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisCompareBar, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget Use the `ais-compare-bar` widget to let users compare products with an Agent Studio agent. The widget renders a comparison bar with the products users selected for comparison and a **Compare** button. When users select **Compare**, the widget opens the [`ais-chat`](/doc/api-reference/widgets/chat/vue) widget on the same index and sends a comparison request with the selected records attached. The widget clears the selection after sending the request. The chat displays the agent's comparison of the attached records without showing the request as a user message. The widget doesn't include controls for selecting products. Add a **Compare** checkbox to each product in your [`ais-hits`](/doc/api-reference/widgets/hits/vue) widget with a custom widget that uses the `connectCompare` connector, as shown in the [example](#examples). To build a custom comparison bar or **Compare** button, use the same connector. All `ais-compare-bar` widgets and `connectCompare` widgets in the same `ais-instant-search` share the same selection, so the toggles and comparison bar update together. The comparison bar stays hidden while the selection is empty. Because `ais-compare-bar` opens the `ais-chat` widget, you don't need an [`ais-chat-trigger`](/doc/api-reference/widgets/chat-trigger/vue) widget. The widget requires a comparison configuration, which controls the comparison goal and instructions. Create it in the Agent Studio dashboard and pass its ID as [`configuration-id`](#param-configuration-id). See also: [Compare products in chat](/doc/guides/algolia-ai/agent-studio/how-to/product-comparison) ## Examples The `CompareToggle` component is a custom widget that renders a **Compare** checkbox for one hit. It uses `connectCompare` with the same configuration and limits as the comparison bar. ```vue CompareToggle.vue icon=code expandable theme={"system"} ``` ```vue App.vue icon=code expandable theme={"system"} ``` ## Props **configuration-id** `string` *required* The ID of the comparison configuration that controls how the agent compares products. Create comparison configurations in the **Components** section of your agent in the Agent Studio dashboard. For more information, see [Compare products in chat](/doc/guides/algolia-ai/agent-studio/how-to/product-comparison). The widget sends `__ALGOLIA_COMPARISON___` as the user message, with `comparison_configuration_id` and `selected_products` as [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context). `selected_products` holds the selected records as a JSON string, with internal attributes such as `_highlightResult` removed. Agent Studio replaces the placeholder with the configured agent instructions, and the chat doesn't show a user message for it. If the configuration doesn't exist or you turned it off, the placeholder reaches the agent unchanged, so only set the ID of an enabled configuration. ```vue Vue icon=code theme={"system"} ``` **min-items** `number` (default: 2) The number of products users must select before they can compare. The **Compare** button is unavailable until users select this number of products. The value must be at least `2`. Set `min-items` to the lower product limit in your comparison configuration. ```vue Vue icon=code theme={"system"} ``` **max-items** `number` (default: 3) The largest number of products users can select. When the selection reaches this limit, the widget ignores attempts to add more products and shows a hint. The value must be greater than or equal to [`min-items`](#param-min-items). Set `max-items` to the upper product limit in your comparison configuration. ```vue Vue icon=code theme={"system"} ``` **transform-items** `(items: object[]) =\> object[]` A function that receives the selected records and returns the records to display in the comparison bar. The widget sends these transformed records to the agent when users select **Compare**. Use this function to remove long attributes, such as product descriptions. Return one record for each selected record and preserve its `objectID`. The selection itself, including `isSelected` and the limits, uses the original records. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-CompareBar`. The root element of the widget. * `ais-CompareBar-header`. The header element. * `ais-CompareBar-title`. The title element. * `ais-CompareBar-count`. The selection count element. * `ais-CompareBar-list`. The list of selected items. * `ais-CompareBar-item`. Each selected item. * `ais-CompareBar-itemLabel`. The label of each selected item. * `ais-CompareBar-itemRemove`. The button that removes an item from the selection. * `ais-CompareBar-hint`. The hint shown when users select fewer than `min-items` or reach `max-items`. * `ais-CompareBar-actions`. The container of the action buttons. * `ais-CompareBar-clearButton`. The button that clears the selection. * `ais-CompareBar-compareButton`. The button that starts the comparison. ```vue Vue icon=code theme={"system"} ``` **translations** `object` A dictionary of translations to customize the UI text and support internationalization. * `title` (`string`). The title of the comparison bar. The default is `Compare`. * `countText` (`(count: number, maxItems: number) => string`). The selection count. The default is `${count} of ${maxItems}`. * `compareButtonText` (`(count: number) => string`). The label of the **Compare** button. The default is `Compare ${count}`. * `clearButtonText` (`string`). The label of the **Clear all** button. * `removeItemLabel` (`(itemLabel: string) => string`). The accessible label of each remove button. The default is `Remove ${itemLabel} from comparison`. * `minItemsHint` (`(minItems: number) => string`). The hint shown while the selection is below `min-items`. The default is `Select at least ${minItems} products to compare.` * `limitReachedHint` (`string`). The hint shown when the selection reaches `max-items`. The default is `Compare limit reached — remove a product to add another.`. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **item** The slot to replace the label of each selected item. By default, the label is the record's `name` or `title`, falling back to its `objectID`. **Scope** * `item: object`. The selected record. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml expandable theme={"system"}

    Compare

    2 of 3
    • Trail Jacket
    • Storm Shell
    ``` The widget doesn't render anything if the selection is empty. Below `min-items` or at `max-items`, it adds a `div.ais-CompareBar-hint` element before the actions. The `instantsearch.css` chat theme anchors the bar to the bottom center of the viewport. Import `instantsearch.css/components/chat.css` to style it. ## Connector render state A custom widget that uses `connectCompare` receives the following properties in `state`: * `items` (`object[]`). The selected records, in selection order, after `transformItems`. Each item has at least an `objectID`. * `isSelected` (`(objectID: string) => boolean`). Whether the record with this `objectID` is in the selection. * `addItem` (`(item: object) => void`). Adds a record to the selection. It doesn't add the record if it's already selected or the selection has reached `maxItems`. Each record must have an `objectID`. * `removeItem` (`(objectID: string) => void`). Removes the record with this `objectID` from the selection. * `toggleItem` (`(item: object) => void`). Adds the record to the selection when it isn't selected, and removes it otherwise. * `clearItems` (`() => void`). Empties the selection. * `canAddItems` (`boolean`). Whether the selection is below `maxItems`. * `canCompare` (`boolean`). Whether the selection has reached `minItems`. * `minItems` (`number`). The resolved `minItems` value. * `maxItems` (`number`). The resolved `maxItems` value. * `compare` (`() => boolean`). Opens the `ais-chat` widget on the same index, sends the compare request with the selected records attached, and clears the selection. Returns `true` when it submits the message and `false` when the selection is below `minItems`, the page doesn't have an `ais-chat` widget, or the chat is busy. The connector accepts the same parameters as the widget, in camel case: `configurationId` (required), `minItems`, `maxItems`, and `transformItems`. Pass the same `configurationId`, `minItems`, and `maxItems` to every `connectCompare` widget and `ais-compare-bar` so they use the same configuration and limits. # ais-configure Source: https://www.algolia.com/doc/api-reference/widgets/configure/vue Adds search parameters to your requests. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisConfigure } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisConfigure, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=configure) > > Preview this widget and its behavior. ## About this widget The `ais-configure` widget lets you provide raw search parameters to the Algolia API without rendering anything. Any props you add to this widget is forwarded to Algolia. For more information about the different parameters you can set, see the [search parameters API reference](/doc/api-reference/search-api-parameters). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **searchParameters** `object` A list of [search parameters](/doc/api-reference/search-api-parameters) to enable when the widget mounts. Props have to use camel case since they're directly forwarded to the Algolia API. In HTML or string templates, you can do this by adding [`.camel`](https://vuejs.org/v2/api/#v-bind) before the value of the prop. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the DOM output When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `searchParameters: object`: the current search parameters applied to the search. * `refine: (object) => void`: the function to change the search parameters applied to the search. ```vue Vue icon=code theme={"system"} ``` ## HTML output This widget doesn't have any HTML output. # useConnector Source: https://www.algolia.com/doc/api-reference/widgets/connector/react React Hook for using InstantSearch connectors in React components. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} const connectorApi = useConnector( connector: Connector, props: object, additionalWidgetProperties: object ) ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { useConnector } from "react-instantsearch"; ``` ## About this Hook A React Hook that lets you use an InstantSearch connector in a React component. Use this for connecting [your own connectors](/doc/guides/building-search-ui/widgets/create-your-own-widgets/js#build-a-custom-connector) with React InstantSearch. ## Examples ```jsx JavaScript theme={"system"} import { useConnector } from "react-instantsearch"; import connectGeoSearch from "instantsearch.js/es/connectors/geo-search/connectGeoSearch"; export function useGeoSearch(props, additionalWidgetProperties) { return useConnector(connectGeoSearch, props, additionalWidgetProperties); } export function GeoSearch(props) { const { items, refine } = useGeoSearch(props, { $$widgetType: "my-organization.geoSearch", }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import { useConnector, AdditionalWidgetProperties } from "react-instantsearch"; import connectGeoSearch, { GeoSearchConnectorParams, GeoSearchWidgetDescription, } from "instantsearch.js/es/connectors/geo-search/connectGeoSearch"; export type UseGeoSearchProps = GeoSearchConnectorParams; export function useGeoSearch( props: UseGeoSearchProps, additionalWidgetProperties: AdditionalWidgetProperties, ) { return useConnector( connectGeoSearch, props, additionalWidgetProperties, ); } export function GeoSearch(props: UseGeoSearchProps) { const { items, refine } = useGeoSearch(props, { $$widgetType: "my-organization.geoSearch", }); return <>{/* Your JSX */}; } ``` ## Parameters **connector** `Connector` *required* The InstantSearch connector to use. To learn more, see [Building a custom connector](/doc/guides/building-search-ui/widgets/create-your-own-widgets/react#build-a-custom-connector). **props** `object` Props to pass to the InstantSearch widget. **additionalWidgetProperties** `object` Adds other properties like `$$widgetType` to the instantiated widget. ## Returns **...connectorApi** `object` The connector API, such as state and functions. # CurrentRefinements Source: https://www.algolia.com/doc/api-reference/widgets/current-refinements/react Shows applied refinements and lets users remove them. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { CurrentRefinements } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=currentRefinements) > > Preview this widget and its behavior. ## About this widget `CurrentRefinements` is a widget that lets you display the list of active refinements in the search. > [!TIP] > > You can also create your own UI with [`useCurrentRefinements`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, CurrentRefinements } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **includedAttributes** `string[]` (default: \[]) The attributes to include (all by default). This parameter can't be used with [`excludedAttributes`](/doc/api-reference/widgets/current-refinements/react#param-excluded-attributes). In the example below, only the `categories` attribute is included. ```jsx JavaScript icon=code theme={"system"} ; ``` **excludedAttributes** `string[]` (default: \['query']) The attributes to exclude from the widget. This parameter can't be used with [`includedAttributes`](/doc/api-reference/widgets/current-refinements/react#param-included-attributes). In the example below, the `brand` attribute is excluded. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.filter((item) => item.attribute !== "brand"); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { CurrentRefinementsProps } from "react-instantsearch"; const transformItems: CurrentRefinementsProps["transformItems"] = (items) => { return items.filter((item) => item.attribute !== "brand"); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `noRefinementRoot`. The root element when there are no refinements. * `list`. The list element. * `noRefinementList`. The list element when there are no refinements. * `item`. Each refinement element. * `label`. The label of each refinement. * `category`. The container of each refinement's value. * `categoryLabel`. The text element of each refinement's value. * `delete`. The delete button of each refinement. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `CurrentRefinements` widget with `useCurrentRefinements`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useCurrentRefinements` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useCurrentRefinements } from "react-instantsearch"; function CustomCurrentRefinements(props) { const { items, canRefine, refine } = useCurrentRefinements(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **includedAttributes** `string[]` (default: \[]) The attributes to include (all by default). This parameter can't be used with [`excludedAttributes`](/doc/api-reference/widgets/current-refinements/react#param-excluded-attributes). In the example below, only the `categories` attribute is included. ```jsx JavaScript icon=code theme={"system"} const currentRefinementsApi = useCurrentRefinements({ includedAttributes: ["categories"], }); ``` **excludedAttributes** `string[]` (default: \['query']) The attributes to exclude from the widget. This parameter can't be used with [`includedAttributes`](/doc/api-reference/widgets/current-refinements/react#param-included-attributes). In the example below, the `brand` attribute is excluded. ```jsx JavaScript icon=code theme={"system"} const currentRefinementsApi = useCurrentRefinements({ excludedAttributes: ["brand"], }); ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.filter((item) => item.attribute !== "brand"); }; function CurrentRefinements() { const currentRefinementsApi = useCurrentRefinements({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseCurrentRefinementsProps } from "react-instantsearch"; const transformItems: UseCurrentRefinementsProps["transformItems"] = ( items, ) => { return items.filter((item) => item.attribute !== "brand"); }; function CurrentRefinements() { const currentRefinementsApi = useCurrentRefinements({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `CurrentRefinementsItem[]` All the currently refined items grouped by attribute. ```ts TypeScript icon=code theme={"system"} type CurrentRefinementsItem = { /** * The index name. */ indexName: string; /** * The index id. */ indexId: string; /** * The attribute on which the refinement is applied. */ attribute: string; /** * The textual representation of this attribute. */ label: string; /** * Currently applied refinements. */ refinements: CurrentRefinementsRefinement[]; /** * Removes the given refinement and triggers a new search. */ refine(refinement: CurrentRefinementsRefinement): void; }; type CurrentRefinementsRefinement = { /** * The attribute on which the refinement is applied. */ attribute: string; /** * The type of the refinement. * * It can be one of those: 'facet'|'exclude'|'disjunctive'|'hierarchical'|'numeric'|'query'|'tag'. */ type: string; /** * The raw value of the refinement. */ value: string | number; /** * The label of the refinement to display. */ label: string; /** * The value of the operator (only if applicable). */ operator?: string; /** * The number of found items (only if applicable). */ count?: number; /** * Whether the count is exhaustive (only if applicable). */ exhaustive?: boolean; }; ``` **canRefine** `boolean` Whether the search state can be refined. **refine** `(value: CurrentRefinementsItem) =\> void` Clears a single refinement and triggers a new search. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useCurrentRefinements } from "react-instantsearch"; function CustomCurrentRefinements(props) { const { items, refine } = useCurrentRefinements(props); return (
      {items.map((item) => (
    • {item.label}: {item.refinements.map((refinement) => ( {refinement.label} ))}
    • ))}
    ); } function isModifierClick(event) { const isMiddleClick = event.button === 1; return Boolean( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey, ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useCurrentRefinements, UseCurrentRefinementsProps, } from "react-instantsearch"; function CustomCurrentRefinements(props: UseCurrentRefinementsProps) { const { items, refine } = useCurrentRefinements(props); return (
      {items.map((item) => (
    • {item.label}: {item.refinements.map((refinement) => ( {refinement.label} ))}
    • ))}
    ); } function isModifierClick(event: React.MouseEvent) { const isMiddleClick = event.button === 1; return Boolean( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey, ); } ``` # ais-current-refinements Source: https://www.algolia.com/doc/api-reference/widgets/current-refinements/vue Shows applied refinements and lets users remove them. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisCurrentRefinements } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisCurrentRefinements > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=currentRefinements) > > Preview this widget and its behavior. ## About this widget The `ais-current-refinements` widget displays a list of [refinements](/doc/api-reference/widgets/refinement-list/vue) applied to the search. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **included-attributes** `string[]` The attributes to include in the widget (all by default). Can't be used with [`excluded-attributes`](#param-excluded-attributes). In the example below, only the `categories` attribute is included in the widget. ```vue Vue icon=code theme={"system"} ``` **excluded-attributes** `string[]` (default: \['query']) The attributes to exclude from the widget. Can't be used with [`included-attributes`](#param-included-attributes). In the example below, the `brand` attribute is excluded from the widget. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-CurrentRefinements`. The root element of the widget. * `ais-CurrentRefinements--noRefinement`. The root element of the widget without refinement. * `ais-CurrentRefinements-list`. The list of refined items. * `ais-CurrentRefinements-item`. The item of the refined items list. * `ais-CurrentRefinements-label`. The label of the refined item. * `ais-CurrentRefinements-delete`. The delete button of each item. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The refinements currently applied to the search. * `createURL: (value: object) => string`. The function to return a link for the search without this refinement. Where an item is an `object` containing: * `indexName: string`. 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.) name on which the refinement is applied. * `indexId: string`. The index id on which the refinement is applied. * `attribute: string`. The name of the attribute with which the refinement is applied. The value is used as the key. * `label: string`. The name of the attribute with which the refinement is applied. The label is used as the display value. * `refine: (value: object) => void`. The function to clear a refinement. * `refinements: object[]`. Each of the individual refinements for this attribute. Where each refinement is an `object` containing: * `type: string`. Can be `"facet"`, `"exclude"`, `"disjunctive"`, `"hierarchical"`, `"numeric"` or `"query"`. * `attribute: string`. The name of the attribute where the refinement is applied. The value is used as the key. * `label: string`. The name of the attribute with which the refinement is applied. The label is used as the display value. * `value: any`. Actual value for this refinement. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of an item. **Scope** * `item: object`. One attribute with [refinements](/doc/api-reference/widgets/refinement-list/vue). * `refine: (value: string) => void`. The function to clear a refinement. * `createURL: (value: string) => void`. The function to return a link for the search without this refinement. Where item is an `object` containing: * `indexName: string`. The index name on which the refinement is applied. * `indexId: string`. The index id on which the refinement is applied. * `attribute: string`. The name of the attribute with which the refinement is applied. The value is used as the key. * `label: string`. The name of the attribute with which the refinement is applied. The label is used as the display value. * `refine: (value: object) => void`. The function to clear a refinement. * `refinements: object[]` each of the individual refinements for this attribute. Where each refinement is an `object` containing: * `type: string`. Can be `"facet"`, `"exclude"`, `"disjunctive"`, `"hierarchical"`, `"numeric"` or `"query"`. * `attribute: string`. The name of the attribute with which the refinement is applied. The value is used as the key. * `label: string`. The name of the attribute with which the refinement is applied. The label is used as the display value. * `value: any`. The actual value for this refinement. ```vue Vue icon=code theme={"system"} ``` **refinement** The slot to override the DOM output of a single refinement. **Scope** * `refinement: object`. One item of the list of [refinements](/doc/api-reference/widgets/refinement-list/vue). * `refine: (value: string) => void`. The function to clear a refinement. * `createURL: (value: string) => void`. The function to return a link for the search without this refinement. Where refinement is an `object` containing: * `type: string`. Can be `"facet"`, `"exclude"`, `"disjunctive"`, `"hierarchical"`, `"numeric"` or `"query"`. * `attribute: string`. The name of the attribute where the refinement is applied. The value is used as the key. * `label: string`. The name of the attribute with which the refinement is applied. The label is used as the display value. * `value: any`. The actual value for this refinement. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    • Category: Movies & TV Shows Others
    • Brand: Algolia
    ``` # ais-dynamic-widgets Source: https://www.algolia.com/doc/api-reference/widgets/dynamic-widgets/react Renders matching refinement widgets in the facet order returned by Algolia. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} {children} ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { DynamicWidgets } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=react) > > Preview this widget and its behavior. ## About this widget This widget displays matching refinement widgets in the order provided by [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering) in the search response. Configure the order with [facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/react) , override it with [Algolia Rules](/doc/guides/managing-results/rules/rules-overview) , or generate it automatically with [Dynamic Facets](/doc/guides/algolia-ai/dynamic-facets) . Dynamic Facets orders facets and facet values based on recent search data. By default, `DynamicWidgets` requests all facets and up to 20 values per facet. These defaults help avoid another request after the matching widgets mount. Override the defaults with [`facets`](#param-facets) and [`maxValuesPerFacet`](#param-max-values-per-facet). During server-side rendering, InstantSearch performs two render passes so that the UI includes the correct refinements. > [!TIP] > > You can also create your own UI with [`useDynamicWidgets`](#hook). ### Requirements Declare each facet attribute in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting). Configure the facet order in the [Algolia dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or with [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering). ## Example ```jsx JavaScript icon=code theme={"system"} import { InstantSearch, DynamicWidgets } from "react-instantsearch"; function App(props) { return ( ); } ``` ## Props **children** `React.ReactNode` The refinement widgets to render in the order specified by [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering). Each child must define an `attribute` or `attributes` prop. ```jsx Direct children theme={"system"} ; ``` ```jsx Children in panel theme={"system"} ; ``` **fallbackComponent** `React.ReactNode` A component to render for each ordered facet attribute that doesn't match a child. The component receives the unmatched facet attribute in its `attribute` prop. ```jsx App.js theme={"system"} ; ``` ```jsx RefinementList.js theme={"system"} export function RefinementList(props) { const { items, refine } = useRefinementList(props); return <>{/* Your JSX */}; } ``` **transformItems** `(items: string[], metadata: \{ results: SearchResults \}) =\> string[]` Transforms the facet attributes before rendering. The second argument contains the search results, which you can use to derive a different attribute list. ```jsx JavaScript theme={"system"} const transformItems = (items, { results }) => { return results.userData.customOrdering; }; function Search() { return ; } ``` ```tsx TypeScript theme={"system"} import type { DynamicWidgetsProps } from "react-instantsearch"; const transformItems: DynamicWidgetsProps["transformItems"] = ( items, { results }, ) => { return results.userData.customOrdering; }; function Search() { return ; } ``` **facets** `['*']|[]` (default: \['\*']) The facets to apply before dynamic widgets get mounted. Setting the value to `['*']` will request all facets and avoid an additional network request once the widgets are rendered. ```jsx All facets theme={"system"} ; ``` ```jsx No facets theme={"system"} ; ``` **maxValuesPerFacet** `number` (default: 20) The maximum number of values to request for each facet. Set this value to at least the highest `limit` or `showMoreLimit` used by a dynamic widget. This helps avoid an additional request after the widget mounts. Set `maxValuesPerFacet` to at least the largest number of pinned values configured for any facet. Otherwise, some pinned values might not appear. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook Use the `useDynamicWidgets` Hook to build a custom dynamic widgets UI. The Hook must run inside [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react). React InstantSearch lets you create your own UI for the `DynamicWidgets` widget with `useDynamicWidgets`. Hooks provide APIs to access the widget state and interact with InstantSearch. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useDynamicWidgets } from "react-instantsearch"; function CustomDynamicWidgets(props) { const { attributesToRender } = useDynamicWidgets(props); return <>{/* Your JSX */}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **transformItems** `function` A function to transform the attributes to render, or using a different source to determine the attributes to render. ```jsx JavaScript theme={"system"} const transformItems = (items, { results }) => { return results.userData.customOrdering; }; function DynamicWidgets() { const dynamicWidgetsApi = useDynamicWidgets({ transformItems }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseDynamicWidgetsProps } from "react-instantsearch"; const transformItems: UseDynamicWidgetsProps["transformItems"] = ( items, { results }, ) => { return results.userData.customOrdering; }; function DynamicWidgets() { const dynamicWidgetsApi = useDynamicWidgets({ transformItems }); return <>{/* Your JSX */}; } ``` **facets** `['*']|[]` (default: \['\*']) The facets to apply before dynamic widgets get mounted. Setting the value to `['*']` will request all facets and avoid an additional network request once the widgets are added. ```jsx Default theme={"system"} function DynamicWidgets() { const dynamicWidgetsApi = useDynamicWidgets({ facets: ["*"], }); return <>{/* Your JSX */}; } ``` ```jsx No facets theme={"system"} function DynamicWidgets() { const dynamicWidgetsApi = useDynamicWidgets({ facets: [], }); return <>{/* Your JSX */}; } ``` **maxValuesPerFacet** `number` (default: 20) The default number of facet values to request. Set this value at least as high as the highest `limit` and `showMoreLimit` of dynamic widgets, to prevent a second network request once that widget mounts. To avoid pinned items not showing in the result, make sure you choose a `maxValuesPerFacet` at least as high as all the most pinned items you have. ```jsx JavaScript icon=code theme={"system"} function DynamicWidgets() { const dynamicWidgetsApi = useDynamicWidgets({ maxValuesPerFacet: 500, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **attributesToRender** `string[]` The ordered facet attributes to render, derived from \[]`renderingContent.facetOrdering.facets.order`]\(/doc/api-reference/api-parameters/renderingContent#param-facets-order). ```jsx JavaScript icon=code theme={"system"} function DynamicWidgets(props) { const { attributesToRender } = useDynamicWidgets(props); return attributesToRender.map((attribute) => ( )); } ``` ### Example ```jsx JavaScript theme={"system"} import { RefinementList, useDynamicWidgets } from "react-instantsearch"; function CustomDynamicWidgets(props) { const { attributesToRender } = useDynamicWidgets(props); return attributesToRender.map((attribute) => ( )); } ``` ```tsx TypeScript theme={"system"} import { RefinementList, useDynamicWidgets, UseDynamicWidgetsProps, } from "react-instantsearch"; function CustomDynamicWidgets(props: UseDynamicWidgetsProps) { const { attributesToRender } = useDynamicWidgets(props); return attributesToRender.map((attribute) => ( )); } ``` # Dynamic widgets Source: https://www.algolia.com/doc/api-reference/widgets/dynamic-widgets/vue Renders matching refinement widgets in the facet order returned by Algolia. ```vue Signature theme={"system"} children ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisDynamicWidgets } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisDynamicWidgets, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=vue) > > Preview this widget and its behavior. ## About this widget This widget displays matching refinement widgets in the order provided by [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering) in the search response. Configure the order with [facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/vue) , override it with [Algolia Rules](/doc/guides/managing-results/rules/rules-overview) , or generate it automatically with [Dynamic Facets](/doc/guides/algolia-ai/dynamic-facets) . Dynamic Facets orders facets and facet values based on recent search data. ### Requirements Declare each facet attribute in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting). Configure the facet order in the [Algolia dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or with [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering). By default, `` requests all facets and up to 20 values per facet. These defaults help avoid another request after matching widgets mount. Override the defaults with [`facets`](#param-facets) and [`max-values-per-facet`](#param-max-values-per-facet). > [!NOTE] > > `` requires Vue InstantSearch 3.8.0 or later. ## Examples ```vue Default theme={"system"} ``` ```vue Multiple requests theme={"system"} ``` ## Options **children** `Widget[]` *required* The refinement widgets to render in the order specified by [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering). Each top-level child must resolve to one unique facet attribute. Define an `attribute` prop for single-attribute widgets or an `attributes` prop for hierarchical widgets. ```vue Widget theme={"system"} ``` ```vue Hierarchical menu theme={"system"} ``` ```vue Panel theme={"system"} ``` ```vue Custom widget theme={"system"} ``` **transform-items** `function` Transforms the facet attributes before rendering. The second argument contains the search results, which you can use to derive a different attribute list. The function receives the ordered facet attributes from [`renderingContent.facetOrdering.facets.order`](/doc/api-reference/api-parameters/renderingContent#param-facets-order) and returns an array. ```vue Vue icon=code theme={"system"} ``` **facets** `['*']|[]` (default: \['\*']) The facets to apply before dynamic widgets get mounted. Setting the value to `['*']` will request all facets and avoid an additional network request once the widgets are added. ```vue Default theme={"system"} ``` ```vue No facets theme={"system"} ``` **max-values-per-facet** `number` (default: 20) The maximum number of values to request for each facet. Set this value to at least the highest `limit` or `show-more-limit` used by a dynamic widget. This helps avoid an additional request after the widget mounts. Set `max-values-per-facet` to at least the largest number of pinned values configured for any facet. Otherwise, some pinned facet values might not appear. ```vue Vue icon=code theme={"system"} ``` # Feeds Source: https://www.algolia.com/doc/api-reference/widgets/feeds/react Use the Feeds component to display multiple independent result sets from a multifeed composition. This widget is **in beta** and is subject to change in minor versions. ```tsx Signature theme={"system"} ReactNode} isolated={false} // Optional props transformFeeds={(feedIDs: string[]) => string[]} /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { Feeds } from 'react-instantsearch'; ``` ## About this widget Use the `Feeds` component to display multiple independent result sets from a [multifeed composition](/doc/guides/compositions/multifeed-compositions). For each feed in the composition, the widget provides an `IndexContext` scoped to that feed and calls `renderFeed` to render its content. Requires a composition-based InstantSearch instance (the `compositionID` option must be set on [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react)). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from 'react'; import { liteClient as algoliasearch } from 'algoliasearch/lite'; import { InstantSearch, Feeds, Hits } from 'react-instantsearch'; const searchClient = algoliasearch( 'ALGOLIA_APPLICATION_ID', 'ALGOLIA_SEARCH_API_KEY' ); function App() { return ( (

    {feedID}

    {hit.name}

    } />
    )} />
    ); } ``` ## Props **renderFeed** `function` *required* A function that renders the content for each feed. Called once for each feed in the composition response. It receives: * `feedID`. The unique identifier of the feed, as defined in the composition configuration. The function renders inside an `IndexContext` scoped to this feed, so any InstantSearch widgets you render here automatically use the correct feed's results. ```jsx JavaScript icon=code theme={"system"} (

    {feedID}

    {hit.name}

    } />
    )} /> ``` **isolated** `boolean` *required* Whether each feed runs an isolated search with its own search parameters. Only `false` is supported, meaning all feeds share the same global search state. The prop is required to make the choice explicit. ```jsx JavaScript icon=code theme={"system"} } /> ``` **transformFeeds** `function` A function to transform, reorder, or filter the feed IDs before rendering. Receives the array of feed IDs from the composition response and must return an array of feed IDs (strings). ```jsx JavaScript icon=code theme={"system"} feedIDs.slice(0, 2)} renderFeed={({ feedID }) => } /> ``` # ais-feeds Source: https://www.algolia.com/doc/api-reference/widgets/feeds/vue Use the ais-feeds widget to display multiple independent result sets from a multifeed composition. This widget is **in beta** and is subject to change in minor versions. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisFeeds } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisFeeds, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget Use the `ais-feeds` widget to display multiple independent result sets from a [multifeed composition](/doc/guides/compositions/multifeed-compositions). For each feed in the composition, the widget provides a scoped index context and exposes a `feedID` slot prop, letting you place any InstantSearch widgets inside. Requires a composition-based InstantSearch instance (the `composition-id` option must be set on [`ais-instant-search`](/doc/api-reference/widgets/instantsearch/vue)). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **isolated** `boolean` *required* Whether each feed runs an isolated search with its own search parameters. Only `false` is supported, meaning all feeds share the same global search state. The prop is required to make the choice explicit. ```vue Vue icon=code theme={"system"} ``` **transform-feeds** `function` A function to transform, reorder, or filter the feed IDs before rendering. Receives the array of feed IDs from the composition response and must return an array of feed IDs (strings). ```vue Vue icon=code theme={"system"} ``` ## Slots **default** `slot` The default slot is rendered once per feed, inside a scoped index context for that feed. It exposes: * `feedID`. The unique identifier of the feed, as defined in the composition configuration. ```vue Vue icon=code theme={"system"} ``` # FrequentlyBoughtTogether Source: https://www.algolia.com/doc/api-reference/widgets/frequently-bought-together/react Shows recommendations from the "Frequently bought together" model. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). > [!NOTE] > > If you're using the deprecated [`recommend-react`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-react) UI library, > see [`FrequentlyBoughtTogether`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-react/FrequentlyBoughtTogether). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { FrequentlyBoughtTogether } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=frequentlyBoughtTogether) > > Preview this widget and its behavior. ## About this widget Use the `FrequentlyBoughtTogether` widget to display a list of frequently bought together items. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) > [!TIP] > > You can also create your own UI with [`useFrequentlyBoughtTogether`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, FrequentlyBoughtTogether } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Item({ item }) { return JSON.stringify(item); } function App() { return ( ); } ``` ### With carousel layout Display the `FrequentlyBoughtTogether` widget as a [scrollable carousel](/doc/api-reference/widgets/frequently-bought-together/react#param-layout-component) ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, FrequentlyBoughtTogether, Carousel, } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Item({ item }) { return JSON.stringify(item); } function App() { return ( ); } ``` ## Props **objectIDs** `string[]` *required* The list of object IDs for which to get recommendations. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). > For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```jsx JavaScript icon=code theme={"system"} ; ``` **itemComponent** `(props: \{ item: THit \}) =\> JSX.Element` A component that renders each recommendation from the results. It receives an `item` prop. When not provided, the widget displays the recommendation as a JSON string. ```jsx JavaScript icon=code theme={"system"} item.objectID} />; ``` **headerComponent** `(props: \{ classNames: string[], items: THit[], translations: object \}) =\> JSX.Element` A component that renders a title header. It receives `classNames`, `items` and `translations` props. When not provided, the widget displays a default header. ```jsx JavaScript icon=code theme={"system"} (

    Recommendations ({items.length})

    )} />; ``` **emptyComponent** `() =\> JSX.Element` A component that renders when there are no recommendations. When not provided, the widget displays a default message. ```jsx JavaScript icon=code theme={"system"}

    No recommendations.

    } />; ``` **layoutComponent** `(props: RecommendLayoutProps) =\> JSX.Element` The layout component to use to wrap all items. React InstantSearch provides a provides a built-in `Carousel` layout component with the following props: `classNames`: * `root`. The Carousel's root element. * `list`. The list of recommendations. * `item`. The list item. * `navigation`. The navigation controls. * `navigationPrevious`. The "Previous" navigation control. * `navigationNext`. The "Next" navigation control. `translations`: * `nextButtonLabel`. The label for the next button. * `nextButtonTitle`. The title for the next button. * `previousButtonLabel`. The label for the previous button. * `previousButtonTitle`. The title for the previous button. * `listLabel`. The label for the list of recommendation. * `previousIconComponent`. The component to render as the previous button. * `nextIconComponent`. The component to render as the next button. ```jsx Carousel theme={"system"} import { Carousel } from "react-instantsearch"; (

    Previous

    } nextIconComponent={() =>

    Next

    } /> )} />; ``` ```jsx Custom theme={"system"} (
      {props.items.map((item) => (
    • {item.objectID}
    • ))}
    )} />; ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```jsx JavaScript icon=code theme={"system"} ; ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: THit[], metadata: \{ results: SearchResults \}) =\> THit[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { FrequentlyBoughtTogetherProps } from 'react-instantsearch'; const transformItems: FrequentlyBoughtTogetherProps[ 'transformItems' ] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The widget's root element. * `emptyRoot`. The root element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of recommendations. * `item`. The list item. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A mapping of keys to translation values. * `title`. The title of the recommendation section. * `sliderLabel`. The label of the horizontal slider. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the widget's root element. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `FrequentlyBoughtTogether` widget with `useFrequentlyBoughtTogether`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useFrequentlyBoughtTogether` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useFrequentlyBoughtTogether } from "react-instantsearch"; function CustomFrequentlyBoughtTogether(props) { const { items } = useFrequentlyBoughtTogether(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **objectIDs** `string[]` *required* The list of object IDs for which to get recommendations. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). > For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```jsx JavaScript icon=code theme={"system"} const { items } = useFrequentlyBoughtTogether({ // ... objectIDs: ["5723537"], }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useFrequentlyBoughtTogether({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useFrequentlyBoughtTogether({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```jsx JavaScript icon=code theme={"system"} const { items } = useFrequentlyBoughtTogether({ // ... queryParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} const { items } = useFrequentlyBoughtTogether({ // ... escapeHTML: false, }); ``` **transformItems** `(items: THit[], metadata: \{ results: SearchResults \}) =\> THit[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function FrequentlyBoughtTogether() { const frequentlyBoughtTogetherApi = useFrequentlyBoughtTogether({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseFrequentlyBoughtTogetherProps } from 'react-instantsearch'; const transformItems: UseFrequentlyBoughtTogetherProps[ 'transformItems' ] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function FrequentlyBoughtTogether() { const frequentlyBoughtTogetherApi = useFrequentlyBoughtTogether({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `THit[]` The matched recommendations returned from Algolia. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useFrequentlyBoughtTogether } from "react-instantsearch"; function CustomFrequentlyBoughtTogether(props) { const { items } = useFrequentlyBoughtTogether(props); return (
      {items.map((item) => (
    1. {JSON.stringify(item).slice(0, 100)}
    2. ))}
    ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useFrequentlyBoughtTogether, UseFrequentlyBoughtTogetherProps, } from "react-instantsearch"; function CustomFrequentlyBoughtTogether( props: UseFrequentlyBoughtTogetherProps, ) { const { items } = useFrequentlyBoughtTogether(props); return (
      {items.map((item) => (
    1. {JSON.stringify(item).slice(0, 100)}
    2. ))}
    ); } ``` # ais-frequently-bought-together Source: https://www.algolia.com/doc/api-reference/widgets/frequently-bought-together/vue Shows recommendations from the "Frequently bought together" model. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisFrequentlyBoughtTogether } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisFrequentlyBoughtTogether, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget Use the `ais-frequently-bought-together` widget to display a list of frequently bought together items. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) ## Examples ```vue Vue icon=code theme={"system"} ``` ### With carousel layout To display the recommendations as a scrollable carousel, set the [`layout`](#param-layout) prop to `carousel`. ```vue Vue icon=code theme={"system"} ``` ## Props **objectIDs** `string[]` *required* The list of object IDs to get recommendations for. If you specify several object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` counts as one Recommend request, > even when you pass multiple IDs in one array. > For example, `["A", "B", "C"]` counts as three requests and > can affect your [quota](https://www.algolia.com/pricing). ```vue Vue icon=code theme={"system"} ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If you omit `limit` or set it to 0, the widget returns all matching recommendations. ```vue Vue icon=code theme={"system"} ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). The widget only returns recommendations with a greater score. ```vue Vue icon=code theme={"system"} ``` **query-parameters** `Omit\` [Search parameters](/doc/api-reference/search-api-parameters) to apply to the recommendation request. ```vue Vue icon=code theme={"system"} ``` **fallback-parameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as an extra 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.) set when there aren't enough recommendations. ```vue Vue icon=code theme={"system"} ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function to transform or reorder recommendations before the widget displays them. It returns an array of items with the same structure. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **layout** `'list' | 'carousel'` (default: 'list') How to display the recommendations: * `list`. A list of items. * `carousel`. A horizontally scrollable carousel with "Previous" and "Next" navigation buttons. The [`item`](#param-item) slot applies to both layouts. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-FrequentlyBoughtTogether`. The widget's root element. * `ais-FrequentlyBoughtTogether--empty`. The root element without results. * `ais-FrequentlyBoughtTogether-title`. The widget's title element. * `ais-FrequentlyBoughtTogether-container`. The container of the list element. * `ais-FrequentlyBoughtTogether-list`. The list of recommendations. * `ais-FrequentlyBoughtTogether-item`. The list item. With the `carousel` layout, the `ais-FrequentlyBoughtTogether-list` and `ais-FrequentlyBoughtTogether-item` classes apply to the carousel's list and items. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. The widget only uses this slot if you don't provide any other slot. **Scope** * `items: object[]`. The recommendations. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of each recommendation. If you don't provide it, the widget displays the recommendation as a JSON string. **Scope** * `item: object`. A single recommendation with all its attributes. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. ```vue Vue icon=code theme={"system"} ``` **header** The slot to override the DOM output of the title header. The widget doesn't render the header when there are no recommendations. If you don't provide it, the widget displays "Frequently bought together" as the title. **Scope** * `items: object[]`. The recommendations. * `translations: object`. The widget's translations, such as `title`. * `classNames: object`. The widget's CSS classes, such as `title`. ```vue Vue icon=code theme={"system"} ``` **empty** The slot to override the DOM output when there are no recommendations. If you don't provide it, the widget displays "No results". ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Frequently bought together

    1. ...
    2. ...
    3. ...
    ``` # useGeoSearch Source: https://www.algolia.com/doc/api-reference/widgets/geo-search/react React Hook for location-based search inside a bounding box on a map. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ## About this Hook The `geoSearch` Hook lets you search for results based on their position within a specified area (a bounding box). It also provides features such as "search on map interactions". > [!NOTE] > > The `geoSearch` Hook doesn't let you [search around a central point](/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-around-a-location) > or [within polygons](/doc/guides/managing-results/refine-results/geolocation/how-to/filter-results-inside-a-polygonal-area). > If you want this, you need to build your own UI on top of the Algolia API. > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/react-instantsearch/geo-search?file=/src/Airports.tsx) > > Run and edit the useGeoSearch example in CodeSandbox. ## Example: React Leaflet ```jsx JavaScript theme={"system"} import { useGeoSearch } from "react-instantsearch"; import { MapContainer, Marker, Popup, TileLayer, useMapEvents, } from "react-leaflet"; export function CustomGeoSearch(props) { const { items, refine } = useGeoSearch(props); function onViewChange({ target }) { refine({ northEast: target.getBounds().getNorthEast(), southWest: target.getBounds().getSouthWest(), }); } useMapEvents({ zoomend: onViewChange, dragend: onViewChange }); return ( {items.map((item) => ( {item.name}
    {item.city}, {item.country}
    ))}
    ); } ``` ```tsx TypeScript theme={"system"} import { useGeoSearch, UseGeoSearchProps } from "react-instantsearch"; import { LeafletEvent } from "leaflet"; import { MapContainer, Marker, Popup, TileLayer, useMapEvents, } from "react-leaflet"; type Airport = { airport_id: string; city: string; country: string; name: string; nb_airline_liaisons: number; }; export function CustomGeoSearch(props: UseGeoSearchProps) { const { items, refine } = useGeoSearch(props); function onViewChange({ target }: LeafletEvent) { refine({ northEast: target.getBounds().getNorthEast(), southWest: target.getBounds().getSouthWest(), }); } useMapEvents({ zoomend: onViewChange, dragend: onViewChange }); return ( {items.map((item) => ( {item.name}
    {item.city}, {item.country}
    ))}
    ); } ``` # HierarchicalMenu Source: https://www.algolia.com/doc/api-reference/widgets/hierarchical-menu/react Shows a hierarchical menu to navigate nested categories. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```js JavaScript icon=code theme={"system"} import { HierarchicalMenu } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=hierarchicalMenu) > > Preview this widget and its behavior. ## About this widget The `HierarchicalMenu` widget displays a hierarchical navigation menu, based on facet attributes. To create a hierarchical menu: 1. Decide on an appropriate [facet hierarchy](/doc/guides/managing-results/refine-results/faceting#hierarchical-facets) 2. Determine your [`attributes`](#param-attributes) for faceting from 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) 3. Display the UI with the hierarchical menu widget. To learn more, see [Facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/react). > [!TIP] > > You can also create your own UI with [`useHierarchicalMenu`](#hook). ### Requirements The objects to use in the hierarchical menu must follow this structure: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories.lvl0": "products", "categories.lvl1": "products > fruits" }, { "objectID": "8976987", "name": "orange", "categories.lvl0": "products", "categories.lvl1": "products > fruits" } ] ``` You can also provide more than one path for each level: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories.lvl0": ["products", "goods"], "categories.lvl1": ["products > fruits", "goods > to eat"] } ] ``` By default, the separator is `>` (with spaces), but you can use a different one by using the [separator](#param-separator) option. > [!NOTE] > > By default, the count of the refined root level is updated to match the count of the actively refined parent level. > Keep the root level count intact by setting [`persistHierarchicalRootCount`](/doc/api-reference/widgets/instantsearch/react#param-future-persist-hierarchical-root-count) in [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, HierarchicalMenu } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **attributes** `string[]` *required* The name of the attributes to generate the menu with. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```jsx JavaScript icon=code theme={"system"} ; ``` **limit** `number` (default: 10) The number of facet values to retrieve. When you enable the [`showMore`](#param-show-more) feature, this is the number of facet values to display before clicking the "Show more" button. ```jsx JavaScript icon=code theme={"system"} ; ``` **showMore** `boolean` (default: false) Whether to display a button that expands the number of items. ```jsx JavaScript icon=code theme={"system"} ; ``` **showMoreLimit** `number` The maximum number of displayed items (only used when [`showMore`](#param-show-more) is set to `true`). ```jsx JavaScript icon=code theme={"system"} ; ``` **separator** `string` The level separator used in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). ```jsx JavaScript icon=code theme={"system"} ; ``` **rootPath** `string` The prefix path to use if the first level isn't the root level. Make sure to also include the root path in your [UI state](/doc/api-reference/widgets/ui-state/react), for example, by setting [`initialUiState`](/doc/api-reference/widgets/instantsearch/react#param-initial-ui-state) or calling [`setUiState`](/doc/api-reference/widgets/use-instantsearch/react#param-set-ui-state). ```jsx JavaScript icon=code theme={"system"} ; ``` **showParentLevel** `boolean` (default: true) Whether to show the siblings of the selected parent level of the current refined value. This option doesn't impact the root level. All root items are always visible. ```jsx JavaScript icon=code theme={"system"} ; ``` **sortBy** `string[] | (a: FacetValue, b: FacetValue) =\> number` (default: \['name:asc'], or `facetOrdering` if set) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` You can also give a function that receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). ```jsx String theme={"system"} ; ``` ```jsx Function theme={"system"} const sortByName = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function Search() { return ( ); } ``` ```tsx Function (TypeScript) theme={"system"} const sortByName: HierarchicalMenuProps["sortBy"] = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function Search() { return ( ); } ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { HierarchicalMenuProps } from "react-instantsearch"; const transformItems: HierarchicalMenuProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `noRefinementRoot`. The root element when there are no refinements. * `list`. The list element. * `item`. Each item element. * `selectedItem`. The selected item element. * `parentItem`. The parent item of the list. * `link`. The link of each item. * `selectedItemLink`. The link of each selected item element. * `label`. The label of each item. * `count`. The count of each item. * `showMore`. The "Show more" button. * `disabledShowMore`. The "Show more" button when it's unavailable. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `showMoreButtonText`. The text for the "Show more" button. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the widget's root element. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `HierarchicalMenu` widget with `useHierarchicalMenu`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useHierarchicalMenu` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useHierarchicalMenu } from "react-instantsearch"; function CustomHierarchicalMenu(props) { const { items, isShowingMore, canToggleShowMore, canRefine, refine, sendEvent, toggleShowMore, createURL, } = useHierarchicalMenu(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **attributes** `string[]` *required* The names of the attributes inside the records. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```jsx JavaScript icon=code theme={"system"} const hierarchicalMenuApi = useHierarchicalMenu({ attributes: [ "categories.lvl0", "categories.lvl1", "categories.lvl2", "categories.lvl3", ], }); ``` **separator** `string` The level separator used in the records. ```jsx JavaScript icon=code theme={"system"} const hierarchicalMenuApi = useHierarchicalMenu({ // ... separator: " - ", }); ``` **rootPath** `string` The path to use if the first level isn't the root level. Make sure to also include the root path in your [UI state](/doc/api-reference/widgets/ui-state/react), for example, by setting [`initialUiState`](/doc/api-reference/widgets/instantsearch/react#param-initial-ui-state) or calling [`setUiState`](/doc/api-reference/widgets/use-instantsearch/react#param-set-ui-state). ```jsx JavaScript icon=code theme={"system"} const hierarchicalMenuApi = useHierarchicalMenu({ // ... rootPath: "Audio > Home Audio", }); ``` **showParentLevel** `boolean` (default: true) Whether to show the siblings of the selected parent level of the current refined value. ```jsx JavaScript icon=code theme={"system"} const hierarchicalMenuApi = useHierarchicalMenu({ // ... showParentLevel: false, }); ``` **limit** `number` (default: 10) How many facet values to retrieve. When you enable the [`showMore`](#param-show-more) feature, this is the number of facet values to display before clicking the Show more button. ```jsx JavaScript icon=code theme={"system"} const hierarchicalMenuApi = useHierarchicalMenu({ // ... limit: 5, }); ``` **showMore** `boolean` (default: false) Whether to display a button that expands the number of items. ```jsx JavaScript icon=code theme={"system"} const hierarchicalMenuApi = useHierarchicalMenu({ // ... showMore: true, }); ``` **showMoreLimit** `number` The maximum number of displayed items. Only used when [`showMore`](#param-show-more) is set to `true`. ```jsx JavaScript icon=code theme={"system"} const hierarchicalMenuApi = useHierarchicalMenu({ // ... showMoreLimit: 30, }); ``` **sortBy** `string[] | (a: RefinementListItem, b: RefinementListItem) =\> number` (default: \['name:asc'] (or `facetOrdering` if set)) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` You can also use a sort function that behaves like the standard JavaScript [`compareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax). When you don't set this parameter, and you've set `facetOrdering` for this facet in `renderingContent`, facets are sorted using `facetOrdering` and use the default order as a fallback. ```jsx JavaScript icon=code theme={"system"} const hierarchicalMenuApi = useHierarchicalMenu({ // ... sortBy: ["count", "name:asc"], }); ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function HierarchicalMenu() { const hierarchicalMenuApi = useHierarchicalMenu({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseHierarchicalMenuProps } from 'react-instantsearch'; const transformItems: UseHierarchicalMenuProps['transformItems'] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function HierarchicalMenu() { const hierarchicalMenuApi = useHierarchicalMenu({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `HierarchicalMenuItem[]` The list of items the widget can display. ```ts TypeScript icon=code theme={"system"} type HierarchicalMenuItem = { /** * Value of the menu item. */ value: string; /** * Human-readable value of the menu item. */ label: string; /** * Number of matched results after refinement is applied. */ count: number; /** * Indicates if the refinement is applied. */ isRefined: boolean; /** * n+1 level of items */ data: HierarchicalMenuItem[] | null; }; ``` **isShowingMore** `boolean` Whether the list is expanded. **canToggleShowMore** `boolean` Whether users can click the "Show more" button. **canRefine** `boolean` Indicates if you can refine the search state. **refine** `(value: string) =\> void` Sets the path of the hierarchical filter and triggers a new search. **sendEvent** `(eventType: string, facetValue: string, eventName?: string) =\> void` The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. * You can learn more about the [`insights`](/doc/api-reference/widgets/insights/react) middleware. * `eventType: 'click'` * `facetValue: string` **toggleShowMore** `() =\> void` Toggles the number of displayed values between [`limit`](/doc/api-reference/widgets/hierarchical-menu/react#param-limit) and [`showMoreLimit`](/doc/api-reference/widgets/hierarchical-menu/react#param-show-more-limit). **createURL** `(value: string) =\> string` Generates a URL for the next state. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useHierarchicalMenu } from "react-instantsearch"; function CustomHierarchicalMenu(props) { const { items, refine, canToggleShowMore, toggleShowMore, isShowingMore, createURL, } = useHierarchicalMenu(props); return ( <> {props.showMore && ( )} ); } function HierarchicalList({ items, createURL, onNavigate }) { if (items.length === 0) { return null; } return (
    ); } function isModifierClick(event) { const isMiddleClick = event.button === 1; return Boolean( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey, ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useHierarchicalMenu, UseHierarchicalMenuProps, } from "react-instantsearch"; function CustomHierarchicalMenu(props: UseHierarchicalMenuProps) { const { items, refine, canToggleShowMore, toggleShowMore, isShowingMore, createURL, } = useHierarchicalMenu(props); return ( <> {props.showMore && ( )} ); } type HierarchicalListProps = Pick< ReturnType, "items" | "createURL" > & { onNavigate(value: string): void; }; function HierarchicalList({ items, createURL, onNavigate, }: HierarchicalListProps) { if (items.length === 0) { return null; } return ( ); } function isModifierClick(event: React.MouseEvent) { const isMiddleClick = event.button === 1; return Boolean( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey, ); } ``` # ais-hierarchical-menu Source: https://www.algolia.com/doc/api-reference/widgets/hierarchical-menu/vue Shows a hierarchical menu to navigate nested categories. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisHierarchicalMenu } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisHierarchicalMenu > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=hierarchicalMenu) > > Preview this widget and its behavior. ## About this widget The `ais-hierarchical-menu` widget displays a hierarchical navigation menu, based on facet attributes. To create a hierarchical menu: 1. Decide on an appropriate [facet hierarchy](/doc/guides/managing-results/refine-results/faceting#hierarchical-facets) 2. Determine your [`attributes`](#param-attributes) for faceting from 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) 3. Display the UI with the hierarchical menu widget. To learn more, see [Facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/vue). ### Requirements The objects to use in the hierarchical menu must follow this structure: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories.lvl0": "products", "categories.lvl1": "products > fruits" }, { "objectID": "8976987", "name": "orange", "categories.lvl0": "products", "categories.lvl1": "products > fruits" } ] ``` You can also provide more than one path for each level: ```json JSON icon=braces theme={"system"} [ { "objectID": "321432", "name": "lemon", "categories.lvl0": ["products", "goods"], "categories.lvl1": ["products > fruits", "goods > to eat"] } ] ``` By default, the separator is `>` (with spaces), but you can use a different one by using the [separator](#param-separator) option. > [!NOTE] > > By default, the count of the refined root level is updated to match the count of the actively refined parent level. > Keep the root level count intact by setting [`persistHierarchicalRootCount`](/doc/api-reference/widgets/instantsearch/vue#param-future-persist-hierarchical-root-count) > in [`ais-instant-search`](/doc/api-reference/widgets/instantsearch/vue). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **attributes** `string[]` *required* The name of the attributes to generate the menu with. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```vue Vue icon=code theme={"system"} ``` **limit** `number` (default: 10) The number of facet values to retrieve. When you enable the [`show-more`](#param-show-more) feature, this is the number of facet values to display before clicking the "Show more" button. ```vue Vue icon=code theme={"system"} ``` **show-more** `boolean` (default: false) Whether to display a button that expands the number of items. ```vue Vue icon=code theme={"system"} ``` **show-more-limit** `number` (default: 20) The maximum number of displayed items (only used when [`show-more`](#param-show-more) is set to `true`). ```vue Vue icon=code theme={"system"} ``` **separator** `string` (default: >) The level separator used in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). ```vue Vue icon=code theme={"system"} ``` **root-path** `string` The prefix path to use if the first level isn't the root level. Make sure to also include the root path in your [UI state](/doc/api-reference/widgets/ui-state/vue), for example, by setting [`initial-ui-state`](/doc/api-reference/widgets/instantsearch/vue#param-initial-ui-state). ```vue Vue icon=code theme={"system"} ``` **show-parent-level** `boolean` (default: true) Whether to show the siblings of the selected parent level of the current refined value. This option doesn't impact the root level. All root items are always visible. ```vue Vue icon=code theme={"system"} ``` **sort-by** `string[] | function` (default: Uses facetOrdering if set, \['name:asc']) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` You can also give a function that receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). If this facet's `facetOrdering` is set in `renderingContent`, and no `sortBy` value is passed to the widget, `facetOrdering` is used with default ordering as a fallback. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-HierarchicalMenu`. The root element of the widget * `ais-HierarchicalMenu--noRefinement`. The root element of the widget with no refinement. * `ais-HierarchicalMenu-list`. The list of menu items. * `ais-HierarchicalMenu-list--child`. The child list of menu items. * `ais-HierarchicalMenu-list--lvl0`. The level 0 list of menu items. * `ais-HierarchicalMenu-list--lvl1`. The level 1 list of menu items (and so on). * `ais-HierarchicalMenu-item`. The menu list item. * `ais-HierarchicalMenu-item--selected`. The selected menu list item. * `ais-HierarchicalMenu-item--parent`. The menu list item containing children. * `ais-HierarchicalMenu-link`. The clickable menu element. * `ais-HierarchicalMenu-link--selected`. The clickable element of a selected menu list item. * `ais-HierarchicalMenu-label`. The label of each item. * `ais-HierarchicalMenu-count`. The count of each item. * `ais-HierarchicalMenu-showMore`. The button used to display more categories. * `ais-HierarchicalMenu-showMore--disabled`. This button is used to display more categories. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The list of available items. * `canToggleShowMore: boolean`. Whether users can click the "Show more" button. * `isShowingMore: boolean`. Whether the list is expanded. * `refine: (value: string) => void`. Sets the path of the hierarchical filter and triggers a new search. * `createURL: (value: string) => string`. The function to generate a URL for the next state. * `toggleShowMore: () => void`. Toggles the number of displayed values between [`limit`](#param-limit) and [`show-more-limit`](#param-show-more-limit). * `sendEvent: (eventType, facetValue) => void`. The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. * To learn more, see the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. * `eventType: 'click'` * `facetValue: string` Where each item is an `object` containing: * `label: string`. The label of the item. * `value: string`. The value of the item. * `count: number`. The number results matching this value. * `isRefined: boolean`. Whether the item is selected. * `data: object[]|null`. The list of children for the current item. ```vue HierarchicalMenu theme={"system"} ``` ```vue HierarchicalMenuList theme={"system"} ``` **showMoreLabel** The slot to override the show more label. **Scope** * `isShowingMore: boolean`. Whether the list is expanded. ```vue Vue icon=code theme={"system"} ``` ## HTML output ### Default ```html HTML icon=code-xml theme={"system"} ``` ### Without show more ```html HTML icon=code-xml theme={"system"} ``` > [!NOTE] > > If SEO is important for your search page, ensure that your custom HTML is optimized for search engines: > > * Use `` tags with `href` attributes to allow search engine bots to follow links. > * Use semantic HTML and include [structured data](https://developers.google.com/search/docs/appearance/structured-data) when relevant. > > For more guidance, see the [SEO checklist](/doc/guides/building-search-ui/resources/seo/js). # Highlight Source: https://www.algolia.com/doc/api-reference/widgets/highlight/react Visually highlights matched terms in search results. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { Highlight } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=react) > > Preview this widget and its behavior. ## About this widget Displays the [highlighted attributes](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/react) of your search results. The required `hit` prop is an Algolia hit provided by [`Hits`](/doc/api-reference/widgets/hits/react), [`InfiniteHits`](/doc/api-reference/widgets/infinite-hits/react) or their Hooks. You can pass a custom object that doesn't come from Algolia, as long as you follow the expected structure. ```json JSON icon=braces theme={"system"} { "_highlightResult": { "attributeName": { "value": "..." } } } ``` ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Hits, Highlight } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Hit({ hit }) { return (

    ${hit.price}

    ); } function App() { return ( ); } ``` ## Props **attribute** `keyof THit` *required* The highlighted attribute. Specify a dot-separated value for nested objects, like `actor.name`. ```jsx JavaScript icon=code theme={"system"} ``` **hit** `THit` *required* The original [`hit` object](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response#hits) provided to the component. It contains all the data for that result, including the highlighted parts. > [!NOTE] > > The `hit` object must contain a `_highlightResult[attribute].value` property. ```jsx JavaScript icon=code theme={"system"} ; ``` **highlightedTagName** `React.ReactType\` (default: mark) The HTML element that wraps the highlight. ```jsx JavaScript icon=code theme={"system"} ; ``` **nonHighlightedTagName** `React.ReactType\` (default: span) The HTML element that wraps the non-highlighted parts of the string. ```jsx JavaScript icon=code theme={"system"} ; ``` **separator** `React.ReactNode` (default: , ) The character between each item when the attribute to highlight is an array. ```jsx JavaScript icon=code theme={"system"} ; ``` **classNames** `Partial\` To customize the appearance, override [the appropriate class](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) in your widget theme. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `highlighted`. The highlighted parts. * `nonHighlighted`. The non-highlighted parts. * `separator`. The separator elements between highlighted parts. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'span'\>` Any `span` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` # ais-highlight Source: https://www.algolia.com/doc/api-reference/widgets/highlight/vue Visually highlights matched terms in search results. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisHighlight } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisHighlight > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=vue) > > Preview this widget and its behavior. ## About this widget Displays the [highlighted attributes](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/vue) of your search results. It isn't a widget but a helper function that works with the [`ais-hits`](/doc/api-reference/widgets/hits/vue) or [`ais-infinite-hits`](/doc/api-reference/widgets/infinite-hits/vue) widgets. ## Examples ### Basic usage ```vue Vue icon=code theme={"system"} ``` ### Access a nested property Given this record: ```json JSON icon=braces theme={"system"} { "objectID": 1, "meta": { "name": "my name" } } ``` Access the highlighted version by specifying the path separating levels with dots: ```vue Vue icon=code theme={"system"} ``` ## Props **attribute** `string` *required* The highlighted attribute. Specify a dot-separated value for nested objects, like `actor.name`. ```vue Vue icon=code theme={"system"} ``` **hit** `object` *required* The original [`hit` object](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response#hits) provided to the function. It contains all the data for that result, including the highlighted parts. > [!NOTE] > > The `hit` object must have a `_highlightResult[attribute].value` property. ```vue Vue icon=code theme={"system"} ``` **highlighted-tag-name** `string` (default: mark) The HTML element that wraps the highlight. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) To customize the appearance, override [the appropriate class](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets) in your widget theme. * `ais-Highlight`. The root element of the widget. * `ais-Highlight-highlighted`. The highlighted part. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"} This is the highlighted text ``` # history Source: https://www.algolia.com/doc/api-reference/widgets/history-router/react Synchronizes the search UI state with the browser URL in React InstantSearch. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```ts Signature theme={"system"} const router = history({ // Optional parameters windowTitle?: function, createURL?: function, parseURL?: function, writeDelay?: number, getLocation?: function, push?: function, start?: function, dispose?: function, cleanUrlOnDispose?: boolean, }) ``` ## Import ```js JavaScript icon=code theme={"system"} import { history } from "instantsearch.js/es/lib/routers"; ``` ## About this function The `history` router is the default for the `InstantSearch` component's [routing](/doc/api-reference/widgets/instantsearch/react#param-routing) prop. The router provides an API that lets you customize some of its behaviors. To get a sense of what you can do with the API, see [Routing URLs](/doc/guides/building-search-ui/going-further/routing-urls/react). > [!NOTE] > > If you're using Next.js, see [`createInstantSearchRouterNext`](/doc/api-reference/widgets/instantsearch-next-router/react). ## Examples ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; const routing = { router: history(), }; function App() { return ( {/* ... */} ); } ``` ## Options **windowTitle** `function` This function lets you dynamically customize the window title based on the provided `routeState`. It's called every time users refine the UI and the history timer completes. ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; const routing = { router: history({ windowTitle(routeState) { const indexState = routeState.indexName || {}; if (!indexState.query) { return "MyWebsite - Results page"; } return `MyWebsite - Results for: ${indexState.query}`; }, }), }; function App() { return ( {/* ... */} ); } ``` **createURL** `function` This function lets you directly change the format of URLs that are created and rendered for the browser URL bar or widgets. It's called every time InstantSearch needs to create a URL. The provided options are: * `qsModule` (object). A module that can parse 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.) string or convert an object to a query string. You can get more information from the [`qs` documentation](https://github.com/ljharb/qs). * `location` (object). An alias to the implementation defined in [`getLocation`](/doc/api-reference/widgets/history-router/react#param-get-location). By default, it returns `window.location`. * `routeState` (object). The `routeState` created by the provided `stateMapping`. When absent, this is an untouched `uiState`. ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; const routing = { router: history({ createURL({ qsModule, location, routeState }) { const { origin, pathname, hash } = location; const indexState = routeState["instant_search"] || {}; const queryString = qsModule.stringify(routeState); if (!indexState.query) { return `${origin}${pathname}${hash}`; } return `${origin}${pathname}?${queryString}${hash}`; }, }), }; function App() { return ( {/* ... */} ); } ``` **parseURL** `function` This function is responsible for parsing the URL string back into a `routeState`. It must be customized if you customized the `createURL` function. It's called every time a user loads or reloads a page, or when they click the back or next buttons of the browser. The provided options are: * `qsModule` (object). A module that can parse a query string or convert an object to a query string. You can get more information from the [`qs` documentation](https://github.com/ljharb/qs). * `location` (object). An alias to the implementation defined in [`getLocation`](/doc/api-reference/widgets/history-router/react#param-get-location). By default, it returns `window.location`. ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; const routing = { router: history({ parseURL({ qsModule, location }) { return qsModule.parse(location.search.slice(1)); }, }), }; function App() { return ( {/* ... */} ); } ``` **writeDelay** `number` (default: 400) This option controls the number of milliseconds the router waits before writing the new URL to the browser. You can think about it this way: "400 ms after the last user action, the URL is pushed to the browser". This helps reduce: * The number of different history entries. If you type "phone", you don't want to have 5 history entries and thus have to click 5 times on the back button to go back to the previous search state * The performance overhead of updating the browser URL too often. Updating the browser URL too often causes recurring performance issues that are hard to track: these issues result from browser extensions reacting to every change. 400 ms is a typically a pretty good `writeDelay`. ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; const routing = { router: history({ writeDelay: 400, }), }; function App() { return ( {/* ... */} ); } ``` **getLocation** `function` This function lets you return a custom implementation of \[`Location`]\([https://developer](https://developer). mozilla.org/en-US/docs/Web/API/Location). It enables support for routing in server environments where `window` isn't available. By default, it generates an error if there isn't a custom implementation outside a browser environment. ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; // Provide `serverUrl` from your server. function App({ serverUrl }) { const routing = { router: history({ getLocation() { if (typeof window !== "undefined") { return window.location; } return new URL(serverUrl); }, }), }; return ( {/* ...*/} ); } ``` **push** `function` This function lets you customize the behavior of the router when it pushes a new URL so that you can delegate it to a third party router or set a custom history state. It's called every time users refine the UI and the history timer completes. The provided option is: * `url` (string). The URL which needs to be pushed to the browser. ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; import { useRouter } from "third-party-router"; function App() { const thirdPartyRouter = useRouter(); const routing = { router: history({ push(url) { thirdPartyRouter.push(url); }, }), }; return ( {/* ... */} ); } ``` **start** `function` This function is called when the router starts. You can use it to synchronize InstantSearch's router with a third party router by hooking events to it. The provided option is: * `onUpdate: () => void`. A function that needs to call to inform InstantSearch that the URL has changed. ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; import { useRouter } from "third-party-router"; function App() { const thirdPartyRouter = useRouter(); const routing = { router: history({ start(onUpdate) { thirdPartyRouter.addEventListener("routeChange", () => onUpdate()); }, }), }; return ( {/* ...*/} ); } ``` **dispose** `function` This function is called when the router gets disposed of. You can use it to detach events you may have set in `start`. The provided option is: ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; import { useRouter } from "third-party-router"; function App() { const thirdPartyRouter = useRouter(); // We need to keep a stable reference to the event listener so that we can remove it later. const routeChangeEventRef = useRef(null); const routing = { router: history({ start(onUpdate) { routeChangeEventRef.current = () => onUpdate(); thirdPartyRouter.addEventListener( "routeChange", routeChangeEventRef.current, ); }, dispose() { thirdPartyRouter.removeEventListener( "routeChange", routeChangeEventRef.current, ); }, }), }; return ( {/* ... */} ); } ``` **cleanUrlOnDispose** `boolean` (default: true) This option controls whether the URL is cleaned up from active refinements when the router is disposed of. It's useful when the search experience is in a modal, to remove query parameters from the URL when the modal is closed. ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { history } from "instantsearch.js/es/lib/routers"; const routing = { router: history({ cleanUrlOnDispose: false, }), }; function App() { return ( {/*...*/} ); } ``` # history Source: https://www.algolia.com/doc/api-reference/widgets/history-router/vue Synchronizes the search UI state with the browser URL in Vue InstantSearch. ```ts Signature theme={"system"} import { history } from 'instantsearch.js/es/lib/routers'; history({ // Optional parameters windowTitle: function, createURL: function, parseURL: function, writeDelay: number, getLocation: function, push: function, start: function, dispose: function, cleanUrlOnDispose: boolean, }); ``` ## About this widget The `history` router is the default for the `ais-instant-search` wrapper's [routing](/doc/api-reference/widgets/instantsearch/vue#param-routing) prop. The router provides an API that lets you customize some of its behaviors. To get a sense of what you can do with the API, see [Routing URLs](/doc/guides/building-search-ui/going-further/routing-urls/vue). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Options **windowTitle** `function` This function lets you dynamically customize the window title based on the provided `routeState`. It's called every time users refine the UI and the history timer completes. ```js JavaScript icon=code theme={"system"} history({ windowTitle(routeState) { const indexState = routeState.indexName || {}; if (!indexState.query) { return "MyWebsite - Results page"; } return `MyWebsite - Results for: ${indexState.query}`; }, }); ``` **createURL** `function` This function lets you directly change the format of URLs that are created and rendered for the browser URL bar or widgets. It's called every time InstantSearch needs to create a URL. The provided options are: * `qsModule` (object). A module that can parse 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.) string or convert an object to a query string. You can get more information from the [`qs` documentation](https://github.com/ljharb/qs). * `location` (object). An alias to the implementation defined in [`getLocation`](/doc/api-reference/widgets/history-router/vue#param-get-location). By default, it returns `window.location`. * `routeState` (object). The `routeState` created by the provided `stateMapping`. When absent, this is an untouched `uiState`. ```js JavaScript icon=code theme={"system"} history({ createURL({ qsModule, location, routeState }) { const { origin, pathname, hash } = location; const indexState = routeState || {}; const queryString = qsModule.stringify(routeState); if (!indexState.query) { return `${origin}${pathname}${hash}`; } return `${origin}${pathname}?${queryString}${hash}`; }, }); ``` **parseURL** `function` This function is responsible for parsing the URL string back into a `routeState`. It must be customized if you customized the `createURL` function. It's called every time a user loads or reloads a page, or when they click the back or next buttons of the browser. The provided options are: * `qsModule` (object). A module that can parse a query string or convert an object to a query string. You can get more information from the [`qs` documentation](https://github.com/ljharb/qs). * `location` (object). An alias to the implementation defined in [`getLocation`](/doc/api-reference/widgets/history-router/vue#param-get-location). By default, it returns `window.location`. ```js JavaScript icon=code theme={"system"} history({ parseURL({ qsModule, location }) { return qsModule.parse(location.search.slice(1)); }, }); ``` **writeDelay** `number` (default: 400) This option controls the number of milliseconds the router waits before writing the new URL to the browser. You can think about it this way: "400 ms after the last user action, the URL is pushed to the browser". This helps reduce: * The number of different history entries. If you type "phone", you don't want to have 5 history entries and thus have to click 5 times on the back button to go back to the previous search state * The performance overhead of updating the browser URL too often. Updating the browser URL too often causes recurring performance issues that are hard to track: these issues result from browser extensions reacting to every change. 400 ms is a typically a pretty good `writeDelay`. ```js JavaScript icon=code theme={"system"} history({ writeDelay: 400, }); ``` **getLocation** `function` This function lets you return a custom implementation of [`Location`](https://developer.mozilla.org/en-US/docs/Web/API/Location). It enables support for routing in server environments where `window` isn't available. By default, it generates an error if there isn't a custom implementation outside a browser environment. ```js JavaScript icon=code theme={"system"} history({ getLocation() { if (typeof window === "undefined") { const url = "..."; // retrieved from the server context return new URL(url); } return window.location; }, }); ``` **push** `function` This function lets you customize the behavior of the router when it pushes a new URL so that you can delegate it to a third party router or set a custom history state. It's called every time users refine the UI and the history timer completes. The provided option is: * `url` (string). The URL which needs to be pushed to the browser. ```js JavaScript icon=code theme={"system"} history({ push(url) { thirdPartyRouter.push(url); }, }); ``` **start** `function` This function is called when the router starts. You can use it to synchronize InstantSearch's router with a third party router by hooking event handlers to it. The provided option is: * `onUpdate: () => void`. A function that needs to call to inform InstantSearch that the URL has changed. It will read the current URL and update the UI state accordingly. ```js JavaScript icon=code theme={"system"} history({ start(onUpdate) { thirdPartyRouter.addEventListener("routeChange", () => { onUpdate(); }); }, }); ``` **dispose** `function` This function is called when the router gets disposed of. You can use it to detach event handlers you may have set in `start`. The provided option is: ```js JavaScript icon=code theme={"system"} let handler; history({ start(onUpdate) { handler = () => onUpdate(); thirdPartyRouter.addEventListener("routeChange", handler); }, dispose() { thirdPartyRouter.removeEventListener("routeChange", handler); }, }); ``` **cleanUrlOnDispose** `boolean` (default: true) This option controls whether the URL is cleaned up from active refinements when the router is disposed of. It's useful when the search experience is in a modal, to remove query parameters from the URL when the modal is closed. ```js JavaScript icon=code theme={"system"} history({ cleanUrlOnDispose: false, }); ``` # HitsPerPage Source: https://www.algolia.com/doc/api-reference/widgets/hits-per-page/react Lets users change the number of search results per page. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { HitsPerPage } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=hitsPerPage) > > Preview this widget and its behavior. ## About this widget `HitsPerPage` is a widget that displays a menu of options to change the number of results per page. If you only want to configure the number of hits per page without displaying a widget, you can use the [`Configure`](/doc/api-reference/widgets/configure/react) widget with the [`hitsPerPage`](/doc/api-reference/api-parameters/hitsPerPage) search parameter. > [!TIP] > > You can also create your own UI with [`useHitsPerPage`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, HitsPerPage } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **items** `HitsPerPagePropsItem[]` *required* The list of available options, with each item: * `label`. The label to display in the option. * `value`. The number of hits to display per page. * `default`. The default hits per page on first search. ```jsx JavaScript theme={"system"} ; ``` ```ts TypeScript theme={"system"} type HitsPerPagePropsItem = { /** * Label to display in the option. */ label: string; /** * Number of hits to display per page. */ value: number; /** * The default hits per page on first search. * @default false */ default?: boolean; }; ``` **transformItems** A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { HitsPerPageProps } from 'react-instantsearch'; const transformItems: HitsPerPageProps['transformItems'] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `select`. The `select` element. * `option`. The `option` element. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `HitsPerPage` widget with `useHitsPerPage`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useHitsPerPage` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useHitsPerPage } from "react-instantsearch"; function CustomHitsPerPage(props) { const { items, refine, createURL, canRefine } = useHitsPerPage(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **items** `HitsPerPagePropsItem[]` *required* The list of available options, with each item: * `label`. The label to display in the option. * `value`. The number of hits to display per page. * `default`. The default hits per page on first search. ```jsx JavaScript theme={"system"} const hitsPerPageApi = useHitsPerPage({ items: [ { label: "8 hits per page", value: 8, default: true }, { label: "16 hits per page", value: 16 }, ], }); ``` ```tsx TypeScript theme={"system"} type HitsPerPagePropsItem = { /** * Label to display in the option. */ label: string; /** * Number of hits to display per page. */ value: number; /** * The default hits per page on first search. * @default false */ default?: boolean; }; ``` **transformItems** A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function HitsPerPage() { const hitsPerPageApi = useHitsPerPage({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseHitsPerPageProps } from 'react-instantsearch'; const transformItems: UseHitsPerPageProps['transformItems'] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function HitsPerPage() { const hitsPerPageApi = useHitsPerPage({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `HitsPerPageItem[]` The list of items the widget can display, with each item: * `label: string`. The label to display in the option. * `value: number`. The number of hits to display per page. * `isRefined: boolean`. Indicates if the item is the current refined value. ```jsx JavaScript theme={"system"} function HitsPerPage(props) { const { items, refine } = useHitsPerPage(props); const { value } = items.find(({ isRefined }) => isRefined) || {}; return ( ); } ``` ```ts TypeScript theme={"system"} type HitsPerPageItem = { /** * The label to display in the option */ label: string; /** * The number of hits to display per page. */ value: number; /** * Whether the item is the current refined value. */ isRefined: boolean; }; ``` **canRefine** `boolean` Indicates whether the search can be refined. ```jsx JavaScript icon=code theme={"system"} function HitsPerPage(props) { const { items, canRefine, refine } = useHitsPerPage(props); const { value } = items.find(({ isRefined }) => isRefined) || {}; return ( ); } ``` **refine** `(value: number) =\> void` Sets the number of hits per page and triggers a search. ```jsx JavaScript icon=code theme={"system"} function HitsPerPage(props) { const { items, refine } = useHitsPerPage(props); const { value } = items.find(({ isRefined }) => isRefined) || {}; return ( ); } ``` **createURL** `(value: number) =\> string` Generates a URL of the next search state. ```jsx JavaScript icon=code theme={"system"} function HitsPerPage(props) { const { items, refine, createURL } = useHitsPerPage(props); return (
    ); } ``` ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useHitsPerPage } from "react-instantsearch"; function CustomHitsPerPage(props) { const { items, refine } = useHitsPerPage(props); const { value: currentValue } = items.find(({ isRefined }) => isRefined) || {}; return ( ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useHitsPerPage, UseHitsPerPageProps } from "react-instantsearch"; function CustomHitsPerPage(props: UseHitsPerPageProps) { const { items, refine } = useHitsPerPage(props); const { value: currentValue } = items.find(({ isRefined }) => isRefined)! || {}; return ( ); } ``` # ais-hits-per-page Source: https://www.algolia.com/doc/api-reference/widgets/hits-per-page/vue Lets users change the number of search results per page. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisHitsPerPage } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisHitsPerPage, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=hitsPerPage) > > Preview this widget and its behavior. ## About this widget The `ais-hits-per-page` widget displays a select element to let users change the number of displayed hits. If you only want to configure the number of hits per page without displaying a widget, you can use the [`ais-configure`](/doc/api-reference/widgets/configure/vue) widget with the [`hitsPerPage`](/doc/api-reference/api-parameters/hitsPerPage) search parameter. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **items** `object[]` *required* The list of available options, with each item: * `label: string`. The label to display in the option. * `value: number`. The number of hits to display per page. * `default: boolean`. Whether it's the default hits per page on first search. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-HitsPerPage`. The root of the widget. * `ais-HitsPerPage-select`. The select element. * `ais-HitsPerPage-option`. The option element of the select. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The list of items the widget can display. * `canRefine: boolean`. Whether a refinement can be applied. * `refine: (value: number) => void`. The function to call with the next value of hits per page. Where each item is an `object` containing: * `label: string`. The label to display in the option. * `value: number`. The number of hits to display per page. * `isRefined: boolean`. Indicates if the item is the current refined value. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # Hits Source: https://www.algolia.com/doc/api-reference/widgets/hits/react Shows a list of search results. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} JSX.Element} classNames={object} ...props={ComponentProps<'div'>} /> ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=hits) > > Preview this widget and its behavior. ## Import ```jsx JavaScript icon=code theme={"system"} import { Hits } from "react-instantsearch"; ``` ## About this widget Use the `Hits` widget to display a list of search results. To set the number of search results, use the [`HitsPerPage`](/doc/api-reference/widgets/hits-per-page/react) widget or pass the [`hitsPerPage`](/doc/api-reference/api-parameters/hitsPerPage) prop to the [`Configure`](/doc/api-reference/widgets/configure/react) widget. See also: * [Structured results](/doc/guides/building-search-ui/ui-and-ux-patterns/structured-results/react) * [Multi-index search](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/react) * [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/react#handle-searches-without-results) > [!TIP] > > You can also create your own UI with [`useHits`](#hook). ## Examples ```jsx JavaScript theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Hits } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Hit({ hit }) { return JSON.stringify(hit); } function App() { return ( ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Hits } from "react-instantsearch"; import type { Hit as AlgoliaHit } from "instantsearch.js"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); type MyHit = AlgoliaHit<{ name: string; }>; function Hit({ hit }: { hit: MyHit }) { return JSON.stringify(hit); } function App() { return ( hitComponent={Hit} /> ); } ``` ## Props **hitComponent** `(props: \{ hit: THit; sendEvent: SendEventForHits \}) =\> JSX.Element` A component that renders each hit from the results. It receives a `hit` and a `sendEvent` (for [`insights`](/doc/api-reference/widgets/insights/react)) prop. When not provided, the widget displays the hit as a JSON string. ```jsx JavaScript icon=code theme={"system"} hit.objectID} />; ``` **bannerComponent** `((props: \{ banner: SearchResults['renderingContent']['widgets']['banners'][0] \}) =\> JSX.Element) | false` A component that renders the banner data on the `renderingContent` property from the Algolia response. It overrides the default banner rendering. When `false`, the widget doesn't render the banner. ```jsx JavaScript icon=code theme={"system"} // Override the default banner rendering } /> // Disable the banner rendering ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from hits string values. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. If you transform an attribute used in the [`Highlight`](/doc/api-reference/widgets/highlight/react) widget, you must also transform the corresponding `item._highlightResult[attribute].value`. ```jsx JavaScript theme={"system"} // Using only items const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; // Using items and results const transformItems = (items, { results }) => { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { HitsProps } from "react-instantsearch"; // Using only items const transformItems: HitsProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; // Using items and results const transformItems: HitsProps["transformItems"] = (items, { results }) => { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The widget's root element. * `emptyRoot`. The root element without results. * `list`. The list of results. * `item`. The list of items. * `bannerRoot`. The root element of the banner. * `bannerImage`. The image element of the banner. * `bannerLink`. The anchor element of the banner. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the widget's root element. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `Hits` widget with `useHits`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useHits` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useHits } from "react-instantsearch"; function CustomHits(props) { const { items, results, banner, sendEvent } = useHits(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from hits string values. ```jsx JavaScript icon=code theme={"system"} const hitsApi = useHits({ escapeHTML: false, }); ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. If you transform an attribute used in the [`Highlight`](/doc/api-reference/widgets/highlight/react) widget, you must also transform the corresponding `item._highlightResult[attribute].value`. ```jsx JavaScript theme={"system"} // Using only items const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; // Using items and results const transformItems = (items, { results }) => { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }; function Hits() { const hitsApi = useHits({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseHitsProps } from 'react-instantsearch'; // Using only items const transformItems: UseHitsProps['transformItems'] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; // Using items and results const transformItems: UseHitsProps['transformItems'] = (items, { results }) => { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }; function Hits() { const hitsApi = useHits({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `THit[]` The matched items returned from Algolia. You can use [Algolia's highlighting feature](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) directly from the render function. **results** `SearchResults\` The complete response from Algolia. It contains the `hits` but also metadata about the page, number of hits, and more. Unless you need to access metadata, use [`items`](#param-items) instead. **banner** `SearchResults\['renderingContent']['widgets']['banners'][0]` The banner data on the `renderingContent` property from the Algolia response. **sendEvent** `(eventType: string, hits: Hit | Hits, eventName?: string) =\> void` The function to send `click` or `conversion` events. The `view` event is automatically sent when this Hook renders hits. Check the [`insights`](/doc/api-reference/widgets/insights/js) documentation to learn more. ### Example ```jsx JavaScript theme={"system"} import React from 'react'; import { useHits } from 'react-instantsearch'; function CustomHits(props) { const { items, sendEvent } = useHits(props); return (
      {items.map((hit) => (
    1. sendEvent('click', hit, 'Hit Clicked')} onAuxClick={() => sendEvent('click', hit, 'Hit Clicked')} >
      {JSON.stringify(hit).slice(0, 100)}
    2. ))}
    ); } ``` ```tsx TypeScript theme={"system"} import React from 'react'; import { useHits, UseHitsProps, } from 'react-instantsearch'; function CustomHits(props: UseHitsProps) { const { items, sendEvent } = useHits(props); return (
      {items.map((hit) => (
    1. sendEvent('click', hit, 'Hit Clicked')} onAuxClick={() => sendEvent('click', hit, 'Hit Clicked')} >
      {JSON.stringify(hit).slice(0, 100)}
    2. ))}
    ); } ``` ## Click and conversion events If the [`insights`](/doc/api-reference/widgets/instantsearch/react#param-insights) option is `true`, the `Hits` component automatically sends a `click` event with the following "shape" to the Insights API whenever users click a hit. ```jsonc JSON icon=braces theme={"system"} { "eventType": "click", "insightsMethod": "clickedObjectIDsAfterSearch", "payload": { "eventName": "Hit Clicked" // … }, "widgetType": "ais.hits" } ``` To customize this event, use the `sendEvent` function in your [`hitComponent`](/doc/api-reference/widgets/hits/react#param-hit-component) and send a custom `click` event. ```jsx JavaScript icon=code theme={"system"} (
    sendEvent("click", hit, "Product Clicked")}>

    {hit.description}

    )} /> ``` The `sendEvent` function also accepts an object as a fourth argument to send directly to the Insights API. You can use it, for example, to send special `conversion` events with a subtype. ```jsx JavaScript icon=code theme={"system"} (

    {hit.description}

    )} /> ``` > [!NOTE] > > Use strings to represent monetary values in major currency units (for example, '5.45'). > This avoids floating-point rounding issues, especially when performing calculations. See also: [Send click and conversion events with React InstantSearch](/doc/guides/building-search-ui/events/react) # ais-hits Source: https://www.algolia.com/doc/api-reference/widgets/hits/vue Shows a list of search results. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisHits } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisHits, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=hits) > > Preview this widget and its behavior. ## About this widget Use the `ais-hits` widget to display a list of results. To set the number of search results, use the [`ais-hits-per-page`](/doc/api-reference/widgets/hits-per-page/vue) or [`ais-configure`](/doc/api-reference/widgets/configure/vue) widget. See also: * [Structured results](/doc/guides/building-search-ui/ui-and-ux-patterns/structured-results/vue) * [Multi-index search](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/vue) * [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/vue#handle-searches-without-results) ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **show-banner** `boolean` (default: true) Whether to display a top banner when banner data is included within the `renderingContent` property from the Algolia API response. ```vue Vue icon=code theme={"system"} ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML entities from hits string values. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-Hits`. The widget's root element. * `ais-Hits-list`. The list of results. * `ais-Hits-item`. The list of items. * `ais-Hits-banner`. The optional banner's root. * `ais-Hits-banner-image`. The image element of the optional banner. * `ais-Hits-banner-link`. The optional anchor element of the optional banner. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) Receives the items and is called before displaying them. It returns a new array with the same "shape" as the original. This is helpful when transforming or reordering items. Don't use `transformItems` to remove items since this will affect your pagination. The entire `results` data is also available, including all [regular response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response) and [helper parameters](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters) (for example, `disjunctiveFacetsRefinements`). If you're transforming an attribute with the [`ais-highlight`](/doc/api-reference/widgets/highlight/vue) widget, you must transform `item._highlightResult[attribute].value`. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) that matched the search. Each element of `items` has the following read-only properties: * `__queryID`: the query ID (only if `clickAnalytics` is set to `true`). * `__position`: the absolute position of the item. * `banner: object`. The banner data returned within the `renderingContent` property from the Algolia API response. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. The `view` event is automatically sent when this connector renders hits. Learn more about these events in the [`insights`](/doc/api-reference/widgets/insights/vue) middleware documentation. * `eventType: 'click' | 'conversion'` * `hit: Hit | Hit[]` * `eventName: string` * `insights: (method: string, payload: object) => void`: (Deprecated) Sends Insights events. * `method: string`. The Insights method to be called. Only search-related methods are supported: `'clickedObjectIDsAfterSearch'`, `'convertedObjectIDsAfterSearch'`. * `payload: object`. The payload to be sent. * `eventName: string`: the name of the event. * `objectIDs: string[]`: a list of `objectID`s. * `index?: string`: the name of 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.) related to the click. * `queryID?: string`: the Algolia `queryID` found in the search response when `clickAnalytics: true`. * `userToken?: string`: a user identifier. * `positions?: number[]`: the position of the click in the list of Algolia search results. When not provided, `index`, `queryID`, and `positions` are inferred by the InstantSearch context and the passed object IDs: * `index` by default is the name of the index that returned the passed object IDs. * `queryID` by default is the ID of the query that returned the passed object IDs. * `positions` by default is the absolute position of the passed object IDs. > [!NOTE] > > For more details about the `payload` property, see the [Insights client documentation](/doc/libraries/search-insights). ```vue items theme={"system"} ``` ```vue insights theme={"system"} ``` **banner** The slot to override the DOM output of the banner. **Scope** * `banner: object`. The banner data returned within the `renderingContent` property from the Algolia API response. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of the item. **Scope** * `item: object`. A single hit with all its attributes. * `index: number`. The relative position of the hit in the list. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    1. ...
    2. ...
    3. ...
    ``` ## Click and conversion events If the [`insights`](/doc/api-reference/widgets/instantsearch/vue#param-insights) option is `true`, the `ais-hits` widget automatically sends a `click` event with the following "shape" to the Insights API whenever users click a hit. ```json JSON icon=braces theme={"system"} { "eventType": "click", "insightsMethod": "clickedObjectIDsAfterSearch", "payload": { "eventName": "Hit Clicked" // … }, "widgetType": "ais.hits" } ``` To customize this event, use the `sendEvent` function in your [`item`](#param-item) slot and send a custom `click` event on the root element. ```vue Vue icon=code theme={"system"} ``` The `sendEvent` function also accepts an object as a fourth argument to send directly to the Insights API. You can use it, for example, to send special `conversion` events with a subtype. ```vue Vue icon=code theme={"system"} ``` > [!NOTE] > > Use strings to represent monetary values in major currency units (for example, '5.45'). > This avoids floating-point rounding issues, especially when performing calculations. See also: [Send click and conversion events with Vue InstantSearch](/doc/guides/building-search-ui/events/vue) # ais-index Source: https://www.algolia.com/doc/api-reference/widgets/index-widget/vue Scopes a group of widgets to a specific index in multi-index search interfaces. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisIndex } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisIndex, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=vue) > > Preview this widget and its behavior. ## About this widget This widget lets you apply widgets to a specific index. It's useful when building interfaces that target multiple indices such as [federated searches](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/vue). The position of `index` in the widgets tree affects which search parameters apply. Widgets that create search parameters forward them to their child `index` widgets. ```vue Vue icon=code theme={"system"} ``` The only exception to this rule is when two widgets own the same part of your UI state, like two [`ais-search-box`](/doc/api-reference/widgets/search-box/vue) widgets or two [`ais-refinement-list`](/doc/api-reference/widgets/refinement-list/vue) widgets on the same attribute. In that case, the latter takes precedence and overrides the search parameters. ```vue Vue icon=code theme={"system"} ``` The same rule applies when you nest multiple `index` widgets. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Options **index-name** `string` *required* The index to search into. ```vue Vue icon=code theme={"system"} ``` **index-id** `string` (default: value provided for index-name) An identifier for the `index` widget. Providing an `indexId` lets different `index` widgets target the same Algolia index. It's helpful for [`routing`](/doc/api-reference/widgets/instantsearch/vue#param-routing). It lets you find the refinements that match an `index` widget. ```vue Vue icon=code theme={"system"} ``` # InfiniteHits Source: https://www.algolia.com/doc/api-reference/widgets/infinite-hits/react Shows search results with a button to load more. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} JSX.Element} showPrevious={boolean} transformItems={function} cache={object} classNames={object} translations={object} ...props={ComponentProps<'div'>} /> ``` ## Import ```js JavaScript icon=code theme={"system"} import { InfiniteHits } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=infiniteHits) > > Preview this widget and its behavior. ## About this widget The `InfiniteHits` widget displays a list of results with a "Show more" button at the bottom of the list. As an alternative to this approach, [the infinite scroll guide](/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/react) describes how to create an automatically scrolling infinite hits experience. See also: [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/react#handle-searches-without-results) > [!TIP] > > You can also create your own UI with [`useInfiniteHits`](#hook). ## Examples ```jsx JavaScript theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, InfiniteHits } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Hit({ hit }) { return JSON.stringify(hit); } function App() { return ( ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, InfiniteHits } from "react-instantsearch"; import type { Hit as AlgoliaHit } from "instantsearch.js"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); type MyHit = AlgoliaHit<{ name: string; }>; function Hit({ hit }: { hit: MyHit }) { return JSON.stringify(hit); } function App() { return ( hitComponent={Hit} /> ); } ``` ## Props **hitComponent** `(props: \{ hit: THit; sendEvent: SendEventForHits \}) =\> JSX.Element` *required* A component that renders each hit from the results. It receives a `hit` and a `sendEvent` (for [`insights`](/doc/api-reference/widgets/insights/react)) prop. When not provided, the widget displays the hit as a JSON string. ```jsx JavaScript icon=code theme={"system"} hit.objectID} />; ``` **bannerComponent** `((props: \{ banner: SearchResults['renderingContent']['widgets']['banners'][0] \}) =\> JSX.Element) | false` A component that renders the banner data on the `renderingContent` property from the Algolia response. It overrides the default banner rendering. When `false`, the widget doesn't render the banner. ```jsx JavaScript icon=code theme={"system"} // Override the default banner rendering } /> // Disable the banner rendering ``` **showPrevious** `boolean` (default: true) Enable the button to load previous results. ```jsx JavaScript icon=code theme={"system"} ; ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from hits string values. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. If you transform an attribute you're using with the [`Highlight`](/doc/api-reference/widgets/highlight/react) widget, you must also transform the corresponding `item._highlightResult[attribute].value`. ```jsx JavaScript theme={"system"} // Using only items const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; // Using items and results const transformItems = (items, { results }) => { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { InfiniteHitsProps } from "react-instantsearch"; // Using only items const transformItems: InfiniteHitsProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; // Using items and results const transformItems: InfiniteHitsProps["transformItems"] = ( items, { results }, ) => { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }; function Search() { return ( ); } ``` **cache** `InfiniteHitsCache` The widget caches all loaded hits. By default, it uses its own internal in-memory cache implementation. Alternatively, use [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) to retain the cache even if users reload the page. You can also implement your own cache object with `read` and `write` methods. This can be handy if you need to persist the data across sessions or if you expect the cached data to grow larger than the browser's [5 MB allowed storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria#web_storage). ```jsx JavaScript theme={"system"} import { createInfiniteHitsSessionStorageCache } from "instantsearch.js/es/lib/infiniteHitsCache"; const sessionStorageCache = createInfiniteHitsSessionStorageCache(); ; ``` ```ts InfiniteHitsCache theme={"system"} type InfiniteHitsCache = { read: ({ state, }: { state: PlainSearchParameters; }) => InfiniteHitsCachedHits | null; write: ({ state, hits, }: { state: PlainSearchParameters; hits: InfiniteHitsCachedHits; }) => void; }; type InfiniteHitsCachedHits = { [page: number]: Hits; }; ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The widget's root element. * `emptyRoot`. The root element without results. * `list`. The list of results. * `item`. The list items. * `loadPrevious`. The "Load previous" button. * `disabledLoadPrevious`. The "Load previous" button when there are no previous results to load. * `loadMore`. The "Load more" button. * `disabledLoadMore`. The "Load more" button when there are no more results to load. * `bannerRoot`. The root element of the banner. * `bannerImage`. The image element of the banner. * `bannerLink`. The anchor element of the banner. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `showPreviousButtonText`. The text for the "Show previous" button. * `showMoreButtonText`. The text for the "Show more" button. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the widget's root element. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `InfiniteHits` widget with `useInfiniteHits`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useInfiniteHits` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useInfiniteHits } from "react-instantsearch"; function CustomInfiniteHits(props) { const { items, currentPageHits, results, banner, isFirstPage, isLastPage, showPrevious, showMore, sendEvent, } = useInfiniteHits(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from hits string values. ```jsx JavaScript icon=code theme={"system"} const infiniteHitsApi = useInfiniteHits({ escapeHTML: false, }); ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. If you transform an attribute you're using with the [`Highlight`](/doc/api-reference/widgets/highlight/react) widget, you must also transform the corresponding `item._highlightResult[attribute].value`. ```jsx JavaScript theme={"system"} // Using only items const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; // Using items and results const transformItems = (items, { results }) => { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }; function InfiniteHits() { const infiniteHitsApi = useInfiniteHits({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseInfiniteHitsProps } from "react-instantsearch"; // Using only items const transformItems: UseInfiniteHitsProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; // Using items and results const transformItems: UseInfiniteHitsProps["transformItems"] = ( items, { results }, ) => { return items.map((item, index) => ({ ...item, position: { index, page: results.page }, })); }; function InfiniteHits() { const infiniteHitsApi = useInfiniteHits({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` **cache** `InfiniteHitsCache` The Hook internally caches all loaded hits using its own internal in-memory cache implementation. The library provides another implementation using [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage), which is more persistent and lets you restore the scroll position when you leave and come back to the search page. You can also provide your own implementation by providing a cache object with `read` and `write` methods. ```jsx JavaScript theme={"system"} import { createInfiniteHitsSessionStorageCache } from "instantsearch.js/es/lib/infiniteHitsCache"; const sessionStorageCache = createInfiniteHitsSessionStorageCache(); const infiniteHitsApi = useInfiniteHits({ cache: sessionStorageCache, }); ``` ```ts TypeScript theme={"system"} type InfiniteHitsCache = { read: ({ state, }: { state: PlainSearchParameters; }) => InfiniteHitsCachedHits | null; write: ({ state, hits, }: { state: PlainSearchParameters; hits: InfiniteHitsCachedHits; }) => void; }; type InfiniteHitsCachedHits = { [page: number]: Hits; }; ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `THit[]` The matched hits returned from Algolia. This returns the combined hits for all the pages that have been requested so far. Use [Algolia's highlighting feature](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) directly from the render function. **currentPageHits** `THit[]` The matched hits from Algolia for the current page. Unlike the [`items`](#param-items) parameter, this only returns the hits for the requested page. This can be useful if you want to customize how to add the new page of hits to the existing list. You can use [Algolia's highlighting feature](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js#highlighting-and-snippeting) directly from the render function. **results** `SearchResults\` The complete response from Algolia. It contains the `hits`, metadata about the page, the number of hits, and more. Unless you need to access metadata, use [`items`](#param-items) instead. **banner** `SearchResults['renderingContent']['widgets']['banners'][0]` The banner data from the `renderingContent` property in the Algolia response. **isFirstPage** `boolean` Whether the first page of hits has been reached. **isLastPage** `boolean` Whether the last page of hits has been reached. **showPrevious** `() =\> void` Loads the previous page of hits. **showMore** `() =\> void` Loads the next page of hits. **sendEvent** `(eventType: string, hits: Hit | Hits, eventName?: string) =\> void` The function to send `click` or `conversion` events. The `view` event is automatically sent when this Hook renders hits. Check the [`insights`](/doc/api-reference/widgets/insights/react) documentation to learn more. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useInfiniteHits } from "react-instantsearch"; function CustomInfiniteHits(props) { const { items, sendEvent, showPrevious, showMore, isFirstPage, isLastPage } = useInfiniteHits(props); return (
      {items.map((hit) => (
    1. sendEvent("click", hit, "Hit Clicked")} onAuxClick={() => sendEvent("click", hit, "Hit Clicked")} >
      {JSON.stringify(hit).slice(0, 100)}…
    2. ))}
    ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useInfiniteHits, UseInfiniteHitsProps } from "react-instantsearch"; function CustomInfiniteHits(props: UseInfiniteHitsProps) { const { items, sendEvent, showPrevious, showMore, isFirstPage, isLastPage } = useInfiniteHits(props); return (
      {items.map((hit) => (
    1. sendEvent("click", hit, "Hit Clicked")} onAuxClick={() => sendEvent("click", hit, "Hit Clicked")} >
      {JSON.stringify(hit).slice(0, 100)}…
    2. ))}
    ); } ``` ## Click and conversion events If the [`insights`](/doc/api-reference/widgets/instantsearch/react#param-insights) option is `true`, the `InfiniteHits` component automatically sends a `click` event with the following "shape" to the Insights API whenever users click a hit. ```json JSON icon=braces theme={"system"} { "eventType": "click", "insightsMethod": "clickedObjectIDsAfterSearch", "payload": { "eventName": "Hit Clicked" // … }, "widgetType": "ais.infiniteHits" } ``` To customize this event, use the `sendEvent` function in your [`hitComponent`](#param-hit-component) and send a custom `click` event. ```jsx JavaScript icon=code theme={"system"} (
    sendEvent("click", hit, "Product Clicked")}>

    {hit.description}

    )} />; ``` The `sendEvent` function also accepts an object as a fourth argument to send directly to the Insights API. You can use it, for example, to send special `conversion` events with a subtype. ```jsx JavaScript icon=code theme={"system"} (

    {hit.description}

    )} />; ``` > [!NOTE] > > Use strings to represent monetary values in major currency units (for example, '5.45'). > This avoids floating-point rounding issues, especially when performing calculations. See also: [Send click and conversion events with React InstantSearch](/doc/guides/building-search-ui/events/react) # ais-infinite-hits Source: https://www.algolia.com/doc/api-reference/widgets/infinite-hits/vue Shows search results with a button to load more. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisInfiniteHits } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisInfiniteHits > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=infiniteHits) > > Preview this widget and its behavior. ## About this widget The `ais-infinite-hits` widget displays a list of results with a "Show more" button at the bottom of the list. As an alternative to this approach, [the infinite scroll guide](/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/vue) describes how to create an automatically scrolling infinite hits experience. To configure the number of hits to show, use the [`ais-hits-per-page`](/doc/api-reference/widgets/hits-per-page/vue) or the [`ais-configure`](/doc/api-reference/widgets/configure/vue) widget. See also: [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/vue#handle-searches-without-results). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **show-banner** `boolean` (default: true) Whether to display a top banner when banner data is included within the `renderingContent` property from the Algolia API response. ```vue Vue icon=code theme={"system"} ``` **escapeHTML** `boolean` (default: true) Whether to escape the raw HTML in the hits. ```vue Vue icon=code theme={"system"} ``` **show-previous** `boolean` (default: false) Enable the button to load previous results. The button is only displayed if the routing option is enabled in [`ais-instant-search`](/doc/api-reference/widgets/instantsearch/vue#param-routing) and users aren't on the first page. Override this behavior with [slots](/doc/api-reference/widgets/infinite-hits/vue#customize-the-ui). ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-InfiniteHits`. The root element of the widget. * `ais-InfiniteHits-list`. The list of results. * `ais-InfiniteHits-item`. The list items. * `ais-InfiniteHits-banner`. The optional banner's root. * `ais-InfiniteHits-banner-image`. The image element of the optional banner. * `ais-InfiniteHits-banner-link`. The optional anchor element of the optional banner. * `ais-InfiniteHits-loadPrevious`. The button to display previous results. * `ais-InfiniteHits-loadMore`. The button to display more results. * `ais-InfiniteHits-loadPrevious--disabled`. The button to display previous results, when it's unavailable. * `ais-InfiniteHits-loadMore--disabled`. The button to display more results, when it's unavailable. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) Receives the items and is called before displaying them. It returns a new array with the same "shape" as the original. This is helpful when transforming, removing, or reordering items. The complete `results` data is also available, including all [regular response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response) and [helper parameters](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters) (for example, `disjunctiveFacetsRefinements`). If you're transforming an attribute with the [`ais-highlight`](/doc/api-reference/widgets/highlight/vue) widget, you must transform `item._highlightResult[attribute].value`. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **cache** `object` (default: in-memory cache object) The widget caches all loaded hits. By default, it uses its own internal in-memory cache implementation. Alternatively, use [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) to retain the cache even if users reload the page. You can also implement your own cache object with `read` and `write` methods. This can be handy if you need to persist the data across sessions or if you expect the cached data to grow larger than the browser's [5 MB allowed storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria#web_storage). ```vue sessionStorage theme={"system"} ``` ```vue custom theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) that matched the search. Each element of `items` has the following read-only properties: * `__queryID`. The query ID (only if `clickAnalytics` is `true`). * `__position`. The absolute position of the item. * `banner: object`. The banner data returned within the `renderingContent` property from the Algolia API response. * `refinePrevious: () => void`. The function to load previous results. * `refineNext: () => void`. The function to load more results. * `isLastPage: boolean`. Whether it's the last page. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. The `view` event is automatically sent when this connector renders hits. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. * `eventType: 'click' | 'conversion'` * `hit: Hit | Hit[]` * `eventName: string` * `insights: (method: string, payload: object) => void`: (Deprecated) Sends Insights events. * `method: string`. The Insights method to be called. Only search-related methods are supported: `'clickedObjectIDsAfterSearch'`, `'convertedObjectIDsAfterSearch'`. * `payload: object`. The payload to be sent. * `eventName: string`. The name of the event. * `objectIDs: string[]`. A list of `objectID`s. * `index?: string`. The name of 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.) related to the click. * `queryID?: string`. The Algolia `queryID` found in the search response when `clickAnalytics: true`. * `userToken?: string`. A user identifier. * `positions?: number[]`. The position of the click in the list of Algolia search results. When not provided, `index`, `queryID`, and `positions` are inferred by the InstantSearch context and the passed object IDs: * `index` by default is the name of index that returned the passed object IDs. * `queryID` by default is the ID of the query that returned the passed object IDs. * `positions` by default is the absolute positions of the passed object IDs. > [!NOTE] > > For more details about the `payload` property, see the [Insights client documentation](/doc/libraries/search-insights). ```vue items theme={"system"} ``` ```vue insights theme={"system"} ``` **loadPrevious** The slot to override the DOM output of the "Show previous" button. **Scope** * `page: number`: the value of the current page. * `isFirstPage: boolean`: whether it's the first page. * `refinePrevious: () => void`: the function to load the previous page. ```vue Vue icon=code theme={"system"} ``` **banner** The slot to override the DOM output of the banner. **Scope** * `banner: object`. The banner data returned within the `renderingContent` property from the Algolia API response. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of the item. **Scope** * `items: object`. A single hit with all its attributes. Each element of `items` has the following read-only properties: * `__queryID`. The query ID (only if `clickAnalytics` is `true`). * `__position`. The absolute position of the item. * `index: number`. The relative position of the hit in the list. * `insights: (method: string, payload: object) => void`. (Deprecated) sends Insights events. * `method: string`. The Insights method to be called. Only search-related methods are supported: `'clickedObjectIDsAfterSearch'`, `'convertedObjectIDsAfterSearch'`. * `payload: object`. The payload to be sent. * `eventName: string`. The name of the event. * `objectIDs: string[]`. A list of object IDs. * `index?: string`. The name of the index related to the click. * `queryID?: string`. The Algolia `queryID` found in the search response when `clickAnalytics: true`. * `userToken?: string`. A user identifier. * `positions?: number[]`. The position of the click in the list of Algolia search results. When not provided, `index`, `queryID`, and `positions` are inferred by the InstantSearch context and the passed `objectIDs`: * `index` by default is the name of index that returned the passed `objectIDs`. * `queryID` by default is the ID of the query that returned the passed `objectIDs`. * `positions` by default is the absolute positions of the passed `objectIDs`. > [!NOTE] > > For more details about the `payload` property, see the [Insights client documentation](/doc/libraries/search-insights). ```vue Vue icon=code theme={"system"} ``` **loadMore** The slot to override the DOM output of the "Show more" button. **Scope** * `page: number`. The value of the current page. * `isLastPage: boolean`. Whether it's the last page. * `refineNext: () => void`. The function to load the next page. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    1. ...
    2. ...
    3. ...
    ``` ## Click and conversion events If the [`insights`](/doc/api-reference/widgets/instantsearch/vue#param-insights) option is `true`, the `ais-infinite-hits` widget automatically sends a `click` event with the following "shape" to the Insights API when a user clicks on a hit. ```json JSON icon=braces theme={"system"} { "eventType": "click", "insightsMethod": "clickedObjectIDsAfterSearch", "payload": { "eventName": "Hit Clicked" // … }, "widgetType": "ais.infiniteHits" } ``` To customize this event, use the `sendEvent` function in your [`item`](#param-item) slot and send a custom `click` event. ```vue Vue icon=code theme={"system"} ``` The `sendEvent` function also accepts an object as a fourth argument to send directly to the Insights API. You can use it, for example, to send special `conversion` events with a subtype. ```vue Vue icon=code theme={"system"}
    ``` > [!NOTE] > > Use strings to represent monetary values in major currency units (for example, '5.45'). > This avoids floating-point rounding issues, especially when performing calculations. See also: [Send click and conversion events with Vue InstantSearch](/doc/guides/building-search-ui/events/vue) # insights Source: https://www.algolia.com/doc/api-reference/widgets/insights/react Middleware for setting the user token and sending click and conversion events to the Insights API. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} const middleware = createInsightsMiddleware({ // Optional parameters insightsClient?: InsightsClient | null, insightsInitParams?: object, onEvent?: function, }) ``` ## Import ```js JavaScript icon=code theme={"system"} import { createInsightsMiddleware } from "instantsearch.js/es/middlewares"; ``` ## About this middleware > [!NOTE] > > You can use [``](/doc/api-reference/widgets/instantsearch/react#param-insights) > instead of setting up the Insights middleware yourself. The `createInsightsMiddleware` creates an insights middleware to help you achieve the following: 1. Set the `userToken` for insights purposes (Analytics, Personalization, etc.). 2. Automatically send events from built-in widgets. You can turn this off if needed. 3. Send events from your own custom widgets. ### Requirements * [`search-insights`](https://github.com/algolia/search-insights.js) v1.6.2 or later. See also: [Send click and conversion events with React InstantSearch](/doc/guides/building-search-ui/events/react) ## Examples ```jsx JavaScript icon=code theme={"system"} import { createInsightsMiddleware } from "instantsearch.js/es/middlewares"; import { useInstantSearch } from "react-instantsearch"; import { useLayoutEffect } from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; function InsightsMiddleware() { const { addMiddlewares } = useInstantSearch(); useLayoutEffect(() => { const middleware = createInsightsMiddleware({ insightsClient: window.aa, }); return addMiddlewares(middleware); }, [addMiddlewares]); return null; } const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( {/* ... */} ); } ``` ## Options **insightsClient** `InsightsClient | null` The [Insights client](/doc/libraries/search-insights?client=javascript) is used to send events. It synchronizes the [user token](/doc/guides/sending-events/concepts/usertoken#exclude-users-who-want-to-opt-out-of-analytics-recommend-and-personalization) between search, Recommend, and analytics calls. When the middleware is enabled, it adds `userToken` and `clickAnalytics` to Recommend requests and fills missing values in the widget's existing `fallbackParameters`. Values in the widget's `queryParameters` and `fallbackParameters` take precedence. To turn off `userToken` synchronization and automatic event sending, set this to `null`. ```jsx JavaScript icon=code theme={"system"} const middleware = createInsightsMiddleware({ insightsClient: aa, }); const userToken = // Get the user token (synchronously or asynchronously). // The `insights` middleware receives a notification // and attaches the `userToken` to search and Recommend calls onwards. aa("setUserToken", userToken); ``` **insightsInitParams** `object` Insights parameters to forward to the Insights client's [`init`](/doc/libraries/search-insights/init) method. With `search-insights >= v1.7.0 and < 2.0.0`, the Insights client accepts `useCookie` and `userToken` parameters in the [`init`](/doc/libraries/search-insights/init) method. You can pass `useCookie: false` to prevent the usage of cookies to store an anonymous `userToken`. You can also pass a custom `userToken` while creating `insights` middleware, if you have one. With `search-insights >= 2.0.0`, the default value of `useCookie` is `false`. > [!NOTE] > > You can set an authenticated user token and a user token in `insightsInitParams`. > Both tokens are passed to the Insights API for sending events. > If both are set, InstantSearch uses the authenticated user token for search queries. ```jsx Cookie theme={"system"} createInsightsMiddleware({ insightsInitParams: { useCookie: true, }, }); ``` ```jsx Tokens theme={"system"} createInsightsMiddleware({ insightsInitParams: { userToken: "user-token", authenticatedUserToken: "auth-user-token", }, }); ``` **onEvent** `(event: InsightsEvent, aa: null | InsightsClient) =\> void` (default: undefined) By default, the middleware sends events to Algolia using the provided `insightsClient`. You can also control events and send them yourself by implementing an `onEvent` method for the middleware to call instead. This method lets you access data and filter or modify the payload. You can also use it to send events to third-party trackers. If you want to use `onEvent` to send events to third-party trackers, but don't want to send them to Algolia, you can set `insightsClient` to `null`, and you don't need the `search-insights` library in your application. **event properties** **event.insightsMethod** `string` The Insights method, such as, `'viewedObjectIDs'`, `'clickedObjectIDsAfterSearch'`. For more information, see the [Insights API reference](/doc/libraries/search-insights). **event.payload** `[key: string]: any` Event payload. **event.widgetType** `string` Widget type given by connectors, such as `'ais.refinementList` or `'ais.hits`. **event.eventType** `string` Event type, such as `'view'`, `'click'`, `'conversion'`, or anything else if you customized it. ```jsx JavaScript icon=code theme={"system"} createInsightsMiddleware({ insightsClient: window.aa, onEvent: (event, aa) => { const { insightsMethod, payload, widgetType, eventType } = event; // Send the event to Algolia if (insightsMethod) { aa(insightsMethod, payload); } // Send the event to a third-party tracker if (widgetType === "ais.hits" && eventType === "click") { thirdPartyTracker.send("Product Clicked", payload); } }, }); ``` ## Custom events Many InstantSearch connectors expose the `sendEvent` method, which you can use for sending custom events from your custom widgets. Here's a list of connectors that expose `sendEvent` and they're exposed at the default slot of corresponding React InstantSearch components. * [`useHierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/react#hook) * [`useHits`](/doc/api-reference/widgets/hits/react#hook) * [`useInfiniteHits`](/doc/api-reference/widgets/infinite-hits/react#hook) * [`useMenu`](/doc/api-reference/widgets/menu/react#hook) * [`useNumericMenu`](/doc/api-reference/widgets/numeric-menu/react) \* * [`useRange`](/doc/api-reference/widgets/range-input/react#hook) \* * [`useRefinementList`](/doc/api-reference/widgets/refinement-list/react#hook) * [`useToggleRefinement`](/doc/api-reference/widgets/toggle-refinement/react#hook) > [!NOTE] > > \* The `sendEvent` method provided to `useNumericMenu` and `useRange` can't be used to send [clicked filter](/doc/libraries/sdk/v1/methods/clicked-filters) events. ```jsx JavaScript icon=code theme={"system"} function Hits() { const { items, sendEvent } = useHits(); return (
      {items.map((item) => (
    1. {item.name}

    2. ))}
    ); } ``` # insights Source: https://www.algolia.com/doc/api-reference/widgets/insights/vue Middleware for setting the user token and sending click and conversion events to the Insights API. ```js Signature theme={"system"} import { createInsightsMiddleware } from "instantsearch.js/es/middlewares"; createInsightsMiddleware({ insightsClient: null | InsightsClient, insightsInitParams?: object, onEvent?: (event: InsightsEvent, aa: null | InsightsClient) => void, }); ``` ## About this widget > [!NOTE] > > Starting from v4.9.0, you can use the [`insights`](/doc/api-reference/widgets/instantsearch/vue#param-insights) > option instead of setting up the Insights middleware yourself. The `createInsightsMiddleware` creates an insights middleware to help you achieve the following: 1. Set the `userToken` for insights purposes (Click Analytics, Personalization, etc.) 2. Automatically send events from built-in widgets. You can turn this off if needed 3. Send events from your own custom widgets ### Requirements * [`search-insights`](https://github.com/algolia/search-insights.js) v1.6.2 or later. * [Vue InstantSearch](https://github.com/algolia/instantsearch/tree/master/packages/vue-instantsearch) v3.7.0 or later. See also: [Send click and conversion events with Vue InstantSearch](/doc/guides/building-search-ui/events/vue) ## Examples ```js JavaScript icon=code theme={"system"} import { createInsightsMiddleware } from "instantsearch.js/es/middlewares"; createInsightsMiddleware({ insightsClient, insightsInitParams, onEvent, }); ``` ## Options **insightsClient** `InsightsClient | null` The [Insights client](/doc/libraries/search-insights?client=javascript) is used to send events. It synchronizes the [user token](/doc/guides/sending-events/concepts/usertoken#exclude-users-who-want-to-opt-out-of-analytics-recommend-and-personalization) between search, Recommend, and analytics calls. When the middleware is enabled, it adds `userToken` and `clickAnalytics` to Recommend requests and fills missing values in the widget's existing `fallbackParameters`. Values in the widget's `queryParameters` and `fallbackParameters` take precedence. To turn off `userToken` synchronization and automatic event sending, set this to `null`. ```vue UMD theme={"system"} ``` ```vue ESM theme={"system"} ``` **insightsInitParams** `object` Insights parameters to forward to the Insights client's [`init`](/doc/libraries/search-insights/init) method. With `search-insights >= v1.7.0 and < 2.0.0`, the Insights client accepts `useCookie` and `userToken` parameters in the [`init`](/doc/libraries/search-insights/init) method. You can pass `useCookie: false` to prevent the usage of cookies to store an anonymous `userToken`. You can also pass a custom `userToken` while creating `insights` middleware, if you have one. With `search-insights >= 2.0.0`, the default value of `useCookie` is `false`. > [!NOTE] > > You can set an authenticated user token and a user token in `insightsInitParams`. > Both tokens are passed to the Insights API for sending events. > If both are set, InstantSearch uses the authenticated user token for search queries. ```js Cookie theme={"system"} createInsightsMiddleware({ insightsInitParams: { useCookie: true, }, }); ``` ```js Tokens theme={"system"} createInsightsMiddleware({ insightsInitParams: { userToken: "user-token", authenticatedUserToken: "auth-user-token", }, }); ``` **onEvent** `(event: InsightsEvent, aa: null | InsightsClient) =\> void` (default: undefined) By default, the middleware sends events to Algolia using the provided `insightsClient`. You can also control events and send them yourself by implementing an `onEvent` method for the middleware to call instead. This method lets you access data and filter or modify the payload. You can also use it to send events to third-party trackers. If you want to use `onEvent` to send events to third-party trackers, but don't want to send them to Algolia, you can set `insightsClient` to `null`, and you don't need the `search-insights` library in your application. **event properties** **event.insightsMethod** `string` The Insights method, such as, `'viewedObjectIDs'`, `'clickedObjectIDsAfterSearch'`. For more information, see the [Insights API reference](/doc/libraries/search-insights). **event.payload** `[key: string]: any` Event payload. **event.widgetType** `string` Widget type given by connectors, such as `'ais.refinementList` or `'ais.hits`. **event.eventType** `string` Event type, such as `'view'`, `'click'`, `'conversion'`, or anything else if you customized it. ```js JavaScript icon=code theme={"system"} createInsightsMiddleware({ insightsClient: window.aa, onEvent: (event, aa) => { const { insightsMethod, payload, widgetType, eventType } = event; // Send the event to Algolia if (insightsMethod) { aa(insightsMethod, payload); } // Send the event to a third-party tracker if (widgetType === "ais.hits" && eventType === "click") { thirdPartyTracker.send("Product Clicked", payload); } }, }); ``` ## Custom events **Connectors** Many of the InstantSearch connectors expose the `sendEvent` method. If you use these connectors to create custom widgets, you can use the method to send custom events. Here's a list of connectors that expose `sendEvent` and they're exposed at the default slot of corresponding Vue InstantSearch components. * `connectAutocomplete`: [`ais-autocomplete`](/doc/api-reference/widgets/autocomplete/vue#param-default) * `connectHierarchicalMenu`: [`ais-hierarchical-menu`](/doc/api-reference/widgets/hierarchical-menu/vue#param-default) * `connectHits`: [`ais-hits`](/doc/api-reference/widgets/hits/vue#param-default) * `connectInfiniteHits`: [`ais-infinite-hits`](/doc/api-reference/widgets/infinite-hits/vue#param-default) * `connectMenu`: [`ais-menu`](/doc/api-reference/widgets/menu/vue#param-default) and [`ais-menu-select`](/doc/api-reference/widgets/menu-select/vue#param-default) * `connectNumericMenu`: [`ais-numeric-menu`](/doc/api-reference/widgets/numeric-menu/vue#param-default) \* * `connectRange`: [`ais-range-input`](/doc/api-reference/widgets/range-input/vue#param-default) \* * `connectRatingMenu`: [`ais-rating-menu`](/doc/api-reference/widgets/rating-menu/vue#param-default) \* * `connectRefinementList`: [`ais-refinement-list`](/doc/api-reference/widgets/refinement-list/vue#param-default) * `connectToggleRefinement`: [`ais-toggle-refinement`](/doc/api-reference/widgets/toggle-refinement/vue#param-default) > [!NOTE] > > The `sendEvent` method provided to `connectNumericMenu`, `connectRange`, and `connectRatingMenu` can't be used to send [clicked filter](/doc/libraries/sdk/v1/methods/clicked-filters) events. ```vue-html Vue theme={"system"} ``` # createInstantSearchRouterNext Source: https://www.algolia.com/doc/api-reference/widgets/instantsearch-next-router/react Creates a Next.js-compatible router for InstantSearch. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```ts Signature theme={"system"} const router = createInstantSearchRouterNext({ singletonRouter: NextSingletonRouter, serverUrl?: string, beforeStart?: function, beforeDispose?: function, beforePopState?: function, routerOptions?: HistoryRouterOptions, }) ``` ## Import ```js JavaScript icon=code theme={"system"} import { createInstantSearchRouterNext } from "react-instantsearch-router-nextjs"; ``` ## About this function `createInstantSearchNextRouter` is a function that creates a Next.js-compatible InstantSearch router to pass to [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) [`routing`](/doc/api-reference/widgets/instantsearch/react#param-routing) prop. It fixes some issues with the default router, such as the page not updating when using the browser's back and forward buttons, or when navigating from a Next.js `Link` containing an InstantSearch query. It's based on [`history`](/doc/api-reference/widgets/history-router/react) so you can easily migrate from it by passing the same options to `routerOptions`. ### Requirements This function is available from the companion [`react-instantsearch-router-nextjs`](https://github.com/algolia/instantsearch/tree/master/packages/react-instantsearch-router-nextjs) package. > [!WARNING] > > You can't use this function with [`getStaticProps`](https://nextjs.org/docs/pages/api-reference/functions/get-static-props). > Use `getServerSideProps` or client-side rendering instead. ## Examples ```jsx With SSR theme={"system"} import { InstantSearch, InstantSearchSSRProvider, getServerState, } from "react-instantsearch"; import { renderToString } from "react-dom/server"; import { createInstantSearchRouterNext } from "react-instantsearch-router-nextjs"; import singletonRouter from "next/router"; export default function SearchPage({ serverState, serverUrl }) { return ( {/* Widgets */} ); } export async function getServerSideProps({ req }) { const protocol = req.headers.referer?.split("://")[0] || "https"; const serverUrl = `${protocol}://${req.headers.host}${req.url}`; const serverState = await getServerState( , { renderToString, }, ); return { props: { serverState, serverUrl, }, }; } ``` ```jsx Without SSR theme={"system"} import { InstantSearch } from "react-instantsearch"; import { createInstantSearchRouterNext } from "react-instantsearch-router-nextjs"; import singletonRouter from "next/router"; export default function SearchPage() { return ( {/* Widgets */} ); } ``` ## Parameters **singletonRouter** `Next.SingletonRouter` *required* The Next.js singleton router instance to use. It's the default export of `next/router`. ```js JavaScript icon=code theme={"system"} import singletonRouter from "next/router"; const router = createInstantSearchRouterNext({ singletonRouter, }); ``` **serverUrl** `string` The URL of the page on the server. It's used by the router to determine the initial state of the search. Required only when using SSR. ```js JavaScript icon=code theme={"system"} export async function getServerSideProps({ req }) { const protocol = req.headers.referer?.split('://')[0] || 'https'; const serverUrl = `${protocol}://${req.headers.host}${req.url}`; return { props: { serverUrl, }, }; } export default function SearchPage({ serverUrl }) { const router = createInstantSearchRouterNext({ // ... serverUrl, }); return ( // InstantSearch components ); } ``` **beforeStart** `(onUpdate: () =\> void) =\> void` A function called before the router starts. You can use it to inform InstantSearch to update on router events by calling `onUpdate`. It's here for troubleshooting purposes or if you're using a custom router with Next.js. ```js JavaScript icon=code theme={"system"} import singletonRouter from "next/router"; const router = createInstantSearchRouterNext({ // ... beforeStart(onUpdate) { singletonRouter.events.on("routeChangeComplete", onUpdate); }, }); ``` **beforeDispose** `() =\> void` A function called before the router gets disposed of. You can use it to detach event handlers you may have set up in `beforeStart`. It's here for troubleshooting purposes or if you're using a custom router with Next.js. ```js JavaScript icon=code theme={"system"} import singletonRouter from "next/router"; let eventHandler; const router = createInstantSearchRouterNext({ // ... beforeStart(onUpdate) { eventHandler = onUpdate; singletonRouter.events.on("routeChangeComplete", eventHandler); }, beforeDispose() { singletonRouter.events.off("routeChangeComplete", eventHandler); }, }); ``` **beforePopState** `(options: object) =\> boolean` A function used by the Next.js router to know whether it should trigger SSR when using back/forward buttons. You can use it to override the default one by writing your own logic. The `ownBeforePopState` is the pre-existing handler that you may have set yourself, and the `libraryBeforePopState` is the default one from the library. It's here for troubleshooting purposes. Options contains the following properties: * `state: NextHistoryState`. The next state to apply. * `ownBeforeState: BeforePopStateCallback`. Either the default handler which just returns `true`, or the one you set yourself. * `libraryBeforeState: BeforePopStateCallback`. The default handler of the library which returns `true` if going on a different page, or `false` if staying on the same page. ```js JavaScript icon=code theme={"system"} const router = createInstantSearchRouterNext({ // ... beforePopState({ state, ownBeforePopState, libraryBeforePopState }) { // You can compose your own logic, ignore the library one, it's up to you when you want to trigger SSR. return ownBeforePopState(state) && libraryBeforePopState(state); }, }); ``` **routerOptions** `HistoryRouterOptions` You can pass options to the underlying history router. These will override the ones created by `createInstantSearchRouterNext`. You can check [`history`](/doc/api-reference/widgets/history-router/react) for more information about the options. ```js JavaScript icon=code theme={"system"} const router = createInstantSearchRouterNext({ // ... routerOptions: { createURL: // ... parseURL: ... }, }); ``` ## Returns HistoryRouter **router** `HistoryRouter` The router to pass to [routing](/doc/api-reference/widgets/instantsearch/react#param-routing). ```jsx JavaScript icon=code theme={"system"} export default function SearchPage() { const routerNext = { router: createInstantSearchRouterNext({ singletonRouter, }), }; return ( {/*Widgets*/} ); } ``` # InstantSearchNext Source: https://www.algolia.com/doc/api-reference/widgets/instantsearch-next/react Root component for InstantSearch on Next.js app router applications. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} {children} ``` ## Import ```js JavaScript icon=code theme={"system"} import { InstantSearchNext } from "react-instantsearch-nextjs"; ``` ## About this component This component replaces the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component as root in Next.js applications using the app router. It supports server-side rendering and routing. Only props that are different from the `InstantSearch` component are shown on this page. To learn more, see [Server-side rendering](/doc/guides/building-search-ui/going-further/server-side-rendering/react) ## Examples ```jsx Search.js theme={"system"} "use client"; import { InstantSearchNext } from "react-instantsearch-nextjs"; export function Search() { return ( {/* Widgets */} ); } ``` For a complete implementation, see the [Next.js app router example](https://github.com/algolia/instantsearch/blob/master/examples/react/next-app-router/app/Search.tsx). ## Props **routing** `boolean | RoutingProps` (default: false) Enables routing for the search state. If true, it uses the default routing configuration. Pass an object for custom routing options. ```jsx Boolean theme={"system"} {/* Widgets */} ; ``` ```jsx Object theme={"system"} {/* Widgets */} ; ``` **instance** `InstantSearch` Lets you pass an existing instance InstantSearch instance to the component. This is useful for advanced use cases such as dynamic category pages, so that navigation doesn't cause InstantSearch to re-initialize. To create an instance, use the `createInstantSearchNextInstance` function from the `react-instantsearch-nextjs` package. ```jsx JavaScript icon=code theme={"system"} import { createInstantSearchNextInstance, InstantSearchNext, } from "react-instantsearch-nextjs"; const instance = createInstantSearchNextInstance(); function Search() { return ( {/* Widgets */} ); } ``` # ais-instant-search Source: https://www.algolia.com/doc/api-reference/widgets/instantsearch/vue Root component for Vue InstantSearch apps. ```vue Signature theme={"system"} ``` ## Import You can import this widget in two ways. > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisInstantSearch } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisInstantSearch, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=vue) > > Preview this widget and its behavior. ## About this widget `ais-instant-search` is the root wrapper component for all widgets. ### Middleware Vue InstantSearch provides middleware to help you connect to other systems: * **Insights.** Use the [`insights`](/doc/api-reference/widgets/insights/vue) middleware to [send click and conversion events](/doc/guides/building-search-ui/events/vue). * **Generic.** With the [`middleware`](/doc/api-reference/widgets/middleware/vue) API, you can inject logic into Vue InstantSearch. For example, to [send events to Google Analytics](/doc/guides/building-search-ui/going-further/integrate-google-analytics/vue). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **index-name** `string` *required* The main 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 which to search. ```vue Vue icon=code theme={"system"} ``` **search-client** `object` *required* Provides a search client to `ais-instant-search`. Read [the custom backend guidance](/doc/guides/building-search-ui/going-further/backend-search/vue) on implementing a custom search client. The client uses a cache to avoid unnecessary search operations, so use a stable reference to the same search client instance rather than creating a new one on each render. Avoid inlining the function call to `algoliasearch` as the prop value in your Vue template. ```vue Vue icon=code theme={"system"} ``` **search-function** `function` This option lets you take control of search behavior. For example, to avoid doing searches at page load. When this option is set, `search.helper` won't emit events. Instead, use [`onStateChange`](#param-on-state-change) or widgets to handle search behavior. A hook is called for every search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.) (with [Algolia's search helper](/doc/guides/building-search-ui/upgrade-guides/js#algolia-search-helper) as a parameter). Begin searching by calling [`helper.search()`](https://instantsearchjs.netlify.app/algoliasearch-helper-js/reference.html#AlgoliaSearchHelper#search). When modifying the state of the helper within `search-function`, the helper resets the page to 0. To keep the current page, store the page information, modify the state, and reapply pagination. ```vue Function theme={"system"} ``` ```vue Prevent page reset theme={"system"} ``` **on-state-change** `function` Triggers when the state changes. This can be useful when performing custom logic on a state change. When using `on-state-change`, the instance is under your control. **You're responsible for updating the UI state (with `setUiState`).** ```vue Vue icon=code theme={"system"} ``` **stalled-search-delay** `number` (default: 200) A time period (in ms) after which the search is considered to have stalled. Read the [slow network guide](/doc/guides/building-search-ui/going-further/improve-performance/vue) for more information. ```vue Vue icon=code theme={"system"} ``` **routing** `boolean | object` (default: false) The router configuration used to save the UI state into the URL or any client-side persistence. **routing attributes** **routing.router** `object` This object stores the UI state. By default, it uses an instance of the [`history`](/doc/api-reference/widgets/history-router/vue) router with its default parameters. **router attributes** **router.onUpdate** `function` Adds an event listener that makes InstantSearch aware of external changes to the storage medium (such as the URL). Typically you'll set up a listener for `popstate`, which triggers a callback with the current `routeState`. **router.read** `function` Reads the routing storage and gets `routeState` object. It doesn't take any parameters but returns an object. **router.write** `function` Pushes `routeState` into routing storage. **router.createURL** `function` Transforms `routeState` into a URL. It receives an object and returns a string (which may be empty). **router.dispose** `function` Cleans up all event listeners. **routing.stateMapping** `object` Transforms the [`uiState`](/doc/api-reference/widgets/ui-state/vue) into the object saved by the router. The default value is provided by [`simple`](/doc/api-reference/widgets/simple-state-mapping/vue). **stateMapping attributes** **stateMapping.stateToRoute** `function` Transforms a `ui-state` representation into `routeState`. It receives an object that contains the UI state of all the widgets n the page. It can return any object that's readable by `routeToState`. **stateMapping.routeToState** `function` Transforms `routeState` into a `ui-state` representation. It receives an object that contains the UI state stored by the router. It can return any object that's readable by `stateToRoute`. For more information, see [Sync your URLs](/doc/guides/building-search-ui/going-further/routing-urls/vue). You can't use [`initialUiState`](#param-initial-ui-state) with routing as the two options override each other. * Use `initialUiState` for simple and static use cases. * Use `routing` for anything complex or dynamic. ```vue Vue icon=code theme={"system"} ``` **initial-ui-state** `object` Adds a [`uiState`](/doc/api-reference/widgets/ui-state/vue) to your `ais-instant-search` instance, which provides an initial state to your widgets. Replace `YourIndexName` with the name of your Algolia index. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-InstantSearch`: the root of the widget. ```vue Vue icon=code theme={"system"} ``` **insights** `boolean | object` (default: false) Enables the Insights middleware and loads the [`search-insights`](https://github.com/algolia/search-insights.js) library (if not already loaded). The Insights middleware sends view and click events automatically, and lets you set up your own [click and conversion events](/doc/guides/building-search-ui/events/vue). To use this option with an object, refer to the [Insights middleware options](/doc/api-reference/widgets/insights/vue#options). ```vue boolean theme={"system"} ``` ```vue object theme={"system"} ``` **insights-client** `function` *deprecated* Use [`insights`](#param-insights) instead. This function is exposed by the `search-insights` library (usually `window.aa`) and is required if you want to [send click and conversion events](/doc/guides/building-search-ui/events/vue) with the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. ```vue Vue icon=code theme={"system"} ``` **future** `object` Test new InstantSearch features without affecting others. **future attributes** **future.preserveSharedStateOnUnmount** `boolean` (default: false) Changes the way `dispose` is used in InstantSearch lifecycle. If `false` (the default), each widget unmounting will also remove its state, even if multiple widgets read that UI state. If `true`, each widget unmounting will only remove its state if it's the last of its type. This lets you dynamically add and remove widgets without losing their state. ```vue Vue icon=code theme={"system"} ``` **future.persistHierarchicalRootCount** `boolean` (default: false) Whether to display a constant facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) value count at the root of a hierarchical menu with active refinements. * If `false` (default), the facet value count at the root level shows the facet value count of the refined (child) facet's parent. * If `true`, the facet value count at the root level shows the sum of the facet value counts of all its children, with or without refined children. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # InstantSearch iOS Source: https://www.algolia.com/doc/api-reference/widgets/ios Overview of InstantSearch iOS widgets. To get started with InstantSearch iOS, see [What is InstantSearch?](/doc/guides/building-search-ui/what-is-instantsearch/ios) ## Basics | | | | - | - | | [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) | The component handling search requests. | | [`SearchBox`](/doc/api-reference/widgets/search-box/ios) | Component that performs a text-based query. | | [`Query`](/doc/api-reference/widgets/configure/ios) | A component to provide any search parameters to Algolia. | | [`RelatedItems`](/doc/api-reference/widgets/configure-related-items/ios) | Experimental component that computes search parameters to fetch related items. | | [`Filter State`](/doc/api-reference/widgets/filter-state/ios) | `FilterState` provides a friendly interface to manage filters applied to your search. | | [`Loading`](/doc/api-reference/widgets/loading/ios) | Components that show a loading indicator during pending requests. | ## Results | | | | - | - | | [`Hits`](/doc/api-reference/widgets/hits/ios) | Component that manages and displays a list of search results. | | [`Highlighting`](/doc/api-reference/widgets/highlight/ios) | Highlighting visually emphasizes matching terms in your search results to help them stand out. | | [`Multi Hits`](/doc/api-reference/widgets/multi-hits/ios) | InstantSearch iOS doesn't offer a Multi Hits widget. | ## Refinements | | | | - | - | | [`Refinement List`](/doc/api-reference/widgets/refinement-list/ios) | Components that perform refinements on a facet. | | [`Filter List (Facet)`](/doc/api-reference/widgets/filter-list-facet/ios) | Component that displays a list of arbitrary facet filters. | | [`Filter List (Numeric)`](/doc/api-reference/widgets/filter-list-numeric/ios) | Components that display a list of arbitrary numeric filters. | | [`Filter List (Tag)`](/doc/api-reference/widgets/filter-list-tag/ios) | Components that display a list of arbitrary tag filters. | | [`Dynamic Facet List`](/doc/api-reference/widgets/dynamic-widgets/ios) | Component presenting the ordered facets, facet values and handling the user interaction to refine search results. The order of facets and their values is defined by the facet merchandising settings of the index. | | [`Hierarchical Menu`](/doc/api-reference/widgets/hierarchical-menu/ios) | Component that displays a hierarchical menu. | | [`Filter Numeric Range`](/doc/api-reference/widgets/range-slider/ios) | Components that apply a numeric range filter. | | [`Filter Numeric Comparison`](/doc/api-reference/widgets/filter-comparison/ios) | Components that apply a numeric comparison filter. | | [`Filter Map`](/doc/api-reference/widgets/filter-map/ios) | Components that apply a single filter from a map. | | [`Current Filters`](/doc/api-reference/widgets/current-refinements/ios) | Components that display the current filters and let users remove them. | | [`Filter Toggle`](/doc/api-reference/widgets/toggle-refinement/ios) | Components that toggle an arbitrary filter on or off. | | [`Clear Filters`](/doc/api-reference/widgets/clear-refinements/ios) | Components that clear the current filters. | ## Metadata | | | | - | - | | [`Stats`](/doc/api-reference/widgets/stats/ios) | Components that display metadata about the current search and its results. | | [`QueryRuleCustomData`](/doc/api-reference/widgets/query-rule-custom-data/ios) | Component that displays custom data from Rules. | ## Sorting | | | | - | - | | [`Sort By`](/doc/api-reference/widgets/sort-by/ios) | Components that display a list of indices, allowing a user to change the way hits are sorted using replica indices. | | [`RelevantSort`](/doc/api-reference/widgets/relevant-sort/ios) | A widget that displays the current search mode when searching in a virtual replica index, and allows users to switch between relevant and regular sorting, which is more exhaustive and can return less relevant results. | # LookingSimilar Source: https://www.algolia.com/doc/api-reference/widgets/looking-similar/react Shows visually similar recommendations based on a provided item. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). > [!NOTE] > > If you're using the deprecated [`recommend-react`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-react) UI library, > see [`LookingSimilar`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-react/LookingSimilar). ```tsx Signature theme={"system"} } /> ``` ## Import ```js JavaScript icon=code theme={"system"} import { LookingSimilar } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=lookingSimilar) > > Preview this widget and its behavior. ## About this widget Use the `LookingSimilar` widget to display a list of similar-looking products based on the image URLs you referenced when [setting up this Recommend model](/doc/guides/algolia-recommend/how-to/lookingSimilar). > [!TIP] > > You can also create your own UI with [`useLookingSimilar`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, LookingSimilar } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Item({ item }) { return JSON.stringify(item); } function App() { return ( ); } ``` ### With a carousel layout Display the `LookingSimilar` widget as a [scrollable carousel](/doc/api-reference/widgets/looking-similar/react#param-layout-component) ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, LookingSimilar, Carousel } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Item({ item }) { return JSON.stringify(item); } function App() { return ( ); } ``` ## Props **objectIDs** `string[]` *required* The list of object IDs for which to get recommendations. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```jsx JavaScript icon=code theme={"system"} ; ``` **itemComponent** `(props: \{ item: THit \}) =\> JSX.Element` A component that renders each recommendation from the results. It receives an `item` prop. When not provided, the widget displays the recommendation as a JSON string. ```jsx JavaScript icon=code theme={"system"} item.objectID} />; ``` **headerComponent** `(props: \{ classNames: string[], items: THit[], translations: object \}) =\> JSX.Element` A component that renders a title header. It receives `classNames`, `items` and `translations` props. When not provided, the widget displays a default header. ```jsx JavaScript icon=code theme={"system"} (

    Recommendations ({items.length})

    )} />; ``` **emptyComponent** `() =\> JSX.Element` A component that renders when there are no recommendations. When not provided, the widget displays a default message. ```jsx JavaScript icon=code theme={"system"}

    No recommendations.

    } />; ``` **layoutComponent** `(props: RecommendLayoutProps) =\> JSX.Element` The layout component to use to wrap all items. React InstantSearch provides a built-in `Carousel` layout component with the following props: `classNames`: * `root`. The Carousel's root element. * `list`. The list of recommendations. * `item`. The list item. * `navigation`. The navigation controls. * `navigationPrevious`. The "Previous" navigation control. * `navigationNext`. The "Next" navigation control. `translations`: * `nextButtonLabel`. The label for the next button. * `nextButtonTitle`. The title for the next button. * `previousButtonLabel`. The label for the previous button. * `previousButtonTitle`. The title for the previous button. * `listLabel`. The label for the list of recommendation. * `previousIconComponent`. The component to render as the previous button. * `nextIconComponent`. The component to render as the next button. ```jsx Carousel theme={"system"} import { Carousel } from "react-instantsearch"; (

    Previous

    } nextIconComponent={() =>

    Next

    } /> )} />; ``` ```jsx Custom theme={"system"} (
      {props.items.map((item) => (
    • {item.objectID}
    • ))}
    )} />; ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```jsx JavaScript icon=code theme={"system"} ; ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as an additional 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.) set when there aren't enough recommendations. ```jsx JavaScript icon=code theme={"system"} ; ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: THit[], metadata: \{ results: SearchResults \}) =\> THit[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { LookingSimilarProps } from "react-instantsearch"; const transformItems: LookingSimilarProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The widget's root element. * `emptyRoot`. The root element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of recommendations. * `item`. The list item. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A mapping of keys to translation values. * `title`. The title of the recommendation section. * `sliderLabel`. The label of the horizontal slider. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the widget's root element. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `LookingSimilar` widget with `useLookingSimilar`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useLookingSimilar` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useLookingSimilar } from "react-instantsearch"; function CustomLookingSimilar(props) { const { items } = useLookingSimilar(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **objectIDs** `string[]` *required* The list of object IDs for which to get recommendations. If you specify multiple `objectIDs`, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). > For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```jsx JavaScript icon=code theme={"system"} const { items } = useLookingSimilar({ // ... objectIDs: ["5723537"], }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useLookingSimilar({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useLookingSimilar({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```jsx JavaScript icon=code theme={"system"} const { items } = useLookingSimilar({ // ... queryParameters: { filters: "category:Book", }, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as additional filters when there aren't enough recommendations. ```jsx JavaScript icon=code theme={"system"} const { items } = useLookingSimilar({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} const { items } = useLookingSimilar({ // ... escapeHTML: false, }); ``` **transformItems** `(items: THit[], metadata: \{ results: SearchResults \}) =\> THit[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function LookingSimilar() { const lookingSimilarApi = useLookingSimilar({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseLookingSimilarProps } from "react-instantsearch"; const transformItems: UseLookingSimilarProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function LookingSimilar() { const lookingSimilarApi = useLookingSimilar({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `THit[]` The matched recommendations returned from Algolia. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useLookingSimilar } from "react-instantsearch"; function CustomLookingSimilar(props) { const { items } = useLookingSimilar(props); return (
      {items.map((item) => (
    1. {JSON.stringify(item).slice(0, 100)}
    2. ))}
    ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useLookingSimilar, UseLookingSimilarProps } from "react-instantsearch"; function CustomLookingSimilar(props: UseLookingSimilarProps) { const { items } = useLookingSimilar(props); return (
      {items.map((item) => (
    1. {JSON.stringify(item).slice(0, 100)}
    2. ))}
    ); } ``` # ais-looking-similar Source: https://www.algolia.com/doc/api-reference/widgets/looking-similar/vue Shows visually similar recommendations based on a provided item. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisLookingSimilar } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisLookingSimilar, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget Use the `ais-looking-similar` widget to display a list of similar-looking products based on the image URLs you referenced when [setting up this Recommend model](/doc/guides/algolia-recommend/how-to/lookingSimilar). ## Examples ```vue Vue icon=code theme={"system"} ``` ### With carousel layout To display the recommendations as a scrollable carousel, set the [`layout`](#param-layout) prop to `carousel`. ```vue Vue icon=code theme={"system"} ``` ## Props **objectIDs** `string[]` *required* The list of object IDs to get recommendations for. If you specify several object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` counts as one Recommend request, > even when you pass multiple IDs in one array. > For example, `["A", "B", "C"]` counts as three requests and > can affect your [quota](https://www.algolia.com/pricing). ```vue Vue icon=code theme={"system"} ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If you omit `limit` or set it to 0, the widget returns all matching recommendations. ```vue Vue icon=code theme={"system"} ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). The widget only returns recommendations with a greater score. ```vue Vue icon=code theme={"system"} ``` **query-parameters** `Omit\` [Search parameters](/doc/api-reference/search-api-parameters) to apply to the recommendation request. ```vue Vue icon=code theme={"system"} ``` **fallback-parameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as an extra 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.) set when there aren't enough recommendations. ```vue Vue icon=code theme={"system"} ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function to transform or reorder recommendations before the widget displays them. It returns an array of items with the same structure. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **layout** `'list' | 'carousel'` (default: 'list') How to display the recommendations: * `list`. A list of items. * `carousel`. A horizontally scrollable carousel with "Previous" and "Next" navigation buttons. The [`item`](#param-item) slot applies to both layouts. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-LookingSimilar`. The widget's root element. * `ais-LookingSimilar--empty`. The root element without results. * `ais-LookingSimilar-title`. The widget's title element. * `ais-LookingSimilar-container`. The container of the list element. * `ais-LookingSimilar-list`. The list of recommendations. * `ais-LookingSimilar-item`. The list item. With the `carousel` layout, the `ais-LookingSimilar-list` and `ais-LookingSimilar-item` classes apply to the carousel's list and items. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. The widget only uses this slot if you don't provide any other slot. **Scope** * `items: object[]`. The recommendations. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of each recommendation. If you don't provide it, the widget displays the recommendation as a JSON string. **Scope** * `item: object`. A single recommendation with all its attributes. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. ```vue Vue icon=code theme={"system"} ``` **header** The slot to override the DOM output of the title header. The widget doesn't render the header when there are no recommendations. If you don't provide it, the widget displays "Looking similar" as the title. **Scope** * `items: object[]`. The recommendations. * `translations: object`. The widget's translations, such as `title`. * `classNames: object`. The widget's CSS classes, such as `title`. ```vue Vue icon=code theme={"system"} ``` **empty** The slot to override the DOM output when there are no recommendations. If you don't provide it, the widget displays "No results". ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Looking similar

    1. ...
    2. ...
    3. ...
    ``` # MenuSelect Source: https://www.algolia.com/doc/api-reference/widgets/menu-select/react Lets users refine search results using a drop-down menu. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ## About this widget The `MenuSelect` widget isn't part of React InstantSearch, but you can make a custom version with the [`useMenu`](/doc/api-reference/widgets/menu/react#hook) Hook. This widget allows a user to select a single value to refine inside a [`select`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) element. ## Examples ```jsx JavaScript theme={"system"} import { useMenu } from "react-instantsearch"; export function MenuSelect(props) { const { items, refine } = useMenu(props); const { value: selectedValue } = items.find((item) => item.isRefined) || { value: "", }; return ( ); } ``` ```tsx TypeScript theme={"system"} import { useMenu, UseMenuProps } from "react-instantsearch"; export function MenuSelect(props: UseMenuProps) { const { items, refine } = useMenu(props); const { value: selectedValue } = items.find((item) => item.isRefined) || { value: "", }; return ( ); } ``` # ais-menu-select Source: https://www.algolia.com/doc/api-reference/widgets/menu-select/vue Lets users refine search results using a drop-down menu. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisMenuSelect } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisMenuSelect > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=menuSelect) > > Preview this widget and its behavior. ## About this widget The `ais-menu-select` widget allows a user to select a single value to refine inside a [`select`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) element. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **attribute** `string` *required* The name of the attribute in the record. ```vue Vue icon=code theme={"system"} ``` **limit** `number` (default: 10) The maximum number of values to display. ```vue Vue icon=code theme={"system"} ``` **sort-by** `string[] | function` (default: Uses facetOrdering if set, \['name:asc', 'count:desc']) How to sort refinements. Must be one or more of the following strings: * `"count:asc"` * `"count:desc"` * `"name:asc"` * `"name:desc"` * `"isRefined"` It's also possible to give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). If `facetOrdering` is set for this facet in `renderingContent`, and no value for `sortBy` is passed to this widget, `facetOrdering` is used, and the default order as a fallback. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `.ais-MenuSelect`. The root element of the widget * `.ais-MenuSelect--noRefinement`. The root element of the widget with no refinement * `.ais-MenuSelect-select`. The`select` element * `.ais-MenuSelect-option`. The `option` elements of the `select` ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The values applicable to this menu. * `canRefine: boolean`. Can the refinement be applied? * `refine: (value: string) => void`. A function to select a refinement. * `createURL: (item: string) => string`. A function to return a link for this refinement. * `sendEvent: (eventType: 'click', facetValue: string) => void`. The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. Where each item is an `object` containing: * `value: string`. The value of the menu item. * `label: string`. Human-readable value of the menu item. * `count: number`. Number of results matched after a refinement is applied. * `isRefined: boolean`. Indicates if the refinement is applied. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the content of an `option` element. Make sure to use `template` as the root, because an `option` element doesn't accept any other child elements. **Scope** * `item: object`. The values applicable to the `option`. Where item is an `object` containing: * `value: string`. The value of the menu item. * `label: string`. Human-readable value of the menu item. * `count: number`. Number of results matched after a refinement is applied. * `isRefined: boolean`. Indicates if the refinement is applied. ```vue Vue icon=code theme={"system"} ``` **defaultOption** The slot to override the content of the `option` element which selects "all items". Make sure to use `template` as the root, because an `option` element doesn't accept any other child elements. **Scope** No scope is provided to this slot. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` > [!NOTE] > > If SEO is important for your search page, ensure that your custom HTML is optimized for search engines: > > * Use `` tags with `href` attributes to allow search engine bots to follow links. > * Use semantic HTML and include [structured data](https://developers.google.com/search/docs/appearance/structured-data) when relevant. > > For more guidance, see the [SEO checklist](/doc/guides/building-search-ui/resources/seo/js). # Menu Source: https://www.algolia.com/doc/api-reference/widgets/menu/react Shows a menu for refining search results based on a selected facet value. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { Menu } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=menu) > > Preview this widget and its behavior. ## About this widget `Menu` is a widget that displays a list of facets and lets users choose a single value. > [!NOTE] > > The `Menu` widget uses a hierarchical refinement internally, so it can't refine values that include the default separator (`>`). > To support these values, use the [`HierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/react) widget instead. ### Requirements Ensure that the attribute provided to the Hook is already declared as an [attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting). > [!TIP] > > You can also create your own UI with [`useMenu`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Menu } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **attribute** `string` *required* The name of the attribute in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```jsx JavaScript icon=code theme={"system"} ; ``` **limit** `number` (default: 10) How many facet values to retrieve. [`showMore`](#param-show-more) and [`showMoreLimit`](#param-show-more-limit) determine the number of facet values to display before clicking the Show more button. ```jsx JavaScript icon=code theme={"system"} ; ``` **showMore** `boolean` (default: false) Whether to display a button that expands the number of items. ```jsx JavaScript icon=code theme={"system"} ; ``` **showMoreLimit** `number` The maximum number of items to display if the widget is showing more than the [`limit`](#param-limit) items. ```jsx JavaScript icon=code theme={"system"} ; ``` **sortBy** `string[] | (a: FacetValue, b: FacetValue) =\> number` (default: \['isRefined', 'name:asc'], or `facetOrdering` if set) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` You can also use a sort function that behaves like the standard JavaScript [`compareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax). If you leave the default setting for this parameter, and you've set `facetOrdering` for this facet in `renderingContent`, facets are sorted using `facetOrdering` and use the default order as a fallback. ```jsx String theme={"system"} ; ``` ```jsx Function theme={"system"} const sortByName = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function Search() { return ( ); } ``` ```tsx Function (TypeScript) theme={"system"} import type { MenuProps } from "react-instantsearch"; const sortByName: MenuProps["sortBy"] = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function Search() { return ( ); } ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { MenuProps } from 'react-instantsearch'; const transformItems: MenuProps['transformItems'] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `noRefinementRoot`. The root element when there are no refinements. * `list`. The list element. * `item`. Each item element.. * `selectedItem`. The selected item element. * `link`. The link of each item. * `label`. The label of each item. * `count`. The count of each item. * `showMore`. The "Show more" button. * `disabledShowMore`. The "Show more" button when it's unavailable. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A mapping of keys to translation values. * `showMoreButtonText`. The text for the "Show more" button. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `Menu` widget with `useMenu`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useMenu` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useMenu } from "react-instantsearch"; function CustomMenu(props) { const { items, createURL, refine, canRefine, isShowingMore, toggleShowMore, canToggleShowMore, sendEvent, } = useMenu(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **attribute** `string` *required* The name of the attribute in the records. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```jsx JavaScript icon=code theme={"system"} const menuApi = useMenu({ attribute: "categories", }); ``` **limit** `number` (default: 10) How many facet values to retrieve. [`showMore`](#param-show-more) and [`showMoreLimit`](#param-show-more-limit) determine the number of facet values to display before clicking the Show more button. ```jsx JavaScript icon=code theme={"system"} const menuApi = useMenu({ // ... limit: 5, }); ``` **showMore** `boolean` (default: false) Whether to display a button that expands the number of items. ```jsx JavaScript icon=code theme={"system"} const menuApi = useMenu({ // ... showMore: true, }); ``` **showMoreLimit** `number` The maximum number of items to display if the widget is showing more than the [`limit`](#param-limit) items. ```jsx JavaScript icon=code theme={"system"} const menuApi = useMenu({ // ... showMoreLimit: 20, }); ``` **sortBy** `string[] | (a: FacetValue, b: FacetValue) =\> number` (default: \['isRefined', 'name:asc'], or `facetOrdering` if set) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` You can also use a sort function that behaves like the standard JavaScript [`compareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax). If you leave the default setting for this parameter, and you've set `facetOrdering` for this facet in `renderingContent`, facets are sorted using `facetOrdering` and use the default order as a fallback. ```jsx String theme={"system"} const menuApi = useMenu({ // ... sortBy: ["count:desc", "name:asc"], }); ``` ```jsx Function theme={"system"} const sortByName = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function Menu() { const menuApi = useMenu({ // ... sortBy, }); return <>{/* Your JSX */}; } ``` ```tsx Function (TypeScript) theme={"system"} import type { UseMenuProps } from "react-instantsearch"; const sortByName: UseMenuProps["sortBy"] = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function Menu() { const menuApi = useMenu({ // ... sortBy, }); return <>{/* Your JSX */}; } ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Menu() { const menuApi = useMenu({ // ... transformItems, }); return <>{/*Your JSX*/}; } ``` ```tsx TypeScript theme={"system"} import type { UseMenuProps } from 'react-instantsearch'; const transformItems: UseMenuProps['transformItems'] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Menu() { const menuApi = useMenu({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `MenuItem[]` The elements that can be refined for the current search results. ```ts theme={"system"} type MenuItem = { /** * The value of the menu item. */ value: string; /** * Human-readable value of the menu item. */ label: string; /** * Number of matched results after refinement is applied. */ count: number; /** * Indicates if the menu item is refined. */ isRefined: boolean; }; ``` **createURL** `(value: string) =\> string` Creates the next state URL of a selected refinement. **refine** `(value: string) =\> string` Applies the selected refinement. **canRefine** `boolean` Whether a refinement can be applied. **isShowingMore** `boolean` Whether the menu is displaying all the menu items. **toggleShowMore** `() =\> void` Toggles the number of values displayed between [`limit`](/doc/api-reference/widgets/menu/react#param-limit) and [`showMoreLimit`](/doc/api-reference/widgets/menu/react#param-show-more-limit). **canToggleShowMore** `boolean` Whether the Show more button can be activated, meaning there are enough additional items to display, or already displaying over the [`limit`](/doc/api-reference/widgets/menu/react#param-limit) items. **sendEvent** `(eventType: string, facetValue: string, eventName?: string) =\> void` Sends an event to the Insights middleware. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useMenu } from "react-instantsearch"; function CustomMenu(props) { const { items, refine, createURL, canToggleShowMore, toggleShowMore, isShowingMore, } = useMenu(props); return ( <> {props.showMore && ( )} ); } ``` ```tsx TypeScript theme={"system"} import React from 'react'; import { useMenu, UseMenuProps } from 'react-instantsearch'; function CustomMenu(props: UseMenuProps) { const { items, refine, createURL, canToggleShowMore, toggleShowMore, isShowingMore, } = useMenu(props); return ( <> {props.showMore && ( )} ); } ``` # ais-menu Source: https://www.algolia.com/doc/api-reference/widgets/menu/vue Shows a menu for refining search results based on a selected facet value. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisMenu } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisMenu > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=menu) > > Preview this widget and its behavior. ## About this widget The `ais-menu` widget displays a menu that lets users choose a single value for a specific attribute. > [!NOTE] > > The `ais-menu` widget uses a hierarchical refinement internally, so it can't refine values that include the default separator (`>`). > To support these values, use the [`ais-hierarchical-menu`](/doc/api-reference/widgets/hierarchical-menu/vue) widget instead. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **attribute** `string` *required* The name of the attribute in the record. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```vue Vue icon=code theme={"system"} ``` **limit** `number` (default: 10) How many facet values to retrieve. When you enable the [`show-more`](#param-show-more) feature, this is the number of facet values to display before clicking the "Show more" button. ```vue Vue icon=code theme={"system"} ``` **show-more** `boolean` (default: false) Whether to display a button that expands the number of items. ```vue Vue icon=code theme={"system"} ``` **show-more-limit** `number` (default: 20) Amount of items to show if showing more. Requires [`show-more`](#param-show-more) to be `true`. ```vue Vue icon=code theme={"system"} ``` **sort-by** `string[] | function` (default: Uses facetOrdering if set, \['name:asc', 'count:desc']) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` It's also possible to give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). If `facetOrdering` is set for this facet in `renderingContent`, and no value for `sortBy` is passed to this widget, `facetOrdering` is used, and the default order as a fallback. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `.ais-Menu`. The root element of the widget. * `.ais-Menu--noRefinement`. The root element of the widget with no refinement. * `.ais-Menu-list`. The list of all menu items. * `.ais-Menu-item`. The menu list item. * `.ais-Menu-item--selected`. The selected menu list item. * `.ais-Menu-link`. The clickable menu element. * `.ais-Menu-label`. The label of each item. * `.ais-Menu-count`. The count of values for each item. * `.ais-Menu-showMore`. The button used to display more categories. * `.ais-Menu-showMore--disabled`. The button used to display more categories, when it's unavailable. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The values applicable to this menu. * `canRefine: boolean`. Can the refinement be applied? * `canToggleShowMore: boolean`. Is [show-more](#param-show-more) possible? * `isShowingMore: boolean`. Is [show-more](#param-show-more) enabled? * `refine: (value: string) => void`. The function to select a refinement. * `createURL: (value: string) => string`. The function to return a link for this refinement. * `sendEvent: (eventType: 'click', facetValue: string) => void`. The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. * You can learn more about the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. Where each item is an `object` containing: * `value: string`. The value of the menu item. * `label: string`. The human-readable value of the menu item. * `count: number`. The number of results matched after a refinement is applied. * `isRefined: boolean`. Indicates if the refinement is applied. ```vue Vue icon=code theme={"system"} ``` **showMoreLabel** The slot to override the DOM output for the label of the "Show more" button. **Scope** * `isShowingMore: boolean`. Is [show-more](#param-show-more) enabled? ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` > [!NOTE] > > If SEO is important for your search page, ensure that your custom HTML is optimized for search engines: > > * Use `` tags with `href` attributes to allow search engine bots to follow links. > * Use semantic HTML and include [structured data](https://developers.google.com/search/docs/appearance/structured-data) when relevant. > > For more guidance, see the [SEO checklist](/doc/guides/building-search-ui/resources/seo/js). # middleware Source: https://www.algolia.com/doc/api-reference/widgets/middleware/react Adds custom logic to your React InstantSearch apps. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```js Signature theme={"system"} function middleware({ instantSearchInstance }) { return { onStateChange({ uiState }) {}, subscribe() {}, unsubscribe() {}, }; } ``` ## About this widget The `middleware` function returns an object with [`onStateChange`](#param-on-state-change), [`subscribe`](#param-subscribe), and [`unsubscribe`](#param-unsubscribe) functions. The `middleware` function doesn't perform any actions itself but lets you inject logic into InstantSearch.js, for example, [sending events to Google Analytics](/doc/guides/building-search-ui/going-further/integrate-google-analytics/react). To [send Algolia click and conversion events](/doc/guides/building-search-ui/events/react), use the [`insights`](/doc/api-reference/widgets/insights/react) middleware. ### Requirements * [InstantSearch.js](https://github.com/algolia/instantsearch) v4.8.3 or later. ## Examples ```jsx JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { return { onStateChange({ uiState }) { // Do something with `uiState` every time the state changes. }, subscribe() { // Do something when the InstantSearch instance starts. }, unsubscribe() { // Do something when the InstantSearch instance is disposed of. }, }; } function Middleware() { const { addMiddlewares } = useInstantSearch(); React.useLayoutEffect(() => { return addMiddlewares(middleware); }, [addMiddlewares]); } function App() { return ( ); } ``` ## Options **instantSearchInstance** `object` *required* You have access to the instance of [`instantsearch`](/doc/api-reference/widgets/instantsearch/js) which lets you read values from the instance or call instance methods like `addWidgets`, `setUiState`, and `refresh`. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { return { onStateChange({ uiState }) { // ... }, subscribe() { // ... }, unsubscribe() { // ... }, }; } ``` ## Hooks **onStateChange** `(\{ uiState \}) =\> void` *required* The function is called with [`uiState`](/doc/api-reference/widgets/ui-state/js) whenever the state changes. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { return { // ... onStateChange({ uiState }) { const indexName = ""; const { query } = uiState[indexName]; const title = document.querySelector(""); title.innerText = query ? `Query: ${query}` : `No query`; }, }; } ``` **subscribe** `() =\> void` *required* This function is called when the InstantSearch instance starts (when `search.start` is called). This is where you can add event listeners, subscribe to an API, and run any side effects. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { let subscription; let listener; return { // ... subscribe() { subscription = someAPI.subscribe(); listener = (event) => { // do something }; document .querySelector("") .addEventListener("click", listener); }, }; } ``` **unsubscribe** `() =\> void` *required* The function is called when the InstantSearch instance is disposed of. You can clean up anything you initiated in the `subscribe` function. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { let subscription; let listener; return { // ... unsubscribe() { subscription.unsubscribe(); document .querySelector("") .removeEventListener("click", listener); }, }; } ``` # middleware Source: https://www.algolia.com/doc/api-reference/widgets/middleware/vue Adds custom logic to your Vue InstantSearch apps. ```vue Signature theme={"system"} ``` ## About this widget The `middleware` function returns an object with [`onStateChange`](#param-on-state-change), [`subscribe`](#param-subscribe), and [`unsubscribe`](#param-unsubscribe) functions. The `middleware` function doesn't perform any actions itself but lets you inject logic into Vue InstantSearch, for example, [sending events to Google Analytics](/doc/guides/building-search-ui/going-further/integrate-google-analytics/vue). To [send Algolia click and conversion events](/doc/guides/building-search-ui/events/vue), use the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. ### Requirements * [Vue InstantSearch](https://github.com/algolia/instantsearch/tree/master/packages/vue-instantsearch) v3.7.0 or later. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Options **instantSearchInstance** `object` *required* You have access to [the instance of `instantsearch`](/doc/api-reference/widgets/instantsearch/js) which lets you read values from the instance or call instance methods like `addWidgets`, `setUiState`, and `refresh`. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { return { onStateChange({ uiState }) { // ... }, subscribe() { // ... }, unsubscribe() { // ... }, }; } ``` ## Hooks **onStateChange** `(\{ uiState \}) =\> void` *required* This function is called with [`uiState`](/doc/api-reference/widgets/ui-state/js) whenever the state changes. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { return { // ... onStateChange({ uiState }) { const indexName = ""; const { query } = uiState[indexName]; const title = document.querySelector(""); title.innerText = query ? `Query: ${query}` : `No query`; }, }; } ``` **subscribe** `() =\> void` *required* This function is called when the InstantSearch instance starts (when `search.start()` is called). This is where you can add event listeners, subscribe to an API, and run any side effects. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { let subscription; let listener; return { // ... subscribe() { subscription = someAPI.subscribe(); listener = (event) => { // do something }; document .querySelector("") .addEventListener("click", listener); }, }; } ``` **unsubscribe** `() =\> void` *required* This function is called when the InstantSearch instance is disposed of. You can clean up anything you initiated in the `subscribe` function. ```js JavaScript icon=code theme={"system"} function middleware({ instantSearchInstance }) { let subscription; let listener; return { // ... unsubscribe() { subscription.unsubscribe(); document .querySelector("") .removeEventListener("click", listener); }, }; } ``` # useNumericMenu Source: https://www.algolia.com/doc/api-reference/widgets/numeric-menu/react React Hook for refining search results by numeric filters. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} const numericMenuApi = useNumericMenu({ attribute: string, items: object[], // Optional parameters transformItems: function, } ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { useNumericMenu } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=numericMenu) > > Preview this widget and its behavior. ## About this Hook React Hook for a list of numeric filters that are pre-configured when creating the widget. Numeric menus let users choose a single value for a specific attribute. ### Requirements The attribute provided to the Hook is already declared as an [attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting). All values must be numbers, not strings. ## Examples ```tsx JavaScript theme={"system"} import React from 'react'; import { useNumericMenu } from 'react-instantsearch'; function NumericMenu(props) { const { items, refine } = useNumericMenu(props); return (
      {items.map((item) => (
    • ))}
    ); } ``` ```tsx TypeScript icon=code theme={"system"} import React from "react"; import { useNumericMenu, UseNumericMenuProps } from "react-instantsearch"; function NumericMenu(props: UseNumericMenuProps) { const { items, refine } = useNumericMenu(props); return (
      {items.map((item) => (
    • ))}
    ); } ``` You can check the [`NumericMenu` example](https://github.com/algolia/instantsearch/blob/master/examples/react/default-theme/src/components/NumericMenu.tsx) for full markup. ## Parameters **attribute** `string` *required* The name of the attribute in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). ```jsx JavaScript icon=code theme={"system"} const numericMenuApi = useNumericMenu({ // ... attribute: "categories", }); ``` **items** `object[]` *required* A list of all the options to display, with: * `label: string`. Label of the option. * `start: number`. The option must be greater than or equal to `start` (lower bound). * `end: number`. The option must be smaller than or equal to `end` (upper bound). ```jsx JavaScript icon=code theme={"system"} const numericMenuApi = useNumericMenu({ // ... items: [ { label: "All" }, { label: "Less than 500$", end: 500 }, { label: "Between 500$ - 1000$", start: 500, end: 1000 }, { label: "More than 1000$", start: 1000 }, ], }); ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript icon=code theme={"system"} const numericMenuApi = useNumericMenu({ // ... transformItems(items) { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }, // or, combined with results transformItems(items, { results }) { return items.map((item) => ({ ...item, label: item.isRefined && results ? `${item.label} (${results.nbHits} hits)` : item.label, })); }, }); ``` ## Returns **items** `NumericMenuItem[]` The list of available options, with each option: * `label: string`. The label for the option. * `value: string`. The encoded URL of the bounds object with the `{start, end}` form. This value can be used verbatim in the web page and can be read by `refine` directly. If you want to inspect the value, you can do `JSON.parse(window.decodeURI(value))` to get the object. * `isRefined: boolean`. Whether the refinement is selected. ```ts TypeScript icon=code theme={"system"} type NumericMenuItem = { value: string; label: string; isRefined: boolean; }; ``` **canRefine** `boolean` Whether the search can be refined. **createURL** `(value: string) =\> string` Creates the next state URL of a selected refinement. **refine** `(value: string) =\> string` Applies the selected refinement. **sendEvent** `(eventType: string, facetValue: string, eventName?: string) =\> void` Sends an event to the Insights middleware. # ais-numeric-menu Source: https://www.algolia.com/doc/api-reference/widgets/numeric-menu/vue Shows a list of numeric filters for refining search results. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisNumericMenu } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisNumericMenu > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=numericMenu) > > Preview this widget and its behavior. ## About this widget The `ais-numeric-menu` widget displays a list of numeric filters in a list. Those numeric filters are pre-configured when creating the widget. ### Requirements The values of the attribute must be numbers, not strings. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **attribute** `string` *required* The name of the attribute in the record. ```vue Vue icon=code theme={"system"} ``` **items** `object[]` *required* A list of all the options to display, with: * `label: string`. Label of the option * `start: number`. The option must be greater than or equal to `start` (lower bound). * `end: number`. The option must be smaller than or equal to `end` (upper bound). ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-NumericMenu`. The root element of the widget. * `ais-NumericMenu--noRefinement`. The root element of the widget without refinement. * `ais-NumericMenu-list`. The ranges list. * `ais-NumericMenu-item`. The ranges list item. * `ais-NumericMenu-item--selected`. The ranges list selected item. * `ais-NumericMenu-label`. The label of each item. * `ais-NumericMenu-radio`. The radio of each item. * `ais-NumericMenu-labelText`. The text of each item. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The list of items the widget can display. * `canRefine: boolean`. Whether a refinement can be applied. * `refine: (value: string) => void`. The function to call with the next value of the refinement. * `createURL: (value: string) => void`. The function to generate a URL for the provided refinement. * `sendEvent: (eventType: 'click', facetValue: string) => void`. The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. With each item an `object` with: * `label: string`. The label for the option. * `value: string`. The encoded URL of the bounds object with a `{start, end}` form. This value can be used verbatim in the webpage and can be read by `refine` directly. If you want to inspect the value, you can do `JSON.parse(window.decodeURI(value))` to get the object. * `isRefined: boolean`. Whether the refinement is selected. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # Pagination Source: https://www.algolia.com/doc/api-reference/widgets/pagination/react Adds controls for navigating search result pages. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```js JavaScript icon=code theme={"system"} import { Pagination } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=pagination) > > Preview this widget and its behavior. ## About this widget `Pagination` is a widget that displays a pagination to browse pages. > [!NOTE] > > Pagination is limited to 1,000 hits per page. > For more information, see [Pagination limitations](/doc/guides/building-search-ui/ui-and-ux-patterns/pagination/react#pagination-limitations). > [!TIP] > > You can also create your own UI with [`usePagination`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Pagination } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **totalPages** `number` The maximum number of pages to browse. ```jsx JavaScript icon=code theme={"system"} ; ``` **padding** `number` (default: 3) The number of pages to display on each side of the current page. ```jsx JavaScript icon=code theme={"system"} ; ``` **showFirst** (default: true) Whether to display the first page link. ```jsx JavaScript icon=code theme={"system"} ; ``` **showPrevious** (default: true) Whether to display the previous page link. ```jsx JavaScript icon=code theme={"system"} ; ``` **showNext** (default: true) Whether to display the next page link. ```jsx JavaScript icon=code theme={"system"} ; ``` **showLast** (default: true) Whether to display the last page link. ```jsx JavaScript icon=code theme={"system"} ; ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `noRefinementRoot`. The root element when there are no refinements. * `list`. The list element. * `item`. Each item element. * `firstPageItem`. The first page element. * `previousPageItem`. The previous page element. * `pageItem`. Each page element. * `selectedItem`. The selected page element. * `disabledItem`. Each page element when it's unavailable. * `nextPageItem`. The next page element. * `lastPageItem`. The last page element. * `link`. The link of each item. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A mapping of keys to translation values. * `firstPageItemText`. The text for the first page item. * `previousPageItemText`. The text for the previous page item. * `nextPageItemText`. The text for the next page item. * `lastPageItemText`. The text for the last page item. * `pageItemText`. The text for the current page item. * `firstPageItemAriaLabel`. The label for the first page item (for screen readers). * `previousPageItemAriaLabel`. The label for the previous page item (for screen readers). * `nextPageItemAriaLabel`. The label for the next page item (for screen readers). * `lastPageItemAriaLabel`. The label for the last page item (for screen readers). * `pageItemAriaLabel`. The label for the current page item (for screen readers). ```jsx JavaScript icon=code theme={"system"} `Page ${currentPage}/${nbPages}`, firstPageItemAriaLabel: "Go to first page", previousPageItemAriaLabel: "Go to previous page", nextPageItemAriaLabel: "Go to next page", lastPageItemAriaLabel: "Go to last page", pageItemAriaLabel: ({ currentPage, nbPages }) => `Go to page ${currentPage} of ${nbPages}`, }} />; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `Pagination` widget with `usePagination`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `usePagination` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { usePagination } from "react-instantsearch"; function CustomPagination(props) { const { pages, currentRefinement, nbHits, nbPages, isFirstPage, isLastPage, canRefine, refine, createURL, } = usePagination(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **totalPages** `number` The maximum number of pages to browse. ```jsx JavaScript icon=code theme={"system"} const paginationApi = usePagination({ totalPages: 2, }); ``` **padding** `number` (default: 3) The number of pages to display on each side of the current page. ```jsx JavaScript icon=code theme={"system"} const paginationApi = usePagination({ padding: 2, }); ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **pages** `number[]` The list of pages based on the current page and [`padding`](/doc/api-reference/widgets/pagination/react#param-padding). ```jsx JavaScript icon=code theme={"system"} function Pagination(props) { const { pages, refine } = usePagination(props); return (
    ); } ``` **currentRefinement** `number` The current page number. ```jsx JavaScript icon=code theme={"system"} function Pagination(props) { const { currentRefinement, pages, refine } = usePagination(props); return ( ); } ``` **nbHits** `number` The number of hits (can be approximate). ```jsx JavaScript icon=code theme={"system"} function PageIndicator(props) { const { currentRefinement, nbPages, nbHits } = usePagination(props); return (

    {currentRefinement + 1} of {nbPages} page(s) for {nbHits} hit(s)

    ); } ``` **nbPages** `number` The number of pages for the total result set. ```jsx JavaScript icon=code theme={"system"} function PageIndicator(props) { const { currentRefinement, nbPages, nbHits } = usePagination(props); return (

    {currentRefinement + 1} of {nbPages} page(s) for {nbHits} hit(s)

    ); } ``` **isFirstPage** `boolean` Whether the current page is the first page. ```jsx JavaScript icon=code theme={"system"} function Pagination(props) { const { isFirstPage, pages, refine } = usePagination(props); return ( ); } ``` **isLastPage** `boolean` Whether the current page is the last page. ```jsx JavaScript icon=code theme={"system"} function Pagination(props) { const { isLastPage, nbPages, pages, refine } = usePagination(props); return ( ); } ``` **canRefine** `boolean` Indicates if the pagination can be refined. ```jsx JavaScript icon=code theme={"system"} function Pagination(props) { const { canRefine, pages, refine } = usePagination(props); if (!canRefine) { return null; } return ( ); } ``` **refine** `(page: number) =\> void` Sets the current page and triggers a search. ```jsx JavaScript icon=code theme={"system"} function Pagination(props) { const { pages, refine } = usePagination(props); return ( ); } ``` **createURL** `(page: number) =\> string` Generates a URL of the next search state. ```jsx JavaScript icon=code theme={"system"} function Pagination(props) { const { canRefine, pages, refine } = usePagination(props); return ( ); } ``` ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { usePagination } from "react-instantsearch"; function CustomPagination(props) { const { pages, currentRefinement, nbPages, isFirstPage, isLastPage, refine, createURL, } = usePagination(props); const firstPageIndex = 0; const previousPageIndex = currentRefinement - 1; const nextPageIndex = currentRefinement + 1; const lastPageIndex = nbPages - 1; return (
      refine(firstPageIndex)} > First refine(previousPageIndex)} > Previous {pages.map((page) => { const label = page + 1; return ( refine(page)} > {label} ); })} refine(nextPageIndex)} > Next refine(lastPageIndex)} > Last
    ); } function PaginationItem({ isDisabled, href, onClick, ...props }) { if (isDisabled) { return (
  • ); } return (
  • { if (isModifierClick(event)) { return; } event.preventDefault(); onClick(event); }} {...props} />
  • ); } function isModifierClick(event) { const isMiddleClick = event.button === 1; return Boolean( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey, ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { usePagination, UsePaginationProps } from "react-instantsearch"; function CustomPagination(props: UsePaginationProps) { const { pages, currentRefinement, nbPages, isFirstPage, isLastPage, refine, createURL, } = usePagination(props); const firstPageIndex = 0; const previousPageIndex = currentRefinement - 1; const nextPageIndex = currentRefinement + 1; const lastPageIndex = nbPages - 1; return (
      refine(firstPageIndex)} > First refine(previousPageIndex)} > Previous {pages.map((page) => { const label = page + 1; return ( refine(page)} > {label} ); })} refine(nextPageIndex)} > Next refine(lastPageIndex)} > Last
    ); } type PaginationItemProps = Omit, "onClick"> & { onClick: NonNullable["onClick"]>; isDisabled: boolean; }; function PaginationItem({ isDisabled, href, onClick, ...props }: PaginationItemProps) { if (isDisabled) { return (
  • ); } return (
  • { if (isModifierClick(event)) { return; } event.preventDefault(); onClick(event); }} {...props} />
  • ); } function isModifierClick(event: React.MouseEvent) { const isMiddleClick = event.button === 1; return Boolean( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey ); } ``` # ais-pagination Source: https://www.algolia.com/doc/api-reference/widgets/pagination/vue Adds controls for navigating search result pages. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisPagination } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisPagination > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=pagination) > > Preview this widget and its behavior. ## About this widget The `ais-pagination` widget displays a pagination system which lets users change the current page of search results. > [!NOTE] > > Pagination is limited to 1,000 hits per page. > For more information, > see [Pagination limitations](/doc/guides/building-search-ui/ui-and-ux-patterns/pagination/vue). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **show-first** `boolean` (default: true) Whether to display the first page link. ```vue Vue icon=code theme={"system"} ``` **show-previous** `boolean` (default: true) Whether to display the previous page link. ```vue Vue icon=code theme={"system"} ``` **show-next** `boolean` (default: true) Whether to display the next page link. ```vue Vue icon=code theme={"system"} ``` **show-last** `boolean` (default: true) Whether to display the last page link. ```vue Vue icon=code theme={"system"} ``` **padding** `number` (default: 3) How many page links to display around the current page. ```vue Vue icon=code theme={"system"} ``` **total-pages** `number` (default: Infinity) The maximum number of pages to display (and to allow navigating to). ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-Pagination`. The root of the widget. * `ais-Pagination--noRefinement`. The root of the widget without refinement. * `ais-Pagination-list`. The list of the page items. * `ais-Pagination-item`. The page item of the list. * `ais-Pagination-item--firstPage`. The "first" item of the list. * `ais-Pagination-item--lastPage`. The "last" item of the list. * `ais-Pagination-item--previousPage`. The "previous" item of the list. * `ais-Pagination-item--nextPage`. The "next" item of the list. * `ais-Pagination-item--page`. The "page" item of the list. * `ais-Pagination-item--selected`. The selected item of the list. * `ais-Pagination-item--disabled`. The list item when it's unavailable. * `ais-Pagination-link`. The clickable element of an item. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `currentRefinement: number`. The number of the selected page. * `nbHits: number`. The total number of hits. * `nbPages: number`. The total number of pages. * `pages: number[]`. The pages to render in the widget. * `isFirstPage: boolean`. Whether the current page is the first page. * `isLastPage: boolean`. Whether the current page is the last page. * `refine: (value: number) => void`. A function to select the next page. * `createURL: (value: number) => void`. A function to generate an URL from a refinement. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` > [!NOTE] > > If SEO is important for your search page, ensure that your custom HTML is optimized for search engines: > > * Use `` tags with `href` attributes to allow search engine bots to follow links. > * Use semantic HTML and include [structured data](https://developers.google.com/search/docs/appearance/structured-data) when relevant. > > For more guidance, see the [SEO checklist](/doc/guides/building-search-ui/resources/seo/js). # ais-panel Source: https://www.algolia.com/doc/api-reference/widgets/panel/vue Wraps other widgets in a panel for a consistent layout. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisPanel } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisPanel, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=panel) > > Preview this widget and its behavior. ## About this widget The `ais-panel` widget wraps other widgets in a consistent panel design. It also reacts by adding a CSS class when the widget no longer can refine. An example is when a [`ais-refinement-list`](/doc/api-reference/widgets/refinement-list/vue) becomes empty because of the current search results. ## Examples ```vue Vue icon=code theme={"system"}

    Panel content

    ``` ## Props **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-Panel`. The root of the widget. * `ais-Panel--noRefinement`. The root of the widget without refinement. * `ais-Panel-header`. The header of the widget. * `ais-Panel-body`. The content of the widget. * `ais-Panel-footer`. The footer of the widget. ```vue Vue icon=code theme={"system"}

    Panel content

    ``` ## Customize the UI **default** The slot to provide a body to the widget. Note that when you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `hasRefinements: boolean`. Whether the inner widget can refine, will be `false` if it doesn't have any possible refinements. ```vue Vue icon=code theme={"system"} ``` **header** The slot to provide a header to the widget. **Scope** * `hasRefinements: boolean`. Whether the inner widget can refine, will be `false` if it doesn't have any possible refinements. ```vue Vue icon=code theme={"system"} ``` **footer** The slot to provide a footer to the widget. **Scope** * `hasRefinements: boolean`. Whether the inner widget can refine, will be `false` if it doesn't have any possible refinements. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    Header
    Panel content
    ``` # PoweredBy Source: https://www.algolia.com/doc/api-reference/widgets/powered-by/react Shows the Algolia logo with a link to the Algolia homepage. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { PoweredBy } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=poweredBy) > > Preview this widget and its behavior. ## About this widget `PoweredBy` is a widget that lets you display the Algolia logo to redirect to the Algolia website. [Algolia requires](https://www.algolia.com/policies/free-services-terms) that you display a "Search by Algolia" logo with a link if you're on a community plan (open source, not-for-profit, or [DocSearch](https://docsearch.algolia.com)). > [!TIP] > > You can also create your own UI with [`usePoweredBy`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, PoweredBy } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **theme** `"light" | "dark"` (default: light) The version of the logo to use, legible on light or dark backgrounds. ```jsx JavaScript icon=code theme={"system"} ; ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `light`. The root element of the widget with the light theme. * `dark`. The root element of the widget with the dark theme. * `link`. The link element. * `logo`. The SVG logo. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `PoweredBy` widget with `usePoweredBy`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `usePoweredBy` Hook returns [APIs](#hook). ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} function CustomPoweredBy() { const { url } = usePoweredBy(); return <>{/* Your JSX */}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **url** `string` The URL to redirect to. ### Example ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { usePoweredBy } from "react-instantsearch"; function CustomPoweredBy() { const { url } = usePoweredBy(); // Download the "Search by Algolia" logo for light and dark themes. // https://algolia.frontify.com/d/1AZwVNcFZiu7/style-guide#/basics/algolia-logo return <>{/* Your JSX */}; } ``` # ais-powered-by Source: https://www.algolia.com/doc/api-reference/widgets/powered-by/vue Shows the Algolia logo with a link to the Algolia homepage. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisPoweredBy } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisPoweredBy > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=poweredBy) > > Preview this widget and its behavior. ## About this widget The `ais-powered-by` widget is used to display the Algolia logo to redirect to the Algolia website. [Algolia requires](https://www.algolia.com/policies/free-services-terms) that you use this widget if you're on a community plan (open source, not-for-profit, or [DocSearch](https://docsearch.algolia.com)). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **theme** `"dark" | "light"` (default: light) The version of the logo to use, legible on light or dark backgrounds. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-PoweredBy`. The root of the widget. * `ais-PoweredBy--light`. The root of the widget with the light theme. * `ais-PoweredBy--dark`. The root of the widget with the dark theme. * `ais-PoweredBy-link`. The link of the widget. * `ais-PoweredBy-logo`. The illustration of the widget. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # QueryRuleContext Source: https://www.algolia.com/doc/api-reference/widgets/query-rule-context/react React Hook for applying rule contexts based on applied filters for triggering context-dependent rules. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ## About this widget This widget lets you apply [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts) based on filters to trigger context-dependent [rules](/doc/guides/managing-results/rules/rules-overview). **The `QueryRuleContext` widget isn't part of React InstantSearch**, but you can make a custom version with [`useQueryRules`](/doc/api-reference/widgets/query-rules/react). ## Examples ```jsx JavaScript theme={"system"} import { useQueryRules } from "react-instantsearch"; export function QueryRuleContext(props) { useQueryRules(props); return null; } ``` ```tsx TypeScript theme={"system"} import { useQueryRules, useQueryRulesProps } from "react-instantsearch"; export function QueryRuleContext(props: useQueryRulesProps) { useQueryRules(props); return null; } ``` # ais-query-rule-context Source: https://www.algolia.com/doc/api-reference/widgets/query-rule-context/vue Applies rule contexts based on applied filters for triggering context-dependent rules. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisQueryRuleContext } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisQueryRuleContext > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget The `ais-query-rule-context` widget lets you apply [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts) based on filters to trigger context dependent [rules](/doc/guides/managing-results/rules/rules-overview). Rules offer a custom experience based on contexts. You might want to customize the users' experience based on the filters of the search (for example, they're visiting the "Mobile" category, they selected the "Thriller" genre, etc.) This widget lets you map these filters to their associated contexts, so you can trigger context based rules on refinement. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **tracked-filters** `object` *required* The filters to track to trigger rule contexts. Each filter is a function which name is the attribute you want to track. They receive their current refinements as arguments. You can either compute the filters you want to track based on those, or return static values. When the tracked values are refined, it toggles the associated rule contexts. The added rule contexts follow the format `ais-{attribute}-{value}` (for example `ais-genre-Thriller`). If the context of your rule follows another format, you can specify it using the [`transformRuleContexts`](#param-transform-rule-contexts) option. Values are escaped so that they only consist of alphanumeric characters, hyphens, and underscores. Non-matching characters are replaced with underscores. ```vue Vue icon=code theme={"system"} ``` **transform-rule-contexts** `function` A function to apply to the rule contexts before sending them to Algolia. This is useful to rename rule contexts that follow a different naming convention. ```vue Vue icon=code theme={"system"} ``` # QueryRulesCustomData Source: https://www.algolia.com/doc/api-reference/widgets/query-rule-custom-data/react Shows custom data from applied index rules. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ## About this widget This widget displays custom data from [Rules](/doc/guides/managing-results/rules/detecting-intent). **The `QueryRulesCustomData` widget isn't part of React InstantSearch**, but you can make a custom version with [`useQueryRules`](/doc/api-reference/widgets/query-rules/react). ## Examples ```jsx JavaScript theme={"system"} import { useQueryRules } from "react-instantsearch"; export function QueryRulesCustomData(props) { const { items } = useQueryRules(props); return ( <> {items.map(({ title, link, banner }) => (

    {title}

    ))} ); } ``` ```tsx TypeScript theme={"system"} import { useQueryRules, useQueryRulesProps } from "react-instantsearch"; export function QueryRulesCustomData(props: useQueryRulesProps) { const { items } = useQueryRules(props); return ( <> {items.map(({ title, link, banner }) => (

    {title}

    ))} ); } ``` # ais-query-rule-custom-data Source: https://www.algolia.com/doc/api-reference/widgets/query-rule-custom-data/vue Shows custom data from applied index rules. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisQueryRuleCustomData } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisQueryRuleCustomData > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget The `ais-query-rule-custom-data` widget displays custom data from [Rules](/doc/guides/managing-results/rules/detecting-intent). You can use this widget to display banners or recommendations returned by rules when they match search parameters. ## Examples ```html HTML icon=code-xml theme={"system"} ``` ## Props **transform-items** `function` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. The following example assumes a rule returned this custom data. ```json JSON icon=braces theme={"system"} { "title": "This is an image", "banner": "image.png", "link": "https://website.com/" } ``` When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The items returned by the rules. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of the item returned by the rules. The following example assumes a Query Rule returned this custom data. ```json JSON icon=braces theme={"system"} { "title": "This is an image", "banner": "image.png", "link": "https://website.com/" } ``` **Scope** * `item: object`. The item returned by the Rules. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # useQueryRules Source: https://www.algolia.com/doc/api-reference/widgets/query-rules/react React Hook for setting rule contexts and showing custom data returned by rules. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```ts Signature theme={"system"} const queryRulesApi = useQueryRules({ // Optional parameters trackedFilters?: function transformRuleContexts?: function transformItems?: function } ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { useQueryRules } from "react-instantsearch"; ``` ## About this Hook The `useQueryRules` Hook lets you interact with Algolia [Rules](/doc/guides/managing-results/rules/rules-overview). ### Rule context You can use the `useQueryRules` Hook to apply [`ruleContexts`](/doc/api-reference/api-parameters/ruleContexts) based on filters to trigger context-dependent [rules](/doc/guides/managing-results/rules/rules-overview). You might want to customize the users' experience based on the filters of the search. For example, when they visit the "Mobile" category, when they select the "Thriller" genre, and so on. Rules offer a custom experience based on contexts. This Hook lets you map 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 its associated rule context so you can trigger context-based rules when applying refinements. ### Rule custom data [Rules](/doc/guides/managing-results/rules/detecting-intent) can return custom data, which is useful to display banners or recommendations depending on the current search parameters. The `useQueryRules` Hook expose custom data from rules. ## Examples ```jsx JavaScript theme={"system"} import React from "react"; import { useQueryRules } from "react-instantsearch"; // Query rule context function CustomQueryRuleContext(props) { useQueryRules(props); return null; } // Query rule custom data function CustomQueryRuleCustomData(props) { const { items } = useQueryRules(props); return ( <> {items.map(({ title, link, banner }) => (

    {title}

    ))} ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useQueryRules, UseQueryRulesProps } from "react-instantsearch"; // Query rule context function CustomQueryRuleContext(props: UseQueryRulesProps) { useQueryRules(props); return null; } // Query rule custom data function CustomQueryRuleCustomData(props: UseQueryRulesProps) { const { items } = useQueryRules(props); return ( <> {items.map(({ title, link, banner }) => (

    {title}

    ))} ); } ``` ## Parameters **trackedFilters** `Record\) =\> Array\\>` The filters to track to trigger rule contexts. Each filter is a function which name is the attribute you want to track. They receive their current refinements as arguments. You can either compute the filters you want to track based on those, or return static values. When the tracked values are refined, it toggles the associated rule contexts. The added rule contexts follow the format `ais-{attribute}-{value}` (for example `ais-genre-Thriller`). If the context of your rule follows another format, you can specify it using the [`transformRuleContexts`](/doc/api-reference/widgets/query-rules/react#param-transform-rule-contexts) option. Values are escaped so that they only consist of alphanumeric characters, hyphens, and underscores. ```jsx JavaScript theme={"system"} const trackedFilters = { genre: () => ["Comedy", "Thriller"], // this tracks two static genre values rating: (values) => values, // this tracks all the rating values }; function QueryRules() { const queryRulesApi = useQueryRules({ trackedFilters, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseQueryRulesProps } from "react-instantsearch"; const trackedFilters: UseQueryRulesProps["trackedFilters"] = { genre: () => ["Comedy", "Thriller"], // this tracks two static genre values rating: (values) => values, // this tracks all the rating values }; function QueryRules() { const queryRulesApi = useQueryRules({ trackedFilters, }); return <>{/* Your JSX */}; } ``` **transformRuleContexts** `(ruleContexts: string[]) =\> string[]` A function to apply to the Rule contexts before sending them to Algolia. This is useful to rename Rule contexts that follow a different naming convention. ```jsx JavaScript theme={"system"} const transformRuleContexts = (ruleContexts) => { return ruleContexts.map((ruleContext) => ruleContext.replace("ais-", "custom-"), ); }; function QueryRules() { const queryRulesApi = useQueryRules({ transformRuleContexts, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseQueryRulesProps } from "react-instantsearch"; const transformRuleContexts: UseQueryRulesProps["transformRuleContexts"] = ( ruleContexts ) => { return ruleContexts.map((ruleContext) => ruleContext.replace("ais-", "custom-") ); }; function QueryRules() { const queryRulesApi = useQueryRules({ transformRuleContexts, }); return <>{/* Your JSX */}; } ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} // Using only items const transformItems = (items) => { return items.filter((item) => typeof item.banner !== "undefined"); }; // Using items and results const transformItems = (items, { results }) => { return items.map((item) => ({ ...item, visible: results.page === 0, })); }; function QueryRules() { const queryRulesApi = useQueryRules({ transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseQueryRulesProps } from "react-instantsearch"; // Using only items const transformItems: UseQueryRulesProps["transformItems"] = (items) => { return items.filter((item) => typeof item.banner !== "undefined"); }; // Using items and results const transformItems: UseQueryRulesProps["transformItems"] = ( items, { results } ) => { return items.map((item) => ({ ...item, visible: results.page === 0, })); }; function QueryRules() { const queryRulesApi = useQueryRules({ transformItems, }); return <>{/* Your JSX */}; } ``` ## Returns **items** `any[]` The items that matched the rule. ```jsx JavaScript icon=code theme={"system"} function QueryRuleCustomData(props) { const { items } = useQueryRules(props); return (
      {items.map((item, index) => (
    • {item.title}
    • ))}
    ); } ``` # RangeInput Source: https://www.algolia.com/doc/api-reference/widgets/range-input/react Lets users enter a numeric range for refining search results. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { RangeInput } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=rangeInput) > > Preview this widget and its behavior. ## About this widget `RangeInput` is a widget to let users select a numeric range using minimum and maximum inputs. > [!TIP] > > You can also create your own UI with [`useRange`](#hook). ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. The values of the attribute must be numbers, not strings. ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, RangeInput } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **attribute** `string` *required* The name of the attribute in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). ```jsx JavaScript icon=code theme={"system"} ; ``` **min** `number` The minimum value for the input. When not provided, the minimum value is automatically computed by Algolia from the data in the index. ```jsx JavaScript icon=code theme={"system"} ; ``` **max** `number` The maximum value for the input. When not provided, the maximum value is automatically computed by Algolia from the data 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.). ```jsx JavaScript icon=code theme={"system"} ; ``` **precision** `number` (default: 0) The number of digits to use after the decimal point. ```jsx JavaScript icon=code theme={"system"} ; ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `noRefinementRoot`. The root element when there are no refinements. * `form`. The form element. * `label`. Each label element. * `input`. Each input element. * `inputMin`. The minimum input element. * `inputMax`. The maximum input element. * `separator`. The separator element between inputs. * `submit`. The submit button. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A mapping of keys to translation values. * `separatorElementText`. The text for the separator element between the minimum and maximum inputs. * `submitButtonText`. The text for the submit button. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `RangeInput` widget with `useRange`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useRange` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useRange } from "react-instantsearch"; function CustomRangeInput(props) { const { start, range, canRefine, refine, sendEvent } = useRange(props); return <>{/* Your JSX */}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **attribute** `string` *required* The name of the attribute in the records. ```js JavaScript icon=code theme={"system"} const rangeApi = useRange({ attribute: "price", }); ``` **min** `number` The minimum value for the input. When not provided, the minimum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} const rangeApi = useRange({ // ... min: 10, }); ``` **max** `number` The maximum value for the input. When not provided, the maximum value is automatically computed by Algolia from the data in the index. ```js JavaScript icon=code theme={"system"} const rangeApi = useRange({ // ... max: 500, }); ``` **precision** `number` (default: 0) The number of digits to use after the decimal point. ```js JavaScript icon=code theme={"system"} const rangeApi = useRange({ // ... precision: 2, }); ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **start** `RangeBoundaries` The current value for the refinement, with `start[0]` as the minimum value and `start[1]` as the maximum value. ```ts TypeScript icon=code theme={"system"} type RangeBoundaries = [number | undefined, number | undefined]; ``` **range** `Range` The current available value for the range. ```ts TypeScript icon=code theme={"system"} type Range = { min: number | undefined; max: number | undefined; }; ``` **canRefine** `boolean` Whether users can refine further. **refine** `(rangeValue: RangeBoundaries) =\> void` Sets a range to filter the results on. Both values are optional, and default to the higher and lower bounds. You can use `undefined` to remove a previously set bound or to set an infinite bound. ```js JavaScript icon=code theme={"system"} const { refine } = useRange(props); // ... const onRangeSubmit = (min, max) => { refine([ Number.isFinite(min) ? min : undefined, Number.isFinite(max) ? max : undefined, ]); }; ``` **sendEvent** `(eventType: string, facetValue: string, eventName?: string) =\> void` Sends an event to the Insights middleware. ### Example ```jsx JavaScript theme={"system"} import React, { useState } from "react"; import { useRange } from "react-instantsearch"; const unsetNumberInputValue = ""; function CustomRangeInput(props) { const { start, range, canRefine, refine } = useRange(props); const step = 1 / Math.pow(10, props.precision || 0); const values = { min: start[0] !== -Infinity && start[0] !== range.min ? start[0] : unsetNumberInputValue, max: start[1] !== Infinity && start[1] !== range.max ? start[1] : unsetNumberInputValue, }; const [prevValues, setPrevValues] = useState(values); const [{ from, to }, setRange] = useState({ from: values.min?.toString(), to: values.max?.toString(), }); if (values.min !== prevValues.min || values.max !== prevValues.max) { setRange({ from: values.min?.toString(), to: values.max?.toString() }); setPrevValues(values); } return (
    { event.preventDefault(); refine([from ? Number(from) : undefined, to ? Number(to) : undefined]); }} > to
    ); } function stripLeadingZeroFromInput(value) { return value.replace(/^(0+)\d/, (part) => Number(part).toString()); } ``` ```tsx TypeScript theme={"system"} import React, { useState } from "react"; import { useRange, UseRangeProps } from "react-instantsearch"; const unsetNumberInputValue = ""; function CustomRangeInput(props: UseRangeProps) { const { start, range, canRefine, refine } = useRange(props); const step = 1 / Math.pow(10, props.precision || 0); const values = { min: start[0] !== -Infinity && start[0] !== range.min ? start[0] : unsetNumberInputValue, max: start[1] !== Infinity && start[1] !== range.max ? start[1] : unsetNumberInputValue, }; const [prevValues, setPrevValues] = useState(values); const [{ from, to }, setRange] = useState({ from: values.min?.toString(), to: values.max?.toString(), }); if (values.min !== prevValues.min || values.max !== prevValues.max) { setRange({ from: values.min?.toString(), to: values.max?.toString() }); setPrevValues(values); } return (
    { event.preventDefault(); refine([from ? Number(from) : undefined, to ? Number(to) : undefined]); }} > to
    ); } function stripLeadingZeroFromInput(value: string): string { return value.replace(/^(0+)\d/, (part) => Number(part).toString()); } ``` # ais-range-input Source: https://www.algolia.com/doc/api-reference/widgets/range-input/vue Lets users enter a numeric range for refining search results. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisRangeInput } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisRangeInput > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=rangeInput) > > Preview this widget and its behavior. ## About this widget The `ais-range-input` widget allows a user to select a numeric range using a minimum and maximum input. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. The values of the attribute must be numbers, not strings. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **attribute** `string` *required* The name of the attribute in the record. ```vue Vue icon=code theme={"system"} ``` **min** `number` The minimum value for the input. When not provided, the minimum value is automatically computed by Algolia from the data in the index. ```vue Vue icon=code theme={"system"} ``` **max** `number` The maximum value for the input. When not provided, the maximum value is automatically computed by Algolia from the data in the index. ```vue Vue icon=code theme={"system"} ``` **precision** `number` (default: 0) The number of digits after the decimal point to use. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-RangeInput`. The root of the widget. * `ais-RangeInput--noRefinement`. The root of the widget without refinement. * `ais-RangeInput-form`. The form wrapper around the inputs and the submit button. * `ais-RangeInput-separator`. The separator between the min and the max. * `ais-RangeInput-button`. The button that triggers the submission of the form. * `ais-RangeInput-label`. The enclosing label of an input. * `ais-RangeInput-input`. The inputs. * `ais-RangeInput-input--min`. The input for the minimum value. * `ais-RangeInput-input--max`. The input for the maximum value. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `currentRefinement: { min: number, max: number }`. A value that contains the currently applied refinement. * `range: { min: number, max: number }`. A value that contains the minimum and maximum available value. * `canRefine: boolean`. Whether the refinement can be applied. * `refine: ({ min: number, max: number }) => void`. A function to select the refinement. * `sendEvent: (eventType: 'click', facetValue: string) => void`. The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. ```vue Vue icon=code theme={"system"} ``` **minLabel** The slot to override the DOM output for the label of the minimum value. ```vue Vue icon=code theme={"system"} ``` **maxLabel** The slot to override the DOM output for the label of the maximum value. ```vue Vue icon=code theme={"system"} ``` **submitLabel** The slot to override the DOM output for the label of the submit button. ```vue Vue icon=code theme={"system"} ``` **separator** The slot to override the DOM output for the separator between the inputs. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    to
    ``` # useRange Source: https://www.algolia.com/doc/api-reference/widgets/range-slider/react React Hook for refining search results by a numeric range. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```ts Signature theme={"system"} const rangeApi = useRange({ attribute: string, // Optional parameters min?: number, max?: number, precision?: number, } ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { useRange } from "react-instantsearch"; ``` ## About this Hook Use this React Hook to filter search results within a numeric range. You can build custom slider components with this Hook. To display a number range input, use the [`RangeInput`](/doc/api-reference/widgets/range-input/react) widget instead. ### Requirements The [`attribute`](#param-attribute) provided to this Hook must be a facet, which you can declare in the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) 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.) setting. The attribute values must be numbers, not strings. ## Examples ### Range slider with React Spectrum ```jsx JavaScript theme={"system"} import React, { useState, useEffect } from "react"; import { useRange } from "react-instantsearch"; import { RangeSlider as SpectrumRangeSlider } from "@adobe/react-spectrum"; // … export function RangeSlider(props) { const { start, range, canRefine, refine } = useRange(props); const { min, max } = range; const [value, setValue] = useState({ start: min, end: max }); const from = Math.max(min, Number.isFinite(start[0]) ? start[0] : min); const to = Math.min(max, Number.isFinite(start[1]) ? start[1] : max); useEffect(() => { setValue({ start: from, end: to }); }, [from, to]); return ( refine([start, end])} isDisabled={!canRefine} /> ); } ``` ```tsx TypeScript theme={"system"} import React, { useState, useEffect } from "react"; import { useRange, type UseRangeProps } from "react-instantsearch"; import { RangeSlider as SpectrumRangeSlider } from "@adobe/react-spectrum"; // … export function RangeSlider(props: UseRangeProps) { const { start, range, canRefine, refine } = useRange(props); const min = (range.min as number) || 0; const max = (range.max as number) || 0; const [value, setValue] = useState({ start: min, end: max, }); const from = Math.max(min, Number.isFinite(start[0] as number) ? (start[0] as number): min); const to = Math.min(max, Number.isFinite(start[1] as number) ? (start[1] as number) : max); useEffect(() => { setValue({ start: from, end: to }); }, [from, to]); return ( refine([start, end])} isDisabled={!canRefine} /> ); } ``` > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/react-instantsearch/range-slider-with-react-spectrum?file=/src/RangeSlider.js) > > Run and edit the `useRange` example in CodeSandbox. ### Range slider with Radix UI ```tsx JavaScript theme={"system"} import React, { useState, useEffect } from "react"; import { useRange } from "react-instantsearch"; import { Slider } from "radix-ui"; // … export function RangeSlider(props) { const { start, range, canRefine, refine } = useRange(props); const { min, max } = range; const [value, setValue] = useState([min, max]); const from = Math.max(min, Number.isFinite(start[0]) ? start[0] : min); const to = Math.min(max, Number.isFinite(start[1]) ? start[1] : max); useEffect(() => { setValue([from, to]); }, [from, to]); return ( ); } ``` ```tsx TypeScript icon=code theme={"system"} import React, { useState, useEffect } from "react"; import { Slider } from "radix-ui"; // … export function RangeSlider(props: UseRangeProps) { const { start, range, canRefine, refine } = useRange(props); const min = (range.min as number) || 0; const max = (range.max as number) || 0; const [value, setValue] = useState([min, max]); const from = Math.max( min, Number.isFinite(start[0] as number) ? (start[0] as number) : min, ); const to = Math.min( max, Number.isFinite(start[1] as number) ? (start[1] as number) : max, ); useEffect(() => { setValue([from, to]); }, [from, to]); return ( refine([min, max]))} disabled={!canRefine} > ); } ``` > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/react-instantsearch/range-slider-with-radix-ui?file=/src/RangeSlider.js) > > Run and edit the `useRange` example in CodeSandbox. ## Parameters **attribute** `string` *required* The name of the numeric attribute in your Algolia records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) to use for refining the search. ```jsx JavaScript icon=code theme={"system"} const rangeApi = useRange({ // ... attribute: "price", }); ``` **min** `number` The minimum value for the range. If not set, Algolia determines it based on the lowest value of the [`attribute`](#param-attribute) prop. ```jsx JavaScript icon=code theme={"system"} const rangeApi = useRange({ // ... min: 10, }); ``` **max** `number` The maximum value for the range. If not set, Algolia determines it based on the highest value of the [`attribute`](#param-attribute) prop. ```jsx JavaScript icon=code theme={"system"} const rangeApi = useRange({ // ... max: 500, }); ``` **precision** `number` (default: 0) The number of decimal places to use when rounding values. Use negative values to round to powers of 10. ```jsx JavaScript icon=code theme={"system"} // Round values to 2 decimal places const rangeApi = useRange({ // ... precision: 2, }); // Round values to the nearest hundred const rangeApi = useRange({ // ... precision: -2, }); ``` ## Returns **start** `[number, number]` The selected range for the refinement, where `start[0]` is the lower bound and `start[1]` is the higher bound. **range** `\{ min: number, max: number \}` The possible bounds based on the attribute's values. **canRefine** `boolean` Whether the search can be refined. **refine** `(range: [number, number]) =\> void` Sets the range used to filter results. Use `undefined` to remove a bound or allow it to be unbounded. Both values are optional and default to the upper and lower bounds. **sendEvent** `(eventType: string, facetValue: string, eventName?: string) =\> void` Sends an event to the Insights API. # ais-range-slider Source: https://www.algolia.com/doc/api-reference/widgets/range-slider/vue Lets users refine search results by a numeric range using a slider. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisRangeInput } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisRangeInput > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=vue) > > Preview this widget and its behavior. ## About this widget Since there are many third-party range sliders for Vue, none is included by default. However, you can use the default slot in [`ais-range-input`](/doc/api-reference/widgets/range-input/vue) to make a slider. The example here uses [Vuetify](https://vuetifyjs.com)'s `v-range-slider`. Make sure you've installed the library for your Vue version and are familiar with the API of `v-range-slider`. For Vue 2: * [Install Vuetify 2](https://v2.vuetifyjs.com/en/getting-started/installation) * [API reference](https://v2.vuetifyjs.com/en/components/range-sliders) for `v-range-slider` For Vue 3: * [Install Vuetify 3+](https://vuetifyjs.com/en/getting-started/installation/#existing-projects) * [API reference](https://vuetifyjs.com/en/components/range-sliders) for `v-range-slider` ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. The values of the attribute must be numbers, not strings. ## Examples ```vue Vue 2 theme={"system"} ``` ```vue Vue 3 theme={"system"} ``` ## Props **attribute** `string` *required* The name of the attribute in the record. ```vue Vue icon=code theme={"system"} ``` **min** `number` The minimum value for the input. When not provided, the minimum value is automatically computed by Algolia from the data in the index. ```vue Vue icon=code theme={"system"} ``` **max** `number` The maximum value for the input. When not provided, the maximum value is automatically computed by Algolia from the data in the index. ```vue Vue icon=code theme={"system"} ``` **precision** `number` (default: 0) The number of digits after the decimal point to use. Use a negative value to round values to powers of 10. For example, a precision of -2 would round a number to the nearest hundred, while a precision of -3 would round it to the nearest thousand. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. when you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `currentRefinement: { min: number, max: number }`. The currently applied refinement. * `range: { min: number, max: number }`. The minimum and maximum available value. * `refine: ({ min: number, max: number }) => void`. A function to select the refinement. ```vue Vue 2 theme={"system"} ``` ```vue Vue 3 theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # useRatingMenu Source: https://www.algolia.com/doc/api-reference/widgets/rating-menu/react React Hook for refining search results by selecting star ratings. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ## About this Hook The `useRatingMenu` Hook isn't part of React InstantSearch. You can make a custom version with [`useConnector`](/doc/api-reference/widgets/connector/react) and [`connectRatingMenu`](/doc/api-reference/widgets/rating-menu/js). This Hook lets users refine search results by clicking on stars (based on the selected attribute). ## Examples ```jsx JavaScript theme={"system"} import { useConnector } from "react-instantsearch"; import connectRatingMenu from "instantsearch.js/es/connectors/rating-menu/connectRatingMenu"; export function useRatingMenu(props) { return useConnector(connectRatingMenu, props); } export function RatingMenu(props) { const { items, refine, createURL } = useRatingMenu(props); return ( ); } ``` ```tsx TypeScript theme={"system"} import { useConnector } from "react-instantsearch"; import connectRatingMenu from "instantsearch.js/es/connectors/rating-menu/connectRatingMenu"; import type { RatingMenuConnectorParams, RatingMenuWidgetDescription, } from "instantsearch.js/es/connectors/rating-menu/connectRatingMenu"; export type UseRatingMenuProps = RatingMenuConnectorParams; export function useRatingMenu(props?: UseRatingMenuProps) { return useConnector( connectRatingMenu, props ); } export function RatingMenu(props: UseRatingMenuProps) { const { items, refine, createURL } = useRatingMenu(props); return ( ); } ``` # ais-rating-menu Source: https://www.algolia.com/doc/api-reference/widgets/rating-menu/vue Lets users refine search results by selecting star ratings. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisRatingMenu } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisRatingMenu > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=ratingMenu) > > Preview this widget and its behavior. ## About this widget The `ais-rating-menu` lets users refine search results by clicking on stars. The stars are based on the selected attribute. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **attribute** `string` *required* The name of the attribute in the record. ```vue Vue icon=code theme={"system"} ``` **max** `number` The maximum value for the rating. This value is exclusive, which means the number of stars will be the provided value, minus one. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-RatingMenu`. The root element of the widget. * `ais-RatingMenu-starSymbol`. The SVG symbol for a filled star. * `ais-RatingMenu-starEmptySymbol`. The SVG symbol for an empty star. * `ais-RatingMenu-list`. The list of ratings. * `ais-RatingMenu-item`. The item of the ratings list. * `ais-RatingMenu-link`. The link of each rating item. * `ais-RatingMenu-starIcon`. The star icon. * `ais-RatingMenu-starIcon--full`. The filled star icon. * `ais-RatingMenu-starIcon--empty`. The empty star icon. * `ais-RatingMenu-label`. The "& Up" label. * `ais-RatingMenu-count`. The number of results for the rating. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The list of available refinements. * `canRefine: boolean`. Whether a refinement can be applied. * `refine: (value: string) => void`. A function to select the next refinement. * `createURL: (item: string) => string`. A function to generate a link from the refinement. * `sendEvent: (eventType: 'click', facetValue: string) => void`. The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. * You can learn more about the middleware. Where each item is an `object` containing: * `count: number`. The number of results that match this refinement. * `isRefined: boolean`. Whether the refinement is selected. * `name: string`. The name corresponding to the number of stars. * `value: string`. The number of stars with a string form. * `stars: boolean[]`. The list of stars to generate with: * `true`. Represents a filled star * `false`. Represents an empty star ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` # RefinementList Source: https://www.algolia.com/doc/api-reference/widgets/refinement-list/react Shows a list of facets for refining search results. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { RefinementList } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=refinementList) > > Preview this widget and its behavior. ## About this widget `RefinementList` is a widget that lets users filter the dataset using multi-select facets. A refinement list only displays the most relevant facet values for the current search context. The [`sortBy`](#param-sort-by) option only affects how the facets are ordered, not which facets are returned. This widget includes a "search for facet values" feature, enabling users to search through the [values of a specific facet attribute](/doc/guides/managing-results/refine-results/faceting#search-for-facet-values). This helps you find uncommon facet values. > [!NOTE] > > The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, > either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. > > If you are using the [`searchable`](#param-searchable) prop, you also need to make the attribute searchable using the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the `searchable` modifier of [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) with the API. ### Disappearing facet values With many facet values, the available options can change depending on the user's query. The refinement widget displays the most common facet values for a given query. A user's chosen value can vanish if they alter the query. This occurs because only the most common facet values are displayed when there are many options. A previously selected value might not appear if it's uncommon for the new query. To also show less common values, adjust the maximum number of values with the [`configure`](/doc/api-reference/widgets/configure/react) widget. It doesn't change how many items are shown: the limits you set with [`limit`](/doc/api-reference/widgets/refinement-list/react#param-limit) and [`showMoreLimit`](/doc/api-reference/widgets/refinement-list/react#param-show-more-limit) still apply. ### How to implement a "Show more" feature A custom `useRefinementList` widget displays up to `showMoreLimit` refinement `items`. You can sort the items as desired before they're trimmed. However, you'll need to slice to the appropriate `limit` and keep track of `isShowingMore` in the local state: ```jsx JavaScript icon=code theme={"system"} const CustomRefinementList = connectRefinementList(function RefinementList({ items, limit, showMoreLimit, }) { const [isShowingMore, toggleShowMore] = React.useState(false); const itemsToDisplay = items.slice(0, isShowingMore ? showMoreLimit : limit); // render using `itemsToDisplay` }); ``` > [!TIP] > > You can also create your own UI with [`useRefinementList`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, RefinementList } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **attribute** `string` *required* The name of the attribute in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```jsx JavaScript icon=code theme={"system"} ; ``` **operator** `'and'|'or'` (default: 'or') How the facets are combined. * `'or'`: Returns results matching any of the selected values. * `'and'`: Returns results matching all selected values. Use [filters or facet filters](/doc/guides/managing-results/refine-results/filtering/in-depth/filters-and-facetfilters) for more complex result refinement. ```jsx JavaScript icon=code theme={"system"} ; ``` **limit** `number` (default: 10) How many facet values to retrieve. When you set [`showMore`](#param-show-more) and [`showMoreLimit`](#param-show-more-limit), this is the number of facet values to display before clicking the Show more button. ```jsx JavaScript icon=code theme={"system"} ; ``` **showMore** `boolean` (default: false) Whether to display a button that expands the number of items. ```jsx JavaScript icon=code theme={"system"} ; ``` **showMoreLimit** `number` The maximum number of items to display if the widget is showing more items. ```jsx JavaScript icon=code theme={"system"} ; ``` **searchable** `boolean` (default: false) Whether to add a search input to let users search for more facet values. You need to make the `attribute` searchable using the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard)) or using the `searchable` modifier of [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) with the API. > [!NOTE] > > In some situations, refined facet values might not be present in the data > returned by Algolia. ```jsx JavaScript icon=code theme={"system"} ; ``` **searchablePlaceholder** `string` The value of the search input's placeholder. ```jsx JavaScript icon=code theme={"system"} ; ``` **sortBy** `string[] | (a: FacetValue, b: FacetValue) =\> number` (default: \['isRefined', 'count:desc', 'name:asc'], or `facetOrdering` if set) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` You can also use a sort function that behaves like the standard JavaScript [`compareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax). When you don't set this parameter, and you've set `facetOrdering` for this facet in `renderingContent`, facets are sorted using `facetOrdering` and use the default order as a fallback. > [!NOTE] > > In some situations, refined facet values might not be present in the data > returned by Algolia. ```jsx String theme={"system"} ; ``` ```jsx Function theme={"system"} const sortByName = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function Search() { return ( ); } ``` ```tsx Function (TypeScript) theme={"system"} import type { RefinementListProps } from "react-instantsearch"; const sortByName: RefinementListProps["sortBy"] = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function Search() { return ( ); } ``` **escapeFacetValues** `boolean` (default: true) Escapes the content of the facet values returned by Algolia. When using this parameter, the highlighting tags are always [`mark`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark). ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { RefinementListProps } from 'react-instantsearch'; const transformItems: RefinementListProps['transformItems'] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `noRefinementRoot`. The root element when there are no refinements. * `searchBox`. The search box element. * `noResults`. The root element of the widget when there are no results. * `list`. The list element. * `item`. Each item element. * `selectedItem`. Each selected item element. * `label`. The label of each item. * `checkbox`. The checkbox of each item. * `labelText`. The text element of each label. * `count`. The count of each item. * `showMore`. The "Show more" button. * `disabledShowMore`. The "Show more" button when it's unavailable. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `submitButtonTitle`. The submit button title of the search box. * `resetButtonTitle`. The reset button title search box. * `noResultsText`. The text to display when searching for facets doesn't return any results. * `showMoreButtonText`. The text for the "Show more" button. * `showMoreButtonLabel`. The static accessible name (`aria-label`) for the **Show more** and **Show less** toggle buttons. This translation is only used when [`showMore`](#param-show-more) is `true`. Use a label that works for both expanded and collapsed states because this value doesn't change as the button expands or collapses. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `RefinementList` widget with `useRefinementList`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useRefinementList` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useRefinementList } from "react-instantsearch"; function CustomRefinementList(props) { const { items, hasExhaustiveItems, createURL, refine, sendEvent, searchForItems, isFromSearch, canRefine, canToggleShowMore, isShowingMore, toggleShowMore, } = useRefinementList(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **attribute** `string` *required* The name of the attribute in the records. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```jsx JavaScript icon=code theme={"system"} const refinementListApi = useRefinementList({ attribute: "categories", }); ``` **operator** `'and'|'or'` (default: or) How the [filters](/doc/api-reference/api-parameters/filters) are combined. * `'or'`: Returns results matching any of the selected values. * `'and'`: Returns results matching all selected values. ```jsx JavaScript icon=code theme={"system"} const refinementListApi = useRefinementList({ // ... operator: "and", }); ``` **limit** `number` (default: 10) How many facet values to retrieve. When you set [`showMore`](#param-show-more) and [`showMoreLimit`](#param-show-more-limit), this is the number of facet values to display before clicking the Show more button. ```jsx JavaScript icon=code theme={"system"} const refinementListApi = useRefinementList({ // ... limit: 5, }); ``` **showMore** `boolean` (default: false) Whether to display a button that expands the number of items. ```jsx JavaScript icon=code theme={"system"} const refinementListApi = useRefinementList({ // ... showMore: true, }); ``` **showMoreLimit** `number` The maximum number of items to display if the widget is showing more items. ```jsx JavaScript icon=code theme={"system"} const refinementListApi = useRefinementList({ // ... showMoreLimit: 20, }); ``` **sortBy** `string[] | (a: FacetValue, b: FacetValue) =\> number` (default: \['isRefined', 'count:desc', 'name:asc'], or `facetOrdering` if set) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` You can also use a sort function that behaves like the standard JavaScript [`compareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax). When you don't set this parameter, and you've set `facetOrdering` for this facet in `renderingContent`, facets are sorted using `facetOrdering` and use the default order as a fallback. > [!NOTE] > > In some situations, refined facet values might not be present in the data > returned by Algolia. ```jsx String theme={"system"} const refinementListApi = useRefinementList({ // ... sortBy: ["count:desc", "name:asc"], }); ``` ```jsx Function theme={"system"} const sortByName = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function RefinementList() { const refinementListApi = useRefinementList({ // ... sortBy, }); return <>{/* Your JSX */}; } ``` ```tsx Function (TypeScript) theme={"system"} import type { UseRefinementListProps } from "react-instantsearch"; const sortByName: UseRefinementListProps["sortBy"] = (a, b) => { return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? -1 : 1; }; function RefinementList() { const refinementListApi = useRefinementList({ // ... sortBy, }); return <>{/* Your JSX */}; } ``` **escapeFacetValues** `boolean` (default: true) Escapes the content of the facet values returned by Algolia. When using this parameter, the highlighting tags are always [`mark`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark). ```jsx JavaScript icon=code theme={"system"} const refinementListApi = useRefinementList({ // ... escapeFacetValues: false, }); ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function RefinementList() { const refinementListApi = useRefinementList({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseRefinementListProps } from 'react-instantsearch'; const transformItems: UseRefinementListProps['transformItems'] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function RefinementList() { const refinementListApi = useRefinementList({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `RefinementListItem[]` The list of filtering values returned by Algolia. ```ts Type definition theme={"system"} type RefinementListItem = { /** * The value of the refinement list item. */ value: string; /** * Human-readable value of the refinement list item. */ label: string; /** * Human-readable value of the searched refinement list item. */ highlighted?: string; /** * Number of matched results after refinement is applied. */ count: number; /** * Indicates if the list item is refined. */ isRefined: boolean; }; ``` **hasExhaustiveItems** `boolean` Whether the results are exhaustive. **createURL** `(value: string) =\> string` Creates the next state URL of a selected refinement. **refine** `(value: string) =\> string` Applies the selected refinement. **sendEvent** `(eventType: string, facetValue: string, eventName?: string) =\> void` Sends an event to the Insights middleware. **searchForItems** `(query: string) =\> void` Searches for values in the list. **isFromSearch** `boolean` Whether the values are from 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.) search. **canRefine** `boolean` Whether a refinement can be applied. **canToggleShowMore** `boolean` Whether the Show more button can be activated, meaning there are enough additional items to display, or already displaying over the [`limit`](/doc/api-reference/widgets/refinement-list/react#param-limit) items. **isShowingMore** `boolean` Whether the menu is displaying all the menu items. **toggleShowMore** `() =\> void` Toggles the number of values displayed between [`limit`](/doc/api-reference/widgets/refinement-list/react#param-limit) and [`showMoreLimit`](/doc/api-reference/widgets/refinement-list/react#param-show-more-limit). ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useRefinementList } from "react-instantsearch"; function CustomRefinementList(props) { const { items, refine, searchForItems, canToggleShowMore, isShowingMore, toggleShowMore, } = useRefinementList(props); return ( <> searchForItems(event.currentTarget.value)} />
      {items.map((item) => (
    • ))}
    ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useRefinementList, UseRefinementListProps } from "react-instantsearch"; function CustomRefinementList(props: UseRefinementListProps) { const { items, refine, searchForItems, canToggleShowMore, isShowingMore, toggleShowMore, } = useRefinementList(props); return ( <> searchForItems(event.currentTarget.value)} />
      {items.map((item) => (
    • ))}
    ); } ``` # ais-refinement-list Source: https://www.algolia.com/doc/api-reference/widgets/refinement-list/vue Shows a list of facets for refining search results. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisRefinementList } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisRefinementList, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=refinementList) > > Preview this widget and its behavior. ## About this widget The `ais-refinement-list` widget is one of the most common widgets in a search UI. With this widget, users can filter the dataset based on facets. The widget only displays the most relevant facet values for the current search context. The sort option only affects the facets that are returned by the engine, not which facets are returned. This widget includes a "search for facet values" feature, enabling users to search through the [values of a specific facet attribute](/doc/guides/managing-results/refine-results/faceting#search-for-facet-values). This helps you find uncommon facet values. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. If you are using the [`searchable`](#param-searchable) prop, you also need to make the attribute searchable using the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the `searchable` modifier of [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) with the API. ### Disappearing facet values With many facet values, the available options can change depending on the user's query. The refinement widget displays the most common facet values for a given query. A user's chosen value can vanish if they alter the query. This occurs because only the most common facet values are displayed when there are many options. A previously selected value might not appear if it's uncommon for the new query. To also show less common values, adjust the maximum number of values with the [`configure`](/doc/api-reference/widgets/configure/vue) widget. It doesn't change how many items are shown: the limits you set with [`limit`](/doc/api-reference/widgets/refinement-list/vue#param-limit) and [`show-more-limit`](/doc/api-reference/widgets/refinement-list/vue#param-show-more-limit) still apply. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **attribute** `string` *required* The name of the attribute in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```vue Vue icon=code theme={"system"} ``` **operator** `string ('or'|'and')` (default: or) How to apply refinements. * `'or'`: Returns results matching any of the selected values. * `'and'`: Returns results matching all selected values. Use [filters or facet filters](/doc/guides/managing-results/refine-results/filtering/in-depth/filters-and-facetfilters) for more complex result refinement. ```vue Vue icon=code theme={"system"} ``` **limit** `number` (default: 10) How many facet values to retrieve. When you enable the [`show-more`](#param-show-more) feature, this is the number of facet values to display before clicking the "Show more" button. ```vue Vue icon=code theme={"system"} ``` **show-more** `boolean` (default: false) Whether to display a button that expands the number of items. ```vue Vue icon=code theme={"system"} ``` **show-more-limit** `number` (default: 20) The maximum number of displayed items (only used when [`show-more`](#param-show-more) is set to `true`). ```vue Vue icon=code theme={"system"} ``` **show-more-button-label** `string` The static accessible name (`aria-label`) for the **Show more** and **Show less** toggle buttons. This prop is only used when [`show-more`](#param-show-more) is `true`. Use a label that works for both expanded and collapsed states because this value doesn't change as the button expands or collapses. ```vue Vue icon=code theme={"system"} ``` **searchable** `boolean` (default: false) Adds a search input to let users search for more facet values. To make this feature work, you need to make the attribute searchable using the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard)) or using the `searchable` modifier of [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) with the API. > [!NOTE] > > In some situations, refined facet values might not be present in the data > returned by Algolia. ```vue Vue icon=code theme={"system"} ``` **searchable-placeholder** `string` (default: Search here…) The value of the search input placeholder. ```vue Vue icon=code theme={"system"} ``` **sort-by** `string[] | function` (default: Uses facetOrdering if set, \['isRefined','count:desc','name:asc']) How to sort refinements. Must be one or more of the following strings: * `"count"` (same as `"count:desc"`) * `"count:asc"` * `"count:desc"` * `"name"` (same as `"name:asc"`) * `"name:asc"` * `"name:desc"` * `"isRefined"` (same as `"isRefined:asc"`) * `"isRefined:asc"` * `"isRefined:desc"` It's also possible to give a function, which receives items two by two, like JavaScript's [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). If `facetOrdering` is set for this facet in `renderingContent`, and no value for `sortBy` is passed to this widget, `facetOrdering` is used, and the default order as a fallback. In some situations, refined facet values might not be present in the data returned by Algolia. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `.ais-RefinementList`. The root element of the widget. * `.ais-RefinementList--noRefinement`. The root element of the widget with no refinement. * `.ais-RefinementList-list`. The list of all items. * `.ais-RefinementList-item`. The list item. * `.ais-RefinementList-item--selected`. The selected list item. * `.ais-RefinementList-label`. The label of each item. * `.ais-RefinementList-labelText`. The text of the label of each item. * `.ais-RefinementList-checkbox`. The checkbox element. * `.ais-RefinementList-count`. The count of values for each item. * `.ais-RefinementList-searchBox`. The search box when the widget is searchable. * `.ais-RefinementList-noResults`. The element rendered when the search doesn't have results. * `.ais-RefinementList-showMore`. The button used to display more categories (enabled). * `.ais-RefinementList-showMore--disabled`. The button used to display more categories (turned off). ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The values applicable to this widget. * `isShowingMore: boolean`. Is [`show-more`](#param-show-more) enabled? * `isFromSearch: boolean`. `true` if there are search results. * `canRefine: boolean`. Can the refinement be applied? * `canToggleShowMore: boolean`. Is [`show-more`](#param-show-more) possible? * `toggleShowMore: () => void`. Toggles the number of displayed values between [`limit`](#param-limit) and [`show-more-limit`](#param-show-more-limit). * `searchForItems: (value: string) => void`. A function to search into the facet values. * `refine: (value: string) => void`. A function to select a refinement. * `createURL: (value: string) => string`. A function to return a link for this refinement. * `sendEvent: (eventType: 'click', facetValue: string) => void`. The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. * You can learn more about the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. Where each item is an `object` containing: * `value: string`. The value of the item. * `label: string`. The human-readable value of the item. * `count: number`. The number of matched results after a refinement is applied. * `isRefined: boolean`. Indicates if the refinement is applied. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of an item. **Scope** * `item: object`. An item object. * `refine: (Item.value) => void`. A function to select a refinement. * `createURL: (Item) => string`. A function to return a link for this refinement. Where an item is an `object` containing: * `value: string`. The value of the item. * `label: string`. The human-readable value of the item. * `count: number`. The number of results matched after a refinement is applied. * `isRefined: boolean`. Indicates if the refinement is applied. ```vue Vue icon=code theme={"system"} ``` **showMoreLabel** The slot to override the DOM output of the "Show more" button. **Scope** * `isShowingMore: boolean`. Is the list of items expanded? ```vue Vue icon=code theme={"system"} ``` **noResults** The slot to override the DOM output of the no results placeholder. **Scope** * `query: string`. The current value of the query. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # RelatedProducts Source: https://www.algolia.com/doc/api-reference/widgets/related-products/react Shows related product and content recommendations based on a provided item. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). > [!NOTE] > > If you're using the deprecated [`recommend-react`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-react) UI library, > see [`RelatedProducts`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-react/RelatedProducts). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { RelatedProducts } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=relatedProducts) > > Preview this widget and its behavior. ## About this widget Use the `RelatedProducts` widget to display a list of related products and related content. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) > [!TIP] > > You can also create your own UI with [`useRelatedProducts`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, RelatedProducts } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Item({ item }) { return JSON.stringify(item); } function App() { return ( ); } ``` ### With carousel layout Display the `RelatedProducts` widget as a [scrollable carousel](/doc/api-reference/widgets/related-products/react#param-layout-component) ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, RelatedProducts, Carousel } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Item({ item }) { return JSON.stringify(item); } function App() { return ( ); } ``` ## Props **objectIDs** `string[]` *required* The list of object IDs to get recommendations for. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in > your [pricing plan](https://www.algolia.com/pricing). For example, if you > want recommendations for the array `["A", "B", "C"]`, you'll consume three > requests from your quota, not one. ```jsx JavaScript icon=code theme={"system"} ``` **itemComponent** `(props: \{ item: THit \}) =\> JSX.Element` A component that renders each recommendation from the results. It receives an `item` prop. When not provided, the widget displays the recommendation as a JSON string. ```jsx JavaScript icon=code theme={"system"} item.objectID} />; ``` **headerComponent** `(props: \{ classNames: string[], items: THit[], translations: object \}) =\> JSX.Element` A component that renders a title header. It receives `classNames`, `items` and `translations` props. When not provided, the widget displays a default header. ```jsx JavaScript icon=code theme={"system"} (

    Recommendations ({items.length})

    )} />; ``` **emptyComponent** `() =\> JSX.Element` A component that renders when there are no recommendations. When not provided, the widget displays a default message. ```jsx JavaScript icon=code theme={"system"}

    No recommendations.

    } />; ``` **layoutComponent** `(props: RecommendLayoutProps) =\> JSX.Element` The layout component to use to wrap all items. React InstantSearch provides a provides a built-in `Carousel` layout component with the following props: `classNames`: * `root`. The Carousel's root element. * `list`. The list of recommendations. * `item`. The list item. * `navigation`. The navigation controls. * `navigationPrevious`. The "Previous" navigation control. * `navigationNext`. The "Next" navigation control. `translations`: * `nextButtonLabel`. The label for the next button. * `nextButtonTitle`. The title for the next button. * `previousButtonLabel`. The label for the previous button. * `previousButtonTitle`. The title for the previous button. * `listLabel`. The label for the list of recommendation. * `previousIconComponent`. The component to render as the previous button. * `nextIconComponent`. The component to render as the next button. ```jsx Carousel theme={"system"} import { Carousel } from "react-instantsearch"; (

    Previous

    } nextIconComponent={() =>

    Next

    } /> )} />; ``` ```jsx Custom theme={"system"} (
      {props.items.map((item) => (
    • {item.objectID}
    • ))}
    )} />; ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```jsx JavaScript icon=code theme={"system"} ; ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as an additional 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.) set when there aren't enough recommendations. ```jsx JavaScript icon=code theme={"system"} ; ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: THit[], metadata: \{ results: SearchResults \}) =\> THit[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { RelatedProductsProps } from 'react-instantsearch'; const transformItems: RelatedProductsProps[ 'transformItems' ] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The widget's root element. * `emptyRoot`. The root element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of recommendations. * `item`. The list item. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A mapping of keys to translation values. * `title`. The title of the recommendation section. * `sliderLabel`. The label of the horizontal slider. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the widget's root element. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `RelatedProducts` widget with `useRelatedProducts`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useRelatedProducts` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useRelatedProducts } from "react-instantsearch"; function CustomRelatedProducts(props) { const { items } = useRelatedProducts(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **objectIDs** `string[]` *required* The list of object IDs for which to get recommendations. If you specify multiple object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` you pass in the array counts towards the number of requests in your [pricing plan](https://www.algolia.com/pricing). > For example, if you want recommendations for the array `["A", "B", "C"]`, you'll consume three requests from your quota, not one. ```jsx JavaScript icon=code theme={"system"} const { items } = useRelatedProducts({ // ... objectIDs: ["5723537"], }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useRelatedProducts({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useRelatedProducts({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```jsx JavaScript icon=code theme={"system"} const { items } = useRelatedProducts({ // ... queryParameters: { filters: "category:Book", }, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as additional filters when there aren't enough recommendations. ```jsx JavaScript icon=code theme={"system"} const { items } = useRelatedProducts({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} const { items } = useRelatedProducts({ // ... escapeHTML: false, }); ``` **transformItems** `(items: THit[], metadata: \{ results: SearchResults \}) =\> THit[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function RelatedProducts() { const relatedProductsApi = useRelatedProducts({ // ... transformItems, }); return <>{/*Your JSX*/}; } ``` ```tsx TypeScript theme={"system"} import type { UseRelatedProductsProps } from 'react-instantsearch'; const transformItems: UseRelatedProductsProps[ 'transformItems' ] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function RelatedProducts() { const relatedProductsApi = useRelatedProducts({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `THit[]` The matched recommendations returned from Algolia. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useRelatedProducts } from "react-instantsearch"; function CustomRelatedProducts(props) { const { items } = useRelatedProducts(props); return (
      {items.map((item) => (
    1. {JSON.stringify(item).slice(0, 100)}
    2. ))}
    ); } ``` ```tsx TypeScript theme={"system"} import React from 'react'; import { useRelatedProducts, UseRelatedProductsProps, } from 'react-instantsearch'; function CustomRelatedProducts( props: UseRelatedProductsProps ) { const { items } = useRelatedProducts(props); return (
      {items.map((item) => (
    1. {JSON.stringify(item).slice(0, 100)}
    2. ))}
    ); } ``` # ais-related-products Source: https://www.algolia.com/doc/api-reference/widgets/related-products/vue Shows related product and content recommendations based on a provided item. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisRelatedProducts } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisRelatedProducts, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget Use the `ais-related-products` widget to display a list of related products and related content. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) ## Examples ```vue Vue icon=code theme={"system"} ``` ### With carousel layout To display the recommendations as a scrollable carousel, set the [`layout`](#param-layout) prop to `carousel`. ```vue Vue icon=code theme={"system"} ``` ## Props **objectIDs** `string[]` *required* The list of object IDs to get recommendations for. If you specify several object IDs, you'll get a single set of aggregated results from the requests, ordered by their score. > [!WARNING] > > Each `objectID` counts as one Recommend request, > even when you pass multiple IDs in one array. > For example, `["A", "B", "C"]` counts as three requests and > can affect your [quota](https://www.algolia.com/pricing). ```vue Vue icon=code theme={"system"} ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If you omit `limit` or set it to 0, the widget returns all matching recommendations. ```vue Vue icon=code theme={"system"} ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). The widget only returns recommendations with a greater score. ```vue Vue icon=code theme={"system"} ``` **query-parameters** `Omit\` [Search parameters](/doc/api-reference/search-api-parameters) to apply to the recommendation request. ```vue Vue icon=code theme={"system"} ``` **fallback-parameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as an extra 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.) set when there aren't enough recommendations. ```vue Vue icon=code theme={"system"} ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function to transform or reorder recommendations before the widget displays them. It returns an array of items with the same structure. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **layout** `'list' | 'carousel'` (default: 'list') How to display the recommendations: * `list`. A list of items. * `carousel`. A horizontally scrollable carousel with "Previous" and "Next" navigation buttons. The [`item`](#param-item) slot applies to both layouts. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-RelatedProducts`. The widget's root element. * `ais-RelatedProducts--empty`. The root element without results. * `ais-RelatedProducts-title`. The widget's title element. * `ais-RelatedProducts-container`. The container of the list element. * `ais-RelatedProducts-list`. The list of recommendations. * `ais-RelatedProducts-item`. The list item. With the `carousel` layout, the `ais-RelatedProducts-list` and `ais-RelatedProducts-item` classes apply to the carousel's list and items. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. The widget only uses this slot if you don't provide any other slot. **Scope** * `items: object[]`. The recommendations. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of each recommendation. If you don't provide it, the widget displays the recommendation as a JSON string. **Scope** * `item: object`. A single recommendation with all its attributes. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. ```vue Vue icon=code theme={"system"} ``` **header** The slot to override the DOM output of the title header. The widget doesn't render the header when there are no recommendations. If you don't provide it, the widget displays "Related products" as the title. **Scope** * `items: object[]`. The recommendations. * `translations: object`. The widget's translations, such as `title`. * `classNames: object`. The widget's CSS classes, such as `title`. ```vue Vue icon=code theme={"system"} ``` **empty** The slot to override the DOM output when there are no recommendations. If you don't provide it, the widget displays "No results". ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Related products

    1. ...
    2. ...
    3. ...
    ``` # useRelevantSort Source: https://www.algolia.com/doc/api-reference/widgets/relevant-sort/react React Hook for switching between search modes, exhaustive or relevant sorting. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ## About this Hook **The `useRelevantSort` Hook isn't part of React InstantSearch**, but you can make a custom version with [`useConnector`](/doc/api-reference/widgets/connector/react) and [`connectRelevantSort`](/doc/api-reference/widgets/relevant-sort/js#customize-the-ui-with-connectrelevantsort). This Hook allows switching between [relevant and exhaustive sorting](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort) when searching in virtual replica indices. ## Examples ```jsx JavaScript theme={"system"} import { useConnector } from "react-instantsearch"; import connectRelevantSort from "instantsearch.js/es/connectors/relevant-sort/connectRelevantSort"; export function useRelevantSort(props) { return useConnector(connectRelevantSort, props); } export function RelevantSort(props) { const { isRelevantSorted, refine } = useRelevantSort(props); const relevancyStrictness = isRelevantSorted ? 0 : undefined; return ( ); } ``` ```tsx TypeScript theme={"system"} import { useConnector } from "react-instantsearch"; import connectRelevantSort from "instantsearch.js/es/connectors/relevant-sort/connectRelevantSort"; import type { RelevantSortConnectorParams, RelevantSortWidgetDescription, } from "instantsearch.js/es/connectors/relevant-sort/connectRelevantSort"; export type UseRelevantSortProps = RelevantSortConnectorParams; export function useRelevantSort(props?: UseRelevantSortProps) { return useConnector< RelevantSortConnectorParams, RelevantSortWidgetDescription >(connectRelevantSort, props); } export function RelevantSort(props: UseRelevantSortProps) { const { isRelevantSorted, refine } = useRelevantSort(props); const relevancyStrictness = isRelevantSorted ? 0 : undefined; return ( ); } ``` # ais-relevant-sort Source: https://www.algolia.com/doc/api-reference/widgets/relevant-sort/vue Lets users switch between search modes, exhaustive or relevant sorting. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisRelevantSort } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisRelevantSort > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=vue) > > Preview this widget and its behavior. ## About this widget Virtual indices let you use [relevant sort](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort), a sorting mechanism that favors relevancy over the attribute you're sorting on. The `ais-relevant-sort` widget displays the current search mode when searching in a virtual replica index, and allows users to switch between relevant and regular sorting, which is more exhaustive but can return less relevant results. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-RelevantSort`. The root element of the widget. * `ais-RelevantSort-text`. The text element. * `ais-RelevantSort-button`. The button element. ```vue Vue icon=code theme={"system"} ``` **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `isRelevantSorted: boolean`. True if the current index is using Relevant sort. * `refine: function`. Function to toggle between showing relevant or all results. ```vue Vue icon=code theme={"system"} ``` **text** The slot to override the introductory text. **Scope** * `isRelevantSorted: boolean`. True if the current index is Relevant sorted. ```vue Vue icon=code theme={"system"} ``` **button** The slot to override the toggle button. **Scope** * `isRelevantSorted: boolean`. True if the current index is using Relevant sort. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    We removed some search results to show you the most relevant ones

    Currently showing all results

    ``` # renderState Source: https://www.algolia.com/doc/api-reference/widgets/render-state/react Object with state data about all widgets. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} const { renderState } = useInstantSearch(); ``` ## About this widget The `renderState` property provides all the data and functions from the widgets. It lets you access the render state of any widget, so you can create custom widgets or refine the search outside the InstantSearch lifecycle. This is mainly useful for creating components that interact with multiple widgets at once, without mounting as many widgets. A good example would be showing which individual 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.) hit is applied. ## Examples You can access the render state of the `searchBox` widget. ```jsx JavaScript icon=code theme={"system"} const indexName = ""; function App({ searchClient }) { return ( ); } function RenderState() { const { renderState, indexRenderState } = useInstantSearch(); console.log(renderState[indexName].searchBox); console.log(indexRenderState.searchBox); /* { query: string; refine: Function; clear: Function; isSearchStalled: boolean; widgetParams: object; } */ return null; } ``` ## Working with virtual widgets To access the `renderState` of widgets, you must add them to InstantSearch. If you don't want to add a widget to the UI, but want to get access to its `renderState`, you can add it as a virtual, or renderless widget. ```jsx JavaScript icon=code theme={"system"} function VirtualPagination(props) { usePagination(props); return null; } function NextPage() { const { indexRenderState } = useInstantSearch(); return ( ); } function App({ searchClient }) { return ( ); } ``` ## Example: interactive categories in a hit This example displays a list of categories in a hit, and lets users refine the search by clicking on them. ```jsx JavaScript icon=code theme={"system"} function App({ searchClient }) { return ( ); } function HitComponent({ hit }) { const { indexRenderState } = useInstantSearch(); const refine = indexRenderState.refinementList?.categories?.refine; return (
      {hit.categories.map((category) => (
    • ))}
    ); } ``` ## Type definition ```ts TypeScript icon=code theme={"system"} type RenderState = { [indexId: string]: IndexRenderState; } type IndexRenderState = Partial<{ searchBox: SearchBoxState; autocomplete: AutocompleteState; breadcrumb: BreadcrumbState; clearRefinements: ClearRefinementsState; configure: ConfigureState; currentRefinements: CurrentRefinementsState; hierarchicalMenu: { [attribute: string]: HierarchicalMenuState; } hits: HitsState; infiniteHits: InfiniteHitsState; analytics: AnalyticsState; places: PlacesState; poweredBy: PoweredByState; range: { [attribute: string]: RangeState; ratingMenu: { [attribute: string]: RatingMenuState; }; numericMenu: { [attribute: string]: NumericMenuState; }; voiceSearch: VoiceSearchState; geoSearch: GeoSearchState; queryRules: QueryRulesState; hitsPerPage: HitsPerPageState; pagination: PaginationState; refinementList: { [attribute: string]: RefinementListState; }; answers: AnswersState; }>; ``` # ResultCard Source: https://www.algolia.com/doc/api-reference/widgets/result-card/react Displays an AI Overview card above the results with an Agent Studio recommendation based on the query and the top hits. For more information, see [Agent Studio](/doc/guides/algolia-ai/agent-studio). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { ResultCard } from "react-instantsearch"; ``` ## About this widget Use `ResultCard` to display an "AI Overview" card above your results. The widget sends the query, the active filters, and the first five hits to an Agent Studio agent as [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context), and asks the agent which of these results it recommends and why. The answer streams into the card, and a skeleton placeholder shows until the first text arrives. The card only shows when the search meets both conditions: * A [rule](/doc/guides/managing-results/rules/rules-overview) that matches the search sets `renderingContent.widgets.resultCard.enabled` to `true` in the search response. For more information, see [Activate the card with a rule](#activate-the-card-with-a-rule). * The query has at least two words. The widget sends a new request only when the query, the filters, or the first five hits change. Going to another page of results doesn't send a new request. Users can collapse the card to its header, which hides the answer, the follow-up suggestions, and the **Continue in chat** button. The answer keeps generating in the meantime, and the card stays collapsed for new searches until users restore it. The card clips long answers and shows a **Show more** button to reveal the full text. If the answer completes without any text, the card doesn't render. When a [`Chat`](/doc/api-reference/widgets/chat/react) widget with the same `agentId` is on the same index, the card shows a **Continue in chat** button and up to two follow-up suggestions from the agent. Both hand the card's conversation to the chat and open it. A suggestion is also sent to the chat as the next message. If the chat is generating a response, the handoff doesn't happen. That makes the widget an [entry point](/doc/api-reference/widgets/chat/react#param-disable-trigger-validation) for the chat, so a page with both doesn't also need a [`ChatTrigger`](/doc/api-reference/widgets/chat-trigger/react) widget. Without a matching `Chat` widget, the card hides these actions and, in development, logs a warning. ## Activate the card with a rule The widget adds the [rule context](/doc/api-reference/api-parameters/ruleContexts) `agent-studio-result-card-AGENT_ID` to every search, where `_` replaces any character in the agent ID other than letters, digits, `_`, and `-`. Use this context as a rule condition to limit the rule to searches that include the widget. To show the card, add a rule whose consequence sets [`renderingContent`](/doc/api-reference/api-parameters/renderingContent) in its search parameters: ```json JSON icon=braces theme={"system"} { "renderingContent": { "widgets": { "resultCard": { "enabled": true } } } } ``` ## Examples ```jsx JavaScript icon=code expandable theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Chat, Hits, ResultCard, SearchBox } from "react-instantsearch"; const searchClient = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY"); function App() { return ( ); } ``` ## Props **agentId** `string` *required* The unique identifier of the agent to connect to. You can find the `agentId` in the [Agent Studio dashboard](https://dashboard.algolia.com/generativeAi/agent-studio/agents). It's required even with a custom [`transport`](#param-transport), because the widget derives its rule context from it. The card only hands its conversation to a `Chat` widget with the same `agentId`. ```jsx JavaScript icon=code theme={"system"} ``` **transport** `HttpChatTransportInitOptions` A custom transport object to send the card's requests to your own backend instead of Agent Studio. It has the same shape as the [`transport`](/doc/api-reference/widgets/chat/react#param-transport) prop of the `Chat` widget. You can't use it together with [`requestOptions`](#param-request-options). ```jsx JavaScript icon=code theme={"system"} ``` **requestOptions** `object` Request options to send with the built-in Agent Studio completion requests. You can't use it together with [`transport`](#param-transport). The object accepts the following properties: * `queryParameters` (`Record`). Query parameters to append to each completion request. * `headers` (`Record | Headers`). Headers to send with each completion request. ```jsx JavaScript icon=code theme={"system"} ``` **layoutComponent** `(props: ResultCardLayoutComponentProps) =\> JSX.Element | null` A component to replace the default card with custom markup. The component owns the full rendering, including every status and the retry and handoff actions. It receives the same values as the [`useResultCard`](#hook) Hook. ```jsx JavaScript icon=code expandable theme={"system"} function CustomLayout({ status, messages, error, retry }) { if (status === "hidden") { return null; } if (status === "failed") { return (

    {error ? error.message : "Something went wrong."}

    ); } const answer = messages .filter((message) => message.role === "assistant") .flatMap((message) => message.parts) .filter((part) => part.type === "text") .map((part) => part.text) .join(""); return (

    {answer || "Loading..."}

    ); } ; ``` **classNames** `Partial\` The CSS classes you can override and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com/) or [Tailwind CSS](https://tailwindcss.com/). * `root`. The root element of the widget. * `header`. The header element. * `headerTitle`. The header title element. * `continueButton`. The **Continue in chat** button. * `minimizeButton`. The button that minimizes or maximizes the card. * `body`. The element that holds the answer and clips it. * `loader`. The skeleton placeholder shown while the answer loads. * `message`. Each answer message. * `suggestions`. The follow-up suggestions container. * `expandButton`. The button that shows or clips a long answer. ```jsx JavaScript icon=code theme={"system"} ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `headerTitle`. The title displayed in the header. Defaults to `"AI Overview"`. * `continueInChatText`. The text of the button that hands the conversation to the chat. Defaults to `"Continue in chat"`. * `minimizeLabel`. The accessible label of the button that minimizes the card. Defaults to `"Minimize"`. * `maximizeLabel`. The accessible label of the button that maximizes a minimized card. Defaults to `"Maximize"`. * `expandText`. The text of the button that shows a clipped answer in full. Defaults to `"Show more"`. * `collapseText`. The text of the button that clips an expanded answer. Defaults to `"Show less"`. * `retryText`. The text of the retry button shown when the request fails. Defaults to `"Retry"`. ```jsx JavaScript icon=code theme={"system"} ``` **...props** `React.ComponentProps\<'section'\>` Any `section` prop to forward to the root element of the widget. It doesn't apply when you set [`layoutComponent`](#param-layout-component). ```jsx JavaScript icon=code theme={"system"} ``` ## Hook Use the `useResultCard` Hook to build a fully custom UI. It accepts the same `agentId`, `transport`, and `requestOptions` props as the widget and returns: * `status` (`'hidden' | 'loading' | 'streaming' | 'complete' | 'failed'`). The card status. It's `hidden` when no rule enables the card or when the query is shorter than two words. It's `loading` while the widget waits to send the request and until the response starts streaming. * `query` (`string`). The query the answer is about. * `messages` (`UIMessage[]`). The card's conversation: the user message, then the assistant answer as it streams. The user message contains the full question that the widget sends to the agent. To display the query, use `query` instead. * `error` (`Error | undefined`). The error of the latest failed request. It's only set when `status` is `failed`. * `suggestions` (`string[] | undefined`). Follow-up suggestions sent by the agent with the answer. * `retry` (`() => void`). Sends the request again. * `canContinueInChat` (`boolean`). Whether a `Chat` widget with the same `agentId` is on the same index. * `continueInChat` (`(message?: string) => void`). Hands the conversation to the `Chat` widget and opens it. When you pass `message`, the chat sends it as the next message. * `expanded` (`boolean`). Whether a long answer shows in full instead of clipped. * `setExpanded` (`(expanded: boolean) => void`). Shows or clips a long answer. * `sendEvent` (`function`). Sends an event to the Insights middleware. ```jsx JavaScript icon=code expandable theme={"system"} import { useResultCard } from "react-instantsearch"; function CustomResultCard() { const { status, messages, canContinueInChat, continueInChat } = useResultCard({ agentId: "AGENT_ID", }); if (status === "hidden") { return null; } const answer = messages .filter((message) => message.role === "assistant") .flatMap((message) => message.parts) .filter((part) => part.type === "text") .map((part) => part.text) .join(""); return (

    {answer || "Loading..."}

    {status === "complete" && canContinueInChat && ( )}
    ); } ``` # ais-result-card Source: https://www.algolia.com/doc/api-reference/widgets/result-card/vue Displays an AI Overview card above the results with an Agent Studio recommendation based on the query and the top hits. This widget is **experimental** and is subject to change in minor versions. For more information, see [Agent Studio](/doc/guides/algolia-ai/agent-studio). ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisResultCard } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisResultCard, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget Use `ais-result-card` to display an "AI Overview" card above your results. The widget sends the query, the active filters, and the first five hits to an Agent Studio agent as [per-turn context](/doc/guides/algolia-ai/agent-studio/how-to/turn-context), and asks the agent which of these results it recommends and why. The answer streams into the card, and a skeleton placeholder shows until the first text arrives. The card only shows when the search meets both conditions: * A [rule](/doc/guides/managing-results/rules/rules-overview) that matches the search sets `renderingContent.widgets.resultCard.enabled` to `true` in the search response. For more information, see [Activate the card with a rule](#activate-the-card-with-a-rule). * The query has at least two words. The widget sends a new request only when the query, the filters, or the first five hits change. Going to another page of results doesn't send a new request. Users can collapse the card to its header, which hides the answer, the follow-up suggestions, and the **Continue in chat** button. The answer keeps generating in the meantime, and the card stays collapsed for new searches until users restore it. The card clips long answers and shows a **Show more** button to reveal the full text. If the answer completes without any text, the card doesn't render. When an [`ais-chat`](/doc/api-reference/widgets/chat/vue) widget with the same `agent-id` is on the same index, the card shows a **Continue in chat** button and up to two follow-up suggestions from the agent. Both hand the card's conversation to the chat and open it. A suggestion is also sent to the chat as the next message. If the chat is generating a response, the handoff doesn't happen. That makes the widget an entry point for the chat, so a page with both doesn't also need an [`ais-chat-trigger`](/doc/api-reference/widgets/chat-trigger/vue) widget. Without a matching `ais-chat` widget, the card hides these actions and, in development, logs a warning. ## Activate the card with a rule The widget adds the [rule context](/doc/api-reference/api-parameters/ruleContexts) `agent-studio-result-card-AGENT_ID` to every search, where `_` replaces any character in the agent ID other than letters, digits, `_`, and `-`. Use this context as a rule condition to limit the rule to searches that include the widget. To show the card, add a rule whose consequence sets [`renderingContent`](/doc/api-reference/api-parameters/renderingContent) in its search parameters: ```json JSON icon=braces theme={"system"} { "renderingContent": { "widgets": { "resultCard": { "enabled": true } } } } ``` ## Examples ```vue Vue icon=code expandable theme={"system"} ``` ## Props **agent-id** `string` *required* The unique identifier of the agent to connect to. You can find the agent ID in the [Agent Studio dashboard](https://dashboard.algolia.com/generativeAi/agent-studio/agents). It's required even with a custom [`transport`](#param-transport), because the widget derives its rule context from it. The card only hands its conversation to an `ais-chat` widget with the same `agent-id`. ```vue Vue icon=code theme={"system"} ``` **transport** `object` A custom transport object to send the card's requests to your own backend instead of Agent Studio. It has the same shape as the [`transport`](/doc/api-reference/widgets/chat/vue#param-transport) prop of the `ais-chat` widget. You can't use it together with [`request-options`](#param-request-options). ```vue Vue icon=code theme={"system"} ``` **request-options** `object` Request options to send with the built-in Agent Studio completion requests. You can't use it together with [`transport`](#param-transport). The object accepts the following properties: * `queryParameters` (`Record`). Query parameters to append to each completion request. * `headers` (`Record | Headers`). Headers to send with each completion request. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-ResultCard`. The root element of the widget. * `ais-ResultCard-header`. The header element. * `ais-ResultCard-headerTitle`. The header title element. * `ais-ResultCard-continueButton`. The **Continue in chat** button. * `ais-ResultCard-minimizeButton`. The button that minimizes or maximizes the card. * `ais-ResultCard-body`. The element that holds the answer and clips it. * `ais-ResultCard-loader`. The skeleton placeholder shown while the answer loads. * `ais-ResultCard-message`. Each answer message. * `ais-ResultCard-suggestions`. The follow-up suggestions container. * `ais-ResultCard-expandButton`. The button that shows or clips a long answer. ```vue Vue icon=code theme={"system"} ``` **translations** `object` A dictionary of translations to customize the UI text and support internationalization. * `headerTitle`. The title displayed in the header. Defaults to `"AI Overview"`. * `continueInChatText`. The text of the button that hands the conversation to the chat. Defaults to `"Continue in chat"`. * `minimizeLabel`. The accessible label of the button that minimizes the card. Defaults to `"Minimize"`. * `maximizeLabel`. The accessible label of the button that maximizes a minimized card. Defaults to `"Maximize"`. * `expandText`. The text of the button that shows a clipped answer in full. Defaults to `"Show more"`. * `collapseText`. The text of the button that clips an expanded answer. Defaults to `"Show less"`. * `retryText`. The text of the retry button shown when the request fails. Defaults to `"Retry"`. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **layout** The slot to replace the whole card with custom markup. The slot owns the full rendering, including every status and the retry and handoff actions. When you use it, the [`class-names`](#param-class-names) and [`translations`](#param-translations) props don't apply. **Scope** * `status: 'hidden' | 'loading' | 'streaming' | 'complete' | 'failed'`. The card status. It's `hidden` when no rule enables the card or when the query is shorter than two words. It's `loading` while the widget waits to send the request and until the response starts streaming. * `query: string`. The query the answer is about. * `messages: UIMessage[]`. The card's conversation: the user message, then the assistant answer as it streams. The user message contains the full question that the widget sends to the agent. To display the query, use `query` instead. * `error: Error | undefined`. The error of the latest failed request. It's only set when `status` is `failed`. * `suggestions: string[] | undefined`. Follow-up suggestions sent by the agent with the answer. * `retry: () => void`. Sends the request again. * `canContinueInChat: boolean`. Whether an `ais-chat` widget with the same `agent-id` is on the same index. * `continueInChat: (message?: string) => void`. Hands the conversation to the `ais-chat` widget and opens it. When you pass `message`, the chat sends it as the next message. * `expanded: boolean`. Whether a long answer shows in full instead of clipped. * `setExpanded: (expanded: boolean) => void`. Shows or clips a long answer. * `sendEvent: function`. Sends an event to the Insights middleware. ```vue Vue icon=code expandable theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ... AI Overview
    ...
    ``` While the answer loads, the body holds a skeleton placeholder instead of the message: ```html HTML icon=code-xml theme={"system"}
    ``` When users collapse the card, the `ais-ResultCard-minimizeButton` button has `aria-expanded="false"` and the `Maximize` label, and the body, the expand button, the **Continue in chat** button, and the suggestions don't render. When users expand a long answer, or when the request fails, the body has the `ais-ResultCard-body--expanded` class. When the answer is complete and isn't clipped, the follow-up suggestions render after the body in a `div` with the `ais-ChatPromptSuggestions ais-ResultCard-suggestions` classes. # ais-search-box Source: https://www.algolia.com/doc/api-reference/widgets/search-box/vue Lets users perform a search using text input. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisSearchBox } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisSearchBox, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=searchBox) > > Preview this widget and its behavior. ## About this widget The `ais-search-box` widget is used to let users set a text-based 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.). This usually is the main entry point to start the search in an InstantSearch context. It's usually placed at the top of a search experience, so that users can start searching right away. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **placeholder** `string` (default: Search here…) The input placeholder. ```vue Vue icon=code theme={"system"} ``` **submit-title** `string` (default: search) The submit button text. ```vue Vue icon=code theme={"system"} ``` **reset-title** `string` (default: clear) The clear button text. ```vue Vue icon=code theme={"system"} ``` **autofocus** `boolean` (default: false) Whether to automatically focus on the input when rendered. ```vue Vue icon=code theme={"system"} ``` **ignore-composition-events** `boolean` (default: false) Whether to update the search state in the middle of a composition session. This is useful when users need to search using non-latin characters. ```vue Vue icon=code theme={"system"} ``` **show-loading-indicator** `boolean` (default: false) Whether to show the loading indicator (replaces the submit button if the search is stalled). ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-SearchBox`. The root element of the widget. * `ais-SearchBox-form`. The form element. * `ais-SearchBox-input`. The input element. * `ais-SearchBox-submit`. The submit button element. * `ais-SearchBox-submitIcon`. Magnifier icon used with The search input. * `ais-SearchBox-reset`. The reset button element. * `ais-SearchBox-resetIcon`. The reset button icon. * `ais-SearchBox-loadingIndicator`. The loading indicator element. * `ais-SearchBox-loadingIcon`. The loading indicator icon. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. Note that when you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `currentRefinement: string`: the current query used for the search. * `isSearchStalled: boolean`: whether InstantSearch has detected that searches are stalled. * `refine: (string) => void`: the function to change the query. ```vue Vue icon=code theme={"system"} ``` **submit-icon** The slot to override the DOM output of the submit icon. **Scope** No props are provided. ```vue Vue icon=code theme={"system"} ``` **reset-icon** The slot to override the DOM output of the reset icon. **Scope** No props are provided. ```vue Vue icon=code theme={"system"} ``` **loading-indicator** The slot to override the DOM output of the loading indicator. **Scope** No props are provided. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"} ``` # getServerState Source: https://www.algolia.com/doc/api-reference/widgets/server-state/react React Hook for retrieving the server-side InstantSearch state in Next.js applications. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```ts Signature theme={"system"} const serverState = await getServerState(children: ReactNode, { renderToString?: (node: ReactElement) => unknown, }) ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { getServerState } from "react-instantsearch"; ``` ## About this function > [!NOTE] > > If you use Next.js's App Router, a dedicated > `react-instantsearch-nextjs` package is available. > To learn more, see [App router](/doc/guides/building-search-ui/going-further/server-side-rendering/react#with-next-js-app-router). `getServerState` is the function that retrieves the server state to pass to [`InstantSearchSSRProvider`](/doc/api-reference/widgets/ssr-provider/react). To learn more, see [Server-side rendering](/doc/guides/building-search-ui/going-further/server-side-rendering/react). ## Examples ```js server.js theme={"system"} import { getServerState } from "react-instantsearch"; const serverState = await getServerState(); ``` ```jsx App.js theme={"system"} import { InstantSearch, InstantSearchSSRProvider } from "react-instantsearch"; function App({ serverState }) { return ( {/* Widgets */} ); } ``` Check the [server-side rendering example](https://github.com/algolia/instantsearch/blob/master/examples/react/ssr/src/server.jsx) for full markup. ## Parameters **children** `React.ReactNode` The part of the app that renders [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react). As an optimization you can pass a minimal version of the application that only uses the Hooks of the widgets used in the search, with the same options. ```js JavaScript icon=code theme={"system"} const serverState = await getServerState(); ``` **renderToString** `(node: React.ReactElement) =\> unknown` The react method used to render the app. This can be `renderToString` from `react-dom/server` or an alternative method. ```jsx JavaScript icon=code theme={"system"} import { renderToString } from "react-dom/server"; const serverState = await getServerState(, { renderToString, }); ``` ## Returns **serverState** `InstantSearchServerState` The server state as a Promise to pass to [`InstantSearchSSRProvider`](/doc/api-reference/widgets/ssr-provider/react). ```ts Type definitions theme={"system"} type InitialResult = { state: SearchParameters; results: SearchResults; }; type InitialResults = Record; type InstantSearchServerState = { initialResults: InitialResults; }; ``` # simple Source: https://www.algolia.com/doc/api-reference/widgets/simple-state-mapping/react Default mapping between InstantSearch state and the URL. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```js Signature theme={"system"} const stateMapping = simple(); ``` ## Import ```js JavaScript icon=code theme={"system"} import { simple } from "instantsearch.js/es/lib/stateMappings"; ``` ## About this function This `simple` state mapping is the default for the `InstantSearch` component's [routing](/doc/api-reference/widgets/instantsearch/react#param-routing) prop. The router provides an API that lets you customize some of its behaviors. For more information, see [Routing URLs](/doc/guides/building-search-ui/going-further/routing-urls/react). The only transformation applied by the function is the omission of `configure`. ```js JavaScript icon=code theme={"system"} import { simple } from "instantsearch.js/es/lib/stateMappings"; const stateMapping = simple(); stateMapping.stateToRoute({ instant_search: { query: "Apple", page: 5, configure: { hitsPerPage: 4, }, }, }); // gives as output: // { // instant_search: { // query: 'Apple', // page: 5, // }, // } ``` ## Examples ```jsx JavaScript icon=code theme={"system"} import { InstantSearch } from "react-instantsearch"; import { simple } from "instantsearch.js/es/lib/stateMappings"; const routing = { stateMapping: simple(), }; function App() { return ( {/* ... */} ); } ``` # simple Source: https://www.algolia.com/doc/api-reference/widgets/simple-state-mapping/vue Default mapping between InstantSearch state and the URL. ```js Signature theme={"system"} import { simple } from "instantsearch.js/es/lib/stateMappings"; const routing = { stateMapping: simple(), }; ``` ## About this widget This `simple` state mapping is the default for the `ais-instant-search` wrapper's [routing](/doc/api-reference/widgets/instantsearch/vue#param-routing) prop. The router provides an API that lets you customize some of its behaviors. For more information, see [Routing URLs](/doc/guides/building-search-ui/going-further/routing-urls/vue). The only transformation applied by the function is the omission of `configure`. ```js JavaScript icon=code theme={"system"} import { simple } from "instantsearch.js/es/lib/stateMappings"; simple().stateToRoute({ instant_search: { query: "Apple", page: 5, configure: { hitsPerPage: 4, }, }, }); // gives as output: // { // instant_search: { // query: 'Apple', // page: 5, // }, // } ``` ## Examples ```vue Vue icon=code theme={"system"} ``` # Snippet Source: https://www.algolia.com/doc/api-reference/widgets/snippet/react Shortens attributes from hits for display in search results. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { Snippet } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=react) > > Preview this widget and its behavior. ## About this widget `Snippet` is a widget that displays attributes in a shorter form (a snippet). Snippeted attributes are also [highlighted](/doc/api-reference/widgets/highlight/react). It uses Algolia's [snippeting feature](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/react#snippeting) with the `hit` object provided by [`Hits`](/doc/api-reference/widgets/hits/react), [`InfiniteHits`](/doc/api-reference/widgets/infinite-hits/react), or their Hooks. To determine which [attributes](#param-attribute) should be snippeted, set them from [the Algolia dashboard](https://dashboard.algolia.com/explorer/configuration/snippeting), [the CLI](/doc/tools/cli/commands/objects/browse#highlighting-and-snippeting), or with the API (using the [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet) parameter): ```jsx JavaScript icon=code theme={"system"} ; ``` With `attributesToSnippet`, you can also set the snippet's size to a specific number of words (it defaults to 10). For example, `attributesToSnippet={['description:5']}`. You can even use this widget with objects other than Algolia search results. The only requirement is that the provided value must have the following structure: ```json JSON icon=braces theme={"system"} { "_snippetResult": { "attributeName": { "value": "..." } } } ``` ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Hits, Snippet } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Hit({ hit }) { return (
    ); } function App() { return ( ); } ``` ## Props **attribute** `keyof THit` *required* The attribute to snippet in the record. For deeply nested objects, specify a dot-separated value like `actor.bio`. ```jsx JavaScript icon=code theme={"system"} ; ``` **hit** `THit` *required* The original `hit` object provided to the component. The object must have a `_snippetResult[attribute].value` property. ```jsx JavaScript icon=code theme={"system"} ; ``` **highlightedTagName** `React.ReactType\` (default: mark) The name of the HTML element to wrap the highlighted parts of the string with. ```jsx JavaScript icon=code theme={"system"} ; ``` **nonHighlightedTagName** `React.ReactType\` (default: span) The name of the HTML element to wrap the non-highlighted parts of the string with. ```jsx JavaScript icon=code theme={"system"} ; ``` **separator** `React.ReactNode` (default: ,) The character between each item when the attribute to highlight is an array. ```jsx JavaScript icon=code theme={"system"} ; ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `highlighted`. The highlighted parts. * `nonHighlighted`. The non-highlighted parts. * `separator`. The separator elements between highlighted parts. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'span'\>` Any `span` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` # ais-snippet Source: https://www.algolia.com/doc/api-reference/widgets/snippet/vue Shortens attributes from hits for display in search results. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisSnippet } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisSnippet > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=vue) > > Preview this widget and its behavior. ## About this widget The `ais-snippet` widget displays attributes in a shorter form (a snippet). Snippeted attributes are also [highlighted](/doc/api-reference/widgets/highlight/vue). It uses Algolia's [snippeting feature](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/vue#snippeting) in combination with the [`ais-hits`](/doc/api-reference/widgets/hits/vue) or [`ais-infinite-hits`](/doc/api-reference/widgets/infinite-hits/vue) widgets. To determine which [attributes](#param-attribute) should be snippeted, set them from [the Algolia dashboard](https://dashboard.algolia.com/explorer/configuration/snippeting), [the CLI](/doc/tools/cli/commands/objects/browse#highlighting-and-snippeting), or with the API (using the [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet) parameter): ```vue Vue icon=code theme={"system"} ``` With `attributesToSnippet`, you can also set the snippet's size to a specific number of words (it defaults to 10). For example, `:attributesToSnippet="['description:5']`. ## Examples ### Basic usage ```vue Vue icon=code theme={"system"} ``` ### Access a nested property Given this record: ```json JSON icon=braces theme={"system"} { "objectID": 1, "meta": { "description": "my description" } } ``` You can access the snippeted version by specifying the path separating levels with dots: ```vue Vue icon=code theme={"system"} ``` ## Props **attribute** `string` *required* The attribute of the record to snippet. For deeply nested objects, specify a dot-separated value like `actor.bio`. ```vue Vue icon=code theme={"system"} ``` **hit** `object` *required* The original `hit` object provided to the function. For this to work, the provided object must have a `_snippetResult[attribute].value` property. ```vue Vue icon=code theme={"system"} ``` **highlighted-tag-name** `string` (default: mark) The name of the HTML element to wrap the highlighted parts of the string. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-Snippet`. The root element of the widget. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"} This is the highlighted text ``` # SortBy Source: https://www.algolia.com/doc/api-reference/widgets/sort-by/react Shows a list of indices for alternative sorting or ranking of search results. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { SortBy } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=sortBy) > > Preview this widget and its behavior. ## About this widget This widget displays a list of indices, allowing a user to change the way hits are sorting (with [replica indices](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas)). Another common use case for this widget is to let users switch between different indices. For this widget to work, you must define all indices that you pass down as options as replicas of the main 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.). > [!TIP] > > You can also create your own UI with [`useSortBy`](#hook). ### Requirements You must set all indices you define in [`items`](#param-items) as [replicas of the main index](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, SortBy } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **items** `SortByProps['items']` *required* A list of different indices to choose from. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { SortByProps } from 'react-instantsearch'; const transformItems: SortByProps['transformItems'] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `select`. The `select` element. * `option`. The `option` element. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `SortBy` widget with `useSortBy`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useSortBy` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useSortBy } from "react-instantsearch"; function CustomSortBy(props) { const { initialIndex, currentRefinement, options, refine, canRefine } = useSortBy(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **items** `UseSortByProps['items']` *required* A list of different indices to choose from. ```js JavaScript icon=code theme={"system"} const sortByApi = useSortBy({ items: [ { label: "Featured", value: "instant_search" }, { label: "Price (asc)", value: "instant_search_price_asc" }, { label: "Price (desc)", value: "instant_search_price_desc" }, ], }); ``` **transformItems** `(items: object[], metadata: \{ results: SearchResults \}) =\> object[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function SortBy() { const sortByApi = useSortBy({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseSortByProps } from "react-instantsearch"; const transformItems: UseSortByProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.label.toUpperCase(), })); }; function SortBy() { const sortByApi = useSortBy({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **initialIndex** `string` The initially selected index. **currentRefinement** `string` The currently selected index. **options** `SortByItem[]` All the available indices. ```ts TypeScript icon=code theme={"system"} type SortByItem = { /** * The name of the index to target. */ value: string; /** * The label of the index to display. */ label: string; }; ``` **refine** `(value: string) =\> void` Switches indices and triggers a new search. **canRefine** `boolean` Whether the search can be refined. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useSortBy } from "react-instantsearch"; function SortBy(props) { const { currentRefinement, options, refine } = useSortBy(props); return ( ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useSortBy, UseSortByProps } from "react-instantsearch"; function SortBy(props: UseSortByProps) { const { currentRefinement, options, refine } = useSortBy(props); return ( ); } ``` # ais-sort-by Source: https://www.algolia.com/doc/api-reference/widgets/sort-by/vue Shows a list of indices for alternative sorting or ranking of search results. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisSortBy } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisSortBy > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=sortBy) > > Preview this widget and its behavior. ## About this widget This widget displays a list of indices, allowing a user to change the way hits are sorting (with [replica indices](/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas)). Another common use case for this widget is to let users switch between different indices. For this widget to work, you must define all indices that you pass down as options as replicas of the main 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.). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **items** `object[]` *required* The list of indices to search in, with each item: * `label: string`. The label of the index to display. * `value: string`. The name of the index to target. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-SortBy`. The root of the widget * `ais-SortBy-select`. The `select` element. * `ais-SortBy-option`. The `option` element of the `select`. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `items: object[]`. The list of items the widget can display. * `currentRefinement: string`. The currently applied refinement. * `canRefine: boolean`. Whether a refinement can be applied. * `refine: (value: string) => void`. The function to call with the next value of the index name. Where each item is an `object` containing: * `label: string`. The label of the index to display. * `value: string`. The name of the index to target. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # InstantSearchSSRProvider Source: https://www.algolia.com/doc/api-reference/widgets/ssr-provider/react Forwards the server-side search state to the InstantSearch root component. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} {children} ``` ## Import ```js JavaScript icon=code theme={"system"} import { InstantSearchSSRProvider } from "react-instantsearch"; ``` ## About this component > [!NOTE] > > If you use Next.js's App Router, a dedicated > `react-instantsearch-nextjs` package is available. > To learn more, see [App router](/doc/guides/building-search-ui/going-further/server-side-rendering/react#with-next-js-app-router). `InstantSearchSSRProvider` is the provider component that forwards the server state to [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react). It's designed to support server-side rendering (SSR) in your InstantSearch application. To retrieve the server state and pass it down to the component, you need to use [`getServerState`](/doc/api-reference/widgets/server-state/react). To learn more, see [Server-side rendering](/doc/guides/building-search-ui/going-further/server-side-rendering/react) ## Examples ```jsx JavaScript theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, InstantSearchSSRProvider } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App({ serverState }) { return ( {/* Widgets */} ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, InstantSearchServerState, InstantSearchSSRProvider, } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); type AppProps = { serverState?: InstantSearchServerState; }; function App({ serverState }: AppProps) { return ( {/* Widgets */} ); } ``` Check the [SSR example](https://github.com/algolia/react-instantsearch/blob/master/examples/server-side-rendering/src/App.js) for full markup. ## Props **initialResults** `InitialResults` The initial results to forward to [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react). Spread the whole server state object returned by [`getServerState`](/doc/api-reference/widgets/server-state/react), which contains `initialResults`. ```jsx JavaScript theme={"system"} {/* Widgets */} ; ``` ```ts TypeScript theme={"system"} type InitialResult = { state: SearchParameters; results: SearchResults; }; type InitialResults = Record; ``` **children** `React.ReactNode` The part of the app that renders [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react). # StateResults Source: https://www.algolia.com/doc/api-reference/widgets/state-results/react Provides the current search state and results to components. > [!NOTE] > > This is the React InstantSearch v7 documentation. > React InstantSearch v7 is the latest version of React InstantSearch and the stable version of React InstantSearch Hooks. > > If you were using React InstantSearch v6, you can [upgrade to v7](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > > If you were using React InstantSearch Hooks, you can still use the React InstantSearch v7 documentation, but check the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7) for necessary changes. > > If you want to keep using React InstantSearch v6, you can find the [archived documentation](https://www.algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ## About this widget To reach the search state, results, or other metadata in React InstantSearch, you can use the [`useInstantSearch`](/doc/api-reference/widgets/use-instantsearch/react) Hook. This covers the same use cases as the `stateResults` widget used to. # ais-state-results Source: https://www.algolia.com/doc/api-reference/widgets/state-results/vue Provides the current search state and results to components. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisStateResults } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisStateResults > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=instantsearch\&flavor=vue) > > Preview this widget and its behavior. ## About this widget The `ais-state-results` allows you reach into the current search state, for example, for conditional rendering or other rendering that depends on what the current refinement or results are. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **catch-error** `boolean` If `true`, errors thrown by InstantSearch are handled by InstantSearch and are available in the `error` property from the slot. Set `catch-error` to `false` if you want to handle these errors elsewhere. ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** The scope is an object with the keys `state` and `results`. **`state`** * `query: string`. The current value of the query. * `page: number`. The currently applied page. * Any other currently applied search parameters. You can find all regular parameters on the [search parameters page](/doc/api-reference/api-parameters). This also includes parameters from the [Helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as, `disjunctiveFacetsRefinements`. **`results`** * `_rawResults: object[]`. The raw response from the Algolia API. * `query: string`. The value of the query from the latest response. * `hits: object[]`. Matching hits from the latest response. * Other values from the latest response. You can find all regular response parameters on [the response format page](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response). This also includes parameters from the [Helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), for example, `disjunctiveFacetsRefinements`. **`status`** The status is a string which can be `idle`, `loading`, `stalled`, or `error`. To display a loading indicator, prefer using `stalled`, as `loading` happens for a short amount of time only. **`error`** If the status is `error`, there is also an error object available in the scope. ```vue No results theme={"system"} ``` ```vue Banner from rules theme={"system"} ``` ```vue Loading indicator theme={"system"} ``` # Stats Source: https://www.algolia.com/doc/api-reference/widgets/stats/react Shows data about the performed search. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { Stats } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=stats) > > Preview this widget and its behavior. ## About this widget `Stats` is a widget that displays the total number of matching hits and the time it took to get them (time spent in the Algolia server). > [!TIP] > > You can also create your own UI with [`useStats`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, Stats } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A dictionary of translations to customize the UI text and support internationalization. * `rootElementText`. The text for the statistics. ```jsx JavaScript icon=code theme={"system"} ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `Stats` widget with `useStats`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useStats` Hook returns [APIs](#hook). ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useStats } from "react-instantsearch"; function CustomStats() { const { hitsPerPage, nbHits, areHitsSorted, nbSortedHits, nbPages, page, processingTimeMS, query, } = useStats(); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ; ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **hitsPerPage** `number` The maximum number of hits returned per page. **nbHits** `number` The number of hits matched by the query. **areHitsSorted** `boolean` Indicated whether relevant sort is applied to the result. **nbSortedHits** `number` The number of sorted hits from relevant sort. **nbPages** `number` The number of returned pages. The calculation is based on the total number of hits (`nbHits`) divided by the number of hits per page (`hitsPerPage`), rounded up to the nearest integer. **page** `number` The position of the current page (zero-based). **processingTimeMS** `number` The time the server took to process the request, in milliseconds. This doesn't include network time. **query** `string` The query send to the server. ### Example ```tsx theme={"system"} import React from "react"; import { useStats } from "react-instantsearch"; function CustomStats() { const { nbHits, processingTimeMS, query } = useStats(); return ( {nbHits.toLocaleString()} results found in{" "} {processingTimeMS.toLocaleString()}ms for {query}. ); } ``` # ais-stats Source: https://www.algolia.com/doc/api-reference/widgets/stats/vue Shows data about the performed search. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisStats } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisStats > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=stats) > > Preview this widget and its behavior. ## About this widget The `ais-stats` widget displays the total number of matching hits and the time it took to get them (time spent in the Algolia server). ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-Stats`. The root element of the widget. * `ais-Stats-text`. The text element. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. Note that when you implement this slot, none of the other slots will change the output, as the default slot surrounds them. ### Scope * `hitsPerPage: number`. The maximum number of hits returned per page. * `nbPages: number`. The number of pages returned. Calculation is based on the total number of hits (`nbHits`) divided by the number of hits per page (`hitsPerPage`), rounded up to the nearest integer. * `nbHits: number`. The number of hits matched by 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.). * `areHitsSorted: boolean`. True if the index is currently using relevant sort and displaying only sorted hits. * `nbSortedHits: number`. The number of sorted hits matched by the query (when using relevant sort). * `page: number`. The index of the current page (zero-based). * `processingTimeMS: number`. The time the server took to process the search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.), in milliseconds. This doesn't include network time. * `query: string`. The query text. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    20,337 results found in 1ms.
    ``` # ToggleRefinement Source: https://www.algolia.com/doc/api-reference/widgets/toggle-refinement/react Lets users toggle a specific filter. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { ToggleRefinement } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=toggleRefinement) > > Preview this widget and its behavior. ## About this widget `ToggleRefinement` is a widget that provides an on/off filter based on an attribute value. For example, you can use this widget to only display products that apply for free shipping, or recipes that are gluten-free. > [!TIP] > > You can also create your own UI with [`useToggleRefinement`](#hook). ### Requirements Make sure to declare the provided [`attribute`](#param-attribute) as an [attribute for faceting](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, ToggleRefinement } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function App() { return ( ); } ``` ## Props **attribute** `string` *required* The name of the attribute in the records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```jsx JavaScript icon=code theme={"system"} ; ``` **label** `string` The label to display for the checkbox. ```jsx JavaScript icon=code theme={"system"} ; ``` **on** `boolean | string | number` The value of the refinement to apply on the attribute when checked. ```jsx boolean theme={"system"} ; ``` ```jsx number theme={"system"} ; ``` ```jsx String theme={"system"} ; ``` **off** `boolean | string | number` The value of the refinement to apply on the attribute when unchecked. ```jsx boolean theme={"system"} ; ``` ```jsx number theme={"system"} ; ``` ```jsx String theme={"system"} ; ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The root element of the widget. * `label`. The label element. * `checkbox`. The checkbox element. * `labelText`. The text element of the label. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the root element of the widget. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `ToggleRefinement` widget with `useToggleRefinement`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useToggleRefinement` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useToggleRefinement } from "react-instantsearch"; function CustomToggleRefinement(props) { const { value, canRefine, refine, sendEvent, createURL } = useToggleRefinement(props); return <>{/* Your JSX */}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **attribute** `string` *required* The name of the attribute in the records. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```jsx JavaScript icon=code theme={"system"} const toggleRefinementApi = useToggleRefinement({ attribute: "free_shipping", }); ``` **on** `boolean | string | number` The value of the refinement to apply on the attribute when checked. ```jsx boolean theme={"system"} const toggleRefinementApi = useToggleRefinement({ // ... on: true, }); ``` ```jsx number theme={"system"} const toggleRefinementApi = useToggleRefinement({ // ... on: 1, }); ``` ```jsx String theme={"system"} const toggleRefinementApi = useToggleRefinement({ // ... on: "yes", }); ``` **off** `boolean | string | number` The value of the refinement to apply on the attribute when unchecked. ```jsx boolean theme={"system"} const toggleRefinementApi = useToggleRefinement({ // ... off: false, }); ``` ```jsx number theme={"system"} const toggleRefinementApi = useToggleRefinement({ // ... off: 0, }); ``` ```jsx String theme={"system"} const toggleRefinementApi = useToggleRefinement({ // ... off: "no", }); ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **value** `ToggleRefinementValue` The current refinement. ```ts TypeScript icon=code theme={"system"} type ToggleRefinementValue = { /** * The attribute name of this toggle. */ name: string; /** * Whether the current option is "on" (true) or "off" (false) */ isRefined: boolean; /** * Number of results if this option is toggled. */ count: number | null; /** * Information about the "on" toggle. */ onFacetValue: ToggleRefinementDetails; /** * Information about the "off" toggle. */ offFacetValue: ToggleRefinementDetails; }; type ToggleRefinementDetails = { /** * whether this option is enabled. */ isRefined: boolean; /** * Number of result if this option is toggled. */ count: number | null; }; ``` **canRefine** `boolean` Whether the search state can be refined. ```jsx JavaScript icon=code theme={"system"} const { canRefine } = useToggleRefinement({ attribute: "free_shipping" }); return ; ``` **refine** `(\{ isRefined: boolean \}) =\> void` Updates to the next state by applying the toggle refinement. ```jsx JavaScript icon=code theme={"system"} const { refine } = useToggleRefinement({ attribute: "free_shipping" }); return ( refine({ isRefined: !event.target.checked })} type="checkbox" /> ); ``` **sendEvent** `(eventType: string, facetValue: string, eventName?: string) =\> void` A function to send `click` events. The `click` event is automatically sent when calling `refine`. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/react) middleware. ```jsx JavaScript icon=code theme={"system"} sendEvent("click", true); // This sends a similar payload to the `insights` middleware. // { // eventType: 'click', // insightsMethod: 'clickedFilters', // payload: { // eventName: 'Filter Applied', // filters: ['free_shipping:true'], // index: '', // }, // widgetType: 'ais.toggleRefinement', // } ``` **createURL** `() =\> string` Generates a URL for the next state. ```jsx JavaScript icon=code theme={"system"} const { createURL, value, refine } = useToggleRefinement({ attribute: "free_shipping", }); return ( refine({ isRefined: !value.isRefined })} > Link to the next state ); ``` ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useToggleRefinement } from "react-instantsearch"; function CustomToggleRefinement(props) { const { value, refine } = useToggleRefinement(props); return ( ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useToggleRefinement, UseToggleRefinementProps, } from "react-instantsearch"; function CustomToggleRefinement(props: UseToggleRefinementProps) { const { value, refine } = useToggleRefinement(props); return ( ); } ``` # ais-toggle-refinement Source: https://www.algolia.com/doc/api-reference/widgets/toggle-refinement/vue Lets users toggle a specific filter. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisToggleRefinement } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisToggleRefinement > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=toggleRefinement) > > Preview this widget and its behavior. ## About this widget The `ais-toggle-refinement` widget provides an on/off filtering feature based on an attribute value. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **attribute** `string` *required* The name of the attribute on which to apply the refinement. To avoid unexpected behavior, you can't use the same `attribute` prop in a different type of widget. ```vue Vue icon=code theme={"system"} ``` **label** `string` *required* The label to display for the checkbox. ```vue Vue icon=code theme={"system"} ``` **on** `string | number | boolean` (default: true) The value of the refinement to apply on the attribute when checked. ```vue Vue icon=code theme={"system"} ``` **off** `string | number | boolean` The value of the refinement to apply on the attribute when unchecked. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-ToggleRefinement`. The root of the widget. * `ais-ToggleRefinement--noRefinement`. The root of the widget without refinement. * `ais-ToggleRefinement-label`. The label of the toggle. * `ais-ToggleRefinement-checkbox`. The checkbox element of the toggle. * `ais-ToggleRefinement-labelText`. The label text of the toggle. * `ais-ToggleRefinement-count`. The count of the toggle. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `value: object`. The current refinement. It contains `name`, `count` and `isRefined`. * `canRefine: boolean`. Whether the refinement can be applied. * `refine: (value: string) => void`. The function to call with the provided value to update the refinement. * `createURL: (value: string) => void`. The function to generate a link from the provided value. * `sendEvent: (eventType: 'click', facetValue: string) => void`. The function to send `click` events. * The `view` event is automatically sent when the facets are rendered. * The `click` event is automatically sent when `refine` is called. To learn more, see the [`insights`](/doc/api-reference/widgets/insights/vue) middleware. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ``` # TrendingFacets Source: https://www.algolia.com/doc/api-reference/widgets/trending-facets/react Shows a list of trending facet values. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). > [!NOTE] > > If you're using the deprecated [`recommend-react`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-react) UI library, > see [`TrendingFacets`](https://www.algolia.com/old-docs/deprecated/recommend/api-reference/recommend-js/trendingFacets/). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { TrendingFacets } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=trendingFacets) > > Preview this widget and its behavior. ## About this widget Use the `TrendingFacets` widget to display a list of trending facet values. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) > [!TIP] > > You can also create your own UI with [`useTrendingFacets`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, TrendingFacets } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Item({ item }) { return {item.facetValue}; } function App() { return ( ); } ``` ## Props **facetName** `string` *required* The facet attribute to get trending values for. ```jsx JavaScript icon=code theme={"system"} ; ``` **itemComponent** `(props: \{ item: TrendingFacetItem \}) =\> JSX.Element` A component that renders each trending facet value. It receives an `item` prop with `facetName`, `facetValue`, and `_score`. When not provided, the widget displays the `facetValue` as plain text. ```jsx JavaScript icon=code theme={"system"} {item.facetValue}} />; ``` **headerComponent** `(props: \{ classNames: object, items: TrendingFacetItem[], translations: object \}) =\> JSX.Element` A component that renders a title header. It receives `classNames`, `items` and `translations` props. When not provided, the widget displays a default header with the title "Trending". ```jsx JavaScript icon=code theme={"system"} (

    Trending ({items.length})

    )} />; ``` **emptyComponent** `() =\> JSX.Element` A component that renders when there are no trending facet values. When not provided, the widget displays a default empty state. ```jsx JavaScript icon=code theme={"system"}

    No trending facets.

    } />; ``` **limit** `number` The number of trending facet values to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as additional filters when there aren't enough recommendations. ```jsx JavaScript icon=code theme={"system"} ; ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: TrendingFacetItem[], metadata: \{ results: RecommendResponse \}) =\> TrendingFacetItem[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, facetValue: item.facetValue.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { TrendingFacetsProps } from "react-instantsearch"; const transformItems: TrendingFacetsProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, facetValue: item.facetValue.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The widget's root element. * `emptyRoot`. The root element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of trending facet values. * `item`. The list item. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A mapping of keys to translation values. * `title`. The title of the widget section. Defaults to `"Trending"`. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the widget's root element. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `TrendingFacets` widget with `useTrendingFacets`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useTrendingFacets` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useTrendingFacets } from "react-instantsearch"; function CustomTrendingFacets(props) { const { items } = useTrendingFacets(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **facetName** `string` *required* The facet attribute to get trending values for. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingFacets({ // ... facetName: "brand", }); ``` **limit** `number` The number of trending facet values to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingFacets({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingFacets({ // ... threshold: 80, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as additional filters when there aren't enough recommendations. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingFacets({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingFacets({ // ... escapeHTML: false, }); ``` **transformItems** `(items: TrendingFacetItem[], metadata: \{ results: RecommendResponse \}) =\> TrendingFacetItem[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, facetValue: item.facetValue.toUpperCase(), })); }; function TrendingFacets() { const trendingFacetsApi = useTrendingFacets({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseTrendingFacetsProps } from "react-instantsearch"; const transformItems: UseTrendingFacetsProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, facetValue: item.facetValue.toUpperCase(), })); }; function TrendingFacets() { const trendingFacetsApi = useTrendingFacets({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `TrendingFacetItem[]` The trending facet values returned from Algolia Recommend. Each item has `facetName`, `facetValue`, and `_score` properties. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useTrendingFacets } from "react-instantsearch"; function CustomTrendingFacets(props) { const { items } = useTrendingFacets(props); return (
      {items.map((item, index) => (
    1. {item.facetValue}
    2. ))}
    ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useTrendingFacets, UseTrendingFacetsProps } from "react-instantsearch"; function CustomTrendingFacets(props: UseTrendingFacetsProps) { const { items } = useTrendingFacets(props); return (
      {items.map((item, index) => (
    1. {item.facetValue}
    2. ))}
    ); } ``` # ais-trending-facets Source: https://www.algolia.com/doc/api-reference/widgets/trending-facets/vue Shows a list of trending facet values. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisTrendingFacets } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisTrendingFacets, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget Use `ais-trending-facets` to display facet values identified by the [trending facet values](/doc/guides/algolia-recommend/overview#trending-items-and-trending-facets-value) model, such as brands whose products are gaining popularity. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **facet-name** `string` *required* The facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) attribute for which to retrieve trending values, such as `brand`. The attribute must be included in the trending facet values model. ```vue Vue icon=code theme={"system"} ``` **limit** `number` The maximum number of trending facet values to retrieve. The widget may return fewer values, depending on the available recommendations and request parameters. If you omit `limit` or set it to `0`, the widget returns all available trending facet values. ```vue Vue icon=code theme={"system"} ``` **threshold** `number` The recommendation score threshold (0 to 100). The widget only returns facet values with a score greater than this threshold. ```vue Vue icon=code theme={"system"} ``` **fallback-parameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as an extra 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.) set when there aren't enough recommendations. ```vue Vue icon=code theme={"system"} ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function to transform or reorder recommendations before the widget displays them. It returns an array of items with the same structure. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-TrendingFacets`. The widget's root element. * `ais-TrendingFacets--empty`. The root element without results. * `ais-TrendingFacets-title`. The widget's title element. * `ais-TrendingFacets-container`. The container of the list element. * `ais-TrendingFacets-list`. The list of trending facet values. * `ais-TrendingFacets-item`. The list item. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. The widget only uses this slot if you don't provide any other slot. **Scope** * `items: object[]`. The trending facet values. Each item has `facetName`, `facetValue`, and `_score` properties. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of each trending facet value. If you don't provide it, the widget displays the `facetValue` as plain text. **Scope** * `item: object`. A single trending facet value with `facetName`, `facetValue`, and `_score` properties. ```vue Vue icon=code theme={"system"} ``` **header** The slot to override the DOM output of the title header. The widget doesn't render the header when there are no recommendations. If you don't provide it, the widget displays "Trending" as the title. **Scope** * `items: object[]`. The trending facet values. * `translations: object`. The widget's translations, such as `title`. * `classNames: object`. The widget's CSS classes, such as `title`. ```vue Vue icon=code theme={"system"} ``` **empty** The slot to override the DOM output when there are no trending facet values. If you don't provide it, the widget displays "No results". ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Trending

    1. ...
    2. ...
    3. ...
    ``` # TrendingItems Source: https://www.algolia.com/doc/api-reference/widgets/trending-items/react Shows a list of trending items. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). > [!NOTE] > > If you're using the deprecated [`recommend-react`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-react) UI library, > see [`TrendingItems`](https://algolia.com/old-docs/deprecated/recommend/api-reference/recommend-react/TrendingItems). ```tsx Signature theme={"system"} } /> ``` ## Import ```jsx JavaScript icon=code theme={"system"} import { TrendingItems } from "react-instantsearch"; ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=react\&widget=trendingItems) > > Preview this widget and its behavior. ## About this widget Use the `TrendingItems` widget to display a list of trending items. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) > [!TIP] > > You can also create your own UI with [`useTrendingItems`](#hook). ## Examples ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, TrendingItems } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Item({ item }) { return JSON.stringify(item); } function App() { return ( ); } ``` ### With carousel layout Display the `TrendingItems` widget as a [scrollable carousel](/doc/api-reference/widgets/trending-items/react#param-layout-component). ```jsx JavaScript icon=code theme={"system"} import React from "react"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import { InstantSearch, TrendingItems, Carousel } from "react-instantsearch"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); function Item({ item }) { return JSON.stringify(item); } function App() { return ( ); } ``` ## Props **facetName** `string` The facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) attribute to get recommendations for. Use this parameter with [`facetValue`](/doc/api-reference/widgets/trending-items/react#param-facet-value). ```jsx JavaScript icon=code theme={"system"} ; ``` **facetValue** `string` The value for the targeted facet name. This parameter should be used along with [`facetName`](/doc/api-reference/widgets/trending-items/react#param-facet-name). ```jsx JavaScript icon=code theme={"system"} ; ``` **itemComponent** `(props: \{ item: THit \}) =\> JSX.Element` A component that renders each recommendation from the results. It receives an `item` prop. When not provided, the widget displays the recommendation as a JSON string. ```jsx JavaScript icon=code theme={"system"} item.objectID} />; ``` **headerComponent** `(props: \{ classNames: string[], items: THit[], translations: object \}) =\> JSX.Element` A component that renders a title header. It receives `classNames`, `items` and `translations` props. When not provided, the widget displays a default header. ```jsx JavaScript icon=code theme={"system"} (

    Recommendations ({items.length})

    )} />; ``` **emptyComponent** `() =\> JSX.Element` A component that renders when there are no recommendations. When not provided, the widget displays a default message. ```jsx JavaScript icon=code theme={"system"}

    No recommendations.

    } />; ``` **layoutComponent** `(props: RecommendLayoutProps) =\> JSX.Element` The layout component to use to wrap all items. React InstantSearch provides a built-in `Carousel` layout component with the following props: `classNames`: * `root`. The Carousel's root element. * `list`. The list of recommendations. * `item`. The list item. * `navigation`. The navigation controls. * `navigationPrevious`. The "Previous" navigation control. * `navigationNext`. The "Next" navigation control. `translations`: * `nextButtonLabel`. The label for the next button. * `nextButtonTitle`. The title for the next button. * `previousButtonLabel`. The label for the previous button. * `previousButtonTitle`. The title for the previous button. * `listLabel`. The label for the list of recommendation. * `previousIconComponent`. The component to render as the previous button. * `nextIconComponent`. The component to render as the next button. ```jsx Carousel theme={"system"} import { Carousel } from "react-instantsearch"; (

    Previous

    } nextIconComponent={() =>

    Next

    } /> )} />; ``` ```jsx Custom theme={"system"} (
      {props.items.map((item) => (
    • {item.objectID}
    • ))}
    )} />; ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} ; ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```jsx JavaScript icon=code theme={"system"} ; ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as additional filters when there aren't enough recommendations. ```jsx JavaScript icon=code theme={"system"} ; ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} ; ``` **transformItems** `(items: THit[], metadata: \{ results: SearchResults \}) =\> THit[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function Search() { return ( ); } ``` ```tsx TypeScript theme={"system"} import type { TrendingItemsProps } from "react-instantsearch"; const transformItems: TrendingItemsProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function Search() { return ( ); } ``` **classNames** `Partial\` The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#style-your-widgets) and pass to the widget's elements. It's useful to style widgets with class-based CSS frameworks like [Bootstrap](https://getbootstrap.com) or [Tailwind CSS](https://tailwindcss.com). * `root`. The widget's root element. * `emptyRoot`. The root element without results. * `title`. The widget's title element. * `container`. The container of the list element. * `list`. The list of recommendations. * `item`. The list item. ```jsx JavaScript icon=code theme={"system"} ; ``` **translations** `Partial\` A mapping of keys to translation values. * `title`. The title of the recommendation section. * `sliderLabel`. The label of the horizontal slider. ```jsx JavaScript icon=code theme={"system"} ; ``` **...props** `React.ComponentProps\<'div'\>` Any `div` prop to forward to the widget's root element. ```jsx JavaScript icon=code theme={"system"} ; ``` ## Hook React InstantSearch let you create your own UI for the `TrendingItems` widget with `useTrendingItems`. Hooks provide APIs to access the widget state and interact with InstantSearch. The `useTrendingItems` Hook accepts [parameters](#parameters) and returns [APIs](#hook). It must be used inside the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component. ### Usage First, create your React component: ```jsx JavaScript icon=code theme={"system"} import { useTrendingItems } from "react-instantsearch"; function CustomTrendingItems(props) { const { items } = useTrendingItems(props); return <>{/*Your JSX*/}; } ``` Then, render the widget: ```jsx JavaScript icon=code theme={"system"} ``` ### Parameters Hooks accept parameters. You can either pass them manually or forward props from a custom component. > [!NOTE] > > When passing functions to Hooks, ensure stable references to prevent unnecessary re-renders. > Use [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) to keep function references stable. > You don't need to do this for arrays and objects. **facetName** `string` The facet attribute for which to get recommendations. This parameter should be used along with [`facetValue`](/doc/api-reference/widgets/trending-items/react#param-facet-value). ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingItems({ // ... facetName: "category", facetValue: "Book", }); ``` **facetValue** `string` The value for the targeted facet name. This parameter should be used along with [`facetName`](/doc/api-reference/widgets/trending-items/react#param-facet-name). ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingItems({ // ... facetName: "category", facetValue: "Book", }); ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If `limit` isn't provided or set to 0, all matching recommendations are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingItems({ // ... limit: 4, }); ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). Only recommendations with a greater score are returned. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingItems({ // ... threshold: 80, }); ``` **queryParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingItems({ // ... queryParameters: { filters: "category:Book", }, }); ``` **fallbackParameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as additional filters when there aren't enough recommendations. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingItems({ // ... fallbackParameters: { filters: "category:Book", }, }); ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```jsx JavaScript icon=code theme={"system"} const { items } = useTrendingItems({ // ... escapeHTML: false, }); ``` **transformItems** `(items: THit[], metadata: \{ results: SearchResults \}) =\> THit[]` A function that receives the list of items before they are displayed. It should return a new array with the same structure. Use this to transform, filter, or reorder the items. The function also has access to the full `results` data, including all standard [response parameters](/doc/guides/building-search-ui/going-further/backend-search/in-depth/understanding-the-api-response/js) and [parameters from the helper](https://community.algolia.com/algoliasearch-helper-js/reference.html#query-parameters), such as `disjunctiveFacetsRefinements`. ```jsx JavaScript theme={"system"} const transformItems = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function TrendingItems() { const trendingItemsApi = useTrendingItems({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import type { UseTrendingItemsProps } from "react-instantsearch"; const transformItems: UseTrendingItemsProps["transformItems"] = (items) => { return items.map((item) => ({ ...item, label: item.name.toUpperCase(), })); }; function TrendingItems() { const trendingItemsApi = useTrendingItems({ // ... transformItems, }); return <>{/* Your JSX */}; } ``` ### APIs Hooks return APIs, such as state and functions. You can use them to build your UI and interact with React InstantSearch. **items** `THit[]` The matched recommendations returned from Algolia. ### Example ```jsx JavaScript theme={"system"} import React from "react"; import { useTrendingItems } from "react-instantsearch"; function CustomTrendingItems(props) { const { items } = useTrendingItems(props); return (
      {items.map((item) => (
    1. {JSON.stringify(item).slice(0, 100)}
    2. ))}
    ); } ``` ```tsx TypeScript theme={"system"} import React from "react"; import { useTrendingItems, UseTrendingItemsProps } from "react-instantsearch"; function CustomTrendingItems(props: UseTrendingItemsProps) { const { items } = useTrendingItems(props); return (
      {items.map((item) => (
    1. {JSON.stringify(item).slice(0, 100)}
    2. ))}
    ); } ``` # ais-trending-items Source: https://www.algolia.com/doc/api-reference/widgets/trending-items/vue Shows a list of trending items. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisTrendingItems } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisTrendingItems, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` ## About this widget Use `ais-trending-items` to display items that are gaining popularity based on conversion events. By default, it retrieves [trending items](/doc/guides/algolia-recommend/overview#trending-items-and-trending-facets-value) across the index. To retrieve trending items within a specific category or other facet value, set `facet-name` and `facet-value`. See also: [Set up Algolia Recommend](/doc/guides/algolia-recommend/how-to/set-up) ## Examples ```vue Vue icon=code theme={"system"} ``` ### With carousel layout To display the recommendations as a scrollable carousel, set the [`layout`](#param-layout) prop to `carousel`. ```vue Vue icon=code theme={"system"} ``` ## Props **facet-name** `string` The facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) attribute to get recommendations for. Use this prop with [`facet-value`](#param-facet-value). ```vue Vue icon=code theme={"system"} ``` **facet-value** `string` The value for the targeted facet name. Use this prop with [`facet-name`](#param-facet-name). ```vue Vue icon=code theme={"system"} ``` **limit** `number` The number of recommendations to retrieve. Depending on the available recommendations and the other request parameters, the actual number of items may be lower than that. If you omit `limit` or set it to 0, the widget returns all matching recommendations. ```vue Vue icon=code theme={"system"} ``` **threshold** `number` The threshold for the recommendations confidence score (between 0 and 100). The widget only returns recommendations with a greater score. ```vue Vue icon=code theme={"system"} ``` **query-parameters** `Omit\` [Search parameters](/doc/api-reference/search-api-parameters) to apply to the recommendation request. ```vue Vue icon=code theme={"system"} ``` **fallback-parameters** `Omit\` List of [search parameters](/doc/api-reference/search-api-parameters) to send as an extra 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.) set when there aren't enough recommendations. ```vue Vue icon=code theme={"system"} ``` **escapeHTML** `boolean` (default: true) Whether to escape HTML tags from recommendations string values. ```vue Vue icon=code theme={"system"} ``` **transform-items** `function` (default: items => items) A function that receives the items before the widget displays them. It returns a new array with the same "shape" as the original. This is helpful when transforming or reordering items. > [!NOTE] > > To [prevent creating infinite loops](https://support.algolia.com/hc/en-us/articles/4406511683217-Vue-InstantSearch-How-do-I-prevent-infinite-loops), > avoid passing arrays, objects, or functions directly in the template. > These values aren't referentially equal on each render, > which causes the widget to re-register every time. > Instead, define them in your component's `data` option and reference them in the template. ```vue Vue icon=code theme={"system"} ``` **layout** `'list' | 'carousel'` (default: 'list') How to display the recommendations: * `list`. A list of items. * `carousel`. A horizontally scrollable carousel with "Previous" and "Next" navigation buttons. The [`item`](#param-item) slot applies to both layouts. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-TrendingItems`. The widget's root element. * `ais-TrendingItems--empty`. The root element without results. * `ais-TrendingItems-title`. The widget's title element. * `ais-TrendingItems-container`. The container of the list element. * `ais-TrendingItems-list`. The list of recommendations. * `ais-TrendingItems-item`. The list item. With the `carousel` layout, the `ais-TrendingItems-list` and `ais-TrendingItems-item` classes apply to the carousel's list and items. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. The widget only uses this slot if you don't provide any other slot. **Scope** * `items: object[]`. The recommendations. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. ```vue Vue icon=code theme={"system"} ``` **item** The slot to override the DOM output of each recommendation. If you don't provide it, the widget displays the recommendation as a JSON string. **Scope** * `item: object`. A single recommendation with all its attributes. * `sendEvent: (eventType, hit, eventName) => void`. The function to send `click` or `conversion` events. ```vue Vue icon=code theme={"system"} ``` **header** The slot to override the DOM output of the title header. The widget doesn't render the header when there are no recommendations. If you don't provide it, the widget displays "Trending items" as the title. **Scope** * `items: object[]`. The recommendations. * `translations: object`. The widget's translations, such as `title`. * `classNames: object`. The widget's CSS classes, such as `title`. ```vue Vue icon=code theme={"system"} ``` **empty** The slot to override the DOM output when there are no recommendations. If you don't provide it, the widget displays "No results". ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}

    Trending items

    1. ...
    2. ...
    3. ...
    ``` # uiState Source: https://www.algolia.com/doc/api-reference/widgets/ui-state/react Object with the state of your search UI. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ## About this widget The `uiState` object represents the state of the search UI and can be saved and returned to later. It's updated whenever the user interacts with the UI and can be used to: * Store the current search query and parameters * Pass data to widgets * Determine which results to show when a user visits a new page ([routing](/doc/api-reference/widgets/instantsearch/react#param-routing)) It doesn't directly store search results: just the state that leads to those results. ### Example This is an example of an InstantSearch `uiState` object. It represents what the user has searched for, how they've refined the results, and how the results are displayed. Replace `INDEX_NAME` with the name of your Algolia 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.). ```ts TypeScript icon=code theme={"system"} import type { UiState } from "instantsearch.js"; const uiState: UiState = { INDEX_NAME: { query: "Hill Valley", refinementList: { colors: ["white", "black"], }, configure: { distinct: true, }, menu: { category: "Decoration", }, hierarchicalMenu: { categories: ["Decoration > Clocks"], }, numericMenu: { price: "100:500", }, ratingMenu: { rating: 4, }, range: { ageInYears: "2:10", }, toggle: { freeShipping: true, }, geoSearch: { boundingBox: "47.3165,4.9665,47.3424,5.0201", }, sortBy: "most_popular_index", page: 2, hitsPerPage: 20, }, }; ``` ### Custom widgets To have your custom widgets interact with the `uiState` object, you need to implement the `getWidgetUiState` and `getWidgetSearchParameters` methods in the connector. To learn more, see [Custom widgets](/doc/guides/building-search-ui/widgets/create-your-own-widgets/react#interact-with-routing). # uiState Source: https://www.algolia.com/doc/api-reference/widgets/ui-state/vue Object with the state of your search UI. ## About this widget The `uiState` object represents the state of the search UI and can be saved and returned to later. It's updated whenever the user interacts with the UI and can be used to: * Store the current search query and parameters * Pass data to widgets * Determine which results to show when a user visits a new page ([routing](/doc/api-reference/widgets/instantsearch/vue#param-routing)) It doesn't directly store search results: just the state that leads to those results. ### Vue InstantSearch v2 `uiState` only works when the widgets responsible for each UI state attribute are mounted. For example, a`searchBox`widget is necessary to provide a`query`. ### Vue InstantSearch v3 and later The `uiState` passed to `initialUiState` or through routing needs to be nested 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.). For example, if you only have a root index called `INDEX_NAME`, use a value like `{ INDEX_NAME: { query: 'query' } }`. ### Example This is an example of an InstantSearch `uiState` object. It represents what the user has searched for, how they've refined the results, and how the results are displayed. ```js JavaScript icon=code theme={"system"} const uiState = { query: "Hill Valley", refinementList: { colors: ["white", "black"], }, configure: { distinct: true, }, menu: { category: "Decoration", }, hierarchicalMenu: { categories: ["Decoration > Clocks"], }, numericMenu: { price: "100:500", }, ratingMenu: { rating: 4, }, range: { ageInYears: "2:10", }, toggle: { freeShipping: true, }, geoSearch: { boundingBox: "47.3165,4.9665,47.3424,5.0201", }, sortBy: "most_popular_index", page: 2, hitsPerPage: 20, }; ``` ### Custom widgets To have your custom widgets interact with the `uiState` object, you need to implement the `getWidgetUiState` and `getWidgetSearchParameters` methods in the connector. For more information, see [Custom widgets](/doc/guides/building-search-ui/widgets/create-your-own-widgets/js#interact-with-routing). # useVoiceSearch Source: https://www.algolia.com/doc/api-reference/widgets/voice-search/react React Hook for providing voice-based search input. > [!NOTE] > > This is the **React InstantSearch v7** documentation. > If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7). > If you were using React InstantSearch Hooks, > this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7). > To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/). ## About this Hook This Hook lets users perform voice-based queries. The `useVoiceSearch` Hook isn't part of React InstantSearch, but you can make a custom version with [`useConnector`](/doc/api-reference/widgets/connector/react) and [`connectVoiceSearch`](/doc/api-reference/widgets/voice-search/js#customize-the-ui-with-connectvoicesearch). ## Examples ```jsx JavaScript theme={"system"} import { useConnector } from "react-instantsearch"; import connectVoiceSearch from "instantsearch.js/es/connectors/voice-search/connectVoiceSearch"; export function useVoiceSearch(props) { return useConnector(connectVoiceSearch, props); } export function VoiceSearch(props) { const { isBrowserSupported, isListening, toggleListening, voiceListeningState, } = useVoiceSearch(props); return <>{/* Your JSX */}; } ``` ```tsx TypeScript theme={"system"} import { useConnector } from "react-instantsearch"; import connectVoiceSearch from "instantsearch.js/es/connectors/voice-search/connectVoiceSearch"; import type { VoiceSearchConnectorParams, VoiceSearchWidgetDescription, } from "instantsearch.js/es/connectors/voice-search/connectVoiceSearch"; export type UseVoiceSearchProps = VoiceSearchConnectorParams; export function useVoiceSearch(props?: UseVoiceSearchProps) { return useConnector( connectVoiceSearch, props, ); } export function VoiceSearch(props: UseVoiceSearchProps) { const { isBrowserSupported, isListening, toggleListening, voiceListeningState, } = useVoiceSearch(props); return <>{/* Your JSX */}; } ``` # ais-voice-search Source: https://www.algolia.com/doc/api-reference/widgets/voice-search/vue Lets users submit search queries using voice input. ```vue Signature theme={"system"} ``` ## Import > [tab] Component > > To ensure optimal bundle sizes, > see [Optimize build size](/doc/guides/building-search-ui/going-further/improve-performance/vue#optimize-build-size). > > ```js Vue icon=code theme={"system"} > import { AisVoiceSearch } from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > export default { > components: { > AisVoiceSearch, > }, > // ... > }; > ``` > [tab] Plugin > > This imports all widgets, even the ones you don't use. > For more information, see [Get started with Vue InstantSearch](/doc/guides/building-search-ui/getting-started/vue). > > ```js JavaScript icon="code" theme={"system"} > import Vue from "vue"; > import InstantSearch from "vue-instantsearch"; > // Use "vue-instantsearch/vue3/es" for Vue 3 > > Vue.use(InstantSearch); > ``` > [card] [See this widget in action](https://instantsearchjs.netlify.app/examples/js/showcase/?view=reference\&flavor=vue\&widget=voiceSearch) > > Preview this widget and its behavior. ## About this widget The `ais-voice-search` widget lets users perform a voice-based 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.). It uses the [Web Speech API](https://w3c.github.io/speech-api), which only Chrome (from version 25) has implemented so far. This means the `voiceSearch` widget only works on desktop Chrome and Android Chrome. It doesn't work on iOS Chrome, which uses the iOS WebKit. ## Examples ```vue Vue icon=code theme={"system"} ``` ## Props **search-as-you-speak** `boolean` (default: false) Whether to trigger the search as you speak. If `false`, search is triggered only after speech is finished. If `true`, search is triggered whenever the engine delivers an interim transcript. ```vue Vue icon=code theme={"system"} ``` **button-title** `string` (default: 'Search by voice') The `title` attribute on the button. ```vue Vue icon=code theme={"system"} ``` **disabled-button-title** `string` (default: 'Search by voice (not supported on this browser)') The `title` attribute on the button when it's unavailable on unsupported browsers. ```vue Vue icon=code theme={"system"} ``` **class-names** `object` (default: \{}) The [CSS classes you can override](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/vue#style-your-widgets): * `ais-VoiceSearch`. The root element of the widget. * `ais-VoiceSearch-button`. The button element. * `ais-VoiceSearch-status`. The status element. ```vue Vue icon=code theme={"system"} ``` ## Customize the UI **default** The slot to override the complete DOM output of the widget. When you implement this slot, none of the other slots will change the output, as the default slot surrounds them. **Scope** * `isBrowserSupported: boolean`. `true` if user's browser supports voice search. * `isListening: boolean`. `true` if listening to user's speech. * `toggleListening: () => void`. Starts listening to user's speech, or stops it if already listening. * `voiceListeningState: object`. An object containing the following states regarding speech recognition: * `status: string`. Current status (`initial`|`askingPermission`|`waiting`|`recognizing`|`finished`|`error`). * `transcript: string`. Currently recognized transcript. * `isSpeechFinal: boolean`. `true` if speech recognition is finished. * `errorCode: string | undefined`. An error code (if any). Refer to the [spec](https://w3c.github.io/speech-api/#speechreco-error) for more information. ```vue Vue icon=code theme={"system"} ``` **buttonText** The slot to override the DOM output inside the button. **Scope** * `isListening: boolean`. `true` if listening to user's speech. * `isBrowserSupported: boolean`. `true` if user's browser supports voice search. * `status: string`. Current status (`initial`|`askingPermission`|`waiting`|`recognizing`|`finished`|`error`). * `errorCode: string | undefined`. An error code (if any). Refer to the [spec](https://w3c.github.io/speech-api/#speechreco-error) for more information. * `transcript: string`. Currently recognized transcript. * `isSpeechFinal: boolean`. `true` if speech recognition is finished. ```vue Vue icon=code theme={"system"} ``` **status** The slot to override the DOM output of the status. **Scope** * `isListening: boolean`. `true` if listening to user's speech. * `isBrowserSupported: boolean`. `true` if user's browser supports voice search. * `status: string`. Current status (`initial`|`askingPermission`|`waiting`|`recognizing`|`finished`|`error`). * `errorCode: string | undefined`. An error code (if any). Refer to the [spec](https://w3c.github.io/speech-api/#speechreco-error) for more information. * `transcript: string`. Currently recognized transcript. * `isSpeechFinal: boolean`. `true` if speech recognition is finished. ```vue Vue icon=code theme={"system"} ``` ## HTML output ```html HTML icon=code-xml theme={"system"}
    ...
    ``` # Vue InstantSearch Source: https://www.algolia.com/doc/api-reference/widgets/vue Overview of Vue InstantSearch widgets. To get started with Vue InstantSearch, see [What is InstantSearch?](/doc/guides/building-search-ui/what-is-instantsearch/vue) ## Basics | | | | - | - | | [`ais-instant-search`](/doc/api-reference/widgets/instantsearch/vue) | The root wrapper component for all widgets. | | [`ais-index`](/doc/api-reference/widgets/index-widget/vue) | This widget lets you apply widgets to a specific Algolia index. | | [`ais-search-box`](/doc/api-reference/widgets/search-box/vue) | A widget to let users set a text-based query. | | [`ais-configure`](/doc/api-reference/widgets/configure/vue) | A widget that lets you provide raw search parameters to the Algolia API without rendering anything. | | [`ais-panel`](/doc/api-reference/widgets/panel/vue) | A widget that wraps other widgets in a consistent panel design. It also reacts when the widget no longer can refine. | | [`ais-autocomplete`](/doc/api-reference/widgets/autocomplete/vue) | A widget that provides the logic to create a connected component that renders results from Algolia. | | [`ais-experimental-autocomplete`](/doc/api-reference/widgets/autocomplete/vue#prebuilt-autocomplete-component) | A prebuilt autocomplete menu with a search box, a results panel, recent searches, and suggestions. It's experimental. | | [`ais-chat`](/doc/api-reference/widgets/chat/vue) | A widget to display a chat interface that interacts with a [generative AI assistant](/doc/guides/algolia-ai/agent-studio). | | [`ais-chat-trigger`](/doc/api-reference/widgets/chat-trigger/vue) | A widget to display a button that opens the [`ais-chat`](/doc/api-reference/widgets/chat/vue) widget's overlay. | | [`ais-feeds`](/doc/api-reference/widgets/feeds/vue) | A widget to display multiple independent result sets from a [multifeed composition](/doc/guides/compositions/multifeed-compositions). | | [`ais-voice-search`](/doc/api-reference/widgets/voice-search/vue) | A widget to let users perform a voice-based query. | | [`insights`](/doc/api-reference/widgets/insights/vue) | A middleware to help set the user token for insights purposes, and send events from built-in and custom widgets. | | [`middleware`](/doc/api-reference/widgets/middleware/vue) | With the middleware API, you can inject logic into Vue InstantSearch. | ## Results | | | | - | - | | [`ais-hits`](/doc/api-reference/widgets/hits/vue) | A widget to display a list of results. | | [`ais-infinite-hits`](/doc/api-reference/widgets/infinite-hits/vue) | A widget to display a list of results with a "Show more" button. | | [`ais-highlight`](/doc/api-reference/widgets/highlight/vue) | Highlighting visually emphasizes matching terms in your search results to help them stand out. | | [`ais-snippet`](/doc/api-reference/widgets/snippet/vue) | A widget that displays attributes in your search results in a shorter form (a snippet). | ## Recommendations | | | | - | - | | [`ais-frequently-bought-together`](/doc/api-reference/widgets/frequently-bought-together/vue) | A widget to display a list of frequently bought together items. | | [`ais-related-products`](/doc/api-reference/widgets/related-products/vue) | A widget to display a list of related products and related content. | | [`ais-trending-items`](/doc/api-reference/widgets/trending-items/vue) | A widget to display a list of trending items. | | [`ais-trending-facets`](/doc/api-reference/widgets/trending-facets/vue) | A widget to display a list of trending facets. | | [`ais-looking-similar`](/doc/api-reference/widgets/looking-similar/vue) | A widget to display a list of visually similar products. | ## Refinements | | | | - | - | | [`ais-refinement-list`](/doc/api-reference/widgets/refinement-list/vue) | One of the most common widget that you can find in a search UI. With this widget, users can filter the dataset based on facets. | | [`ais-color-refinement-list`](/doc/api-reference/widgets/color-refinement-list/vue) | A widget that filters results based on color facet values. It helps users quickly visualize the kind of color that products have. Not available in Vue InstantSearch. | | [`ais-dynamic-widgets`](/doc/api-reference/widgets/dynamic-widgets/vue) | Widget to conditionally render other widgets based on the facet merchandising settings of the index. | | [`ais-hierarchical-menu`](/doc/api-reference/widgets/hierarchical-menu/vue) | Widget that displays a hierarchical menu. | | [`ais-range-slider`](/doc/api-reference/widgets/range-slider/vue) | A widget that allows a user to select a numeric range using a slider with two handles. Not available in Vue InstantSearch. | | [`ais-menu`](/doc/api-reference/widgets/menu/vue) | A widget that displays a menu that lets users choose a single value for a specific attribute. | | [`ais-current-refinements`](/doc/api-reference/widgets/current-refinements/vue) | A widget that displays a list of refinements applied to the search. | | [`ais-range-input`](/doc/api-reference/widgets/range-input/vue) | A widget that allows a user to select a numeric range using a minimum and maximum input. | | [`ais-menu-select`](/doc/api-reference/widgets/menu-select/vue) | A widget that allows a user to select a single value to refine in a drop-down menu. | | [`ais-toggle-refinement`](/doc/api-reference/widgets/toggle-refinement/vue) | A widget that provides an on/off filtering feature based on an attribute value. | | [`ais-numeric-menu`](/doc/api-reference/widgets/numeric-menu/vue) | A widget that displays a list of numeric filters in a list. Those numeric filters are pre-configured with creating the widget. | | [`ais-rating-menu`](/doc/api-reference/widgets/rating-menu/vue) | A widget that lets users refine search results by clicking on stars. | | [`ais-clear-refinements`](/doc/api-reference/widgets/clear-refinements/vue) | A widget that displays a button that lets users clean every refinement applied to the search. | ## Pagination | | | | - | - | | [`ais-pagination`](/doc/api-reference/widgets/pagination/vue) | A widget that displays a pagination system which lets users change the current page of search results. | | [`ais-hits-per-page`](/doc/api-reference/widgets/hits-per-page/vue) | A widget that displays a select element to let users change the number of displayed hits. | ## Metadata | | | | - | - | | [`ais-breadcrumb`](/doc/api-reference/widgets/breadcrumb/vue) | A secondary navigation scheme that lets users see where the current page is in relation to the facet's hierarchy. | | [`ais-stats`](/doc/api-reference/widgets/stats/vue) | A widget that displays the total number of matching hits and the time it took to get them. | | [`ais-powered-by`](/doc/api-reference/widgets/powered-by/vue) | A widget to display the Algolia logo to redirect to the Algolia website. | | [`ais-state-results`](/doc/api-reference/widgets/state-results/vue) | A widget that allows you reach into the current search state. | | [`ais-query-rule-custom-data`](/doc/api-reference/widgets/query-rule-custom-data/vue) | A widget to display custom data from Rules. | | [`ais-query-rule-context`](/doc/api-reference/widgets/query-rule-context/vue) | A widget to set Rule contexts without rendering anything. | ## Sorting | | | | - | - | | [`ais-sort-by`](/doc/api-reference/widgets/sort-by/vue) | A widget that displays a list of indices, allowing a user to change the way hits are sorted with replica indices. | | [`ais-relevant-sort`](/doc/api-reference/widgets/relevant-sort/vue) | A widget that displays the current search mode when searching in a virtual replica index, and allows users to switch between Relevant and regular sorting, which is more exhaustive and can return less relevant results. | ## Routing | | | | - | - | | [`simple`](/doc/api-reference/widgets/simple-state-mapping/vue) | A state mapping used by default routing. | | [`history`](/doc/api-reference/widgets/history-router/vue) | A router used by default routing. | | [`uiState`](/doc/api-reference/widgets/ui-state/vue) | An object that represents a search UI state. | # InstantSearch Android Source: https://www.algolia.com/doc/api-reference/widgets/android Overview of InstantSearch Android widgets. To get started with InstantSearch Android, see [What is InstantSearch?](/doc/guides/building-search-ui/what-is-instantsearch/android) ## Basics | | | | - | - | | [`Searcher`](/doc/api-reference/widgets/instantsearch/android) | The component handling search requests. | | [`SearchBox`](/doc/api-reference/widgets/search-box/android) | Components that perform a text-based query. | | [`Query`](/doc/api-reference/widgets/configure/android) | A component to provide any search parameter to Algolia. | | [`RelatedItems`](/doc/api-reference/widgets/configure-related-items/android) | Experimental component that computes search parameters to fetch related items. | | [`Filter State`](/doc/api-reference/widgets/filter-state/android) | Filter State provides a friendly interface to manage filters applied to your search. | | [`Loading`](/doc/api-reference/widgets/loading/android) | Components that show a loading indicator during pending requests. | ## Results | | | | - | - | | [`Hits`](/doc/api-reference/widgets/hits/android) | Components that manage and display a list of search results. | | [`Infinite Hits (Paging)`](/doc/api-reference/widgets/infinite-hits/android) | Components that manage and display a paginated list of search results. | | [`Highlighting`](/doc/api-reference/widgets/highlight/android) | Highlighting visually emphasizes matching terms in your search results to help them stand out. | | [`Multi Hits`](/doc/api-reference/widgets/multi-hits/android) | Components that manage and display a paginated list of search results from multiple indices. | ## Refinements | | | | - | - | | [`Refinement List`](/doc/api-reference/widgets/refinement-list/android) | Components that perform refinements on a facet. | | [`Filter List`](/doc/api-reference/widgets/filter-list/android) | Components that display a list of arbitrary filters. | | [`Filter List (Facet)`](/doc/api-reference/widgets/filter-list-facet/android) | Components that display a list of arbitrary facet filters. | | [`Filter List (Numeric)`](/doc/api-reference/widgets/filter-list-numeric/android) | Components that display a list of arbitrary numeric filters. | | [`Filter List (Tag)`](/doc/api-reference/widgets/filter-list-tag/android) | Components that display a list of arbitrary tag filters. | | [`Dynamic Facets`](/doc/api-reference/widgets/dynamic-widgets/android) | Component that presents the ordered facets and facet values, and handles the user interaction to refine search results. The order of facets and their values is defined by the facet merchandising settings of the index. | | [`Hierarchical Menu`](/doc/api-reference/widgets/hierarchical-menu/android) | Component that displays a hierarchical menu. | | [`Filter Numeric Range`](/doc/api-reference/widgets/range-slider/android) | Components that apply a numeric range filter. | | [`Filter Numeric Comparison`](/doc/api-reference/widgets/filter-comparison/android) | Components that apply a numeric comparison filter. | | [`Filter Map`](/doc/api-reference/widgets/filter-map/android) | Components that apply a single filter from a map. | | [`Current Filters`](/doc/api-reference/widgets/current-refinements/android) | Components that display the current filters and let users remove them. | | [`Filter Toggle`](/doc/api-reference/widgets/toggle-refinement/android) | Components that toggle an arbitrary filter on or off. | | [`Clear Filters`](/doc/api-reference/widgets/clear-refinements/android) | Components that clear the current filters. | ## Metadata | | | | - | - | | [`Stats`](/doc/api-reference/widgets/stats/android) | Components that display metadata about the current search and its results. | | [`QueryRuleCustomData`](/doc/api-reference/widgets/query-rule-custom-data/android) | Component that displays custom data from Rules. | ## Sorting | | | | - | - | | [`Sort By`](/doc/api-reference/widgets/sort-by/android) | Components that display a list of indices, allowing a user to change the way hits are sorted using replica indices. | | [`RelevantSort`](/doc/api-reference/widgets/relevant-sort/android) | A widget that displays the current search mode when searching in a virtual replica index, and allows users to switch between relevant and regular sorting, which is more exhaustive and can return less relevant results. | # Clear Filters Source: https://www.algolia.com/doc/api-reference/widgets/clear-refinements/android Lets users reset all applied search refinements. ```kotlin Signature theme={"system"} FilterClearConnector( filterState: FilterState, groupIDs: List, mode: ClearMode, viewModel: FilterClearViewModel ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Clear Filters example code on GitHub. ## About this widget `ClearFilters` lets users clear all refinements that are currently active within the given `FilterState`. To add `ClearFilters` to your search experience, use these components: * `FilterClearViewModel`. The logic for clearing refinements in the `FilterState`. * `FilterState`. The current state of the filters. * `FilterClearView`. The view that renders the clear filter UI. ## Examples ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val color = "color" val category = "category" val groupColor = groupOr(color) val groupCategory = groupOr(category) val filters = filters { group(groupColor) { facet(color, "red") facet(color, "green") } group(groupCategory) { facet(category, "shoe") } } val filterState = FilterState(filters) val clearAll = FilterClearConnector(filterState = filterState) val clearSpecified = FilterClearConnector( filterState = filterState, groupIDs = listOf(groupColor), mode = ClearMode.Specified ) val clearExcept = FilterClearConnector( filterState = filterState, groupIDs = listOf(groupColor), mode = ClearMode.Except ) val connection = ConnectionHandler( clearSpecified, clearExcept, searcher.connectFilterState(filterState) ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) connection += clearAll.connectView(FilterClearViewImpl(filtersClearAll)) connection += clearSpecified.connectView(FilterClearViewImpl(buttonClearSpecified)) connection += clearExcept.connectView(FilterClearViewImpl(buttonClearExcept)) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connection.disconnect() } } ``` ### Compose UI InstantSearch provides the `FilterClear` as a state model, which is an implementation of the `FilterClearView` interface. You need to connect `FilterClear` to the `FilterClearConnector` or `FilterClearViewModel` like any other `FilterClearView` implementation. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val color = "color" val category = "category" val groupColor = groupOr(color) val groupCategory = groupOr(category) val filters = filters { group(groupColor) { facet(color, "red") facet(color, "green") } group(groupCategory) { facet(category, "shoe") } } val filterState = FilterState() val filterClear = FilterClear() val clearAll = FilterClearConnector(filterState = filterState) val connections = ConnectionHandler(clearAll) init { connections += searcher.connectFilterState(filterState) connections += clearAll.connectView(filterClear) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyClearButton(clearAll) // your own composable to trigger filter clear } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connections.disconnect() } } ``` ## Parameters **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that will hold your filters. **groupIDs** `List\` The group IDs of filters to clear. All filters will be cleared if unspecified. **mode** `ClearMode` Whether to clear the `Specified` filters or all filters `Except` them. **viewModel** `FilterClearViewModel` The logic for clearing refinements in the FilterState. ## View **view** `FilterClearView` *required* The view that renders the clear filter UI. ```kotlin Kotlin icon=code theme={"system"} val view = FilterClearViewImpl(myButton) // using your own Android view filterClearConnector.connectView(view) ``` # Clear Filters Source: https://www.algolia.com/doc/api-reference/widgets/clear-refinements/flutter Lets users reset all applied search refinements. ## About this widget Let users clear all currently active refinements using [`FilterState`](/doc/api-reference/widgets/filter-state/flutter) by calling [`clear`](/doc/api-reference/widgets/filter-state/flutter#param-clear) or [`clearExcept`](/doc/api-reference/widgets/filter-state/flutter#param-clear-except). ## Examples Following example uses a `IconButton` with a callback to clear *all* filters. ```dart Dart icon=code theme={"system"} class ClearFilters extends StatelessWidget { const ClearFilters(this.filterState, {super.key}); final FilterState filterState; @override Widget build(BuildContext context) { return IconButton( onPressed: () => filterState.clear(), // clear all filters icon: const Icon(Icons.clear_all), ); } } ``` # Clear Filters Source: https://www.algolia.com/doc/api-reference/widgets/clear-refinements/ios Lets users reset all applied search refinements. ```swift Signature theme={"system"} FilterClearConnector( filterState: FilterState, interactor: FilterClearInteractor, clearMode: ClearMode, filterGroupIDs: [FilterGroup.ID]?, controller: FilterClearController ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Clear Filters example code on GitHub. ## About this widget `Clear Filters` lets users clear all filters that are currently active within the given `FilterState`. ## Examples Instantiate a `FilterClearConnector`. ```swift Swift icon=code theme={"system"} let filterState: FilterState = .init() let clearFiltersController: FilterClearButtonController = .init(button: UIButton()) let filterClearConnector: FilterClearConnector = .init(filterState: filterState, clearMode: .specified, filterGroupIDs: [.and(name: "color"), .or(name: "category", filterType: .facet)], controller: clearFiltersController) ``` ## Parameters **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that holds your filters. **interactor** `FilterClearInteractor` *required* The logic applied to Clear Filters. **clearMode** `ClearMode` Whether to clear the `specified` filters or all filters `except` them. **filterGroupIDs** `[FilterGroup.ID]?` The group IDs of filters to clear. All filters will be cleared if unspecified. **controller** `FilterClearController` The Controller interfacing with a concrete clear filters view. ## Low-level API If you want to fully control the `Clear Filters` components and connect them manually, use the following components: * `FilterClearInteractor`. The logic for clearing filters in the `FilterState`. * `FilterState`. The current state of the filters. * `FilterClearController`. The controller that interfaces with a concrete clear filters view. ```swift Swift icon=code theme={"system"} let filterState: FilterState = .init() let filterClearInteractor: FilterClearInteractor = .init() let clearFiltersController: FilterClearButtonController = .init(button: UIButton()) filterClearInteractor.connectFilterState(filterState, filterGroupIDs: [ .and(name: "color"), .or(name: "category", filterType: .facet) ], clearMode: .specified) filterClearInteractor.connectController(clearFiltersController) ``` ## Customizing your view The controllers provided by default, like the `FilterClearButtonController` work well when you want to use native UIKit with their default behavior. If you want to use another component (other than a `UIButton`) such as a `UIView`, a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the `FilterClearController` protocol. ### Protocol `var onClick: ((Facet) -> Void)?`: Closure to call when the clear filters button is clicked. ### Example ```swift Swift icon=code theme={"system"} public class FilterClearButtonController: FilterClearController { public let button: UIButton public var onClick: (() -> Void)? public init(button: UIButton) { self.button = button button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) } @objc private func didTapButton() { onClick?() } } ``` ### SwiftUI InstantSearch provides the `FilterClearObservableController` data model, which is an implementation of the `FilterClearController` protocol adapted for usage with SwiftUI. `FilterClearObservableController` must be connected to the `FilterClearConnector` or `FilterClearInteractor` like any other `FilterClearController` implementation. The example of the clear filter view using the `Button` component provided by SwiftUI: ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var filterClearController: FilterClearObservableController var body: some View { Button("Clear filters") { filterClearController.clear() } } } ``` If you prefer to create a custom clear filters SwiftUI view, you can directly use the `FilterClearObservableController` as a data model. It provides the `clear` function to streamline the design process of your custom SwiftUI view. # RelatedItems Source: https://www.algolia.com/doc/api-reference/widgets/configure-related-items/android Finds parameters to retrieve related items. ## About this widget The `RelatedItems` component computes search parameters to fetch related items. You can pass a hit as the reference for computing search parameters and retrieving the related items. To add `RelatedItems` to your search experience, use the following components: * `Searcher`. A new [`HitsSearcher`](/doc/api-reference/widgets/instantsearch/android#hitssearcher) that handles your searches for related items. * `HitsView`. A `data class` representing a search result. * `T`. An `Indexable` data class representing the hit to get related items. This component acts similarly to the [`Hits`](/doc/api-reference/widgets/hits/android) component, but it only modifies the results. > [card] [Explore example code](https://github.com/algolia/instantsearch-android/blob/master/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/showcase/androidview/search/SearchAsYouTypeShowcase.kt) > > Browse the RelatedItems example code on GitHub. ## Examples You can use `deserialize` to convert raw hits into your own data class using the `Serializable` annotation. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.core.Indexable class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val connection = ConnectionHandler() val adapter = ProductAdapter() val product = Product( objectID = "objectID123", name = "productName", brand = "Amazon", categories = listOf("Streaming Media Players", "TV & Home Theater") ) val matchingPatterns = listOf( MatchingPattern("brand", 1, Product::brand), MatchingPattern("categories", 2, Product::categories) ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) connection += searcher.connectRelatedHitsView(adapter, product, matchingPatterns) { response -> response.hits.deserialize(Product.serializer()) } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } @Serializable data class Product( override val objectID: String, val name: String, val brand: String, val categories: List ) : Indexable class ProductAdapter : RecyclerView.Adapter(), HitsView { private var products: List = listOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder { return ProductViewHolder(parent.inflate(R.layout.list_item_product)) } override fun onBindViewHolder(holder: ProductViewHolder, position: Int) { val item = products[position] holder.bind(item) } override fun setHits(hits: List) { products = hits notifyDataSetChanged() } override fun getItemCount(): Int { return products.size } } class ProductViewHolder(private val view: View) : RecyclerView.ViewHolder(view) { fun bind(product: Product) { view.itemName.text = product.name } } ``` ### Compose UI ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.core.Indexable class MyActivity : AppCompatActivity() { private val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) private val relatedItemsSearcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) private val productsHits = HitsState() private val relatedItems = HitsState() private val matchingPatterns: List> = listOf( MatchingPattern("brand", 1, Product::brand), MatchingPattern("categories", 2, Product::categories) ) private var relatedItemConnection: Connection? = null private val productsSearchConnection = searcher .connectHitsView(productsHits) { it.hits.deserialize(Product.serializer()) } .also { it.connect() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyRelatedItemsScreen( hits = productsHits.hits, relatedItems = relatedItems.hits, onProductClick = ::relatedItemsOf ) } searcher.query = searcher.query.copy(hitsPerPage = 3) // Limit to 3 results configureSearcher(searcher) configureSearcher(relatedItemsSearcher) searcher.searchAsync() } private fun relatedItemsOf(product: Product) { relatedItemConnection?.disconnect() relatedItemConnection = relatedItemsSearcher.connectRelatedHitsView( relatedItems, product, matchingPatterns ) { response -> response.hits.deserialize(Product.serializer()) } relatedItemConnection?.connect() relatedItemsSearcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() relatedItemsSearcher.cancel() relatedItemConnection?.disconnect() productsSearchConnection.disconnect() } } ``` ## Parameters **hit** `T : Indexable` *required* The reference hit to compute the search parameters to send to Algolia. You can retrieve this hit from any location (app state, your backend, the history, etc.) ```kotlin Kotlin icon=code theme={"system"} val product = Product( objectID = "objectID123", name = "productName", brand = "Amazon", categories = listOf("Streaming Media Players", "TV & Home Theater") ) ``` **matchingPatterns** `List\\>` *required* A schema that creates [scored filters](/doc/guides/managing-results/refine-results/filtering/in-depth/filter-scoring) based on the hit's attributes. In the example below, the `brand` value gets a score of 1 while the `category` values get a score of 2. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.core.Indexable data class Product( override val objectID: String, val name: String, val brand: String, val categories: List ) : Indexable val matchingPatterns: List> = listOf( MatchingPattern("brand", 1, Product::brand), MatchingPattern("categories", 2, Product::categories) ) ``` The hit above would generate the following search parameters: ```json JSON icon=braces theme={"system"} { "sumOrFiltersScores": true, "facetFilters": ["objectID:-1234"], "optionalFilters": [ ["brand:Amazon"], [ [ "categories:TV & Home Theater", "categories:Streaming Media Players" ] ] ] } ``` **presenter** `(SearchResponse) -\> List\` *required* A function that transforms the search response into a list of results of your `T` class. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.client.model.search.SearchResponse val presenter: (SearchResponse) -> List = { response -> response.hits.deserialize(Product.serializer()) } searcher.connectRelatedHitsView(adapter, product, matchingPatterns, presenter) ``` # RelatedItems Source: https://www.algolia.com/doc/api-reference/widgets/configure-related-items/ios Finds parameters to retrieve related items. ## About this widget The `RelatedItems` components computes search parameters to fetch related items. You pass it a hit, which will be the reference in computing the search parameters and retrieving the related items. To add `RelatedItems` to your search experience, use these components: * `Searcher`. A new [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches for related items. * `HitsInteractor`. The hits `Interactor` to display the related items. * `HitsController`. The controller that interfaces with a concrete hits view. In a way, this component acts similarly to the [`Hits`](/doc/api-reference/widgets/hits/ios) component, but it only modifies the results. > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the RelatedItems example code on GitHub. ## Examples Connect `HitsInteractor` and `HitsController` with each other using the provided connection method, and connect the `HitsInteractor` with `Searcher`. This example uses the `HitsTableController` provided by InstantSearch. Now, each time you launch a new search: * `Searcher` receives new results and transmit them to `HitsInteractor` * `HitsInteractor` parses search results and notifies `HitsController` * `HitsController` refreshes the view presenting the hits ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let hitsInteractor: HitsInteractor = .init() let hitsTableViewController = CustomHitsTableViewController() let matchingPatterns: [MatchingPattern] = [ MatchingPattern(attribute: "brand", score: 3, filterPath: \.brand), MatchingPattern(attribute: "categories", score: 2, filterPath: \.categories), ] let hit: Hit = .init(objectID: "objectID123", object: product) override func viewDidLoad() { super.viewDidLoad() setup() } func setup() { hitsInteractor.connectSearcher(searcher, withRelatedItemsTo: hit, with: matchingPatterns) hitsInteractor.connectController(hitsTableViewController) searcher.search() } ``` ## Parameters **hit** `Hit\` *required* The reference hit to compute the search parameters to send to Algolia. You can retrieve this hit from any location (app state, your backend, the history, etc.). ```swift Swift icon=code theme={"system"} let product = Product.init(name: "productName", brand: "Amazon", categories: ["Streaming Media Players", "TV & Home Theater"]) let hit: Hit = .init(objectID: "1234", object: product) ``` **matchingPatterns** `[MatchingPattern\]` *required* A schema that creates [scored filters](/doc/guides/managing-results/refine-results/filtering/in-depth/filter-scoring) based on the hit's attributes. In the example below, the `brand` value gets a score of 1 while the `category` values gets a score of 2. ```swift Swift icon=code theme={"system"} struct Product: Codable { let name: String let brand: String let type: String let categories: [String] let image: URL } let matchingPatterns: [MatchingPattern] = [ MatchingPattern(attribute: "brand", score: 1, filterPath: \.brand), MatchingPattern(attribute: "categories", score: 2, filterPath: \.categories) ] ``` The hit above would generate the following search parameters: ```json JSON icon=braces theme={"system"} { "sumOrFiltersScores": true, "facetFilters": ["objectID:-1234"], "optionalFilters": [ ["brand:Amazon"], [ [ "categories:TV & Home Theater", "categories:Streaming Media Players" ] ] ] } ``` ## Customizing your view Related items list appear in the controller implementing `HitsController` protocol. Read more about customizing `Hits` view in the [HitsController documentation](/doc/api-reference/widgets/hits/ios#hitscontroller). # Query Source: https://www.algolia.com/doc/api-reference/widgets/configure/android Adds search parameters to your requests. ## About this widget The `Query` class lets you provide raw search parameters to the Algolia API. > [!NOTE] > > **You can't set the `filters` parameter**. > Use [`FilterState`](/doc/api-reference/widgets/filter-state/android) instead. For more information about the different parameters you can set, see the [search parameters API reference](/doc/api-reference/search-api-parameters). > [card] [Explore example code](https://github.com/algolia/instantsearch-android/blob/master/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/showcase/androidview/search/SearchAsYouTypeShowcase.kt) > > Browse the Query example code on GitHub. ## Examples ```kotlin Kotlin icon=code theme={"system"} searcher.query.hitsPerPage = 20 searcher.query.analytics = false ``` # Search State Source: https://www.algolia.com/doc/api-reference/widgets/configure/flutter Adds search parameters to your requests. ## About this widget The `SearchState` class lets you provide raw search parameters to the Algolia API. > [!NOTE] > > **You can't set the `filters` parameter**. > Use [`FilterState`](/doc/api-reference/widgets/filter-state/flutter) instead. For more information about the different parameters you can set, see the [search parameters API reference](/doc/api-reference/search-api-parameters). > [card] [View live demo](https://algolia-flutter-showcase.netlify.app/#/path=search-state%2Flimit-hits-per-page) > > Open the live Search State example in your browser. ## Examples ```dart Dart icon=code theme={"system"} var state = SearchState( indexName: "YourIndexName", hitsPerPage: 20, analytics: true, ); ``` # Query Source: https://www.algolia.com/doc/api-reference/widgets/configure/ios Adds search parameters to your requests. ## About this widget The `Query` class lets you provide raw search parameters to the Algolia API. > [!NOTE] > > **You can't set the `filters` parameter**. > Use [`FilterState`](/doc/api-reference/widgets/filter-state/ios) instead. For more information about the different parameters you can set, see the [search parameters API reference](/doc/api-reference/search-api-parameters). > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Query example code on GitHub. ## Examples You an access the `Query` of the Searcher with its `request` property. ```swift Swift icon=code theme={"system"} // HitsSearcher and FacetSearcher searcher.request.query.hitsPerPage = 20 searcher.request.query.analytics = false ``` # Current Filters Source: https://www.algolia.com/doc/api-reference/widgets/current-refinements/android Shows applied refinements and lets users remove them. ```kotlin Signature theme={"system"} FilterCurrentConnector( filters: Map, filterState: FilterState, groupIDs: List ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Current Filters example code on GitHub. ## About this widget Shows the currently active [refinements](/doc/api-reference/widgets/refinement-list/android) within a given `FilterState` and lets users remove filters individually. ## Examples ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) private val color = "color" private val price = "price" private val tags = "_tags" private val groupColor = FilterGroupID(color) private val groupPrice = FilterGroupID(price) private val groupTags = FilterGroupID(tags) private val filters = filters { group(groupColor) { facet(color, "red") facet(color, "green") } group(groupTags) { tag("mobile") } group(groupPrice) { comparison(price, NumericOperator.NotEquals, 42) range(price, IntRange(0, 100)) } } val filterState = FilterState(filters) val currentFiltersColor = FilterCurrentConnector(filterState, listOf(groupColor)) val currentFiltersAll = FilterCurrentConnector(filterState) val connection = ConnectionHandler( currentFiltersColor, currentFiltersAll, searcher.connectFilterState(filterState) ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) connection += currentFiltersAll.connectView( FilterCurrentViewImpl(chipGroupAll, R.layout.filter_chip) ) connection += currentFiltersColor.connectView( FilterCurrentViewImpl(chipGroupColors, R.layout.filter_chip) ) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connection.disconnect() } } ``` ### Low-level API If you want to fully control the Current Filters components and connect them manually, use the following components: * `FilterCurrentViewModel`. The logic for current refinements in the `FilterState`. * `FilterState`. The current state of the filters. * `FilterCurrentView`. The view that renders the current filters. * `FilterCurrentPresenter`. Optional. The presenter that defines the way you want to display the Filters. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val viewModel = FilterCurrentViewModel() val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view: FilterCurrentView = FilterCurrentViewImpl(chipGroup) // with your `ChipGroup` view connection += searcher.connectFilterState(filterState) connection += viewModel.connectFilterState(filterState) connection += viewModel.connectView(view) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Compose UI InstantSearch provides the `FilterCurrentState` as a state model, which is an implementation of the `FilterCurrentView` interface. You need to connect `FilterCurrentState` to the `FilterCurrentConnector` or `FilterCurrentViewModel` like any other `FilterCurrentView` implementation. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val color = "color" val price = "price" val tags = "_tags" val groupColor = FilterGroupID(color) val groupPrice = FilterGroupID(price) val groupTags = FilterGroupID(tags) val filters = filters { group(groupColor) { facet(color, "red") facet(color, "green") } group(groupTags) { tag("mobile") } group(groupPrice) { comparison(price, NumericOperator.NotEquals, 42) range(price, IntRange(0, 100)) } } val filterState = FilterState(filters) val chipGroupColors = FilterCurrentState() val currentFiltersColor = FilterCurrentConnector(filterState, listOf(groupColor)) val chipGroupAll = FilterCurrentState() val currentFiltersAll = FilterCurrentConnector(filterState) val connections = ConnectionHandler(currentFiltersColor, currentFiltersAll) init { connections += searcher.connectFilterState(filterState) connections += currentFiltersAll.connectView(chipGroupAll) connections += currentFiltersColor.connectView(chipGroupColors) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Column { // your own UI composable to display filters chipGroupAll.filters.forEach { (filterGroupAndID, filter) -> Chip(text = filter, onClick = { chipGroupAll.selectFilter(filterGroupAndID) }) } chipGroupColors.filters.forEach { (filterGroupAndID, filter) -> Chip(text = filter, onClick = { chipGroupColors.selectFilter(filterGroupAndID) }) } } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connections.disconnect() } } ``` ## Parameters **filters** `Map\` The default filters to display. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that will hold your filters. **groupIDs** `List\` When specified, only matching current [refinements](/doc/api-reference/widgets/refinement-list/android) will be displayed. ## View **view** `FilterCurrentView` *required* The view that renders the current filters. **presenter** `FilterCurrentPresenter` (default: FilterCurrentPresenterImpl()) The presenter that defines the way filters are displayed. ```kotlin Kotlin icon=code theme={"system"} val view = FilterCurrentViewImpl(chipGroup, chipLayout) // with your view and layout val presenter = object : FilterCurrentPresenter { // Implement the interface here } currentFilters.connectView(view, presenter) ``` # Current Filters Source: https://www.algolia.com/doc/api-reference/widgets/current-refinements/flutter Shows applied refinements and lets users remove them. ## About this widget Shows the active [refinements](/doc/api-reference/widgets/refinement-list/flutter) in a given `FilterState`. Users can remove filters one by one. ## Examples ```dart Dart icon=code theme={"system"} class CurrentFilters extends StatelessWidget { const CurrentFilters(this.filterState, {super.key}); final FilterState filterState; @override Widget build(BuildContext context) { return StreamBuilder( stream: filterState.filters, builder: (context, snapshot) { if (snapshot.hasData) { final filters = snapshot.data!; return Wrap( spacing: 6.0, runSpacing: 6.0, children: [...inputChipsOf(filters)], ); } else { return Container(); } }); } /// Get [filters] as list of [InputChip]s. List inputChipsOf(Filters filters) { final chips = []; for (var filterGroup in filters.toFilterGroups()) { for (var filter in filterGroup) { final chip = InputChip( label: Text(label(filter)), onDeleted: () => filterState.remove(filterGroup.groupID, {filter}), isEnabled: true, ); chips.add(chip); } } return chips; } /// Get [filter] as a label String label(Filter filter) { if (filter is FilterFacet) { return filter.value; } else if (filter is FilterTag) { return filter.value; } else if (filter is FilterNumeric) { final numericValue = filter.value; if (numericValue is NumericRange) { return '${filter.attribute}: ${numericValue.lowerBound}..${numericValue.upperBound}'; } else if (numericValue is NumericComparison) { return '${filter.attribute} ${numericValue.operator.operator} ${numericValue.number}'; } else { throw UnsupportedError(numericValue.runtimeType.toString()); } } else { throw UnsupportedError(runtimeType.toString()); } } } ``` # Current Filters Source: https://www.algolia.com/doc/api-reference/widgets/current-refinements/ios Shows applied refinements and lets users remove them. ```swift Signature theme={"system"} CurrentFiltersConnector( filterState: FilterState, groupIDs: Set?, interactor: CurrentFiltersInteractor, controller: CurrentFiltersController, presenter: Presenter ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Current Filters example code on GitHub. ## About this widget Shows each currently active 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.) in a[`FilterState`](/doc/api-reference/widgets/filter-state/ios) and lets users remove it. ## Examples Instantiate a `CurrentFiltersConnector`. ```swift Swift icon=code theme={"system"} let filterState = FilterState() let groupID: FilterGroup.ID = .and(name: "color") let currentFiltersTableController: CurrentFilterListTableController = .init(tableView: UITableView()) let currentFiltersConnector = CurrentFiltersConnector(filterState: filterState, groupIDs: [groupID], controller: currentFiltersTableController) ``` ## Parameters **filterState** `FilterState` *required* The [FilterState](/doc/api-reference/widgets/filter-state/ios) that holds your filters. **groupIDs** `Set\?` *required* When specified, only display current filters matching these filter group ids. **interactor** `CurrentFiltersInteractor` *required* The logic applied to the current filters. **controller** `CurrentFiltersController` The Controller interfacing with a concrete current filters view. **presenter** `Presenter\` The Presenter defining how filters appears in the controller. ## Presenter **Filter Presenter** `(Filter) -\> String` The presenter that defines the way you want to display a filter. ```swift Swift icon=code theme={"system"} currentFiltersInteractor.connectController(currentFiltersController, customPresenter) let customPresenter: (Filter) -> String = { filter in let attributeName = filter.filter.attribute.name switch filter { case .facet(let facetFilter): switch facetFilter.value { case .bool: return filter.filter.attribute.name case .float(let floatValue): return "\(attributeName): \(floatValue)" case .string(let stringValue): return stringValue } case .numeric(let numericFilter): switch numericFilter.value { case .comparison(let comp): return "\(attributeName) \(comp.0) \(comp.1)" case .range(let range): return "\(attributeName): \(range.lowerBound) to \(range.upperBound)" } case .tag(let tagFilter): return tagFilter.value } } ``` ## Low-level API If you want to fully control the `Current Filters` components and connect them manually, you can use the following components: * `CurrentFiltersInteractor`. The logic for current filters in the `FilterState`. * `FilterState`. The current state of the filters. * `CurrentFiltersController`. The controller that interfaces with a concrete current filter view. * `Presenter`. Optional. The presenter that defines the way you want to display a filter. ```swift Swift icon=code theme={"system"} let filterState = FilterState() let groupID: FilterGroup.ID = .and(name: "color") let currentFiltersTableController: CurrentFilterListTableController = .init(tableView: UITableView()) let currentFiltersInteractor = CurrentFiltersInteractor() currentFiltersInteractor.connectFilterState(filterState, filterGroupID: groupID) currentFiltersInteractor.connectController(currentFiltersTableController) ``` ## Customizing your view The controllers provided by default, like the `CurrentFilterListTableController` work well when you want to use native UIKit with their default behavior. If you want to use another component (other than a `UITableView`) such as a `UICollectionView`, a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the `CurrentFiltersController` protocol. ### Protocol `func setItems(_ item: [FilterAndID])`: Function called when current filters are refreshed and need to be updated. Note that FilterAndID is a `struct` that contains the `filter`, its `ID`, and the `text` representation of the filter `var onRemoveItem: ((FilterAndID) -> Void)?`: Closure to call when a "remove filter" intention is detected on the corresponding current filter. `func reload()`: Function called when the view needs to reload itself with new data. ### Example ```swift Swift icon=code theme={"system"} open class CurrentFilterListTableController: NSObject, CurrentFiltersController, UITableViewDataSource, UITableViewDelegate { open var onRemoveItem: ((FilterAndID) -> Void)? public let tableView: UITableView public var items: [FilterAndID] = [] private let cellIdentifier = "CurrentFilterListTableControllerCellID" public init(tableView: UITableView) { self.tableView = tableView super.init() tableView.dataSource = self tableView.delegate = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier) } open func setItems(_ item: [FilterAndID]) { items = item } open func reload() { tableView.reloadData() } // MARK: - UITableViewDataSource open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count } open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) let filterAndID = items[indexPath.row] cell.textLabel?.text = filterAndID.text return cell } // MARK: - UITableViewDelegate open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { onRemoveItem?(items[indexPath.row]) } } ``` ### SwiftUI InstantSearch provides the `CurrentFiltersObservableController` data model, which is an implementation of the `CurrentFiltersController` protocol adapted for usage with SwiftUI. `CurrentFiltersObservableController` must be connected to the `CurrentFiltersConnector` or `CurrentFiltersConnector` like any other `CurrentFiltersController` implementation. The example of the current filters view presenting the grouped filters. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var currentFiltersController: CurrentFiltersObservableController var body: some View { VStack { Text("Filters") .font(.title) let filtersPerGroup = Dictionary(grouping: currentFiltersController.filters) { el in el.id } .mapValues { $0.map(\.filter) } .map { $0 } ForEach(filtersPerGroup, id: \.key) { (group, filters) in HStack { Text(group.description) .bold() .padding(.leading, 5) Spacer() } .padding(.vertical, 5) .background(Color(.systemGray5)) ForEach(filters, id: \.self) { filter in HStack { Text(filter.description) .padding(.leading, 5) Spacer() } } } Spacer() } } } ``` If you prefer to create a custom SwiftUI view that presents each facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).), use the `CurrentFiltersObservableController` as a data model. It provides the `filters` property along with `toggle` and `isSelected` functions to streamline the design process of your custom SwiftUI view. # Dynamic facet list Source: https://www.algolia.com/doc/api-reference/widgets/dynamic-widgets/android Displays facets and facet values in the order returned by Algolia. ```kotlin Signature theme={"system"} DynamicFacetListConnector( searcher: SearcherIndex, filterState: FilterState, orderedFacets: List, selections: SelectionsPerAttribute, selectionModeForAttribute: Map, filterGroupForAttribute: Map ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the example code on GitHub. ## About this widget This widget displays matching refinement widgets in the order provided by [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering) in the search response. Configure the order with [facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/android) , override it with [Algolia Rules](/doc/guides/managing-results/rules/rules-overview) , or generate it automatically with [Dynamic Facets](/doc/guides/algolia-ai/dynamic-facets) . Dynamic Facets orders facets and facet values based on recent search data. ### Requirements Declare each facet attribute in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting). Configure the facet order in the [Algolia dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or with [`renderingContent`](/doc/api-reference/api-parameters/renderingContent). Set the query's [`facets`](/doc/api-reference/api-parameters/facets) property and include each facet attribute to retrieve. > [!NOTE] > > Dynamic facet list requires InstantSearch Android v2.1.1 or later. ## Example Create a `DynamicFacetListConnector`, set the query's facets, and run the initial search. ```kotlin Kotlin icon=code expandable theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_SEARCH_API_KEY", indexName = "INDEX_NAME" ) val filterState = FilterState() val color = "color" val country = "country" val brand = "brand" val size = "size" val dynamicFacets = DynamicFacetListConnector( searcher = searcher, filterState = filterState, selectionModeForAttribute = mapOf( color to SelectionMode.Multiple, country to SelectionMode.Multiple ), filterGroupForAttribute = mapOf( brand to (brand to FilterOperator.Or), color to (color to FilterOperator.Or), ) ) val connection = ConnectionHandler(dynamicFacets) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val dynamicFacetListView = DynamicFacetListAdapter(DynamicFacetListViewHolderImpl.Factory) connection += dynamicFacets.connectView(dynamicFacetListView) searcher.query = searcher.query.copy(facets = listOf(brand, color, size, country)) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connection.clear() } } ``` ## Parameters **searcher** `SearcherIndex` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that runs searches. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that stores the current filters. **orderedFacets** `List\` The initial ordered facets. **selections** `SelectionsPerAttribute` Maps each facet attribute to its selected facet values. **selectionModeForAttribute** `Map\` Maps each facet attribute to its facet-value selection mode. The default mode is `Single`. **filterGroupForAttribute** `Map\` Maps each facet attribute to the filter group that stores its filters in the filter state. ## View **view** `DynamicFacetListView` *required* The view that renders the dynamic facet list. ```kotlin Kotlin icon=code theme={"system"} val facetListAdapter = FacetListAdapter(FacetListViewHolderImpl.Factory) val facetListPresenter = FacetListPresenterImpl(listOf(FacetSortCriterion.CountDescending, FacetSortCriterion.AlphabeticalAscending)) facetListCategory.connectView(facetListAdapter, facetListPresenter) ``` ## Low-level API Use the following components to connect the dynamic facet list: * [`Searcher`](/doc/api-reference/widgets/instantsearch/android): runs searches. * `FilterState`: stores the current filter state. * `DynamicFacetsListViewModel`: manages dynamic facet state and behavior. ```kotlin Kotlin icon=code expandable theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "ALGOLIA_APPLICATION_ID", apiKey = "ALGOLIA_SEARCH_API_KEY", indexName = "INDEX_NAME" ) val filterState = FilterState() val color = "color" val country = "country" val brand = "brand" val size = "size" val dynamicFacetViewModel = DynamicFacetListViewModel( selectionModeForAttribute = mapOf( color to SelectionMode.Multiple, country to SelectionMode.Multiple ) ) val connection = ConnectionHandler(dynamicFacets) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) connection += dynamicFacetViewModel.connectSearcher(searcher) connection += dynamicFacetViewModel.connectFilterState(filterState, mapOf( brand to (brand to FilterOperator.Or), color to (color to FilterOperator.Or)) ) // Your implementation of `DynamicFacetListView` val dynamicFacetListView = DynamicFacetListAdapter(DynamicFacetListViewHolderImpl.Factory) connection += dynamicFacets.connectView(dynamicFacetListView) searcher.query = searcher.query.copy(facets = listOf(brand, color, size, country)) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connection.clear() } } ``` ## Customize the view The `DynamicFacetListView` interface displays the ordered facets and handles user interactions. Use this interface to provide a custom view: ```kotlin Kotlin icon=code theme={"system"} // Implements DynamicFacetListView. val myDynamicFacetListView = MyDynamicFacetListView() dynamicFacets.connectView(myDynamicFacetListView) ``` ### Android views `DynamicFacetListAdapter` is a built-in implementation of `DynamicFacetListView` that extends `ListAdapter`. Pass a `DynamicFacetListViewHolder.Factory` implementation to the adapter: ```kotlin Kotlin icon=code theme={"system"} // Implements DynamicFacetListViewHolder.Factory. val factory = DynamicFacetListViewHolderImpl.Factory val dynamicFacetListView = DynamicFacetListAdapter(factory) dynamicFacets.connectView(dynamicFacetListView) ``` ### Compose UI `DynamicFacetListState` implements `DynamicFacetListView` for Compose UI. Use its `facetOrder` and `facetSelections` properties and its `toggle` and `isSelected` functions to build a custom Compose UI. #### Implementation example ```kotlin Kotlin icon=code expandable theme={"system"} import com.algolia.client.model.search.FacetHits @Composable fun DynamicFacetsList(modifier: Modifier = Modifier, dynamicFacetListState: DynamicFacetListState) { LazyColumn(modifier) { dynamicFacetListState.facetOrder.forEach { (attribute, facets) -> item { Header(attribute = attribute) } items(facets) { facet -> FacetRow( modifier = Modifier.fillMaxWidth(), facet = facet, isSelected = dynamicFacetListState.isSelected(facet, attribute), onClick = { dynamicFacetListState.toggle(facet, attribute) } ) } } } } @Composable fun Header(modifier: Modifier = Modifier, attribute: String) { Text(modifier = modifier.padding(horizontal = 16.dp, vertical = 8.dp), text = attribute) } @Composable fun FacetRow(modifier: Modifier = Modifier, facet: FacetHits, isSelected: Boolean, onClick: () -> Unit = {}) { Card(modifier = modifier, onClick = onClick) { Row(Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { Text(modifier = Modifier.weight(1f), text = facet.value) if (isSelected) Icon(Icons.Default.Check, contentDescription = null) Text(text = facet.count.toString()) } } } ``` # Dynamic facet list Source: https://www.algolia.com/doc/api-reference/widgets/dynamic-widgets/ios Displays facets and facet values in the order returned by Algolia. ```swift Signature theme={"system"} DynamicFacetListConnector( searcher: Searcher, filterState: FilterState, orderedFacets: [AttributedFacets], selections: [Attribute: Set], selectionModeForAttribute: [Attribute: SelectionMode], filterGroupForAttribute: [Attribute: FilterGroupDescriptor], controller: DynamicFacetListController ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the example code on GitHub. ## About this widget This widget displays matching refinement widgets in the order provided by [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering) in the search response. Configure the order with [facet display](/doc/guides/building-search-ui/ui-and-ux-patterns/facet-display/ios) , override it with [Algolia Rules](/doc/guides/managing-results/rules/rules-overview) , or generate it automatically with [Dynamic Facets](/doc/guides/algolia-ai/dynamic-facets) . Dynamic Facets orders facets and facet values based on recent search data. ### Requirements Declare each facet attribute in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting). Use the [Algolia dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or the API. Configure the facet order with [`renderingContent.facetOrdering`](/doc/api-reference/api-parameters/renderingContent#param-facet-ordering). Add each facet attribute to the query's [`facets`](/doc/api-reference/api-parameters/facets) property with `Searcher.request.query.facets`. > [!NOTE] > > Dynamic facet list requires InstantSearch iOS v7.12.0 or later. > In v8, passing `controller` when you create the connector crashes your app. > Upgrade to v9, or omit `controller` and call the connector's `connectController` method. ## Example Create a `DynamicFacetListConnector`, set the query's facets, and run the initial search. ```swift Swift icon=code expandable theme={"system"} let searcher = HitsSearcher(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_SEARCH_API_KEY", indexName: "INDEX_NAME") searcher.request.query.facets = ["brand", "color", "size", "country"] let filterState = FilterState() let dynamicFacetListController = DynamicFacetListTableViewController() let dynamicFacetListConnector = DynamicFacetListConnector(searcher: searcher, filterState: filterState, selectionModeForAttribute: [ "color": .multiple, "country": .multiple ], filterGroupForAttribute: [ "brand": ("brand", .or), "color" : ("color", .or), ], controller: dynamicFacetListController) searcher.connectFilterState(filterState) searcher.search() ``` ## Parameters **searcher** `Searcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that runs searches. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that stores the current filters. **orderedFacets** `[AttributedFacets]` The initial ordered facets. **selections** `[Attribute: Set\]` Maps each facet attribute to its selected facet values. **selectionModeForAttribute** `[Attribute: SelectionMode]` Maps each facet attribute to its facet-value selection mode. The default mode is `.single`. **filterGroupForAttribute** `[Attribute: FilterGroupDescriptor]` Maps each facet attribute to the filter group that stores its filters in the filter state. **controller** `DynamicFacetListController` The controller that displays the ordered facets and handles selections. ## Low-level API Use the following components to connect the dynamic facet list: * [`Searcher`](/doc/api-reference/widgets/instantsearch/ios): runs searches. * `FilterState`: stores the current filter state. * `DynamicFacetListInteractor`: manages dynamic facet state and behavior. * `DynamicFacetListController`: displays the ordered facets and handles user interactions. ```swift Swift icon=code expandable theme={"system"} let searcher = HitsSearcher(appID: "ALGOLIA_APPLICATION_ID", apiKey: "ALGOLIA_SEARCH_API_KEY", indexName: "INDEX_NAME") searcher.request.query.facets = ["brand", "color", "size", "country"] let filterState = FilterState() let dynamicFacetListController = DynamicFacetListTableViewController() let dynamicFacetListInteractor = DynamicFacetListInteractor(selectionModeForAttribute: [ "color": .multiple, "country": .multiple ]) dynamicFacetListInteractor.connectSearcher(searcher) dynamicFacetListInteractor.connectFilterState(filterState, filterGroupForAttribute: [ "brand": ("brand", .or), "color" : ("color", .or), ]) dynamicFacetListInteractor.connectController(dynamicFacetListController) searcher.connectFilterState(filterState) searcher.search() ``` ## Customize the view Use a built-in controller or use `DynamicFacetListController` to customize how the dynamic facet list appears and handles user interactions. ### UIKit `DynamicFacetListTableViewController` is a built-in `UITableViewController` that conforms to the `DynamicFacetListController` protocol. To use a `UICollectionView`, a third-party view, or custom behavior, create a controller that conforms to `DynamicFacetListController`. #### `DynamicFacetListController` protocol | Member | Description | | - | - | | `func setOrderedFacets(_ orderedFacets: [AttributedFacets])` | Updates the ordered facets. | | `func setSelections(_ selections: [Attribute: Set])` | Updates the selected facet values. | | `var didSelect: ((Attribute, Facet) -> Void)?` | Runs when users select a facet. | ### SwiftUI `DynamicFacetListObservableController` conforms to `DynamicFacetListController` for SwiftUI. Use its `orderedFacets` and `selections` properties and its `toggle` and `isSelected` methods to build a custom SwiftUI view. #### Build a SwiftUI view ```swift Swift icon=code expandable theme={"system"} struct ContentView: View { @ObservedObject var dynamicFacetListController: DynamicFacetListObservableController var body: some View { ScrollView { ForEach(dynamicFacetListController.orderedFacets, id: \.attribute) { orderedFacet in VStack(spacing: 0) { // Facet header ZStack { Color(.systemGray5) Text(orderedFacet.attribute.rawValue) .fontWeight(.semibold) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 5) } // Facet values ForEach(orderedFacet.facets, id: \.value) { facet in VStack(spacing: 0) { FacetRow(facet: facet, isSelected: dynamicFacetListController.isSelected(facet, for: orderedFacet.attribute)) .onTapGesture { dynamicFacetListController.toggle(facet, for: orderedFacet.attribute) } .frame(minHeight: 44, idealHeight: 44, maxHeight: .infinity, alignment: .center) .padding(.horizontal, 5) } } } } } } } ``` # Filter Numeric Comparison Source: https://www.algolia.com/doc/api-reference/widgets/filter-comparison/android Lets users refine search results based on numeric comparison. ```kotlin Signature theme={"system"} FilterComparisonConnector( filterState: FilterState, attribute: String, operator: NumericOperator, number: T?, groupID: FilterGroupID ) where T : Number, T : Comparable ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Filter Numeric Comparison example code on GitHub. ## About this widget Filter Numeric Comparison is a view to filter on a numeric value using a comparison operator. ## Examples ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val price = "price" val operator = NumericOperator.GreaterOrEquals val filterComparison = FilterComparisonConnector( filterState = filterState, attribute = price, operator = operator ) val connection = ConnectionHandler(filterComparison, searcher.connectFilterState(filterState)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val priceView: NumberView = MyFilterPriceView() // your `NumberView` implementation connection += filterComparison.connectView(priceView) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Low-level API If you want to fully control the Filter Numeric Comparison components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. * `FilterState`. The current state of the filters. * `NumberViewModel`. The logic applied to the numeric value. * `NumberView`. The view that renders the numeric value. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val price = "price" val operator = NumericOperator.Greater val viewModel = NumberViewModel(range = 0..10) val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view: NumberView = MyFilterPriceView() // your `NumberView` implementation searcher.query = searcher.query.copy(facets = (searcher.query.facets ?: emptyList()) + listOf(price)) connection += searcher.connectFilterState(filterState) connection += viewModel.connectView(view) connection += viewModel.connectFilterState(filterState, price, operator) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Compose UI InstantSearch provides the `NumberState` as a state model, which is an implementation of the `NumberView` interface. You need to connect `NumberState` to the `FilterComparisonConnector` or `NumberViewModel` like any other `NumberView` implementation. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val price = "price" val priceState = NumberState() val filterComparison = FilterComparisonConnector( filterState = filterState, attribute = price, operator = NumericOperator.GreaterOrEquals ) val connections = ConnectionHandler(filterComparison) init { connections += searcher.connectFilterState(filterState) connections += filterComparison.connectView(priceState) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyFilterPrice(priceState) // your own UI composable to display `text` and `computation` } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` ## Parameters **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that holds filters. **attribute** `String` *required* The attribute to filter on. **operator** `NumericOperator` *required* The `NumericOperator` used to perform a numeric comparison. **number** `T : Number, T : Comparable\` Initial numeric value to filter on. **groupID** `FilterGroupID` Groups all created filters under an ID and composes them with this operator. Defaults to the used attribute, with `FilterOperator.And` between filters in this group. ## View **view** `NumberView\` *required* The view that renders the numeric value. **presenter** `NumberPresenter\` (default: NumberPresenterImpl) How to display the numeric value. ```kotlin Kotlin icon=code theme={"system"} val view: NumberView = MyFilterYearView() // your `NumberView` implementation filterNumericComparison.connectView(view) { year -> year?.toString() ?: "" } ``` # Filter Numeric Comparison Source: https://www.algolia.com/doc/api-reference/widgets/filter-comparison/ios Lets users refine search results based on numeric comparison. ```swift Signature theme={"system"} FilterComparisonConnector( searcher: HitsSearcher, filterState: FilterState, attribute: Attribute, numericOperator: Filter.Numeric.Operator, number: Number?, bounds: ClosedRange?, operator: RefinementOperator, groupName: String? ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Filter Numeric Comparison example code on GitHub. ## About this widget Filter Numeric Comparison is a view to filter on a numeric value using a comparison operator. ## Examples Instantiate a `FilterComparisonConnector` and launch an initial search on its searcher. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourApiKey", indexName: "YourIndexName") let filterState = FilterState() let stepperController = NumericStepperController() let filterComparisonConnector = FilterComparisonConnector(searcher: searcher, filterState: filterState, numericOperator: .greaterThanOrEqual, attribute: "price", controller: stepperController) searcher.search() ``` ### Low-level API If you want to fully control the Filter Numeric Comparison components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `FilterState`. The current state of the filters. * `NumberInteractor`. The logic applied to the numeric value. * `NumberController`. The view controller that renders the numeric value. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourApiKey", indexName: "YourIndexName") let filterState = FilterState() let numberInteractor = NumberInteractor() let stepperController = NumericStepperController() interactor.connectSearcher(searcher, attribute: "price") interactor.connectFilterState(filterState, attribute: attribute, numericOperator: numericOperator, operator: .greaterThanOrEqual) interactor.connectNumberController(stepperController) searcher.search() ``` ### SwiftUI InstantSearch provides the `NumberObservableController` as a state model, which is an implementation of the `NumberController` interface. You need to connect `NumberObservableController` to the `FilterComparisonConnector` or `NumberInteractor` like any other `NumberController` implementation. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var priceController: NumberObservableController var body: some View { HStack { Stepper(value: $priceController.value, in: priceController.bounds, step: 0.1) { HStack { Text("Price:") Spacer() Text(String(format: "%.2f", priceController.value)) } } } } } ``` If you prefer to create a custom SwiftUI view that presents the numeric range, you can directly use the `NumberObservableController` as a data model. It provides `value` and `bounds` properties to streamline the design process of your custom SwiftUI view. ## Parameters **searcher** `HitsSearcher` *required* The Searcher that handles your searches **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that holds filters. **attribute** `Attribute` *required* The attribute to filter on. **numericOperator** `Filter.Numeric.Operator` *required* The comparison operator to apply. **number** `Number: Comparable & DoubleRepresentable` The initial numeric value to filter on. **bounds** `ClosedRange\?` The optional bounds limiting the max and the min value of the number. **operator** `RefinementOperator` *required* Whether the filter is added to a conjunctive (`and`) or a disjunctive (`or`) group in the filter state. **groupName** `String?` Filter group name in the filter state. If not specified, the attribute value is used as the group name. ## Controller **controller** `NumberController` *required* Controller interfacing with a concrete number view. ```swift Swift icon=code theme={"system"} let controller = MyNumberController() // your `NumberController` implementation filterComparisonConnector.connectNumberController(controller) ``` # FilterList (Facet) Source: https://www.algolia.com/doc/api-reference/widgets/filter-list-facet/android Shows a list of facet filters for refining search results. ```kotlin Signature theme={"system"} FilterListConnector.Facet( filters: List, filterState: FilterState, selectionMode: SelectionMode, groupID: FilterGroupID ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the FilterList (Facet) example code on GitHub. ## About this widget `FilterList.Facet` is a filtering view that displays any kind of [facet filters](/doc/api-reference/api-parameters/facetFilters) and lets users refine their search results by selecting them. Compared to the [`RefinementList`](/doc/api-reference/widgets/refinement-list/android), which takes its values from the search response facets, this widget displays facet filters that you add yourself. ## Examples ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val color = "color" val groupColor = groupAnd(color) val facetFilters = listOf( Filter.Facet(color, "red"), Filter.Facet(color, "green"), Filter.Facet(color, "blue"), Filter.Facet(color, "yellow"), Filter.Facet(color, "black") ) val filterListConnector = FilterListConnector.Facet( filters = facetFilters, filterState = filterState, selectionMode = SelectionMode.Single, groupID = groupColor ) val connection = ConnectionHandler(filterListConnector, searcher.connectFilterState(filterState)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val filterListView: FilterListView.Facet = MyFilterListFacetAdapter() // your `FilterListView.Facet` implementation connection += filterListConnector.connectView(filterListView) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Low-level API If you want to fully control the `FilterList.Facet` components and connect them manually, use the following components: * `Searcher`: The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. * `FilterState`: The current state of the filters. * `FilterListViewModel.Facet`: The logic applied to the facet filters. * `FilterListView.Facet`: The view that renders the facet filters. * `FilterPresenter`: Optional. The presenter to customize the display of the filters. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val color = "color" val filters = listOf( Filter.Facet(color, "red"), Filter.Facet(color, "green"), Filter.Facet(color, "blue"), Filter.Facet(color, "yellow"), Filter.Facet(color, "black") ) val viewModel = FilterListViewModel.Facet(filters) val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view: FilterListView.Facet = MyFilterListFacetAdapter() // your `FilterListView.Facet` implementation connection += searcher.connectFilterState(filterState) connection += viewModel.connectFilterState(filterState) connection += viewModel.connectView(view) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Compose UI InstantSearch provides the `FilterListState` as a state model, which is an implementation of the `FilterListView` interface. You need to connect `FilterListState` to the `FilterListConnector` or `FilterListViewModel` like any other `FilterListView` implementation. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val color = "color" val groupColor = groupAnd(color) val facetFilters = listOf( Filter.Facet(color, "red"), Filter.Facet(color, "green"), Filter.Facet(color, "blue"), Filter.Facet(color, "yellow"), Filter.Facet(color, "black") ) val filterListState = FilterListState() val filterListConnector = FilterListConnector.Facet( filters = facetFilters, filterState = filterState, selectionMode = SelectionMode.Single, groupID = groupColor ) val connections = ConnectionHandler(filterListConnector) init { connections += searcher.connectFilterState(filterState) connections += filterListConnector.connectView(filterListState) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { filterListState.items.forEach { selectableFacet -> FilterRow( // your own UI composable to display `SelectableItem` selectableFilter = selectableFacet, onClick = { filterListState.onSelection?.invoke(it) } ) } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` ## Parameters **filters** `List\` *required* The facet filters to display. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that will hold your filters. **selectionMode** `SelectionMode` Whether the list can have `Single` or `Multiple` selections. **groupID** `FilterGroupID` The identifier for the group of filters. ## View **view** `FilterListView.Facet` The view that renders the filters. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter val view: FilterListView.Facet = MyFilterListFacetAdapter() filterListConnector.connectView(view) // Example of FilterListView.Facet implementation class MyFilterListFacetAdapter : FilterListView.Facet, ListAdapter, FilterListViewHolder>(DiffUtilItem()) { override var onSelection: Callback? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterListViewHolder { return FilterListViewHolder(parent.inflate(R.layout.list_item_selectable)) } override fun onBindViewHolder(holder: FilterListViewHolder, position: Int) { val (filter, selected) = getItem(position) holder.bind(FilterPresenterImpl()(filter), selected) { onSelection?.invoke(filter) } } override fun setItems(items: List>) { submitList(items) } } class DiffUtilItem : DiffUtil.ItemCallback>() { override fun areItemsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem.first == newItem.first } override fun areContentsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem == newItem } } ``` # Filter List (Facet) Source: https://www.algolia.com/doc/api-reference/widgets/filter-list-facet/ios Shows a list of facet filters for refining search results. ```swift Signature theme={"system"} FacetFilterListConnector( facetFilters: [FacetFilter], selectionMode: SelectionMode, filterState: FilterState, operator: RefinementOperator, groupName: String, controller: FilterListTableController ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Filter List (Facet) example code on GitHub. ## About this widget `FacetFiltersList` is a filtering component that displays [facet filters](/doc/api-reference/api-parameters/facetFilters) and lets users refine the search results by selecting them. Compared to the [`RefinementList`](/doc/api-reference/widgets/refinement-list/ios), which takes its values from the search response facets, this widget displays facet filters that you add yourself. ## Examples Instantiate a `FacetFilterListConnector` and launch an initial search on its [`Searcher`](/doc/api-reference/widgets/instantsearch/ios). ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState = FilterState() let filters: [Filter.Facet] = ["red", "blue", "green", "yellow", "black"].map { .init(attribute: "color", stringValue: $0) } let filterListTableView: UITableView = .init() let filterListController: FilterListTableController = .init(tableView: filterListTableView) let filterListConnector = FacetFilterListConnector(facetFilters: filters, selectionMode: .multiple, filterState: filterState, operator: .and, groupName: "Facet Filters", controller: filterListController) searcher.connectFilterState(filterState) searcher.search() ``` ## Parameters **facetFilters** `[FacetFilter]` *required* The facet filters to display. **selectionMode** `SelectionMode` Whether a user can select `.single` or `.multiple` values. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that holds your filters. **operator** `RefinementOperator` *required* Whether to apply `and` or `or` behavior to the filters in the `FilterState`. For example, with `or` behavior, the filter sent to Algolia is `_tags:promo OR color:green`. **groupName** `String` *required* Filter group name. **controller** `FilterListTableController\` Controller interfacing with a concrete filter list view. ## Low-level API If you want to fully control the `FacetFilterList` components and connect them manually, you can use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `FilterState`. The current state of the filters. * `FacetFilterListInteractor`. The logic applied to the facet filters. * `FilterListController`. The view that will render the facet filters. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState = FilterState() let filters: [Filter.Facet] = ["red", "blue", "green", "yellow", "black"].map { .init(attribute: "color", stringValue: $0) } let filterListTableView: UITableView = .init() let filterListController: FilterListTableController = .init(tableView: filterListTableView) let filterListInteractor = FacetFilterListInteractor(items: filters, selectionMode: .multiple) filterListInteractor.connectFilterState(filterState, operator: .and, groupName: "Facet Filters") filterListInteractor.connectController(filterListController) searcher.connectFilterState(filterState) searcher.search() ``` ## Customizing your view The controllers provided by default, such as `FilterListTableController`, work well when you want to use native UIKit components with their default behavior. If you want to use another component such as a `UICollectionView`, a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the `FacetFilterListController` protocol. ### Protocol `var onClick: ((Filter.Facet) -> Void)?`: Closure to call when a filter is clicked. `func setSelectableItems(selectableItems: [SelectableItem])` Function called when a new array of selectable facets is updated. This is the UI State of the refinement list. `func reload()` Function called when the list view requires a reload. ### Implementation example ```swift Swift icon=code theme={"system"} class FacetFilterListTableController: NSObject, FacetFilterListController, UITableViewDataSource, UITableViewDelegate { public var onClick: ((Filter.Facet) -> Void)? public let tableView: UITableView public var selectableItems: [SelectableItem] = [] public var filterPresenter: FilterPresenter? let cellID: String public init(tableView: UITableView, cellID: String = "cellID") { self.tableView = tableView self.cellID = cellID super.init() tableView.dataSource = self tableView.delegate = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID) } // MARK: - FilterListController public func setSelectableItems(selectableItems: [SelectableItem]) { self.selectableItems = selectableItems } public func reload() { tableView.reloadData() } // MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return selectableItems.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) let filter = selectableItems[indexPath.row] let filterPresenter = self.filterPresenter ?? DefaultPresenter.Filter.present cell.textLabel?.text = filterPresenter(Filter(filter.item)) cell.accessoryType = filter.isSelected ? .checkmark : .none return cell } // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectableItem = selectableItems[indexPath.row] onClick?(selectableItem.item) } } ``` ### SwiftUI InstantSearch provides the `FilterList` SwiftUI view which you can embed in your views. It uses `FilterListObservableController` as a data model, which is an implementation of the `SelectableListController` protocol adapted for usage with SwiftUI. `FilterListObservableController` must be connected to the `FacetFilterListConnector` or `FacetFilterListInteractor` like any other `SelectableListController` implementation. You have to define the appearance of the view representing a single facet filter and its selection state. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var facetFiltersController: FilterListObservableController var body: some View { FilterList(facetFiltersController) { filter, isSelected in // declare the view presenting a single facet filter and its selection state HStack { Text(filter.value.description) Spacer() if isSelected { Image(systemName: "checkmark") .foregroundColor(.accentColor) } } .contentShape(Rectangle()) .frame(idealHeight: 44) .padding(.horizontal, 5) } } } ``` If you prefer to create a custom SwiftUI view that presents the list of facet filters, you can directly use the `FilterListObservableController` as a data model. It provides `filters` and `selections` properties along with `toggle` and `isSelected` functions to streamline the design process of your custom SwiftUI view. # FilterList (numeric) Source: https://www.algolia.com/doc/api-reference/widgets/filter-list-numeric/android Shows a list of numeric filter options for refining search results. ```kotlin Signature theme={"system"} FilterListConnector.Numeric( filters: List, filterState: FilterState, selectionMode: SelectionMode, groupID: FilterGroupID ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the FilterList (numeric) example code on GitHub. ## About this widget `FilterList.Numeric` is a filtering view that displays any kind of [numeric filters](/doc/api-reference/api-parameters/numericFilters) and lets users refine the search results by selecting them. Compared to the [`RefinementList`](/doc/api-reference/widgets/refinement-list/android), which takes its values from the search response facets, this widget displays numeric filters that you add yourself. ## Examples ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val price = "price" val groupColor = groupAnd(price) val numericFilters = listOf( Filter.Numeric(price, NumericOperator.Less, 5), Filter.Numeric(price, 5..10), Filter.Numeric(price, 10..25), Filter.Numeric(price, 25..100), Filter.Numeric(price, NumericOperator.Greater, 100) ) val filterListConnector = FilterListConnector.Numeric( filters = numericFilters, filterState = filterState, groupID = groupColor ) val connection = ConnectionHandler(filterListConnector, searcher.connectFilterState(filterState)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val filterListView: FilterListView.Numeric = MyFilterListNumericAdapter() // your `FilterListView.Numeric` implementation connection += filterListConnector.connectView(filterListView) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Low-level API If you want to fully control the `FilterList.Numeric` components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. * `FilterState`. The current state of the filters. * `FilterListViewModel.Numeric`. The logic applied to the numeric filters. * `FilterListView.Numeric`. The view that renders the numeric filters. * `FilterPresenter`. Optional. The presenter to customize the display of the filters. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val price = "price" val filters = listOf( Filter.Numeric(price, NumericOperator.Less, 5), Filter.Numeric(price, 5..10), Filter.Numeric(price, 10..25), Filter.Numeric(price, 25..100), Filter.Numeric(price, NumericOperator.Greater, 100) ) val viewModel = FilterListViewModel.Numeric(filters) val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view: FilterListView.Numeric = MyFilterListNumericAdapter() // your `FilterListView.Numeric` implementation connection += searcher.connectFilterState(filterState) connection += viewModel.connectFilterState(filterState) connection += viewModel.connectView(view) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Compose UI InstantSearch provides the `FilterListState` as a state model, which is an implementation of the `FilterListView` interface. You need to connect `FilterListState` to the `FilterListConnector` or `FilterListViewModel` like any other `FilterListView` implementation. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val price = "price" val groupColor = groupAnd(price) val numericFilters = listOf( Filter.Numeric(price, NumericOperator.Less, 5), Filter.Numeric(price, 5..10), Filter.Numeric(price, 10..25), Filter.Numeric(price, 25..100), Filter.Numeric(price, NumericOperator.Greater, 100) ) val filterListState = FilterListState() val filterListConnector = FilterListConnector.Numeric( filters = numericFilters, filterState = filterState, groupID = groupColor ) val connections = ConnectionHandler(filterListConnector) init { connections += searcher.connectFilterState(filterState) connections += filterListConnector.connectView(filterListState) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { filterListState.items.forEach { selectableFacet -> FilterRow( // your own UI composable to display `SelectableItem` selectableFilter = selectableFacet, onClick = { filterListState.onSelection?.invoke(it) } ) } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` ## Parameters **filters** `List\` *required* The numeric filters to display. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that will hold your filters. **selectionMode** `SelectionMode` Whether the list can have `Single` or `Multiple` selections. **groupID** `FilterGroupID` The identifier for the group of filters. ## View **view** `FilterListView.Numeric` The view that renders the filters. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter val view: FilterListView.Numeric = MyFilterListNumericAdapter() filterListConnector.connectView(view) // Example of `FilterListView.Numeric` implementation class MyFilterListNumericAdapter : FilterListView.Numeric, ListAdapter, FilterListViewHolder>(DiffUtilItem()) { override var onSelection: Callback? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterListViewHolder { return FilterListViewHolder(parent.inflate(R.layout.list_item_selectable)) } override fun onBindViewHolder(holder: FilterListViewHolder, position: Int) { val (filter, selected) = getItem(position) holder.bind(FilterPresenterImpl()(filter), selected) { onSelection?.invoke(filter) } } override fun setItems(items: List>) { submitList(items) } } class DiffUtilItem : DiffUtil.ItemCallback>() { override fun areItemsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem.first == newItem.first } override fun areContentsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem == newItem } } ``` # Filter List (Numeric) Source: https://www.algolia.com/doc/api-reference/widgets/filter-list-numeric/ios Shows a list of numeric filter options for refining search results. ```swift Signature theme={"system"} NumericFilterListConnector( numericFilters: [NumericFilter], selectionMode: SelectionMode, filterState: FilterState, operator: RefinementOperator, groupName: String, controller: FilterListTableController ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Filter List (Numeric) example code on GitHub. ## About this widget `NumericFilterList` is a filtering component that displays [numeric filters](/doc/api-reference/api-parameters/numericFilters) and lets users refine the search results by selecting them. Compared to the [`RefinementList`](/doc/api-reference/widgets/refinement-list/ios), which takes its values from the search response facets, this widget displays numeric filters that you add yourself. ## Examples Instantiate a `NumericFilterListConnector` and launch an initial search on its [`Searcher`](/doc/api-reference/widgets/instantsearch/ios). ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState = FilterState() let filters: [Filter.Numeric] = [ .init(attribute: "price", operator: .lessThan, value: 5), .init(attribute: "price", range: 5...10), .init(attribute: "price", range: 10...25), .init(attribute: "price", range: 25...100), .init(attribute: "price", operator: .greaterThan, value: 100) ] let filterListTableView: UITableView = .init() let filterListController: FilterListTableController = .init(tableView: filterListTableView) let filterListConnector = NumericFilterListConnector(numericFilters: filters, selectionMode: .multiple, filterState: filterState, operator: .and, groupName: "Numeric Filters", controller: filterListController) searcher.connectFilterState(filterState) searcher.search() ``` ## Parameters **numericFilters** `[NumericFilter]` *required* The numeric filters to display. **selectionMode** `SelectionMode` Whether a user can select `.single` or `.multiple` values. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that holds your filters. **operator** `RefinementOperator` *required* Whether to apply `and` or `or` behavior to the filters in the `FilterState`. For example, with `or` behavior, the filter sent to Algolia is `_tags:promo OR color:green`. **groupName** `String` *required* Filter group name. **controller** `FilterListTableController\` Controller interfacing with a concrete filter list view. ## Low-level API If you want to fully control the `NumericFilterList` components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `FilterState`. The current state of the filters. * `NumericFilterListInteractor`. The logic applied to the numeric filters. * `FilterListController`. The view that will render the numeric filters. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState = FilterState() let filters: [NumericFilter] = [ .init(attribute: "price", operator: .lessThan, value: 5), .init(attribute: "price", range: 5...10), .init(attribute: "price", range: 10...25), .init(attribute: "price", range: 25...100), .init(attribute: "price", operator: .greaterThan, value: 100) ] let filterListInteractor = NumericFilterListInteractor(items: filters, selectionMode: .multiple) filterListInteractor.connectFilterState(filterState, operator: .and, groupName: "Numeric Filters") searcher.connectFilterState(filterState) searcher.search() ``` ## Customizing your view The default controllers, such as `FilterListTableController`, work well when you want to use native UIKit with their default behavior. If you want to use another component such as a `UICollectionView`, a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the `NumericFilterListController` protocol. ### Protocol `var onClick: ((Filter.Numeric) -> Void)?`: Closure to call when a filter is clicked. `func setSelectableItems(selectableItems: [SelectableItem])` Function called when a new array of selectable facets is updated. This is the UI State of the refinement list. `func reload()` Function called when the list view requires a reload. ### Implementation example ```swift Swift icon=code theme={"system"} class NumericFilterListTableController: NSObject, NumericFilterListController, UITableViewDataSource, UITableViewDelegate { public var onClick: ((Filter.Numeric) -> Void)? public let tableView: UITableView public var selectableItems: [SelectableItem] = [] public var filterPresenter: FilterPresenter? let cellID: String public init(tableView: UITableView, cellID: String = "cellID") { self.tableView = tableView self.cellID = cellID super.init() tableView.dataSource = self tableView.delegate = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID) } // MARK: - FilterListController public func setSelectableItems(selectableItems: [SelectableItem]) { self.selectableItems = selectableItems } public func reload() { tableView.reloadData() } // MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return selectableItems.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) let filter = selectableItems[indexPath.row] let filterPresenter = self.filterPresenter ?? DefaultPresenter.Filter.present cell.textLabel?.text = filterPresenter(Filter(filter.item)) cell.accessoryType = filter.isSelected ? .checkmark : .none return cell } // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectableItem = selectableItems[indexPath.row] onClick?(selectableItem.item) } } ``` ### SwiftUI InstantSearch provides the `FilterList` SwiftUI view which you can embed in your views. It uses `FilterListObservableController` as a data model, which is an implementation of the `SelectableListController` protocol adapted for usage with SwiftUI. `FilterListObservableController` must be connected to the `NumericFilterListConnector` or `NumericFilterListInteractor` like any other `SelectableListController` implementation. You have to define the appearance of the view representing a single numeric filter and its selection state. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var filterListController: FilterListObservableController var body: some View { FilterList(filterListController) { filter, isSelected in // declare the view presenting a single numeric filter and its selection state HStack { Text(filter.description) Spacer() if isSelected { Image(systemName: "checkmark") .foregroundColor(.accentColor) } } .contentShape(Rectangle()) .frame(idealHeight: 44) .padding(.horizontal, 5) } } } ``` If you prefer to create a custom SwiftUI view that presents the list of numeric filters, you can directly use the `FilterListObservableController` as a data model. It provides `filters` and `selections` properties along with `toggle` and `isSelected` functions to streamline the design process of your custom SwiftUI view. # FilterList (Tag) Source: https://www.algolia.com/doc/api-reference/widgets/filter-list-tag/android Shows a list of tag filters for refining search results. ```kotlin Signature theme={"system"} FilterListConnector.Tag( filters: List, filterState: FilterState, selectionMode: SelectionMode, groupID: FilterGroupID ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the FilterList (Tag) example code on GitHub. ## About this widget `FilterList.Tag` is a filtering view that displays any kind of [tag filters](/doc/api-reference/api-parameters/tagFilters) and lets users refine the search results by selecting them. Compared to the [`RefinementList`](/doc/api-reference/widgets/refinement-list/android), which takes its values from the search response facets, this widget displays tag filters that you add yourself. ## Examples ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val tags = "_tags" val groupTags = groupOr(tags) val filters = listOf( Filter.Tag("free shipping"), Filter.Tag("coupon"), Filter.Tag("free return"), Filter.Tag("on sale"), Filter.Tag("no exchange") ) val filterListConnector = FilterListConnector.Tag( filters = filters, filterState = filterState, groupID = groupTags ) val connection = ConnectionHandler(filterListConnector, searcher.connectFilterState(filterState)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val filterListView: FilterListView.Tag = MyFilterListTagAdapter() // your `FilterListView.Tag` implementation connection += filterListConnector.connectView(filterListView) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Low-level API If you want to fully control the `FilterList.Tag` components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. * `FilterState`. The current state of the filters. * `FilterListViewModel.Tag`. The logic applied to the tag filters. * `FilterListView.Tag`. The view that renders the tag filters. * `FilterPresenter`. Optional. The presenter to customize the display of the filters. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val filters = listOf( Filter.Tag("free shipping"), Filter.Tag("coupon"), Filter.Tag("free return"), Filter.Tag("on sale"), Filter.Tag("no exchange") ) val viewModel = FilterListViewModel.Tag(filters) val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view: FilterListView.Tag = MyFilterListTagAdapter() // your `FilterListView.Tag` implementation connection += searcher.connectFilterState(filterState) connection += viewModel.connectFilterState(filterState) connection += viewModel.connectView(view) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Compose UI InstantSearch provides the `FilterListState` as a state model, which is an implementation of the `FilterListView` interface. You need to connect `FilterListState` to the `FilterListConnector` or `FilterListViewModel` like any other `FilterListView` implementation. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val tags = "_tags" val groupTags = groupOr(tags) val filters = listOf( Filter.Tag("free shipping"), Filter.Tag("coupon"), Filter.Tag("free return"), Filter.Tag("on sale"), Filter.Tag("no exchange") ) val filterListState = FilterListState() val filterListConnector = FilterListConnector.Tag( filters = filters, filterState = filterState, groupID = groupTags ) val connections = ConnectionHandler(filterListConnector) init { connections += searcher.connectFilterState(filterState) connections += filterListConnector.connectView(filterListState) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { filterListState.items.forEach { selectableFacet -> FilterRow( // your own UI composable to display `SelectableItem` selectableFilter = selectableFacet, onClick = { filterListState.onSelection?.invoke(it) } ) } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` ## Parameters **filters** `List\` *required* The tag filters to display. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that will hold your filters. **selectionMode** `SelectionMode` Whether the list can have `Single` or `Multiple` selections. **groupID** `FilterGroupID` The identifier for the group of filters. ## View **view** `FilterListView.Tag` *required* The view that renders the tag filters. ## View **view** `FilterListView.Tag` *required* The view that renders the tag filters. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter val view: FilterListView.Tag = MyFilterListView() filterListConnector.connectView(view) // Example of `FilterListView.Tag` implementation class MyFilterListTagAdapter : FilterListView.Tag, ListAdapter, FilterListViewHolder>(DiffUtilItem()) { override var onSelection: Callback? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterListViewHolder { return FilterListViewHolder(parent.inflate(R.layout.list_item_selectable)) } override fun onBindViewHolder(holder: FilterListViewHolder, position: Int) { val (filter, selected) = getItem(position) holder.bind(FilterPresenterImpl()(filter), selected) { onSelection?.invoke(filter) } } override fun setItems(items: List>) { submitList(items) } } class DiffUtilItem : DiffUtil.ItemCallback>() { override fun areItemsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem.first == newItem.first } override fun areContentsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem == newItem } } ``` # Filter List (Tag) Source: https://www.algolia.com/doc/api-reference/widgets/filter-list-tag/ios Shows a list of tag filters for refining search results. ```swift Signature theme={"system"} TagFilterListConnector( tagFilters: [TagFilter], selectionMode: SelectionMode, filterState: FilterState, operator: RefinementOperator, groupName: String, controller: FilterListTableController ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Filter List (Tag) example code on GitHub. ## About this widget `TagFilterList` is a filtering component that displays [tag filters](/doc/api-reference/api-parameters/tagFilters) and lets users refine the search results by selecting them. Compared to the [`RefinementList`](/doc/api-reference/widgets/refinement-list/ios), which takes its values from the search response facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).), this widget displays tag filters that you add yourself. ## Examples Instantiate a `TagFilterListConnector` and launch an initial search on its [`Searcher`](/doc/api-reference/widgets/instantsearch/ios). ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState = FilterState() let filters: [Filter.Tag] = [ "coupon", "free shipping", "free return", "on sale", "no exchange" ] let filterListTableView: UITableView = .init() let filterListController: FilterListTableController = .init(tableView: filterListTableView) let filterListConnector = TagFilterListConnector(tagFilters: filters, selectionMode: .multiple, filterState: filterState, operator: .and, groupName: "Tag Filters", controller: filterListController) searcher.connectFilterState(filterState) searcher.search() ``` ## Parameters **tagFilters** `[TagFilter]` *required* The tag filters to display. **selectionMode** `SelectionMode` Whether a user can select `.single` or `.multiple` values. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that holds your filters. **operator** `RefinementOperator` *required* Whether to apply an `and` or `or` behavior to the filters in the filter state. For example with `or`, the filter sent to Algolia is `_tags:promo OR color:green`. **groupName** `String` *required* Filter group name. **controller** `FilterListTableController\` Controller interfacing with a concrete filter list view. ## Low-level API If you want to fully control the `TagFilterList` components and connect them manually, you can use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `FilterState`. The current state of the filters. * `TagFilterListInteractor`. The logic applied to the tag filters. * `FilterListController`. The view that will render the tag filters. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState = FilterState() let filters: [Filter.Tag] = [ "coupon", "free shipping", "free return", "on sale", "no exchange" ] let filterListTableView: UITableView = .init() let filterListController: FilterListTableController = .init(tableView: filterListTableView) let filterListInteractor = TagFilterListInteractor(items: filters, selectionMode: .multiple) filterListInteractor.connectFilterState(filterState, operator: .and, groupName: "Tag Filters") filterListInteractor.connectController(filterListController) searcher.connectFilterState(filterState) searcher.search() ``` ## Customizing your view The default controllers, such as the `FilterListTableController`, work well when you want to use native UIKit with their default behavior. If you want to use another component such as a `UICollectionView`, a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the `TagFilterListController` protocol. ### Protocol `var onClick: ((Filter.Tag) -> Void)?`: Closure to call when a filter is clicked. `func setSelectableItems(selectableItems: [SelectableItem])` Function called when a new array of selectable facets is updated. This is the UI State of the refinement list. `func reload()` Function called when the list view requires a reload. ### Implementation example ```swift Swift icon=code theme={"system"} class TagFilterListTableController: NSObject, TagFilterListController, UITableViewDataSource, UITableViewDelegate { public var onClick: ((Filter.Tag) -> Void)? public let tableView: UITableView public var selectableItems: [SelectableItem] = [] public var filterPresenter: FilterPresenter? let cellID: String public init(tableView: UITableView, cellID: String = "cellID") { self.tableView = tableView self.cellID = cellID super.init() tableView.dataSource = self tableView.delegate = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID) } // MARK: - FilterListController public func setSelectableItems(selectableItems: [SelectableItem]) { self.selectableItems = selectableItems } public func reload() { tableView.reloadData() } // MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return selectableItems.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) let filter = selectableItems[indexPath.row] let filterPresenter = self.filterPresenter ?? DefaultPresenter.Filter.present cell.textLabel?.text = filterPresenter(Filter(filter.item)) cell.accessoryType = filter.isSelected ? .checkmark : .none return cell } // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectableItem = selectableItems[indexPath.row] onClick?(selectableItem.item) } } ``` ### SwiftUI InstantSearch provides the `FilterList` SwiftUI view which you can embed in your views. It uses `FilterListObservableController` as a data model, which is an implementation of the `SelectableListController` protocol adapted for usage with SwiftUI. `FilterListObservableController` must be connected to the `TagFilterListConnector` or `TagFilterListInteractor` like any other `SelectableListController` implementation. You have to define the appearance of the view representing a single tag filter and its selection state. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var filterListController: FilterListObservableController var body: some View { FilterList(filterListController) { filter, isSelected in // declare the view presenting a single tag filter and its selection state HStack { Text(filter.value.description) Spacer() if isSelected { Image(systemName: "checkmark") .foregroundColor(.accentColor) } } .contentShape(Rectangle()) .frame(idealHeight: 44) .padding(.horizontal, 5) } } } ``` If you prefer to create a custom SwiftUI view that presents the list of tag filters, you can directly use the `FilterListObservableController` as a data model. It provides `filters` and `selections` properties along with `toggle` and `isSelected` functions to streamline the design process of your custom SwiftUI view. # FilterList Source: https://www.algolia.com/doc/api-reference/widgets/filter-list/android Shows a list of filters for refining search results. ```kotlin Signature theme={"system"} FilterListConnector.All( filters: List, filterState: FilterState, selectionMode: SelectionMode, groupID: FilterGroupID ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the FilterList example code on GitHub. ## About this widget `FilterList` is a filtering view that displays any kind of [filters](/doc/guides/managing-results/refine-results/filtering), and lets users refine the search results by selecting them. Compared to the [`RefinementList`](/doc/api-reference/widgets/refinement-list/android), which takes its values from the search response facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).), this widget displays filters that you add yourself. ## Examples ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity: AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val color = "color" val price = "price" val filters = listOf( Filter.Numeric(price, 5..10), Filter.Tag("coupon"), Filter.Facet(color, "red"), Filter.Facet(color, "black"), Filter.Numeric(price, NumericOperator.Greater, 100) ) val groupAll = groupAnd(all) val filterListConnector = FilterListConnector.All( filters = filters, filterState = filterState, groupID = groupAll ) val connection = ConnectionHandler(filterListConnector, searcher.connectFilterState(filterState)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val filterListView: FilterListView.All = MyFilterListAdapter() // your `FilterListView.All` implementation connection += filterListConnector.connectView(filterListView) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Low-level API If you want to fully control the `FilterList` components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. * `FilterState`. The current state of the filters. * `FilterListViewModel.All`. The logic applied to the filters. * `FilterListView.All`. The view that renders the filters. * `FilterPresenter`. Optional. The presenter to customize the display of the filters. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val color = "color" val price = "price" val filters = listOf( Filter.Numeric(price, 5..10), Filter.Tag("coupon"), Filter.Facet(color, "red"), Filter.Facet(color, "black"), Filter.Numeric(price, NumericOperator.Greater, 100) ) val viewModel = FilterListViewModel.All(filters) val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view: FilterListView.All = MyFilterListAdapter() // your `FilterListView.All` implementation connection += searcher.connectFilterState(filterState) connection += viewModel.connectFilterState(filterState) connection += viewModel.connectView(view) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Compose UI InstantSearch provides the `FilterListState` as a state model, which is an implementation of the `FilterListView` interface. You need to connect `FilterListState` to the `FilterListConnector` or `FilterListViewModel` like any other `FilterListView` implementation. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity: AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val color = "color" val price = "price" val filters = listOf( Filter.Numeric(price, 5..10), Filter.Tag("coupon"), Filter.Facet(color, "red"), Filter.Facet(color, "black"), Filter.Numeric(price, NumericOperator.Greater, 100) ) val groupAll = groupAnd(all) val filterListState = FilterListState() val filterList = FilterListConnector.All( filters = filters, filterState = filterState, groupID = groupAll ) val connections = ConnectionHandler(filterList) init { connections += searcher.connectFilterState(filterState) connections += filterList.connectView(filterListState) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { filterListState.items.forEach { selectableFacet -> FilterRow( // your own UI composable to display `SelectableItem` selectableFilter = selectableFacet, onClick = { filterListState.onSelection?.invoke(it) } ) } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` ## Parameters **filters** `List\` *required* The filters to display. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that will hold your filters. **selectionMode** `SelectionMode` Whether the list can have `Single` or `Multiple` selections. **groupID** `FilterGroupID` The identifier for the group of filters. ## View **view** `FilterListView.All` The view that renders the filters. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter val view: FilterListView.All = MyFilterListAdapter() filterListConnector.connectView(view) // Example of FilterListView.All implementation class MyFilterListAdapter : FilterListView.All, ListAdapter, FilterListViewHolder>(DiffUtilItem()) { override var onSelection: Callback? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterListViewHolder { return FilterListViewHolder(parent.inflate(R.layout.list_item_selectable)) } override fun onBindViewHolder(holder: FilterListViewHolder, position: Int) { val (filter, selected) = getItem(position) holder.bind(FilterPresenterImpl()(filter), selected) { onSelection?.invoke(filter) } } override fun setItems(items: List>) { submitList(items) } } class DiffUtilItem : DiffUtil.ItemCallback>() { override fun areItemsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem.first == newItem.first } override fun areContentsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem == newItem } } ``` # Filter Map Source: https://www.algolia.com/doc/api-reference/widgets/filter-map/android Applies a selected filter from a predefined map. ```kotlin Signature theme={"system"} FilterMapConnector( filters: Map, filterState: FilterState, selected: Int?, groupID: FilterGroupID ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Filter Map example code on GitHub. ## About this widget Components holding a map of filters, and that can apply a single filter at a time. ## Examples ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val gender = "gender" val groupGender = groupAnd(gender) val filters = mapOf( R.id.male to Filter.Facet(gender, "male"), R.id.female to Filter.Facet(gender, "female") ) val filterMap = FilterMapConnector( filters = filters, filterState = filterState, groupID = groupGender ) val connection = ConnectionHandler(filterMap, searcher.connectFilterState(filterState)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewGender = FilterMapViewRadioGroup(radioGroupGender) connection += filterMap.connectView(viewGender) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connection.disconnect() } } ``` ### Low-level API If you want to fully control the Filter Map components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles searches. * `FilterState`. The current state of the filters. * `FilterMapViewModel`. The logic applied to the filter map. * `FilterMapView`. The view that renders the filter map. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val gender = "gender" val filterState = FilterState() val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filters = mapOf( 0 to Filter.Facet(gender, "male"), 1 to Filter.Facet(gender, "female") ) val viewModel = FilterMapViewModel(filters) val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view = FilterMapViewRadioGroup(radioGroupGender) connection += viewModel.connectFilterState(filterState) connection += viewModel.connectView(view) connection += viewModel.connectFilterState(filterState) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connection.disconnect() } } ``` ### Compose UI InstantSearch provides the `FilterMapState` as a state model, which is an implementation of the `FilterMapView` interface. `FilterMapState` must be connected to the `FilterMapConnector` or `FilterMapViewModel` like any other `FilterMapView` implementation. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val gender = "gender" val groupGender = groupAnd(gender) val filters = mapOf( 0 to Filter.Facet(gender, "male"), 1 to Filter.Facet(gender, "female") ) val filterMapState = FilterMapState() val filterMap = FilterMapConnector( filters = filters, filterState = filterState, groupID = groupGender ) val connections = ConnectionHandler(filterMap) init { connections += searcher.connectFilterState(filterState) connections += filterMap.connectView(filterMapState) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyFilterMap(filterMapState) // your own UI composable to display filter map options } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connections.disconnect() } } ``` ## Parameters **filters** `Map\` *required* The map of filters to be held. The key is an unique identifier for the filter value. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter val gender = "gender" val filters = mapOf( 0 to Filter.Facet(gender, "male"), 1 to Filter.Facet(gender, "female") ) ``` **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that holds filters. **selected** `Int?` (default: null) The key of the filter selected by default. **groupID** `FilterGroupID` (default: FilterGroupID(FilterOperator.And)) Groups all created filters under an ID. ## View **view** `FilterMapView` *required* The view that renders the filter map. **presenter** (default: FilterPresenterImpl()) How to display filters. ```kotlin Kotlin icon=code theme={"system"} val view = FilterMapViewRadioGroup(radioGroupGender) val presenter = FilterPresenterImpl() filterMap.connectView(view, presenter) ``` # Filter Map Source: https://www.algolia.com/doc/api-reference/widgets/filter-map/ios Applies a selected filter from a predefined map. ```swift Signature theme={"system"} FilterMapConnector( searcher: HitsSearcher, filterState: FilterState, items: [Int: Filter], selected: Int, attribute: Attribute, operator: RefinementOperator, groupName: String? ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Filter Map example code on GitHub. ## About this widget Components holding a map of filters, and that can apply a single filter at a time. ## Examples ```swift Swift icon=code theme={"system"} class SearchController { let searcher: HitsSearcher let filterState: FilterState let filterMapConnector: FilterMapConnector let controller = SegmentedController(segmentedControl: .init()) init() { let gender: Attribute = "gender" let male = Filter.Facet(attribute: "gender", stringValue: "male") let female = Filter.Facet(attribute: "gender", stringValue: "female") let notSpecified = Filter.Facet(attribute: "gender", stringValue: "not specified") let items: [Int: Filter.Facet] = [ 0: male, 1: female, 2: notSpecified, ] self.searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourAPIKey", indexName: "YourIndexName") self.filterState = FilterState() self.filterMapConnector = .init(searcher: searcher, filterState: filterState, items: items, selected: 0, attribute: gender, operator: .or, controller: controller, presenter: DefaultPresenter.Filter.present) searcher.connectFilterState(filterState) searcher.search() } } ``` ### Low-level API If you want to fully control the Filter Map components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles searches. * `FilterState`. The current state of the filters. * `FilterMapInteractor`. The logic applied to the filter map. * `FilterMapController`. The view that renders the filter map. ```swift Swift icon=code theme={"system"} class SearchController { let searcher: HitsSearcher let filterState: FilterState let filterMapInteractor: FilterMapInteractor let controller = SegmentedController(segmentedControl: .init()) init() { let gender: Attribute = "gender" let male = Filter.Facet(attribute: "gender", stringValue: "male") let female = Filter.Facet(attribute: "gender", stringValue: "female") let notSpecified = Filter.Facet(attribute: "gender", stringValue: "not specified") self.searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourAPIKey", indexName: "YourIndexName") let items: [Int: Filter.Facet] = [ 0: male, 1: female, 2: notSpecified, ] self.filterState = FilterState() self.filterMapInteractor = .init(items: items, selected: 0) searcher.connectFilterState(filterState) filterMapInteractor.connectSearcher(searcher, attribute: gender) filterMapInteractor.connectFilterState(filterState, attribute: gender, operator: .or) filterMapInteractor.connectController(controller, presenter: DefaultPresenter.Filter.present) searcher.search() } } ``` ## Parameters **searcher** `HitsSearcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that holds filters. **items** `[Int: Filter]` *required* The map of filters to be held. The key is an unique identifier for the filter value. ```swift Swift icon=code theme={"system"} let gender = Attribute("gender") let filters = [ 0: Filter.Facet(attribute: gender, value: "male"), 1: Filter.Facet(attribute: gender, value: "female") 2: Filter.Facet(attribute: gender, value: "not specified") ] ``` **selected** `Int` *required* The key of the filter selected by default. **attribute** `Attribute` *required* The attribute to filter. **operator** `RefinementOperator` *required* Whether facets are combined with `and` or `or` behavior in `filterState`. For example if you select `color` as the attribute and an `or` behavior, the filter sent to Algolia is `color:red OR color:green`. **groupName** `String?` (default: .none) Filter group name. ## Controller **controller** `SelectableSegmentController` *required* The view that renders the filter map. **presenter** (default: DefaultPresenter.Filter.present) How to display filters. ```swift Swift icon=code theme={"system"} let controller = SegmentedController(segmentedControl: .init()) let presenter = DefaultPresenter.Filter.present filterMap.connectController(controller, presenter: presenter) ``` # FilterState Source: https://www.algolia.com/doc/api-reference/widgets/filter-state/android Provides interface for managing applied filters. ## About this widget A `FilterState` is a class that holds one or several filters, organized in groups. The `FilterState` can be modified at any moment by adding or removing filters, which will be applied to searches performed by the connected [`Searcher`](/doc/api-reference/widgets/instantsearch/android). You can use three filter types: * `Filter.Facet` * `Filter.Numeric` * `Filter.Tag` A group of filters must be identified by a `FilterGroupID`. A `FilterGroupID` must have: * A name * A `FilterOperator`, which can either be `FilterOperator.And` or `FilterOperator.Or` A `FilterOperator` indicates which type of boolean operator should be applied between each filter in the group. See also: * [Get started with imperative UI](/doc/guides/building-search-ui/getting-started/how-to/programmatically/android) * [Filtering](/doc/guides/managing-results/refine-results/filtering) * [Filtering and boolean operators](/doc/guides/managing-results/refine-results/filtering/in-depth/combining-boolean-operators) > [card] [Explore example code](https://github.com/algolia/instantsearch-android/blob/master/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/showcase/androidview/search/SearchAsYouTypeShowcase.kt) > > Browse the FilterState example code on GitHub. ## Examples Add and remove filters from a `FilterState`. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter val filterState = FilterState() val filterGroupID = FilterGroupID("myGroup", FilterOperator.And) val color = "color" val red = Filter.Facet(color, "red") val green = Filter.Facet(color, "green") val blue = Filter.Facet(color, "blue") filterState.add(filterGroupID, red, green, blue) // "color:red AND color:green AND color:blue" filterState.remove(filterGroupID, green) // "color:red AND color:blue" ``` Notify and listen to changes. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.filter.Filter val filterState = FilterState() val filterGroupID = FilterGroupID("myGroup", FilterOperator.And) val color = "color" val facets = setOf( Filter.Facet(color, "red"), Filter.Facet(color, "green"), Filter.Facet(color, "blue") ) filterState.notify { add(filterGroupID, facets) } filterState.filters.subscribe { filters -> assertEquals(facets, filters.getFacetFilters(filterGroupID)) } ``` Convert a `FilterState` to an [SQL-like string expression](/doc/guides/managing-results/refine-results/filtering/in-depth/combining-boolean-operators) which can be used with search parameters. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.client.model.search.SearchParamsObject val query = SearchParamsObject() val filtersString = FilterGroupsConverter.SQL(filterState.toFilterGroups()) val queryWithFilters = query.copy(filters = filtersString) ``` # FilterState Source: https://www.algolia.com/doc/api-reference/widgets/filter-state/flutter Provides interface for managing applied filters. ## About this widget A `FilterState` holds one or several filters, organized in groups, and emits changes of added or removed filters, which are applied to searches performed by connected components. You can add [three types of filters](/doc/guides/managing-results/refine-results/filtering): * `FilterFacet` * `FilterNumeric` * `FilterTag` A group of filters must be identified by a `FilterGroupID`. A `FilterGroupID` has: * An optional `name` * A `FilterOperator` indicating which type of boolean operator should be applied between each filter in the group: `FilterOperator.and` or `FilterOperator.or`. For more information, see [Combining boolean operators](/doc/guides/managing-results/refine-results/filtering/in-depth/combining-boolean-operators). ## Examples ### Create The following is an example of creating and setting up a `FilterState` with different filters (facets, tags, and numerical): ```dart Dart icon=code theme={"system"} const authors = FilterGroupID('author', FilterOperator.or); const genres = FilterGroupID('genres'); const numbers = FilterGroupID('numbers'); final filterState = FilterState() ..add(authors, [Filter.facet('author', 'Shakespeare')]) ..add(genres, [Filter.tag('drama')]) ..add(numbers, [Filter.range('rating', lowerBound: 3, upperBound: 5)]) ..add(numbers, [Filter.comparison('price', NumericOperator.less, 50)]); ``` The preceding code snippet corresponds to the following expression: `(author:Shakespeare) AND (_tags:drama) AND (rating:3 TO 5 AND price < 50)` ### Read Listen to [`filters`](#param-filters) stream to get filtering changes: ```dart Dart icon=code theme={"system"} StreamBuilder( stream: filterState.filters, builder: (context, snapshot) { final filters = snapshot.data; final count = filters?.getFilters().length ?? 0; return Text('Filters applied: $count'); }); ``` Use [`snapshot`](#param-snapshot) to get the latest `Filters` value submitted by [`filters`](#param-filters) stream: ```dart Dart icon=code theme={"system"} final filters = filterState.snapshot(); ``` ### Update `FilterState` can be updated using methods such as [`add`](#param-add), [`set`](#param-set) and [`remove`](#param-remove), each modification triggers a [`filters`](#param-filters) submission. Use [`modify`](#param-modify) to run multiple modifications (atomically), and trigger a single [`filters`](#param-filters) submission: ```dart Dart icon=code theme={"system"} filterState.modify((filters) async => filters .add(authors, [Filter.facet('author', 'J. K. Rowling')]) .remove(authors, [Filter.facet('author', 'Shakespeare')])); ``` ### Delete Remove all or some filter groups using [`clear`](#param-clear) and [`clearExcept`](/doc/api-reference/widgets/filter-state/flutter#param-clear-except): ```dart Dart icon=code theme={"system"} // Remove all filter groups filterState.clear(); // Clear filter group 'authors' filterState.clear(authors); // Clear all filter groups except 'authors' filterState.clearExcept([authors]); ``` ## Fields **filters** `Stream\` Filters groups stream (facet, tag, numeric, and hierarchical). ```dart Dart icon=code theme={"system"} filterState.filters.listen((filters) { // Count of applied filters final count = filters.getFilters().length; print('${count} filter(s) applied'); // Applied facet filters filters.facetGroups.forEach( (key, value) => print("${key.name}: ${value.join(',')}")); }); ``` ## Methods **add** Adds a list of filters to a group by its `groupID`. ```dart Dart icon=code theme={"system"} filterState.add(groupID, filters); ``` **set** Overrides `filters` list with a provided `map`. ```dart Dart icon=code theme={"system"} filterState.set(map); ``` **remove** Removes a list of `filters` from a group by its `groupID`. ```dart Dart icon=code theme={"system"} filterState.remove(groupID, filters); ``` **toggle** Toggles a `filter` in given group by its `groupID`. ```dart Dart icon=code theme={"system"} filterState.toggle(groupID, filter); ``` **contains** Checks if a `filter` exists in a group. ```dart Dart icon=code theme={"system"} filterState.contains(groupID, filter); ``` **addHierarchical** Adds a `HierarchicalFilter` to a group. ```dart Dart icon=code theme={"system"} filterState.addHierarchical(attribute, hierarchicalFilter); ``` **removeHierarchical** Removes `HierarchicalFilter` group by its attribute. ```dart Dart icon=code theme={"system"} filterState.removeHierarchical(attribute); ``` **clear** Clears provided group IDs. If none provided, all filters will be cleared. ```dart Dart icon=code theme={"system"} filterState.clear(groupIDs); ``` **clearExcept** Clears all groups except provided group IDs. ```dart Dart icon=code theme={"system"} filterState.clearExcept(groupIDs); ``` **modify** *Asynchronously* updates [`filters`](#param-filters). Useful to apply multiple consecutive update operations without firing multiple filters events. ```dart Dart icon=code theme={"system"} filterState.modify((filters) async => filters .add(groupID, filters) .remove(groupID, filters; ``` **snapshot** Gets the latest `Filters` value submitted by [`filters`](#param-filters): ```dart Dart icon=code theme={"system"} final filters = filterState.snapshot(); ``` **dispose** Releases all underlying resources. ```dart Dart icon=code theme={"system"} filterState.dispose(); ``` # FilterState Source: https://www.algolia.com/doc/api-reference/widgets/filter-state/ios Provides interface for managing applied filters. ## About this widget A `FilterState` consists of one or several filters, organized in groups. Each group can contain one or more of these filter types: * `Facet.Filter` * `Facet.Numeric` * `Facet.Tag` You can read more about each filter type in the [filtering guide](/doc/guides/managing-results/refine-results/filtering). `FilterState` provides a simple interface to deal with filter grouping and their respective boolean operator. For more details, you can read more about [filter grouping and boolean operators](/doc/guides/managing-results/refine-results/filtering/in-depth/combining-boolean-operators). Filter groups can be: * **Conjunctive groups** Search results will only contain hits that match all the filters in a conjunctive filter group. In other words, it represents a boolean and relationship between filters in this group. For example, if `FilterState` contains a conjunctive group of filters `size:42` and `category:shirt`, records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) that match both filters will be returned. A conjunctive group may contain filters of any type at the same time. * **Disjunctive groups** Search results will contain hits that match any the filters in a disjunctive filter group. In other words, it represents a boolean or relationship between filters in this group. For example, if `FilterState` contains a disjunctive group of filters `color:red` and `color:blue`, records that match any of these filters will be returned. Disjunctive group may only contain filters of the same type. For example, you can't put a facet filter and a tag filter in the same disjunctive group. The set of groups in `FilterState` behave as if they're combined by an `AND` operator. That means that a record matches the set of filters in `FilterState` only if it satisfies all the groups simultaneously. > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the FilterState example code on GitHub. ## Examples To access filter groups, `FilterState` provides a subscript syntax. Each group is identified by its name (string). You can access groups by using `[and: "groupName"]` for conjunctive groups and `[or: "groupName"]` for disjunctive groups. Groups are automatically created when you add the first filter, and are deleted when they're empty. ```swift Swift icon=code theme={"system"} let filterState = FilterState() // Add filter to conjunctive group filterState[and: "conjunctiveGroup"].add(Filter.Facet(attribute: "category", stringValue: "shirts")) // Add filter to disjunctive group filterState[or: "disjunctiveGroup"].add(Filter.Facet(attribute: "color", stringValue: "red")) print(filterState.debugDescription) /* Output: FilterState { "conjunctiveGroup": ( "category":"shirts" ) "disjunctiveGroup": ( "color":"red" ) } */ ``` Disjunctive groups with different type of filters are considered as different groups. ```swift Swift icon=code theme={"system"} let filterState = FilterState() filterState[or: "disjunctiveGroup"].add(Filter.Facet(attribute: "color", stringValue: "red"), Filter.Facet(attribute: "color", stringValue: "blue")) filterState[or: "disjunctiveGroup"].add(Filter.Numeric(attribute: "price", range: 10...100)) filterState[or: "disjunctiveGroup"].add(Filter.Tag(stringLiteral: "sales")) print(filterState.debugDescription) // Three different groups have been created with the same name according to type /* Output: FilterState { "disjunctiveGroup": ( "_tags":"sales" ) "disjunctiveGroup": ( "color":"blue" OR "color":"red" ) "disjunctiveGroup": ( "price":10.0 TO 100.0 ) } */ ``` `FilterState` group subscripts provide convenient operations such as removing filters: ```swift Swift icon=code theme={"system"} let filterState = FilterState() let tagFilter = Filter.Tag(stringLiteral: "sales") filterState[and: "conjunctiveGroup"].add(tagFilter) print(filterState.debugDescription) /* Output: FilterState { "conjunctiveGroup": ( "_tags":"sales" ) } */ filterState[and: "conjunctiveGroup"].remove(tagFilter) print(filterState.debugDescription) /* Output: FilterState {} */ ``` and toggling: ```swift Swift icon=code theme={"system"} let filterState = FilterState() let tagFilter = Filter.Tag(stringLiteral: "sales") filterState[and: "conjunctiveGroup"].toggle(tagFilter) print(filterState.debugDescription) /* Output: FilterState { "conjunctiveGroup": ( "_tags":"sales" ) } */ filterState[and: "conjunctiveGroup"].toggle(tagFilter) print(filterState.debugDescription) /* Output: FilterState {} */ ``` ## Transform to SQL syntax string InstantSearch provides a convenient way to transform a `FilterState` into a valid [SQL-like string expression](/doc/guides/managing-results/refine-results/filtering/in-depth/combining-boolean-operators) which can be used with [`Query`](/doc/api-reference/widgets/configure/ios). ```swift Swift icon=code theme={"system"} let filterState: FilterState = ... // convert FilterState to list of filter groups let filterGroups = filterState.toFilterGroups() // convert list of filter groups to Algolia filter SQL syntax let filters = FilterGroupConverter().sql(filterGroups) // set filters to Query let query: Query = ... query.facetFilters = filters ``` ### CustomStringConvertible conformity `FilterState` conforms to the `CustomStringConvertible` protocol which provides the same valid SQL-like string with the `description` property. Therefore, the previous snippet can be reduced to this: ```swift Swift icon=code theme={"system"} let filterState: FilterState = ... let query: Query = ... query.facetFilters = filterState.description ``` # Algolia for Flutter Source: https://www.algolia.com/doc/api-reference/widgets/flutter Overview of Algolia components for Flutter. To get started with Algolia for Flutter, see [Algolia for Flutter](/doc/guides/building-search-ui/what-is-instantsearch/flutter) ## Basics | | | | - | - | | [`Searcher`](/doc/api-reference/widgets/instantsearch/flutter) | The component handling search requests. | | [`Query`](/doc/api-reference/widgets/configure/flutter) | A component to provide any search parameters to Algolia. | | [`Filter State`](/doc/api-reference/widgets/filter-state/flutter) | Filter State provides a friendly interface to manage filters applied to your search. | | [`Loading`](/doc/api-reference/widgets/loading/flutter) | Components that show a loading indicator during pending requests. | ## Results | | | | - | - | | [`Highlighting`](/doc/api-reference/widgets/highlight/flutter) | Highlighting visually emphasizes matching terms in your search results to help them stand out. | ## Refinements | | | | - | - | | [`Refinement List`](/doc/api-reference/widgets/refinement-list/flutter) | Components that perform refinements on a facet. | | [`Current Filters`](/doc/api-reference/widgets/current-refinements/flutter) | Components that display the current filters and let users remove them. | | [`Filter Toggle`](/doc/api-reference/widgets/toggle-refinement/flutter) | Components that toggle an arbitrary filter on or off. | | [`Clear Filters`](/doc/api-reference/widgets/clear-refinements/flutter) | Components that clear the current filters. | ## Metadata | | | | - | - | | [`Stats`](/doc/api-reference/widgets/stats/flutter) | Components that display metadata about the current search and its results. | ## Sorting | | | | - | - | | [`Sort By`](/doc/api-reference/widgets/sort-by/flutter) | Components that display a list of indices, allowing a user to change the way hits are sorted using replica indices. | # Hierarchical Menu Source: https://www.algolia.com/doc/api-reference/widgets/hierarchical-menu/android Shows a hierarchical menu to navigate nested categories. ```kotlin Signature theme={"system"} HierarchicalConnector( searcher: HitsSearcher, attribute: String, filterState: FilterState, hierarchicalAttributes: List, separator: String ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Hierarchical Menu example code on GitHub. ## About this widget A `Hierarchical Menu` is a component that displays a hierarchical navigation menu, based on facet attributes. ## Examples ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val hierarchicalCategory = "categories" val hierarchicalCategoryLvl0 = "$categories.lvl0" val hierarchicalCategoryLvl1 = "$categories.lvl1" val hierarchicalCategoryLvl2 = "$categories.lvl2" val hierarchicalAttributes = listOf( hierarchicalCategoryLvl0, hierarchicalCategoryLvl1, hierarchicalCategoryLvl2 ) val filterState = FilterState() val separator = " > " val hierarchical = HierarchicalConnector( searcher = searcher, attribute = hierarchicalCategory, filterState = filterState, hierarchicalAttributes = hierarchicalAttributes, separator = separator ) val connection = ConnectionHandler(hierarchical, searcher.connectFilterState(filterState)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view : HierarchicalView = HierarchicalAdapter() // your HierarchicalView implementation connection += hierarchical.connectView(view, HierarchicalPresenterImpl(separator)) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Low-level API If you want to fully control the Hierarchical Menu components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. * `FilterState`. The current state of the filters. * `HierarchicalViewModel`. The logic applied to the hierarchical facets. * `HierarchicalView`. The concrete hierarchical view. * `HierarchicalPresenter`. The presenter that controls the sorting and other settings of the hierarchical menu. * `HierarchicalItem`. A single node in the hierarchy. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val hierarchicalCategory = "categories" val hierarchicalCategoryLvl0 = "$categories.lvl0" val hierarchicalCategoryLvl1 = "$categories.lvl1" val hierarchicalCategoryLvl2 = "$categories.lvl2" val hierarchicalAttributes = listOf( hierarchicalCategoryLvl0, hierarchicalCategoryLvl1, hierarchicalCategoryLvl2 ) val separator = " > " val viewModel = HierarchicalViewModel(hierarchicalCategory, hierarchicalAttributes, separator) val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view: HierarchicalView = HierarchicalAdapter() // your `HierarchicalView` implementation connection += searcher.connectFilterState(filterState) connection += viewModel.connectFilterState(filterState) connection += viewModel.connectSearcher(searcher) connection += viewModel.connectView(view, HierarchicalPresenterImpl(separator)) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connection.disconnect() } } ``` ### Compose UI InstantSearch provides `HierarchicalState` as a state model, which implements the `HierarchicalView` interface. You need to connect `HierarchicalState` to the `HierarchicalConnector` or `HierarchicalViewModel` like any other `HierarchicalView` implementation. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val hierarchicalCategory = "categories" val hierarchicalCategoryLvl0 = "$categories.lvl0" val hierarchicalCategoryLvl1 = "$categories.lvl1" val hierarchicalCategoryLvl2 = "$categories.lvl2" val hierarchicalAttributes = listOf( hierarchicalCategoryLvl0, hierarchicalCategoryLvl1, hierarchicalCategoryLvl2 ) val filterState = FilterState() val separator = " > " val hierarchicalState = HierarchicalState() val hierarchical = HierarchicalConnector( searcher = searcher, attribute = hierarchicalCategory, filterState = filterState, hierarchicalAttributes = hierarchicalAttributes, separator = separator ) val connections = ConnectionHandler(hierarchical) init { connections += searcher.connectFilterState(filterState) connections += hierarchical.connectView(hierarchicalState, HierarchicalPresenterImpl(separator)) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { LazyColumn { items(hierarchicalState.hierarchicalItems) { item -> HierarchicalItem( // your own UI composable to display `HierarchicalItem` item = item, onClick = { hierarchicalState.onSelectionChanged?.invoke(item.facet.value) } ) } } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` ## Parameters **searcher** `HitsSearcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/android#hitssearcher) that handles your searches. **attribute** `String` *required* The attribute to filter. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that holds your filters. **hierarchicalAttributes** `List\` *required* The names of the hierarchical attributes that you need to target, in ascending order. **separator** `String` *required* The delimiter used to separate hierarchical facets. Usually something like " > ", including spaces as shown in this example. ## View **view** `HierarchicalView` *required* The view that renders the hierarchical menu. **presenter** `HierarchicalPresenter` *required* How to display the list of `HierarchicalItem`. Takes a list of `HierarchicalItem` and returns a new list of `HierarchicalItem`. A `HierarchicalItem` contains a `Facet`, its `level`, and a `displayName`. ```kotlin Kotlin icon=code theme={"system"} val view: HierarchicalView = HierarchicalAdapter() val separator = " > " val presenter = HierarchicalPresenterImpl(separator) hierarchical.connectView(view, presenter) // Example of `HierarchicalView` implementation class HierarchicalAdapter : HierarchicalView, ListAdapter(DiffUtilItem()) { override var onSelectionChanged: Callback? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HierarchicalViewHolder { return HierarchicalViewHolder(parent.inflate(R.layout.list_item_selectable, false)) } override fun onBindViewHolder(holder: HierarchicalViewHolder, position: Int) { val item = getItem(position) holder.bind(item) { onSelectionChanged?.invoke(item.facet.value) } } override fun setTree(tree: List) { submitList(tree) } } class DiffUtilItem : DiffUtil.ItemCallback>() { override fun areItemsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem.first == newItem.first } override fun areContentsTheSame(oldItem: SelectableItem, newItem: SelectableItem): Boolean { return oldItem == newItem } } ``` # Hierarchical Menu Source: https://www.algolia.com/doc/api-reference/widgets/hierarchical-menu/ios Shows a hierarchical menu to navigate nested categories. ```swift Signature theme={"system"} HierarchicalConnector( searcher: SingleIndexSearcher, filterState: FilterState, hierarchicalAttributes: [Attribute], separator: String, controller: HierarchicalController, presenter: HierarchicalPresenter ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Hierarchical Menu example code on GitHub. ## About this widget A `Hierarchical Menu` is a component that displays a hierarchical navigation menu, based on facet attributes. ## Examples Instantiate a `HierarchicalConnector` and launch an initial search on its [`Searcher`](/doc/api-reference/widgets/instantsearch/ios). ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState: FilterState = .init() searcher.connectFilterState(filterState) let hierarchicalAttributes: [Attribute] = [ "categories.lvl0", "categories.lvl1", "categories.lvl2", ] let hierarchicalTableViewController: HierarchicalTableViewController = .init(tableView: UITableView()) let hierarchicalConnector: HierarchicalConnector = .init(searcher: searcher, filterState: filterState, hierarchicalAttributes: hierarchicalAttributes, separator: " > ", controller: hierarchicalTableViewController, presenter: DefaultPresenter.Hierarchical.present) searcher.search() ``` ## Parameters **searcher** `HitsSearcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that holds your filters. **hierarchicalAttributes** `[Attribute]` *required* The names of the hierarchical attributes to target, in ascending order. **separator** `String` *required* The delimiter used to separate hierarchical facets. Usually something like " > ", including spaces as shown in this example. **controller** `HierarchicalController` The Controller interfacing with a concrete hierarchical view. **presenter** `HierarchicalPresenter` The Presenter defining how hierarchical facets appear in the controller. ## Presenter **Hierarchical Presenter** `(([HierarchicalFacet]) -\> [HierarchicalFacet])?` (default: nil) The presenter defining how to display the `HierarchicalFacet` list. Takes a list of `HierarchicalFacet` as input and returns a new `HierarchicalFacet` list. A `HierarchicalFacet` is a tuple of a `Facet`, its `level`, and whether it's `selected` or not. ```swift Swift icon=code theme={"system"} public static let present: HierarchicalPresenter = { facets in let levels = Set(facets.map { $0.level }).sorted() guard !levels.isEmpty else { return facets } var output: [HierarchicalFacet] = [] output.reserveCapacity(facets.count) levels.forEach { level in let facetsForLevel = facets .filter { $0.level == level } .sorted { $0.facet.value < $1.facet.value } let indexToInsert = output .lastIndex { $0.isSelected } .flatMap { output.index(after: $0) } ?? output.endIndex output.insert(contentsOf: facetsForLevel, at: indexToInsert) } return output } ``` ## Low-level API If you want to fully control the `Hierarchical Menu` components and connect them manually, you can use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `FilterState`. The current state of the filters. * `HierarchicalInteractor`. The logic applied to the hierarchical facets. * `HierarchicalController`. The controller that interfaces with a concrete hierarchical facet list view. * `HierarchicalPresenter`. Optional. The presenter that controls the sorting and other settings of the refinement facet list view. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState: FilterState = .init() let hierarchicalAttributes: [Attribute] = [ "categories.lvl0", "categories.lvl1", "categories.lvl2", ] let hierarchicalTableViewController: HierarchicalTableViewController = .init(tableView: UITableView()) let hierarchicalInteractor: HierarchicalInteractor = .init(hierarchicalAttributes: hierarchicalAttributes, separator: " > ") searcher.connectFilterState(filterState) hierarchicalInteractor.connectSearcher(searcher: searcher) hierarchicalInteractor.connectFilterState(filterState) hierarchicalInteractor.connectController(hierarchicalTableViewController) searcher.search() ``` ## Customizing your view The default controllers, for example, `HierarchicalTableViewController`, work well when you want to use native UIKit with their default behavior. If you want to use another component such as a `UICollectionView`, a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the `HierarchicalController` protocol. ### Protocol `var onClick: ((String) -> Void)?`: Closure to call when a new hierarchical facet is clicked. `func setItem(_ item: [HierarchicalFacet])` Function called when a new array of hierarchical facets is updated. This is the UI State of the refinement list. Make sure to reload your view here when you get the new items. ### Example ```swift Swift icon=code theme={"system"} open class HierarchicalTableViewController: NSObject, HierarchicalController { public var onClick: ((String) -> Void)? var items: [HierarchicalFacet] var tableView: UITableView let cellID: String public init(tableView: UITableView, cellID: String = "HierarchicalFacet") { self.tableView = tableView self.items = [] self.cellID = cellID super.init() tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID) tableView.dataSource = self tableView.delegate = self } public func setItem(_ item: [HierarchicalFacet]) { self.items = item tableView.reloadData() } } extension HierarchicalTableViewController: UITableViewDataSource { open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count } open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) let maxSelectedLevel = Set(items.filter { $0.isSelected }.map { $0.level }).max() ?? 0 let item = items[indexPath.row] cell.textLabel?.text = "\(item.facet.description)" cell.indentationLevel = item.level cell.accessoryType = item.level == maxSelectedLevel && item.isSelected ? .checkmark : .none return cell } } extension HierarchicalTableViewController: UITableViewDelegate { open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let item = items[indexPath.row] onClick?(item.facet.value) } } ``` ### SwiftUI InstantSearch provides the `HierarchicalList` SwiftUI view which you can embed in your views. It uses `HierarchicalObservableController` as a data model, which is an implementation of the `HierarchicalController` protocol adapted for usage with SwiftUI. `HierarchicalObservableController` must be connected to the `HierarchicalConnector` or `HierarchicalInteractor` like any other `HierarchicalController` implementation. You can define the appearance of the view representing a single hierarchical facet and its selection state or use the `HierarchicalFacetRow` view provided by InstantSearch. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject let hierarchicalController: HierarchicalObservableController var body: some View { HierarchicalList(hierarchicalController) { facet, nestingLevel, isSelected in // Use the implementation provided by InstantSearch // HierarchicalFacetRow(facet: facet, nestingLevel: nestingLevel, isSelected: isSelected) // Or declare a custom single hierarchical facet view HStack(spacing: 10) { Image(systemName: isSelected ? "chevron.down" : "chevron.right") .font(.callout) Text("\(facet.value) (\(facet.count))") .fontWeight(isSelected ? .semibold : .regular) .contentShape(Rectangle()) Spacer() } .padding(.leading, CGFloat(nestingLevel * 20)) } } } ``` If you prefer to create a custom SwiftUI view that presents the list of hierarchical facets, you can directly use the `HierarchicalObservableController` as a data model. It provides the `hierarchicalFacets` property along with the `toggle` function to streamline the design process of your custom SwiftUI view. # Highlighting Source: https://www.algolia.com/doc/api-reference/widgets/highlight/android Visually highlights matched terms in search results. ## About this widget Displays the [highlighted attributes](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/android) of your search results. With InstantSearch Android, the `Highlightable` interface and `HighlightedString` objects simplify highlighting the correct words in a search response that match your query. See also: [Get started with imperative UI](/doc/guides/building-search-ui/getting-started/how-to/programmatically/android) > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Highlighting example code on GitHub. ## Examples Consider 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.). Each movie record consists of two fields: `title` and `year`. Algolia's response to the query "red" could look like: ```json JSON icon=braces theme={"system"} { "title": "The Shawshank Redemption", "year": 1994, "genre": ["action", "adventure"], "actors": ["Tim Robbins", "Morgan Freeman"], "objectID": "439817390", "_highlightResult": { "title": { "value": "The Shawshank Redemption", "matchLevel": "full", "fullyHighlighted": false, "matchedWords": ["red"] } } } ``` To display those movies in your interface, you likely have created a `data class` that looks something like the following: ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.core.Indexable @Serializable data class Movie( val title: String, val year: String, val genre: List, override val objectID: String, ) : Indexable ``` Update it to add some highlighting. Implementing `Highlightable` will deserialize the `_highlightResult` for each movie, and make it available through the `getHighlight{s}` methods. Create `@Transient` attributes for each highlight to display, being either single values or lists: ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.core.Indexable @Serializable data class Movie( val title: String, val year: String, val genre: List, override val objectID: String, override val _highlightResult: JsonObject? ) : Indexable, Highlightable { @Transient public val highlightedTitle: HighlightedString? get() = getHighlight("title") @Transient public val highlightedGenres: List? get() = getHighlights("genre") @Transient public val highlightedActors: List? get() = getHighlights("actors") } ``` Use these highlighted strings in the interface, for example in a `MovieViewHolder`. ### Using highlighted strings in an Android view * Directly as a `SpannedString`, with the highlight defaulting to bold: ```kotlin Kotlin icon=code theme={"system"} TextUtils.concat(highlightedTitle?.toSpannedString(), " ($year)") ``` * As a customized `SpannedString`, specifying a `ParcelableSpan` to use as highlight style: ```kotlin Kotlin icon=code theme={"system"} highlightedGenres?.toSpannedString(BackgroundColorSpan(Color.YELLOW)) ?: buildSpannedString { italic { append("unknown genre") } } ``` * Any way you want, iterating on `HighlightedString#tokens` to process it however you like: ```kotlin Kotlin icon=code theme={"system"} // Displays actors with highlighted parts in uppercase highlightedActors?.joinToString { highlight -> highlight.tokens.joinToString("") { if (it.highlighted) it.content.uppercase() else it.content } } ``` ### Compose UI * Directly as a `AnnotatedString`, with the highlight defaulting to bold: ```kotlin Kotlin icon=code theme={"system"} highlightedTitle?.toAnnotatedString() ``` * As a customized `AnnotatedString`, specifying a `SpanStyle` to use as highlight style: ```kotlin Kotlin icon=code theme={"system"} highlightedGenres?.toAnnotatedString(SpanStyle(background = Color.Yellow)) ?: AnnotatedString("unknown genre", SpanStyle(fontStyle = FontStyle.Italic)) ``` * Any way you want, iterating on `HighlightedString#tokens` to process it however you like: ```kotlin Kotlin icon=code theme={"system"} // Displays actors with highlighted parts in uppercase highlightedActors?.joinToString { highlight -> highlight.tokens.joinToString("") { if (it.highlighted) it.content.uppercase() else it.content } } ``` # Highlighting Source: https://www.algolia.com/doc/api-reference/widgets/highlight/flutter Visually highlights matched terms in search results. ## About this widget Displays the [highlighted attributes](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/flutter) of your search results. The `HighlightedString` objects simplify highlighting the right words in a search response that match your query. ## Examples Consider a movies index. Each movie record consists of two fields: `title` and `year`. Algolia's response to the query "red" could look like: ```json JSON icon=braces theme={"system"} { "objectID": "439817390", "title": "The Shawshank Redemption", "_highlightResult": { "title": { "value": "The Shawshank Redemption", "matchLevel": "full", "fullyHighlighted": false, "matchedWords": ["red"] } } } ``` To display those movies in your interface, you likely have created a `data class` that looks something like the following: ```dart Dart icon=code theme={"system"} class Movie { String? title; Movie({this.title}); static Movie fromHit(Hit hit) { return Movie(title: hit['title']); } } ``` It includes a static method to build a `Movie` instance from the `Hit` object. ### Extract highlighted strings from search hit JSON Complete the `Movie` class with the `highlightedTitle` property of the `HighlightedString` type. Update the `fromJson` method so that it parses the `HighlightedString` from the hit using the `getHighlightedString` method. ```dart Dart icon=code theme={"system"} class Movie { String? title; HighlightedString? highlightedTitle; Movie({this.title, this.highlightedTitle}); static Movie fromJson(Hit hit) { return Movie( title: hit['title'], highlightedTitle: hit.getHighlightedString('title')); } } ``` A `HighlightedString` can be created directly from the raw tagged string. ```dart Dart icon=code theme={"system"} final rawHighlightedTitle = 'The Shawshank Redemption'; final highlightedTitle = HighlightedString.of(rawHighlightedTitle); ``` ### Using highlighted strings in Flutter The `HighlightedString` provides a `toInlineSpans()` method which produces a list of `InlineSpan` instances. The `highlightedTextStyle` parameter of this method defines the style of the highlighted part of the text. These instances, in turn, can be used as a parameter in Flutter's [RichText widget](https://api.flutter.dev/flutter/widgets/RichText-class.html) as follows: ```dart Dart icon=code theme={"system"} RichText( text: TextSpan( style: const TextStyle(color: Colors.black), children: movie.highlightedTitle!.toInlineSpans( highlightedTextStyle: const TextStyle(color: Colors.orange)))); ``` # Highlighting Source: https://www.algolia.com/doc/api-reference/widgets/highlight/ios Visually highlights matched terms in search results. ## About this widget Displays the [highlighted attributes](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/ios) of your search results. > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Highlighting example code on GitHub. ## Examples Consider 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.). Each movie record consists of two fields: `title` and `year`. Algolia's response to the query "red" could look like: ```json JSON icon=braces theme={"system"} { "title": "The Shawshank Redemption", "year": 1994, "objectID": "439817390", "_highlightResult": { "title": { "value": "The Shawshank Redemption", "matchLevel": "full", "fullyHighlighted": false, "matchedWords": ["red"] } } } ``` Consider the `_highlightResult`'s value: `"The Shawshank Redemption"`. The part of the string to highlight is marked with `em` tags or [your custom tags](/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/ios#pre-and-post-tags). `HighlightedString` is constructed with a raw tagged string, detects the tags and creates a `TaggedString`. This tagged string provides following properties: * `input`. The input string. * `output`. The input string without its tags. * `taggedRanges`. A list of ranges defining highlighted ranges in `output`. You can build a highlighted string in iOS is with an `NSAttributedString`. InstantSearch provides `HighlightedString` and `NSAttributedString` extensions to simplify this. ```swift Swift icon=code theme={"system"} let rawHighlightedString = "The Shawshank Redemption" let highlightedString = HighlightedString(string: rawHighlightedString) // Attributes to apply for a highlighted part of the string let highLightingAttributes: [NSAttributedString.Key: Any] = [ .foregroundColor: UIColor.red ] // Create attributed string highlighted part of which is red let attributedString = NSAttributedString(highlightedString: highlightedString, attributes: attributes) ``` The produced `NSAttributedString` can be assigned to a UIKit component that supports it. ```swift Swift icon=code theme={"system"} let attributedString: NSAttributedString = ... let label: UILabel = ... label.attributedText = attributedString ``` The `Hit` wrapper structure uses the `HighlightedString` structure. You can extract a highlighted string for an attribute using its `hightlightedString(forKey key: String)` function. Example: ```swift Swift icon=code theme={"system"} /* JSON search response { "title": "The Shawshank Redemption", "year": 1994, "objectID": "439817390", "_highlightResult": { "title": { "value": "The Shawshank Redemption", "matchLevel": "full", "fullyHighlighted": false, "matchedWords": [ "red" ] }, } } */ struct Movie: Codable { let title: String let year: Int } let movieHit: Hit = ... /* parse JSON response */ let highlightedMovieTitle: HighlightedString? = movieHit.hightlightedString(forKey: "title") ``` You can read more about usage of the `Hit` wrapper structure in the [`Hits` reference](/doc/api-reference/widgets/hits/ios). # Hits Source: https://www.algolia.com/doc/api-reference/widgets/hits/android Shows a list of search results. ## About this widget Use the `Hits` view and helper components to display a list of search results. Use [`HitsPaging`](/doc/api-reference/widgets/infinite-hits/android) if you want to use [infinite scrolling](/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/android) in your search. To add `Hits` to your search experience, use these components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. * `T`. A `data class` representing a search result. * `HitsView`. The view that renders objects of type `T`. See also: * [Get started with imperative UI](/doc/guides/building-search-ui/getting-started/how-to/programmatically/android) * [Multi-index search](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/android) * [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/android#handle-searches-without-results) > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Hits example code on GitHub. ## Examples You can use `deserialize` to convert raw hits into your own `data class` using the `Serializable` annotation. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val connection = ConnectionHandler() val adapter = MovieAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) connection += searcher.connectHitsView(adapter) { response -> response.hits.deserialize(Movie.serializer()) } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } @Serializable data class Movie( val title: String ) class MovieViewHolder(val view: TextView): RecyclerView.ViewHolder(view) { fun bind(data: Movie) { view.text = data.title } } class MovieAdapter : RecyclerView.Adapter(), HitsView { private var movies: List = listOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder { return MovieViewHolder(TextView(parent.context)) } override fun onBindViewHolder(holder: MovieViewHolder, position: Int) { val movie = movies[position] holder.bind(movie) } override fun setHits(hits: List) { movies = hits notifyDataSetChanged() } override fun getItemCount(): Int { return movies.size } } ``` ### Compose UI InstantSearch provides the `LoadingState` as a state model, which is an implementation of the `LoadingView` interface. You need to connect `LoadingState` to the `LoadingConnector` or `LoadingViewModel` like any other `LoadingView` implementation. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val hitsState = HitsState() val connections = ConnectionHandler() init { connections += searcher.connectHitsView(hitsState) { it.hits.deserialize(Movie.serializer()) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { LazyColumn { items(hitsState.hits) { movie -> Text(movie.title) } } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` ## Parameters **transform** `(SearchResponse) -\> List\` *required* A function transforming the search response into a list of results of your class `T`. ```kotlin Kotlin icon=code theme={"system"} import com.algolia.client.model.search.SearchResponse val transform: (SearchResponse) -> List = { response -> response.hits.map { hit -> Movie(hit.json.getPrimitive("title").content) } } searcher.connectHitsView(adapter, transform) ``` # Hits Source: https://www.algolia.com/doc/api-reference/widgets/hits/ios Shows a list of search results. ```swift Signature theme={"system"} HitsConnector( appID: ApplicationID, apiKey: APIKey, indexName: IndexName, infiniteScrolling: InfiniteScrolling, showItemsOnEmptyQuery: Bool, filterState: FilterState, controller: HitsController ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Hits example code on GitHub. ## About this widget Use the `Hits` component to display a list of search results. The component automatically reloads results when new hits are fetched from Algolia. `HitsConnector` is a generic class you can customize by implementing a class or structure that represents your record and conforms to the [`Codable`](https://developer.apple.com/documentation/swift/codable) protocol. If, for some reason, the engine can't parse a record into the type you provided, the `onError` event of the `Interactor` instance will be triggered. If you prefer to deal with raw JSON objects, set `JSON` as the type of record and use the `rawHitAtIndex(_ row: Int) -> [String: Any]?` method to access a hit. See also: * [Multi-index search](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/ios) * [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/ios#handle-searches-without-results) ## Examples Define the custom type representing the hits in your index conforming to `Codable` protocol. ```swift Swift icon=code theme={"system"} struct CustomHitModel: Codable { let name: String } ``` Instantiate a `HitsConnector` and launch an initial search on its searcher. ```swift Swift icon=code theme={"system"} let filterState: FilterState = .init() let hitsTableViewController = CustomHitsTableViewController() let hitsConnector = HitsConnector(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName", filterState: filterState, controller: hitsTableViewController) hitsConnector.searcher.search() ``` ## Parameters **searcher** `HitsSearcher | PlacesSearcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. **interactor** `HitsInteractor` *required* The logic applied to the hits. **filterState** `FilterState` The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) for your filters. **appID** `ApplicationID` *required* The ID of your application. **apiKey** `APIKey` *required* Your application's [search-only API key](/doc/guides/security/api-keys#search-only-api-key). **indexName** `IndexName` *required* Name of the index to search. **infiniteScrolling** `InfiniteScrolling` Whether [infinite scrolling](/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/ios) is enabled. `offset` defines the threshold for loading the next "page" of results. For example, if the index of the last visible hit is 10, and you set`offset` to 3, the engine will display the next page if the hit at index 13 (10+3) isn't loaded. If set to `.off`, this parameter turns off infinite scrolling. **showItemsOnEmptyQuery** `Bool` (default: true) If `false`, no results are displayed when users haven't entered any 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.) text. **controller** `HitsController` The controller interfacing with a concrete hits view. ## Low-level API To fully control the `Hits` components and connect them manually, use the following components: * [`Searcher`](/doc/api-reference/widgets/instantsearch/ios). Handles your searches. * `HitsInteractor`. The logic applied to the hits. * `HitsController`. The controller that interfaces with a concrete hits view. * `FilterState`. The current state of the filters. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState: FilterState = .init() let hitsInteractor: HitsInteractor = .init() let hitsTableViewController = CustomHitsTableViewController() hitsInteractor.connectSearcher(searcher) hitsInteractor.connectFilterState(filterState) hitsInteractor.connectController(hitsTableViewController) searcher.search() ``` Now, each time you launch a new search: * The `Searcher` receives new results and transmits them to `HitsInteractor` * The `HitsInteractor` parses search results and notifies `HitsController` * The `HitsController` refreshes the view presenting the hits ## HitsController The default controllers, `HitsTableViewController` and `HitsCollectionViewController`, let you create a basic `Hits` view based on `UITableView` and `UICollectionView` components from UIKit. You must configure these controllers using the `TableViewCellConfigurable`/`CollectionViewCellConfigurable` implementation protocols that define how your hit model is bound to a concrete cell class. Here is an example of the implementation using the `UITableViewCell` and `CustomHitModel`. ```swift Swift icon=code theme={"system"} struct CustomCellConfigurator: TableViewCellConfigurable { let model: CustomHitModel init(model: CustomHitModel, indexPath: IndexPath) { self.model = model } func configure(_ cell: UITableViewCell) { cell.textLabel?.text = model.name } } ``` Define a convenient type alias for your view controller using the `CellConfigurable` implementation. ```swift Swift icon=code theme={"system"} typealias CustomHitsTableViewController = HitsTableViewController ``` ## Customization You can subclass the `HitsTableViewController` or `HitsCollectionViewController`to customize their behavior. ```swift Swift icon=code theme={"system"} class MoreCustomHitsTableViewController: HitsTableViewController { ... } ``` ## Customizing your view The default controllers, `HitsTableViewController` and `HitsCollectionViewController`, work well when using native UIKit components with default behaviors. If you want to use another component as a hits view or introduce some custom behavior to the already provided UIKit component, you can create a controller conforming to the `HitsController` protocol. ### Protocol `var hitsSource: DataSource?`: Reference to an entity providing a list of hits. `func reload()`: Function called when a reload of the hits view is required. `func scrollToTop()`: Function called when scrolling to the top of the hits view. ### Implementation example ```swift Swift icon=code theme={"system"} public class CustomHitsTableViewController: NSObject, HitsController { public let tableView: UITableView public weak var hitsSource: HitsInteractor? public init(tableView: UITableView) { self.tableView = tableView } public func reload() { tableView.reloadData() } public func scrollToTop() { guard tableView.numberOfRows(inSection: 0) != 0 else { return } let indexPath = IndexPath(row: 0, section: 0) self.tableView.scrollToRow(at: indexPath, at: .top, animated: false) } } ``` ### SwiftUI InstantSearch provides the `HitsList` SwiftUI view, which you can embed in your views. It uses `HitsObservableController` as a data model. `HitsObservableController` is an implementation of the `HitsController` protocol adapted for usage with SwiftUI. `HitsObservableController` must be connected to the `HitsConnector` or `HitsInteractor` like any other `HitsController` implementation. Define the view representing a single hit. ```swift Swift icon=code theme={"system"} struct HitItem: Codable { let name: String } struct ContentView: View { @ObservedObject var hitsController: HitsObservableController var body: some View { HitsList(hitsController) { (hit, _) in // declare the view representing a single hit VStack(alignment: .leading, spacing: 10) { Text(hit?.name ?? "") Divider() } } noResults: { Text("No Results") .frame(maxWidth: .infinity, maxHeight: .infinity) } } } ``` If you prefer to create a custom SwiftUI view that presents the list hits, use `HitsObservableController` as a data model. It provides the `hits` property to streamline the design process of your custom SwiftUI view. The `notifyAppearanceOfHit(atIndex)` function of `HitsObservableController` might be called on the appearance of each hit to ensure the correct operation of the infinite scrolling feature. ## Result metadata and Hit structure Each hit returned by Algolia is enriched with search metadata, like [`highlightResult`, `objectID`, and `snippetResult`](/doc/rest-api/search/search-single-index#response-hits). InstantSearch provides the `Hit` wrapper structure to parse these. This generic structure encapsulates your record type and gives strongly typed access to a hit's metadata. For example, consider the following record structure: ```swift Swift icon=code theme={"system"} struct Movie: Codable { let title: String let year: Int } /* An appropriate structure for representing a record in the following JSON format: { "title": "Titanic", "year": 1997 } */ ``` Conforming to the `Codable` protocol, the record is ready to use with the `HitsInteractor` as follows: ```swift Swift icon=code theme={"system"} let hitsInteractor = HitsInteractor() ``` However, by doing this, the engine will ignore all the hit metadata. To keep this metadata, wrap your record structure into the provided `Hit` structure. ```swift Swift icon=code theme={"system"} let hitsInteractor = HitsInteractor>() ``` You can extract your `Movie` object by accessing the `object` field of `Hit`: ```swift Swift icon=code theme={"system"} let movieHit: Hit = hitsInteractor.hit(atIndex: ...) let movie: Movie = movieHit.object ``` The `Hit` structure gives access to the following fields: ```swift Swift icon=code theme={"system"} // Hit identifier attributed by Algolia let objectID: String // Wrapped record object let object: T // Snippeted attributes. let snippetResult: TreeModel? // Highlighted attributes. // Each attribute contains an object or an array of objects (if the attribute in question is an array) with the following attributes. let highlightResult: TreeModel? // Ranking information. let rankingInfo: RankingInfo? // Geolocation information let geolocation: Point? ``` # Infinite Hits (Paging) Source: https://www.algolia.com/doc/api-reference/widgets/infinite-hits/android Shows a paginated list of search results. ## About this widget A view with helpers that displays a paginated list of search results. It uses Android Architecture Components' [Paging library](https://developer.android.com/topic/libraries/architecture/paging) and [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) to provide lifecycle-aware, observable search results that can be loaded as users scroll. To add infinite hits to your search experience, add the following to your `build.gradle` file: ```groovy theme={"system"} implementation 'com.algolia:instantsearch-android-paging3:5.+' ``` Use these components: * [`Searcher`](/doc/api-reference/widgets/instantsearch/android) to handle your searches. * `Paginator`. A pagination utility that will load hits incrementally. * `PagingConfig` to configure loading behavior. * `T`. A `data class` representing a search result. * `FilterState` so that the paginated list refreshes when 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.) changes. As an alternative to this approach, [the infinite scroll guide](/doc/guides/building-search-ui/ui-and-ux-patterns/infinite-scroll/android) describes how to create an automatically scrolling infinite hits experience. See also: [Searches without results](/doc/guides/building-search-ui/going-further/conditional-display/android#handle-searches-without-results) > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Infinite Hits (Paging) example code on GitHub. ## Examples ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val paginator = Paginator( searcher = searcher, pagingConfig = PagingConfig(pageSize = 10), transformer = { it.deserialize(Movie.serializer()) } ) val filterState = FilterState() val adapter = MovieAdapter() val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) connection += filterState.connectPaginator(paginator) paginator.liveData.observe(this) { adapter.submitData(lifecycle, it) } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() searcher.cancel() connection.disconnect() } } @Serializable data class Movie( val title: String ) class MovieViewHolder(val view: TextView) : RecyclerView.ViewHolder(view) { fun bind(data: Movie) { view.text = data.title } } class MovieAdapter : PagingDataAdapter(MovieDiffUtil) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder { return MovieViewHolder(TextView(parent.context)) } override fun onBindViewHolder(holder: MovieViewHolder, position: Int) { val movie = getItem(position) if (movie != null) holder.bind(movie) } object MovieDiffUtil : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean { return oldItem == newItem } override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean { return oldItem.title == newItem.title } } } ``` ### Compose UI InstantSearch provides the `Paginator`, which exposes a `flow` property for paging data and an `invalidate()` method to stop loading. You must also connect `Paginator` to other components, such as `SearchBoxConnector`, `FilterState`, or `FacetListConnector`. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val pagingConfig = PagingConfig(pageSize = 10) val hitsPaginator = Paginator(searcher, pagingConfig) { it.deserialize(Movie.serializer()) } val searchBoxState = SearchBoxState() val searchBox = SearchBoxConnector(searcher) val connections = ConnectionHandler() init { connections += searchBox.connectView(searchBoxState) connections += searchBox.connectPaginator(hitsPaginator) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { SearchBox( searchBoxState = searchBoxState ) val hitsPaging = hitsPaginator.flow.collectAsLazyPagingItems() LazyColumn { items(hitsPaging) { movie -> movie?.let { Text(it.title) } } } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` # Searcher Source: https://www.algolia.com/doc/api-reference/widgets/instantsearch/android Manages search sessions and requests. ## About this widget This component is handled by objects that implement the [`Searcher` interface](https://github.com/algolia/instantsearch-android/blob/master/instantsearch-core/src/commonMain/kotlin/com/algolia/instantsearch/core/searcher/Searcher.kt). These objects manage the search session and process each search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.) as it occurs. Algolia provides you with several out of the box searchers to help build your InstantSearch experience: * `HitsSearcher`: searches a single 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.). * `FacetSearcher`: searches for facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) values. * `MultiSearcher`: searches in multiple indices. This is useful for a federated search, or query suggestions search experience. > [card] [Explore example code](https://github.com/algolia/instantsearch-android/blob/master/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/showcase/androidview/search/SearchAsYouTypeShowcase.kt) > > Browse the Searcher example code on GitHub. ## Examples Instantiating a `HitsSearcher`: ```kotlin Kotlin icon=code theme={"system"} val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "index_name", isAutoSendingHitsViewEvents = true ) ``` Instantiating a `FacetSearcher`: ```kotlin Kotlin icon=code theme={"system"} val searcher = FacetsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "index_name", attribute = "color" ) ``` Instantiating a `MultiSearcher`: ```kotlin Kotlin icon=code theme={"system"} val multiSearcher = MultiSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", ) val hitsSearcher = multiSearcher.addHitsSearcher(indexName = "index_name1") val facetsSearcher = multiSearcher.addFacetsSearcher( indexName = "index_name1", attribute = "facet_name" ) ``` ## HitsSearcher **indexName** `String` *required* The index to search into. ```kotlin Kotlin icon=code theme={"system"} val indexName = "index_name" val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = indexName ) ``` **query** `SearchParamsObject` (default: SearchParamsObject()) 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.). ```kotlin Kotlin icon=code theme={"system"} val query = SearchParamsObject(analytics = false) val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "index_name", query = query ) ``` **isAutoSendingHitsViewEvents** `Boolean` (default: false) Flag defining whether the automatic hits view Insights events (An event is a specific action a user takes in your app or on your website.) sending is enabled. > [!NOTE] > > The default value was `true` until version 3.3.1. ```kotlin Kotlin icon=code theme={"system"} val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "index_name", isAutoSendingHitsViewEvents = true, ) ``` **userToken** `String` The user token assigned to automatically send click and conversion events in the `HitsSearcher` component. If not explicitly set during initialization, the `HitsSearcher` generates and assigns a default user token. **requestOptions** `RequestOptions` (default: null) RequestOptions that apply to the search. ```kotlin Kotlin icon=code theme={"system"} val requestOptions = RequestOptions( headers = mapOf("X-Forwarded-For" to "1.2.3.4") ) val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "index_name", requestOptions = requestOptions ) ``` ## FacetSearcher **indexName** `String` *required* The index to search into. ```kotlin Kotlin icon=code theme={"system"} val indexName = "index_name" val searcher = FacetsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = indexName, attribute = "color" ) ``` **attribute** `String` *required* An attribute to search facet values for. ```kotlin Kotlin icon=code theme={"system"} val attribute = "color" val searcher = FacetsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "index_name", attribute = attribute ) ``` **query** `SearchParamsObject` (default: SearchParamsObject()) A query used to perform the search. ```kotlin Kotlin icon=code theme={"system"} val query = SearchParamsObject(analytics = true) val searcher = FacetsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "index_name", attribute = "color", query = query ) ``` **requestOptions** `RequestOptions` (default: null) `RequestOptions` that apply to the search. ```kotlin Kotlin icon=code theme={"system"} val requestOptions = RequestOptions( headers = mapOf("X-Forwarded-For" to "1.2.3.4") ) val searcher = FacetsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "index_name", attribute = "facet_name", requestOptions = requestOptions ) ``` ## MultiSearcher **strategy** `MultipleQueriesStrategy` (default: None) The strategy of the query. Can be one of the following values: * `None`. Run the sequence of queries until the end. This is recommended when each query is of equal importance, meaning all records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) of all queries need to be returned. * `StopIfEnoughMatches`. Run queries one by one, but stop as soon as the cumulated number of hits is at least `hitsPerPage`. This is recommended when each query is an alternative, and where, if the first returns enough records, there is no need to perform the remaining queries. ```kotlin Kotlin icon=code theme={"system"} val multiSearcher = MultiSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", strategy = MultipleQueriesStrategy.None ) ``` **requestOptions** `RequestOptions` (default: RequestOptions()) `RequestOptions` that apply to the search. ```kotlin Kotlin icon=code theme={"system"} val requestOptions = RequestOptions( headers = mapOf("X-Forwarded-For" to "1.2.3.4") ) val multiSearcher = MultiSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", requestOptions = requestOptions ) ``` ## Methods **search** Triggers the search and returns a search response. ```kotlin Kotlin icon=code theme={"system"} searcher.search() ``` **searchAsync** Triggers the search. Notifies all listeners of the results. ```kotlin Kotlin icon=code theme={"system"} searcher.searchAsync() ``` **cancel** Cancels the ongoing search requests. ```kotlin Kotlin icon=code theme={"system"} searcher.cancel() ``` **setQuery** Sets the query to the string provided. ```kotlin Kotlin icon=code theme={"system"} searcher.setQuery("foo") ``` ## Events **onLoadingChanged** Triggered when the status of the search request is changed. ```kotlin Kotlin icon=code theme={"system"} searcher.onLoadingChanged += { loading -> print(if (loading) "Currently loading search response" else "Done loading") } ``` **onResponseChanged** Triggered when a new response has arrived. ```kotlin Kotlin icon=code theme={"system"} searcher.onResponseChanged += { response -> val hits = response.hits.deserialize(MovieHit.serializer()) // Do something with hits... } ``` > [!NOTE] > > The deserialize extension is deprecated in 4.x. > Consider parsing hits directly from `SearchResponse.hits`, which returns `List` from the Algolia client. **onErrorChanged** Triggered when an error was encountered during a search request. ```kotlin Kotlin icon=code theme={"system"} searcher.onErrorChanged += { errorTextView.text = it.localizedMessage } ``` # Hits Searcher Source: https://www.algolia.com/doc/api-reference/widgets/instantsearch/flutter Manages search sessions and requests. ```dart Signature theme={"system"} HitsSearcher( String applicationID, String apiKey, String indexName, // Optional parameters bool disjunctiveFacetingEnabled, Duration debounce, ) HitsSearcher.create( String applicationID, String apiKey, SearchState state, // Optional parameters bool disjunctiveFacetingEnabled, Duration debounce, ) FacetSearcher( String applicationID, String apiKey, String indexName, String facet, // Optional parameters Duration debounce, ) FacetSearcher.create( String applicationID, String apiKey, FacetSearchState state, // Optional parameters Duration debounce, ) MultiSearcher( String applicationID, String apiKey, // Optional parameters EventTracker eventTracker ) ``` > [card] [View live demo](https://algolia-flutter-showcase.netlify.app/#/?path=hitssearcher%2Fsearch-box-%26-hits-list) > > Open the live Hits Searcher example in your browser. ## About this widget The component handling a search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.), `HitsSearcher` has the following behavior: * Distinct state changes (including initial state) will trigger a search operation * State changes are debounced * On a new search request, any previous ongoing search calls will be cancelled Algolia for Flutter comes with these searchers: * `HitsSearcher`. Searches a single 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.). * `FacetSearcher`. Searches for facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) values. * `MultiSearcher`. Aggregates the hits and facet searchers. This is useful for building a federated search, or query suggestions. ## Examples 1. Create a `HitsSearcher` 2. Update search state with [`query`](#param-query) and [`applyState`](/doc/api-reference/widgets/instantsearch/flutter#param-apply-state) 3. Listen to search [`responses`](#param-responses) and build the UI 4. [`dispose`](#param-dispose) of underlying resources ```dart Dart icon=code theme={"system"} class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @override State createState() => _SearchScreenState(); } class _SearchScreenState extends State { // 1. Create a Hits Searcher final hitsSearcher = HitsSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', ); @override Widget build(BuildContext context) => Scaffold( appBar: AppBar( // 2. Run your search operations title: TextField( onChanged: (input) => hitsSearcher.query(input), decoration: const InputDecoration( hintText: 'Search...', prefixIcon: Icon(Icons.search), fillColor: Colors.white, filled: true, ), ), ), // 3.1 Listen to search responses body: StreamBuilder( stream: hitsSearcher.responses, builder: (_, snapshot) { if (snapshot.hasData) { final response = snapshot.data; final hits = response?.hits.toList() ?? []; // 3.2 Display your search hits return ListView.builder( itemCount: hits.length, itemBuilder: (_, i) => ListTile(title: Text(hits[i]['title'])), ); } else { return const Center(child: CircularProgressIndicator()); } }, ), ); @override void dispose() { super.dispose(); // 4. Release underling resources hitsSearcher.dispose(); } } ``` ## HitsSearcher **applicationID** `String` *required* The ID of your application. ```dart Dart icon=code theme={"system"} final hitsSearcher = HitsSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', ); ``` **apiKey** `String` *required* Your application's [Search-only API key](/doc/guides/security/api-keys#search-only-api-key). ```dart Dart icon=code theme={"system"} final hitsSearcher = HitsSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', ); ``` **indexName** `String` *required* The index to search into. ```dart Dart icon=code theme={"system"} final hitsSearcher = HitsSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', ); ``` **state** `SearchState` *required* Initial search state. ```dart Dart icon=code theme={"system"} final hitsSearcher = HitsSearcher.create( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', state: const SearchState(indexName: 'YourIndexName'), ); ``` **disjunctiveFacetingEnabled** `bool` (default: true) Whether disjunctive faceting is enabled. ```dart Dart icon=code theme={"system"} final hitsSearcher = HitsSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', disjunctiveFacetingEnabled: true, ); ``` **debounce** `Duration` Search operation debounce duration. ```dart Dart icon=code theme={"system"} final hitsSearcher = HitsSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', debounce: const Duration(milliseconds: 100), ); ``` ### Fields **responses** `Stream\` Stream of search responses. ```dart Dart icon=code theme={"system"} StreamBuilder( stream: hitsSearcher.responses, builder: (context, snapshot) { if (snapshot.hasData) { final response = snapshot.data!; return Column( children: [ Text('${response.nbHits} hits found'), ListView.builder( itemCount: response.hits.length, itemBuilder: (context, index) { final hit = response.hits[index]; return ListTile(title: Text(hit['name'])); }, ), ], ); } else { return const CircularProgressIndicator(); } }, ); ``` **state** `Stream\` Stream of search states. ```dart Dart icon=code theme={"system"} StreamBuilder( stream: searcher.state, builder: (context, snapshot) { if (snapshot.hasData) { final query = snapshot.data?.query ?? ''; return Text('Query: $query'); } else { return const CircularProgressIndicator(); } }, ); ``` **eventTracker** `HitsEventTracker` Instance responsible for handling and sending user events (An event is a specific action a user takes in your app or on your website.) related to search interactions, such as clicks, conversions, and views. These events help to personalize the user's search experience by providing insights into user behavior. `eventTracker` is automatically integrated with the `HitsSearcher` to track events when users interact with the search results. ```dart Dart icon=code theme={"system"} hitsSearcher.eventTracker.clickedObjects( indexName: 'YourIndexName', objectIDs: ['objectID1', 'objectID2'], eventName: 'your_event_name', ); ``` ### Methods **query** Triggers a search operation with given 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.). ```dart Dart icon=code theme={"system"} hitsSearcher.query('book'); ``` **applyState** Applies a search state configuration and triggers a search operation. ```dart Dart icon=code theme={"system"} hitsSearcher.applyState((state) => state.copyWith(query: 'book', page: 0)); ``` **snapshot** Gets the latest `SearchResponse` value submitted by [`responses`](#param-responses) stream: ```dart Dart icon=code theme={"system"} final response = hitsSearcher.snapshot(); ``` **dispose** Releases all underlying resources ```dart Dart icon=code theme={"system"} hitsSearcher.dispose(); ``` ## FacetSearcher **applicationID** `String` *required* The ID of your application. ```dart Dart icon=code theme={"system"} final facetSearcher = FacetSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', facet: 'facet_attribute', ); ``` **apiKey** `String` *required* Your application's [Search-only API key](/doc/guides/security/api-keys#search-only-api-key). ```dart Dart icon=code theme={"system"} final facetSearcher = FacetSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', facet: 'facet_attribute', ); ``` **indexName** `String` *required* The index to search into. ```dart Dart icon=code theme={"system"} final facetSearcher = FacetSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', facet: 'facet_attribute', ); ``` **facet** `String` *required* The facet name to search into when doing search for facet values. ```dart Dart icon=code theme={"system"} final facetSearcher = FacetSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', facet: 'facet_attribute', ); ``` **state** `FacetSearchState` *required* Initial facet search state. ```dart Dart icon=code theme={"system"} final facetSearcher = FacetSearcher.create( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', state: FacetSearchState( searchState: SearchState(indexName: 'YourIndexName'), facet: 'facet_attribute', ), ); ``` **debounce** `Duration` Search operation debounce duration. ```dart Dart icon=code theme={"system"} final facetSearcher = FacetSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', facet: 'facet_attribute', debounce: const Duration(milliseconds: 100), ); ``` ### Fields **responses** `Stream\` Stream of search responses. ```dart Dart icon=code theme={"system"} StreamBuilder( stream: facetSearcher.responses, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { final response = snapshot.data; final facets = response?.facetHits.toList() ?? []; return ListView.builder( itemCount: facets.length, itemBuilder: (BuildContext context, int index) { final facet = facets[index]; return ListTile( title: Text(facet.value) ); }, ); } else { return const CircularProgressIndicator(); } }, ) ``` **state** `Stream\` Stream of facet search states ```dart Dart icon=code theme={"system"} StreamBuilder( stream: searcher.state, builder: (context, snapshot) { if (snapshot.hasData) { final query = snapshot.data?.facetQuery ?? ''; return Text('Facet query: $query'); } else { return const CircularProgressIndicator(); } }, ); ``` ### Methods **query** Triggers a search operation with given search query ```dart Dart icon=code theme={"system"} facetSearcher.query('book'); ``` **applyState** Applies a search state configuration and triggers a search operation ```dart Dart icon=code theme={"system"} facetSearcher.applyState((state) => state.copyWith(facetQuery: 'book')); ``` **snapshot** Gets the latest `FacetSearchResponse` value submitted by [`responses`](#param-responses-1) stream: ```dart Dart icon=code theme={"system"} final response = facetSearcher.snapshot(); ``` **dispose** Releases all underlying resources ```dart Dart icon=code theme={"system"} facetSearcher.dispose(); ``` ## MultiSearcher **strategy** `MultipleQueriesStrategy` `MultiSearcher` lets you simultaneously search for hits and facet values in different indices of the same Algolia application. It instantiates and manages `HitsSearcher` and `FacetSearcher` instances. Once created, these searchers behave like their independently instantiated counterparts. ```dart Dart icon=code theme={"system"} final multiSearcher = MultiSearcher( applicationID = ApplicationID("YourApplicationID"), apiKey = APIKey("YourSearchOnlyApiKey"), eventTracker: Insights('MY_APPLICATION_ID', 'MY_API_KEY'), ) ``` ### Methods **dispose** Releases all underlying resources ```dart Dart icon=code theme={"system"} multiSearcher.dispose(); ``` **addHitsSearcher** Adds a new `HitsSearcher` to the multi-searcher. ```dart Dart icon=code theme={"system"} final hitsSearcher = multiSearcher.addHitsSearcher( initialState: const SearchState( indexName: 'instant_search', ), ); ``` **addFacetSearcher** Adds a new `FacetSearcher` to the multi-searcher. ```dart Dart icon=code theme={"system"} final facetSearcher = multiSearcher.addFacetSearcher( initialState: const FacetSearchState( facet: 'brand', searchState: SearchState( indexName: 'instant_search', ), ) ); ``` # Searcher Source: https://www.algolia.com/doc/api-reference/widgets/instantsearch/ios Manages search sessions and requests. ## About this widget Objects conforming to the [Searcher protocol](https://github.com/algolia/instantsearch-core-swift/blob/master/Sources/Searcher/Searcher.swift) manage the search sessions. Algolia provides you with several out of the box searchers to help build your InstantSearch experience: * `HitsSearcher`. Searches a single 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.). * `FacetSearcher`. Searches for facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) values. * `MultiSearcher`. Aggregates the hits and facet searchers. This is useful for building a federated search, or query suggestions. > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Searcher example code on GitHub. ## Examples Instantiate a `HitsSearcher`. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyApiKey", indexName: "index_name", isAutoSendingHitsViewEvents: true) ``` Instantiate a `FacetSearcher`. ```swift Swift icon=code theme={"system"} let searcher = FacetSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyApiKey", indexName: "index_name", facetName: "facet_name") ``` Instantiate a `MultiSearcher`. ```swift Swift icon=code theme={"system"} let searcher = MultiSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyApiKey") searcher.addHitsSearcher(indexName: "index_name1") searcher.addFacetsSearcher(indexName: "index_name1", attribute: "facet_name") searcher.addHitsSearcher(indexName: "index_name2") ``` ## HitsSearcher **indexName** `String` *required* The index to search. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyApiKey", indexName: "index_name") ``` **query** `Query` (default: Query()) 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.). ```swift Swift icon=code theme={"system"} let query = Query() query.analytics = true let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyApiKey", indexName: "index_name", query: query) ``` **isAutoSendingHitsViewEvents** `Bool` (default: false) Whether to automatically send view events (An event is a specific action a user takes in your app or on your website.) for hits. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyApiKey", indexName: "index_name", isAutoSendingHitsViewEvents: true) ``` ## FacetSearcher **indexName** `String` *required* The index to search into. ```swift Swift icon=code theme={"system"} let searcher = FacetSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyApiKey", indexName: "index_name") ``` **facetName** `String` *required* The facet name to search into when doing search for facet values. ```swift Swift icon=code theme={"system"} let searcher = FacetSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyApiKey", indexName: "index_name", facetName: "facet_name") ``` **query** `Query` (default: Query()) The query used when doing a search. ```swift Swift icon=code theme={"system"} let query = Query() query.analytics = true let searcher = FacetSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyApiKey", indexName: "index_name", query: query) ``` ## Methods **search** Triggers the search. Notifies all listeners of the results. ```swift Swift icon=code theme={"system"} searcher.search() ``` **cancel** Cancels every ongoing search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.). ```swift Swift icon=code theme={"system"} searcher.cancel() ``` ## Events **isLoading** Triggered when the `Searcher` starts or finishes query execution. ```swift Swift icon=code theme={"system"} searcher.isLoading.subscribe(with: self) { (_, isLoading) in } ``` **onQueryChanged** Triggered when the query text is modified. ```swift Swift icon=code theme={"system"} searcher.onQueryChanged.subscribe(with: self) { (_, query) in } ``` **onResults** Triggered when a new search result is received. ```swift Swift icon=code theme={"system"} searcher.onResults.subscribe(with: self) { (_, results) in } ``` **onError** Triggered when a new error is received. ```swift Swift icon=code theme={"system"} searcher.onError.subscribe(with: self) { (_, args) in let (query, error) = args } ``` # Loading Source: https://www.algolia.com/doc/api-reference/widgets/loading/android Shows a loading indicator during pending search requests. ```kotlin Signature theme={"system"} LoadingConnector( searcher: Searcher, viewModel: LoadingViewModel, debouncer: Debouncer ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/blob/master/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/showcase/androidview/search/SearchAsYouTypeShowcase.kt) > > Browse the Loading example code on GitHub. ## About this widget Components that show a loading indicator during pending requests. To add a loading indicator to your search experience, use these components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. * `LoadingViewModel`. The logic applied to the loading indicator. * `LoadingView`. The concrete view displayed during loading. ## Examples ```kotlin Kotlin icon=code theme={"system"} class MyActivity: AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val loading = LoadingConnector(searcher) val connection = ConnectionHandler(loading) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val swipeRefreshLayout = SwipeRefreshLayout(this) val view: LoadingView = LoadingViewSwipeRefreshLayout(swipeRefreshLayout) connection += loading.connectView(view) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ## Parameters **searcher** `Searcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. **viewModel** `LoadingViewModel` The logic applied to the loading indicator. **debouncer** `Debouncer` Delays searcher operations by a specified time duration. ## ViewModel **isLoading** `Boolean` When true, the interface starts in a loading state. ## ViewModel **isLoading** `Boolean` (default: false) When true, the interface starts in a loading state. ```kotlin Kotlin icon=code theme={"system"} LoadingViewModel(isLoading = true) ``` ## View **view** `LoadingView` *required* The concrete view displayed during load. ```kotlin Kotlin icon=code theme={"system"} val swipeRefreshLayout = SwipeRefreshLayout(this) val view: LoadingView = LoadingViewSwipeRefreshLayout(swipeRefreshLayout) loading.connectView(view) ``` ### Compose UI InstantSearch provides the `LoadingState` as a state model, which is an implementation of the `LoadingView` interface. You need to connect `LoadingState` to the `LoadingConnector` or `LoadingViewModel` like any other `LoadingView` implementation. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val loadingState = LoadingState() val loading = LoadingConnector(searcher) val connections = ConnectionHandler(loading) init { connections += loading.connectView(loadingState) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { SwipeRefresh( state = rememberSwipeRefreshState(loadingState.loading), onRefresh = { loadingState.reload() }, ) { //... } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` # Loading Source: https://www.algolia.com/doc/api-reference/widgets/loading/flutter Shows a loading spinner during pending search requests. ## About this widget ## Examples Use Flutter's [`StreamBuilder`](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html) to display the spinner. ```dart Dart icon=code theme={"system"} class SearchHits extends StatelessWidget { const SearchHits(this.responses, {super.key}); final Stream responses; @override Widget build(BuildContext context) { return StreamBuilder( stream: responses, builder: (context, snapshot) { if (snapshot.hasData) { // display hits list } else if (snapshot.hasError) { // display error message } else { // display loading indicator return const CircularProgressIndicator(); } }, ); } } ``` # Loading Source: https://www.algolia.com/doc/api-reference/widgets/loading/ios Shows a loading indicator during pending search requests. ```ts Signature theme={"system"} LoadingConnector( searcher: SingleIndexSearcher, interactor: LoadingInteractor, controller: LoadingController ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase/Search/PaginationSingleIndex) > > Browse the Loading example code on GitHub. ## About this widget Components that show a loading indicator during pending requests. ## Examples Instantiate a `LoadingConnector`. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let activityIndicatorController: ActivityIndicatorController = .init(activityIndicator: UIActivityIndicatorView()) let loadingConnector: LoadingConnector = .init(searcher: searcher, controller: activityIndicatorController) // Execute a search which will spin the loading indicator until the results arrive searcher.search() ``` ## Parameters **searcher** `Searcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. **interactor** `LoadingInteractor` The business logic that handles showing a loading indicator. **controller** `LoadingController` Controller that interfaces with a concrete loading view. ## Low-level API If you want to fully control the loading indicator components and connect them manually, you can use these components. The loading indicator consists of the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `LoadingInteractor`. The logic that handles showing a loading indicator * `LoadingController`. The controller that interfaces with a concrete loading indicator. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let loadingInteractor: LoadingInteractor = .init() let activityIndicatorController: ActivityIndicatorController = .init(activityIndicator: UIActivityIndicatorView()) loadingInteractor.connectSearcher(searcher) loadingInteractor.connectController(activityIndicatorController) // Execute a search which will spin the loading indicator until the results arrive searcher.search() ``` ## Customizing your view The default controllers, such as `ActivityIndicatorController`, work well when you want to use native UIKit with their default behavior like `UIActivityIndicatorView`. If you want to use another component such as a third-party view, or want to introduce some custom behavior to the already provided UIKit components, you can create your own controller conforming to the `LoadingController` protocol. ### Protocol ```swift Swift icon=code theme={"system"} func setItem(_ item: Bool)` ``` Function called when a new state of the loading indicator is set. ### Example ```swift Swift icon=code theme={"system"} public class ActivityIndicatorController: LoadingController { let activityIndicator: UIActivityIndicatorView public init (activityIndicator: UIActivityIndicatorView) { self.activityIndicator = activityIndicator } public func setItem(_ item: Bool) { item ? activityIndicator.startAnimating() : activityIndicator.stopAnimating() } } ``` ### SwiftUI InstantSearch provides the `LoadingObservableController` data model, which is an implementation of the `LoadingController` protocol adapted for usage with SwiftUI. `LoadingObservableController` must be connected to the `LoadingConnector` or `LoadingConnector` like any other `LoadingController` implementation. The example of the loading view using the `ProgressView` component provided by SwiftUI. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var loadingController: LoadingObservableController var body: some View { VStack { Text("Loading") if loadingController.isLoading { ProgressView() } } } } ``` If you prefer to create a custom SwiftUI view that presents the loading indicator, you can directly use the `LoadingObservableController` as a data model. It provides the `isLoading` property to streamline the design process of your custom SwiftUI view. # Multi Hits Source: https://www.algolia.com/doc/api-reference/widgets/multi-hits/android Shows paginated search results from multiple indices. ## About this widget To use this widget to display a paginated list of results from multiple categories, you need these components: * `MultiSearcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches * `FilterState`. Tracks any applied filters (such as genre or year) and updates search results when the 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.) changes. * `HitsState`. Hits UI state * `SearchBoxState`. Search box UI state The widget can handle various kinds of search results. For example, you can display different types of results, such as movies or actors, each represented by a unique data class. See also: * [Get started with imperative UI](/doc/guides/building-search-ui/getting-started/how-to/programmatically/android) * [Multi-index search](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/android) > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Multi Hits example code on GitHub. ## Examples ### Activity ```kotlin Kotlin icon=code theme={"system"} class SearchActivity : ComponentActivity() { private val viewModel: SearchViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { SearchScreen( searchBoxState = viewModel.searchBoxState, actorsState = viewModel.actorsState, moviesState = viewModel.moviesState, ) } } } } ``` ### View model ```kotlin Kotlin icon=code theme={"system"} import com.algolia.client.model.search.SearchParamsObject class SearchViewModel : ViewModel() { private val multiSearcher = MultiSearcher( applicationID = "YourApplicationID", apiKey = "YourAPIKey" ) private val actorsSearcher = multiSearcher.addHitsSearcher( indexName = "YourActorsIndex", query = SearchParamsObject(hitsPerPage = 5) ) private val moviesSearcher = multiSearcher.addHitsSearcher(indexName = "YourMoviesIndex") private val searchBoxConnector = SearchBoxConnector(multiSearcher) private val connections = ConnectionHandler(searchBoxConnector) val searchBoxState = SearchBoxState() val actorsState = HitsState() val moviesState = HitsState() init { connections += searchBoxConnector.connectView(searchBoxState) connections += actorsSearcher.connectHitsView(actorsState) { it.hits.deserialize(Actor.serializer()) } connections += moviesSearcher.connectHitsView(moviesState) { it.hits.deserialize(Movie.serializer()) } multiSearcher.searchAsync() } override fun onCleared() { super.onCleared() multiSearcher.cancel() connections.clear() } } ``` ### UI ```kotlin Kotlin icon=code theme={"system"} @Composable fun SearchScreen( modifier: Modifier = Modifier, searchBoxState: SearchBoxState, actorsState: HitsState, moviesState: HitsState, ) { val scrollState = rememberScrollState() Column( modifier = modifier .fillMaxWidth() .verticalScroll(scrollState) ) { SearchBox( modifier = Modifier .fillMaxWidth() .padding(12.dp), searchBoxState = searchBoxState, ) Actors(actors = actorsState.hits) Movies(movies = moviesState.hits) } } @Composable private fun Actors( modifier: Modifier = Modifier, actors: List, ) { if (actors.isEmpty()) return Column(modifier) { SectionTitle( modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 4.dp), title = "Actors" ) actors.forEach { actor -> Actor(actor = actor) } } } @Composable private fun Actor( modifier: Modifier = Modifier, actor: Actor ) { Row( modifier .fillMaxWidth() .background(MaterialTheme.colors.surface) .padding(horizontal = 24.dp, vertical = 12.dp) ) { Icon( imageVector = Icons.Default.AccountCircle, tint = MaterialTheme.colors.onSurface.copy(alpha = 0.2f), contentDescription = null ) Text( text = actor.highlightedName?.toAnnotatedString() ?: AnnotatedString(actor.name), modifier = Modifier.padding(start = 12.dp), ) } } @Composable private fun Movies( modifier: Modifier = Modifier, movies: List ) { if (movies.isEmpty()) return Column(modifier) { SectionTitle( modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), title = "Movies" ) movies.forEach { movie -> Movie(movie = movie) } } } @Composable private fun Movie(modifier: Modifier = Modifier, movie: Movie) { Row( modifier .fillMaxWidth() .background(MaterialTheme.colors.surface) .padding(horizontal = 24.dp, vertical = 12.dp) ) { Icon( imageVector = Icons.Default.Movie, tint = MaterialTheme.colors.onSurface.copy(alpha = 0.2f), contentDescription = null ) Text( text = movie.highlightedTitle?.toAnnotatedString() ?: AnnotatedString(movie.title), modifier = Modifier.padding(start = 12.dp), ) } } @Composable private fun SectionTitle(modifier: Modifier = Modifier, title: String) { Text( modifier = modifier, text = title, style = MaterialTheme.typography.subtitle2, color = MaterialTheme.colors.onBackground.copy(alpha = 0.4f), ) } ``` ### Models ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.core.Indexable @Serializable data class Actor( val name: String, override val objectID: String, override val _highlightResult: JsonObject? ) : Indexable, Highlightable { val highlightedName get() = getHighlight("name") } ``` ```kotlin Kotlin icon=code theme={"system"} import com.algolia.instantsearch.core.Indexable @Serializable data class Movie( val title: String, override val objectID: String, override val _highlightResult: JsonObject? ) : Indexable, Highlightable { val highlightedTitle get() = getHighlight("title") } ``` # Multi Hits Source: https://www.algolia.com/doc/api-reference/widgets/multi-hits/ios Tips for building multi-index search interfaces with InstantSearch iOS. ## About this widget InstantSearch iOS doesn't offer a Multi Hits widget. Instead, to build a multi-index search experience, compose multiple [HitsSearcher](/doc/api-reference/widgets/instantsearch/ios) within a single [MultiSearcher](/doc/api-reference/widgets/instantsearch/ios). Then you can connect a separate [Hits widget](/doc/api-reference/widgets/hits/ios) to each HitsSearcher. See also: [Multi-index search](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/ios). > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Multi Hits example code on GitHub. # QueryRuleCustomData Source: https://www.algolia.com/doc/api-reference/widgets/query-rule-custom-data/android Shows custom data from applied index rules. ```kotlin Signature theme={"system"} QueryRuleCustomDataConnector( searcher: HitsSearcher, initialItem: T?, presenter: QueryRuleCustomDataPresenter?, ) ``` ## About this widget The `QueryRuleCustomData` widget displays custom data from [Rules](/doc/guides/managing-results/rules/detecting-intent). You can use this widget to display banners or recommendations returned by rules when they match search parameters. > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the QueryRuleCustomData example code on GitHub. ## Examples Instantiate a `QueryRuleCustomDataConnector` and launch an initial search on its [`Searcher`](/doc/api-reference/widgets/instantsearch/android), triggering a Rule that returns custom data. ### Android view ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { @Serializable data class Banner(val text: String) // Custom data model val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val queryRuleCustomData = QueryRuleCustomDataConnector(searcher) val connection = ConnectionHandler(queryRuleCustomData) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val bannerView = TextView(this) queryRuleCustomData.subscribe { banner -> banner?.text?.let { bannerView.text = it } // Display the banner } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Compose UI InstantSearch provides the `QueryRuleCustomDataState` as a state model, which is an implementation of the `QueryRuleCustomDataPresenter` interface. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val queryRuleCustomDataState = QueryRuleCustomDataState() val queryRuleCustomData = QueryRuleCustomDataConnector( searcher = searcher, presenter = queryRuleCustomDataState ) val connection = ConnectionHandler(queryRuleCustomData) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyQueryRuleCustomData(queryRuleCustomDataState) // your own UI composable to display your custom data } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } @Serializable data class Banner(val text: String) // your custom data model ``` ## Parameters **searcher** `HitsSearcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. **initialItem** `T?` Initial model value. **presenter** `QueryRuleCustomDataPresenter\?` The Presenter defining how a model appears. ```kotlin Kotlin icon=code theme={"system"} val queryRuleCustomData = QueryRuleCustomDataConnector(searcher) { banner -> bannerView.text = banner.text } ``` ## Low-level API If you want to fully control the `QueryRuleCustomData` components and connect them manually, you can use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `QueryRuleCustomDataViewModel`. The component encapsulating the logic applied to the custom model. * `QueryRuleCustomDataPresenter`. Defines how to interact with a model. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { @Serializable data class Banner(val text: String) // Custom data model val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val queryRuleCustomDataViewModel = QueryRuleCustomDataViewModel(Banner.serializer()) val queryRuleCustomData = QueryRuleCustomDataConnector(searcher, queryRuleCustomDataViewModel) val connection = ConnectionHandler(queryRuleCustomData) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val bannerView = TextView(this) queryRuleCustomData.subscribe { banner -> banner?.text?.let { bannerView.text = it } // Display the banner } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ## Serialization You must provide a `DeserializationStrategy` implementation to deserialize your custom models. To do this, annotate your custom model class with `@Serialization`. The Kotlin serialization compiler plugin then automatically generates an implementation for you, accessible using the `.serializer()` function on the class's companion object. ### Example Suppose your custom JSON data contains a banner URL that can be decoded to the following structure: ```kotlin Kotlin icon=code theme={"system"} @Serializable data class Banner(val bannerUrl: String) ``` You can then either explicitly pass your serializer to the widget: ```kotlin Kotlin icon=code theme={"system"} val queryRuleCustomData = QueryRuleCustomData(searcher, Banner.serializer()) ``` Or, pass your custom model class as a generic argument: ```kotlin Kotlin icon=code theme={"system"} val queryRuleCustomData = QueryRuleCustomData(searcher) ``` # QueryRuleCustomData Source: https://www.algolia.com/doc/api-reference/widgets/query-rule-custom-data/ios Shows custom data from applied index rules. ## About this widget The `QueryRuleCustomData` widget displays custom data from [rules](/doc/guides/managing-results/rules/detecting-intent). You may want to use this widget to display banners or recommendations returned by Rules, and that match search parameters. > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase/QueryRuleCustomData/) > > Browse the QueryRuleCustomData example code on GitHub. ## Examples Instantiate a `QueryRuleCustomDataConnector` and launch an initial search on its [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) triggering a Rule that returns custom data. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let bannerViewController = BannerViewController() let queryRuleCustomDataConnector = QueryRuleCustomDataConnector(searcher: searcher, controller: bannerViewController) searcher.search() ``` ## Parameters **searcher** `HitsSearcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. **interactor** `QueryRuleCustomDataInteractor\` *required* The logic applied to the custom model. **controller** `ItemController` The Controller interfacing with a concrete custom data view. **presenter** `Presenter\` The Presenter defining how a model appears in the controller. ## Low-level API If you want to fully control the `QueryRuleCustomData` components and connect them manually, you can use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `QueryRuleCustomDataInteractor`. The logic applied to the custom model. * `ItemController`. The Controller interfacing with a concrete custom data view. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let queryRuleCustomDataInteractor: QueryRuleCustomDataInteractor = .init() let bannerViewController: BannerViewController = .init() queryRuleCustomDataInteractor.connectSearcher(searcher) queryRuleCustomDataInteractor.connectController(bannerViewController) ``` ## Customizing your view If you want to use a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the `ItemController` protocol. ### Protocol `func setItem(_ item: String?)`: Function called when new metadata are received. ### Example Suppose your custom JSON data contains a banner URL that can be decoded to the following structure: ```swift Swift icon=code theme={"system"} struct Banner: Decodable { let bannerURL: URL } ``` You could implement the `BannerViewController` presenting the banner image as follows: ```swift Swift icon=code theme={"system"} class BannerViewController: UIViewController, ItemController { let bannerView: UIImageView = .init() override func viewDidLoad() { super.viewDidLoad() // some setup layout code } func setItem(_ item: Banner?) { guard let bannerURL = item?.bannerURL else { bannerView.image = nil return } URLSession.shared.dataTask(with: bannerURL) { (data, _, _) in self.bannerView.image = data.flatMap(UIImage.init) }.resume() } } ``` ### SwiftUI To create a rule custom data view with SwiftUI start with implementation of the `ItemController` containing your custom model as a published property. ```swift Swift icon=code theme={"system"} class BannerObservableController: ObservableObject, ItemController { @Published var banner: Banner? func setItem(_ item: Banner?) { self.banner = item } } ``` Then connect this controller to the `QueryRuleCustomDataConnector` or `QueryRuleCustomDataInteractor` and build a SwiftUI view presenting the fetched custom model. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var bannerController: BannerObservableController var body: some View { if let bannerURL = bannerController.banner?.bannerURL { // present a banner using the fetched URL } } } ``` # Filter Numeric Range Source: https://www.algolia.com/doc/api-reference/widgets/range-slider/android Lets users refine results between two numeric values using a slider. ```kotlin Signature theme={"system"} FilterRangeConnector( filterState: FilterState, attribute: String, bounds: ClosedRange?, range: ClosedRange? ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the Filter Numeric Range example code on GitHub. ## About this widget Filter Numeric Range is a filtering view made to filter between two numeric values. The most common interface for this is a slider. The [Android View](https://github.com/algolia/instantsearch-android/tree/master/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/showcase/compose/filter/range) and [Compose UI](https://github.com/algolia/instantsearch-android/tree/master/examples/android/src/main/kotlin/com/algolia/instantsearch/examples/android/showcase/compose/filter/range) code samples (part of the [Android widget showcase repository on GitHub](https://github.com/algolia/instantsearch-android/tree/master/examples)) illustrate the use of sliders. ## Examples ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val price = "price" val groupID = FilterGroupID(price) val primaryBounds = 0..15 val initialRange = 0..15 val filters = filters { group(groupID) { range(price, initialRange) } } val filterState = FilterState(filters) val range = FilterRangeConnector( filterState = filterState, attribute = price, range = initialRange, bounds = primaryBounds ) val connection = ConnectionHandler(range, searcher.connectFilterState(filterState)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view: NumberRangeView = MyNumberRangeView(textLabel) // your `NumberRangeView` implementation connection += range.connectView(view) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Low-level API If you want to fully control the Filter Numeric Range components and connect them manually, use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/android) that handles your searches. * `FilterState`. The current state of the filters. * `FilterRangeViewModel`. The logic applied to the numeric ranges. * `NumberRangeView`. The view that renders the numeric range filter. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val attribute = "price" val viewModel = FilterRangeViewModel() val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view: NumberRangeView = MyNumberRangeView(textLabel) // your `NumberRangeView` implementation connection += searcher.connectFilterState(filterState) connection += viewModel.connectFilterState(filterState, attribute) connection += viewModel.connectView(view) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Compose UI InstantSearch provides the `NumberRangeState` as a state model, which is an implementation of the `NumberRangeView` interface. You need to connect `NumberRangeState` to the `FilterRangeConnector` or `FilterRangeViewModel` like any other `NumberRangeView` implementation. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val price = "price" val groupID = FilterGroupID(price) val primaryBounds = 0..15 val initialRange = 0..15 val filters = filters { group(groupID) { range(price, initialRange) } } val filterState = FilterState(filters) val sliderState = NumberRangeState() val range = FilterRangeConnector( filterState = filterState, attribute = price, range = initialRange, bounds = primaryBounds ) val connections = ConnectionHandler(range) init { connections += searcher.connectFilterState(filterState) connections += range.connectView(sliderState) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyRangeSlider(sliderState) // your own UI composable to display range and bounds as `Range` } searcher.searchAsync() } fun Range.toClosedFloatRange(): ClosedFloatingPointRange { return min.toFloat()..max.toFloat() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` ## Parameters **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that will hold your filters. **attribute** `String` *required* The attribute to filter. **bounds** `ClosedRange\?` The limits of the acceptable range within which values are coerced. **range** `ClosedRange\?` The range of values within the bounds. ## View **view** `NumberRangeView` *required* The view that renders the numeric range filter. ```kotlin Kotlin icon=code theme={"system"} val view = MyNumberRangeView(textLabel) range.connectView(view) // Example of `NumberRangeView` implementation class MyNumberRangeView(val view: TextView) : NumberRangeView { override var onRangeChanged: Callback>? = null private var bounds: Range? = null override fun setBounds(bounds: Range?) { this.bounds = bounds view.text = bounds?.let { "Bounds: ${it.min} to ${it.max}" } ?: "No bounds" } override fun setRange(range: Range?) = Unit } ``` # Filter Numeric Range Source: https://www.algolia.com/doc/api-reference/widgets/range-slider/ios Lets users refine results between two numeric values using a slider. ```swift Signature theme={"system"} NumberRangeConnector( filterState: FilterState, attribute: Attribute, bounds: ClosedRange?, range: ClosedRange, operator: RefinementOperator, groupName: String?, controller: NumberRangeController ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Filter Numeric Range example code on GitHub. ## About this widget `Filter Numeric Range` is a filtering component made to filter between two numeric values. The most common interface for this is a slider. ## Examples Instantiate a `NumberRangeConnector` and launch an initial search on its searcher. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourApiKey", indexName: "YourIndexName") let filterState: FilterState = .init() let numberRangeController: RangeSliderController = .init(rangeSlider: .init()) let numericRangeConnector: NumberRangeConnector = .init(searcher: searcher, filterState: filterState, attribute: "price", controller: numberRangeController) searcher.search() ``` ## Parameters **searcher** `HitsSearcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that holds your filters. **attribute** `Attribute` *required* The attribute to filter. **interactor** `NumberRangeInteractor\` *required* The logic applied to the numeric range. **bounds** `ClosedRange\?` The bounds limiting the max and the min value of the range. **range** `ClosedRange\` The initial range value. **operator** `RefinementOperator` Whether the filter is added to a conjunctive (`and`) or a disjunctive (`or`) group in the filter state. **groupName** `String` Filter group name in the [`FilterState`](/doc/api-reference/widgets/filter-state/ios) **controller** `NumberRangeController` The Controller interfacing with a concrete number range view. ## Low-level API If you want to fully control the `Filter Numeric Range` components and connect them manually, you can use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `FilterState`. The current state of the filters. * `NumberRangeInteractor`. The logic applied to the numeric ranges. * `NumberRangeController`. The controller that interfaces with a concrete numeric range view. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourApiKey", indexName: "YourIndexName") let filterState: FilterState = .init() let numericRangeInteractor: NumberRangeInteractor = .init() let numberRangeController: RangeSliderController = .init(rangeSlider: .init()) let priceAttribute: Attribute = "price" searcher.connectFilterState(filterState) numericRangeInteractor.connectFilterState(filterState, attribute: priceAttribute) numericRangeInteractor.connectSearcher(searcher, attribute: priceAttribute) numericRangeInteractor.connectController(numberRangeController) searcher.search() ``` ## Customizing your view If you want to use a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the `NumberRangeController` protocol. ### Protocol `func setBounds(_ bounds: ClosedRange)`: Function called when the minimum and maximum of numeric filter view have been defined. `var onRangeChanged: ((ClosedRange) -> Void)?` Closure to call when new bounds is set for the numeric range filter. ### Example Suppose you have a range slider control providing the following API: ```swift Swift icon=code theme={"system"} class RangeSlider: UIControl { var lowerValue: Double var upperValue: Double var minimumValue: Double var maximumValue: Double } ``` Then, you could implement the `NumberRangeController` including this control as follows: ```swift Swift icon=code theme={"system"} class RangeSliderController: NumberRangeController { let rangeSlider: RangeSlider var onRangeChanged: ((ClosedRange) -> Void)? func setBounds(_ bounds: ClosedRange) { rangeSlider.minimumValue = bounds.lowerBound rangeSlider.maximumValue = bounds.upperBound setItem(bounds) } func setItem(_ item: ClosedRange) { rangeSlider.lowerValue = item.lowerBound rangeSlider.upperValue = item.upperBound } init(rangeSlider: RangeSlider) { self.rangeSlider = rangeSlider rangeSlider.addTarget(self, action: #selector(onValueChanged(sender:)), for: [.touchUpInside, .touchUpOutside]) } @objc func onValueChanged(sender: RangeSlider) { onRangeChanged?(sender.lowerValue...sender.upperValue) } } ``` ### SwiftUI InstantSearch provides the `NumberRangeObservableController` data model, which is an implementation of the `NumberRangeController` protocol adapted for usage with SwiftUI. `NumberRangeObservableController` must be connected to the `NumberRangeConnector` or `NumberRangeConnector` like any other `NumberRangeController` implementation. The example of the number range view using two `Stepper` components provided by SwiftUI. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var numberRangeController: NumberRangeObservableController var body: some View { let range = numberRangeController.range VStack{ Stepper(onIncrement: { if let range = makeRange(range.lowerBound+1, range.upperBound) { numberRangeController.range = range } }, onDecrement: { if let range = makeRange(range.lowerBound-1, range.upperBound) { numberRangeController.range = range } }, label: { Text("Lower bound: \(range.lowerBound)") }) Stepper(onIncrement: { if let range = makeRange(range.lowerBound, range.upperBound+1) { numberRangeController.range = range } }, onDecrement: { if let range = makeRange(range.lowerBound, range.upperBound-1) { numberRangeController.range = range } }, label: { Text("Upper bound: \(numberRangeController.range.upperBound)") }) } } func makeRange(_ lowerBound: Int, _ upperBound: Int) -> ClosedRange? { if lowerBound < upperBound && numberRangeController.bounds.contains(lowerBound) && numberRangeController.bounds.contains(upperBound) { return lowerBound...upperBound } else { return nil } } } ``` If you prefer to create a custom SwiftUI view that presents the numeric range, you can directly use the `NumberRangeObservableController` as a data model. It provides `range` and `bounds` properties to streamline the design process of your custom SwiftUI view. # RefinementList Source: https://www.algolia.com/doc/api-reference/widgets/refinement-list/android Shows a list of facets for refining search results. ```kotlin Signature theme={"system"} FacetListConnector( searcher: HitsSearcher, filterState: FilterState, attribute: String, selectionMode: SelectionMode, items: List, persistentSelection: Boolean, groupID: FilterGroupID, ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the RefinementList example code on GitHub. ## About this widget `RefinementList` is a filtering view that displays [facets](/doc/guides/managing-results/refine-results/filtering) and lets users refine their search results by filtering on specific values. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. ## Examples ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val facetList = FacetListConnector( searcher = searcher, filterState = FilterState(), attribute = "facetName", selectionMode = SelectionMode.Multiple ) val connection = ConnectionHandler(facetList) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // You can use the default `FacetListAdapter`, or implement your own UI component that implements the `FacetListView` interface. val facetListAdapter = FacetListAdapter(FacetListViewHolderImpl.Factory) connection += facetList.connectView(facetListAdapter) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Low-level API If you want to fully control the `RefinementList` components and connect them manually, use the following components: * `FilterState`. The current state of the filters. * `FacetListViewModel`. The logic applied to the facets. * `FacetListView`. The view that renders facets. * `FacetListPresenter`. Optional. The presenter that controls the sorting and other settings of the facet list view. ```kotlin Kotlin icon=code theme={"system"} class MyActivity: AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val filterState = FilterState() val attribute = "facetName" val viewModel = FacetListViewModel() val presenter = FacetListPresenterImpl(listOf(FacetSortCriterion.CountDescending), limit = 5) val connection = ConnectionHandler() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // You can use the default FacetListAdapter, or write your own UI component that implements the FacetListView interface. val view: FacetListView = FacetListAdapter(MyFacetListViewHolder.Factory) connection += searcher.connectFilterState(filterState) connection += viewModel.connectFilterState(filterState, attribute, FilterOperator.Or) connection += viewModel.connectSearcher(searcher, attribute) connection += viewModel.connectView(view, presenter) searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connection.disconnect() searcher.cancel() } } ``` ### Compose UI InstantSearch provides the `FacetListState` as a state model, which is an implementation of the `FacetListView` interface. You need to connect `FacetListState` to the `FacetListConnector` or `FacetListViewModel` like any other `FacetListView` implementation. ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val facetListState = FacetListState() val facetList = FacetListConnector( searcher = searcher, filterState = FilterState(), attribute = "facetName", selectionMode = SelectionMode.Multiple ) val connections = ConnectionHandler(facetList) init { connections += facetList.connectView(facetListState) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // You can use the default `FacetListAdapter`, or implement your own UI component that implements the `FacetListView` interface. setContent { facetListState.items.forEach { selectableFacet -> FacetRow( // your own UI composable to display `SelectableItem` selectableFacet = selectableFacet, onClick = { facet -> facetListState.onSelection?.invoke(facet) } ) } } searcher.searchAsync() } override fun onDestroy() { super.onDestroy() connections.disconnect() searcher.cancel() } } ``` ## Parameters **searcher** `HitsSearcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/android#hitssearcher) that handles your searches. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/android) that holds your filters. **attribute** `String` *required* The attribute to filter. **selectionMode** `SelectionMode` Whether the list can have `Single` or `Multiple` selections. **items** `List\` If specified, the default facet values to display. **persistentSelection** `Boolean` (default: false) When true, the selection will be kept even if it doesn't match current results anymore. **groupID** `FilterGroupID` Filters operator, which can either be `FilterOperator.And` or `FilterOperator.Or`. Use [filters or facet filters](/doc/guides/managing-results/refine-results/filtering/in-depth/filters-and-facetfilters) for more complex result refinement. ## View **view** `FacetListView` *required* The view that renders the facets. **presenter** `FacetListPresenter` (default: null) The presenter that controls the sorting and other settings of the facet list view. ```kotlin Kotlin icon=code theme={"system"} val facetListAdapter = FacetListAdapter(FacetListViewHolderImpl.Factory) val facetListPresenter = FacetListPresenterImpl(listOf(FacetSortCriterion.CountDescending, FacetSortCriterion.AlphabeticalAscending)) facetListCategory.connectView(facetListAdapter, facetListPresenter) ``` ## Presenter **sortBy** `List\` (default: listOf(FacetSortCriterion.CountDescending)) How to sort facets. Must be one or more of the following values: * `FacetSortCriterion.IsRefined` * `FacetSortCriterion.CountAscending` * `FacetSortCriterion.CountDescending` * `FacetSortCriterion.AlphabeticalAscending` * `FacetSortCriterion.AlphabeticalDescending` ```kotlin Kotlin icon=code theme={"system"} // Tie-breaking algorithm where refined values are shown first. // If refined values are tied, show the facets with the largest counts. // If counts are tied, show facets in alphabetical order. FacetListPresenterImpl(listOf(IsRefined, CountDescending, AlphabeticalAscending)) ``` **limit** `Int` (default: 10) The number of facet values to retrieve. ```kotlin Kotlin icon=code theme={"system"} FacetListPresenterImpl(limit = 5) ``` # Facet List Source: https://www.algolia.com/doc/api-reference/widgets/refinement-list/flutter Shows a list of facets for refining search results. ```dart Signature theme={"system"} HitsSearcher.buildFacetList( _FilterState_ filterState, String attribute, // Optional parameters _FilterOperator_ operator, _SelectionMode_ selectionMode, _bool_ persistent, ) ``` > [card] [View live demo](https://algolia-flutter-showcase.netlify.app/#/?path=facet-list%2Fmultiple-facet-lists) > > Open the live Facet List example in your browser. ## About this widget `FacetList` is a filtering component that displays [facets](/doc/guides/managing-results/refine-results/filtering) and lets users refine their search results by filtering on specific values. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. ## Examples ### Create facet list Create `FacetList` with [`HitsSearcher`](/doc/api-reference/widgets/instantsearch/flutter) and [`FilterState`](/doc/api-reference/widgets/filter-state/flutter): ```dart Dart icon=code theme={"system"} // Create a FilterState final filterState = FilterState(); // Create a HitsSearcher final hitsSearcher = HitsSearcher( applicationID: 'YourApplicationID', apiKey: 'YourSearchOnlyApiKey', indexName: 'YourIndexName', )..connectFilterState(filterState); // Create a FacetList final facetList = hitsSearcher.buildFacetList( filterState: filterState, attribute: 'YourAttribute', ); ``` ### Display facets Get selectable facet changes by listening to [`facets`](#param-facets) submissions. Call [`toggle`](#param-toggle) to select or deselect a facet value: ```dart Dart icon=code theme={"system"} StreamBuilder>( stream: facetList.facets, // Listen to `facets` stream builder: (context, snapshot) { if (snapshot.hasData) { final facets = snapshot.data ?? []; return ListView.builder( itemCount: facets.length, itemBuilder: (BuildContext context, int index) { final selectable = facets[index]; final facet = selectable.item; return ListTile( title: Text('${facet.value} (${facet.count})'), trailing: selectable.isSelected ? const Icon(Icons.check) : null, onTap: () => facetList.toggle(facet.value), // Toggle a facet value on tap ); }, ); } else { return const LinearProgressIndicator(); } }, ); ``` ### Dispose Call [`dispose`](#param-dispose) to release underlying resources: ```dart Dart icon=code theme={"system"} facetList.dispose(); ``` ## Parameters **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/flutter) that holds your filters. **attribute** `String` *required* The attribute to filter. **operator** `FilterOperator` Filters operator, which can either be `FilterOperator.and` or `FilterOperator.or`. Use [filters or facet filters](/doc/guides/managing-results/refine-results/filtering/in-depth/filters-and-facetfilters) for more complex result refinement. **selectionMode** `SelectionMode` Whether the list can have `SelectionMode.single` or `SelectionMode.multiple` selections. **persistent** `bool` (default: false) When `true`, the selection will be kept even if it doesn't match current results anymore. ## Fields **facets** `Stream\\>` A stream of facets with their selection status. ```dart Dart icon=code theme={"system"} facetList.facets.listen((facets) { for (var facet in facets) { print("${facet.item} ${facet.isSelected ? 'x' : '-'}"); } }); ``` **eventTracker** `FilterEventTracker` `FilterEventTracker` instance responsible for sending Insights events related to user interactions with facets, such as clicking, viewing, and converting filters. With the `eventTracker`, you can track user's behavior, which can help you personalize their search experience. ```dart Dart icon=code theme={"system"} facetList.eventTracker.clickedFilters( indexName: 'INDEX_NAME', eventName: 'your_event_name', values: ['value1', 'value2'], ); ``` ## Methods **toggle** Select or deselect the provided facet value depending on the current selection state. ```dart Dart icon=code theme={"system"} facetList.toggle('blue'); ``` **snapshot** Gets the latest `List` value submitted by [`facets`](#param-facets): ```dart Dart icon=code theme={"system"} final selectableFacet = facetList.snapshot(); ``` **dispose** Releases all underlying resources. ```dart Dart icon=code theme={"system"} facetList.dispose(); ``` # Refinement List Source: https://www.algolia.com/doc/api-reference/widgets/refinement-list/ios Shows a list of facets for refining search results. ```swift Signature theme={"system"} FacetListConnector( searcher: SingleIndexSearcher, filterState: FilterState, attribute: Attribute, selectionMode: SelectionMode, facets: [Facet], operator: RefinementOperator, groupName: String, controller: FacetListController, presenter: FacetListPresenter ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-ios/tree/master/Examples/Showcase) > > Browse the Refinement List example code on GitHub. ## About this widget `RefinementList` is a filtering view that displays [facets](/doc/guides/managing-results/refine-results/filtering) and lets users refine the search results by filtering on specific values. ### Requirements The [`attribute`](#param-attribute) provided to the widget must be in attributes for faceting, either on the [dashboard](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard) or using the [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting) parameter with the API. ## Examples Instantiate a `FacetListConnector` and launch an initial search on its searcher. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState: FilterState = .init() let categoryTableViewController: UITableViewController = .init() let categoryListController: FacetListTableController = .init(tableView: categoryTableViewController.tableView) let facetListPresenter: FacetListPresenter = .init(sortBy: [.count(order: .descending)], limit: 5, showEmptyFacets: false) let categoryConnector: FacetListConnector = .init(searcher: searcher, filterState: filterState, attribute: "category", selectionMode: .multiple, facets: [.init(value: "initial facet", count: 10)], operator: .and, controller: categoryListController, presenter: facetListPresenter) searcher.search() ``` ## Parameters **searcher** `HitsSearcher` *required* The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. **filterState** `FilterState` *required* The [`FilterState`](/doc/api-reference/widgets/filter-state/ios) that holds your filters. **attribute** `Attribute` *required* The attribute to filter. **selectionMode** `SelectionMode` Whether a user can select `.single` or `.multiple` values. **facets** `[Facet]` If specified, the default facet values to apply. **operator** `RefinementOperator` *required* Whether to apply `and` or `or` behavior to the facets in the `FilterState`. For example, if you select `color` as the attribute and an `or` behavior,\ the filter sent to Algolia will be `color:red OR color:green`. Use [filters or facet filters](/doc/guides/managing-results/refine-results/filtering/in-depth/filters-and-facetfilters) for more complex result refinement. **groupName** `String` Filter group name. **controller** `FacetListController` Controller interfacing with a concrete facet list view. **presenter** `SelectableListPresentable` Presenter defining how a facet appears in the controller. ## Presenter **sortBy** `[FacetSortCriterion]` (default: \[.count(order: .descending)]) How to sort facets. Must be one or more of the following values: * `.count(order: .descending)` * `.count(order: .ascending)` * `.alphabetical(order: .descending)` * `.alphabetical(order: .ascending)` * `.isRefined` ```swift Swift icon=code theme={"system"} // Tie-breaking algorithm where refined values are shown first. // If refined values are tied, show the facets with the largest counts. // If counts are tied, show facets in alphabetical order. facetListPresenter = FacetListPresenter( sortBy: [.isRefined, .count(order: .descending), .alphabetical(order: .ascending)] ) ``` **limit** `Int` (default: 10) The number of facet values to retrieve. ```swift Swift icon=code theme={"system"} facetListPresenter = .init(limit: 5) ``` **showEmptyFacets** `Bool` (default: true) Whether to show facets with a facet count of 0. ```swift Swift icon=code theme={"system"} facetListPresenter = .init(showEmptyFacets: false) facetListPresenter = .init(showEmptyFacets: true) ``` ## Low-level API If you want to fully control the `RefinementList` components and connect them manually, you can use the following components: * `Searcher`. The [`Searcher`](/doc/api-reference/widgets/instantsearch/ios) that handles your searches. * `FilterState`. The current state of the filters. * `FacetListInteractor`. The logic applied to the facets. * `FacetListController`. The controller that interfaces with a concrete facet list view. * `FacetListPresenter`. Optional. The presenter that controls the sorting and other settings of the facet list view. ```swift Swift icon=code theme={"system"} let searcher = HitsSearcher(appID: "YourApplicationID", apiKey: "YourSearchOnlyAPIKey", indexName: "YourIndexName") let filterState: FilterState = .init() let facetListInteractor: FacetListInteractor = .init(facets: [.init(value: "initial facet", count: 10)], selectionMode: .multiple) let categoryTableViewController: UITableViewController = .init() let categoryListController: FacetListTableController = .init(tableView: categoryTableViewController.tableView) let facetListPresenter: FacetListPresenter = .init(sortBy: [.count(order: .descending)], limit: 5, showEmptyFacets: false) facetListInteractor.connectSearcher(searcher, with: "category") facetListInteractor.connectFilterState(filterState, with: "category", operator: .or) facetListInteractor.connectController(categoryListController) facetListInteractor.connectController(categoryListController, with: facetListPresenter) searcher.connectFilterState(filterState) searcher.search() ``` ## Customizing your view The controllers provided by default, like the `FacetListTableViewController` work well when you want to use native UIKit with their default behavior. If you want to use another component such as a `UICollectionView`, a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the `FacetListController` protocol. ### Protocol `var onClick: ((Facet) -> Void)?`: Closure to call when a new facet is clicked. `func setSelectableItems(selectableItems: [SelectableItem])` Function called when a new array of selectable facets is updated. This is the UI State of the refinement list. `func reload()` Function called when you want ti reload the list view. ### Implementation example ```swift Swift icon=code theme={"system"} open class FacetListTableViewController: NSObject, FacetListController { public var onClick: ((Facet) -> Void)? public var tableView: UITableView var selectableItems: [SelectableItem] = [] var cellID: String public init(tableView: UITableView, cellID: String = "FacetList") { self.tableView = tableView self.cellID = cellID super.init() tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID) tableView.dataSource = self tableView.delegate = self } public func setSelectableItems(selectableItems: [SelectableItem]) { self.selectableItems = selectableItems } public func reload() { tableView.reloadData() } } extension FacetListTableViewController: UITableViewDataSource { open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return selectableItems.count } open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) let selectableRefinement: SelectableItem = selectableItems[indexPath.row] let facetAttributedString = NSMutableAttributedString(string: selectableRefinement.item.value) let facetCountStringColor = [NSAttributedString.Key.foregroundColor: UIColor.gray, .font: UIFont.systemFont(ofSize: 14)] let facetCountString = NSAttributedString(string: " (\(selectableRefinement.item.count))", attributes: facetCountStringColor) facetAttributedString.append(facetCountString) cell.textLabel?.attributedText = facetAttributedString cell.accessoryType = selectableRefinement.isSelected ? .checkmark : .none return cell } } extension FacetListTableViewController: UITableViewDelegate { open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectableItem = selectableItems[indexPath.row] self.onClick?(selectableItem.item) } } ``` ### SwiftUI InstantSearch provides the `FacetList` SwiftUI view which you can embed in your views. It uses `FacetListObservableController` as a data model, which is an implementation of the `FacetListController` protocol adapted for usage with SwiftUI. `FacetListObservableController` must be connected to the `FacetListConnector` or `FacetListInteractor`, like any other `FacetListController` implementation. You can define the appearance of the view representing a single facet and its selection state or use the `FacetRow` view provided by InstantSearch. ```swift Swift icon=code theme={"system"} struct ContentView: View { @ObservedObject var facetListController: FacetListObservableController var body: some View { FacetList(facetListController) { facet, isSelected in // Use the implementation provided by InstantSearch // FacetRow(facet: facet, isSelected: isSelected) // Or declare a custom single facet view HStack { Text(facet.value) Spacer() if isSelected { Image(systemName: "checkmark") .foregroundColor(.accentColor) } } .contentShape(Rectangle()) .frame(idealHeight: 44) .padding(.horizontal, 5) } } } ``` If you prefer to create a custom SwiftUI view that presents the list of facets, you can directly use the `FacetListObservableController` as a data model. It provides `facets` and `selections` properties along with convenient `toggle` and `isSelected` functions to streamline the design process of your custom SwiftUI view. # RelevantSort Source: https://www.algolia.com/doc/api-reference/widgets/relevant-sort/android Lets users switch between search modes, exhaustive or relevant sorting. ```kotlin Signature theme={"system"} RelevantSortConnector( searcher: HitsSearcher, viewModel: RelevantSortViewModel ) ``` > [card] [Explore example code](https://github.com/algolia/instantsearch-android/tree/master/examples/android) > > Browse the RelevantSort example code on GitHub. ## About this widget Virtual indices let you use [relevant sort](/doc/guides/managing-results/refine-results/sorting/in-depth/relevant-sort), a sorting mechanism that favors relevancy over the attribute you're sorting on. The `relevantSort` widget displays the current search mode when searching in a virtual replica index, and allows users to switch between relevant and regular sorting, which is more exhaustive but can return less relevant results. ## Examples Instantiate a `RelevantSortConnector` and connect it to a `RelevantSortView`. ### Android view ```kotlin Kotlin icon=code theme={"system"} class MyActivity : AppCompatActivity() { val searcher = HitsSearcher( applicationID = "YourApplicationID", apiKey = "YourSearchOnlyAPIKey", indexName = "YourIndexName" ) val relevantSort = RelevantSortConnector(searcher) val connection = ConnectionHandler(relevantSort) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val banner = findViewById(R.id.banner) val button = findViewById `; }, }, }, ]; }, }); ``` ```jsx JSX theme={"system"} /** @jsx h */ import { h } from "preact"; import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources({ setQuery, refresh }) { return [ { // ... templates: { item({ item }) { return (
    Icon
    {item.query}
    ); }, }, }, ]; }, }); ``` ## Reference The state object changes as users interact with Autocomplete but you can also change the state with [setters](#setters). ### State object The state object is available in all lifecycle hooks. **activeItemId** `number | null` The active (highlighted) item. **query** `string` The search input value. As the query changes, the items retrieved from sources also change. **completion** `string | null` The completed version of the query. **isOpen** `boolean` (default: false) Whether the Autocomplete display panel is open or not. **collections** `AutocompleteCollection[]` The Autocomplete collection of items. **status** `"idle" | "loading" | "stalled" | "error"` (default: idle) The Autocomplete status. **context** `AutocompleteContext` The global context passed to lifecycle hooks. For more information, see [Context](/doc/ui-libraries/autocomplete/core-concepts/context). ### Setters **setActiveItemId** `(value: number | null) =\> void` Sets the active (highlighted) item. `null` clears the selection. **setQuery** `(value: string) =\> void` Sets the query. **setIsOpen** `(value: boolean) =\> void` Sets whether the Autocomplete display panel is open or not. **setStatus** `(value: "idle" | "loading" | "stalled" | "error") =\> void` Sets Autocomplete status. **setCollections** `(value: Collection[]) =\> void` Sets the Autocomplete collection of items. **setContext** `(value: AutocompleteContext) =\> void` Sets the context passed to lifecycle hooks. For more information, see [Context](/doc/ui-libraries/autocomplete/core-concepts/context). # Displaying items with templates Source: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/templates Templates let you customize the display of your autocomplete items. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). Once you've set up your [data sources](/doc/ui-libraries/autocomplete/core-concepts/sources), you need to define how they display. Autocomplete templates let you customize the appearance and layout of each item. ## Render each item Autocomplete uses a virtual DOM for rendering. This ensures optimal performance even with frequent updates, safeguards against cross-site scripting (XSS) attacks, and supports inline event handling. Templates can return any valid virtual DOM elements (VNodes). For example, templates can return strings, HTML strings, or Preact components. ### Strings ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item }) { return item.name; }, }, }, ]; }, }); ``` ### HTML strings Templates can return HTML strings using an `html` tagged template: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, html }) { return html`
    ${item.name}
    `; }, }, }, ]; }, }); ``` ### Preact components Templates can return [Preact](https://preactjs.com/) components using JSX: ```jsx JSX icon=code theme={"system"} /** @jsx h */ import { h } from "preact"; import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item }) { return
    {item.name}
    ; }, }, }, ]; }, }); ``` > [!NOTE] > > Autocomplete uses [Preact 10](https://preactjs.com/guide/v10/whats-new/) to render templates by default. > It isn't compatible with earlier versions. ## Return HTML Native HTML elements aren't valid VNodes, which means you can't return a template string that contains HTML, or an HTML element. But if you're not using a virtual DOM implementation in your app, **you can still return HTML with the provided `html` tagged template**. Every Autocomplete template provides an `html` function that you can use as a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). Using `html` lets you provide templates as an HTML string It works directly in the browser without a build step. > [!NOTE] > > The `html` function is only available starting from Autocomplete v1.6.0. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, html }) { return html`
    ${item.name}
    ${item.name}
    `; }, }, }, ]; }, }); ``` > [!NOTE] > > Internet Explorer 11 doesn't support tagged template literals. > If you need to support Internet Explorer 11, check out the [suggested solutions](#internet-explorer-11-support). ### Components and layouts You can use the provided [components](#components) in your templates by using their function form or interpolating them. ```js Function theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, components, html }) { return html`
    ${item.name}
    ${components.Highlight({ hit: item, attribute: "name" })}
    `; }, }, }, ]; }, }); ``` ```js Interpolation theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, html }) { return html`
    ${item.name}
    <${components.Highlight} hit="${item}" attribute="name" />
    `; }, }, }, ]; }, }); ``` The `html` function is also exposed in [`render`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-render) and [`renderNoResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-render-no-results) to customize the panel layout. ### Loops and conditional rendering You can use plain JavaScript to build dynamic templates. For example, use [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) to loop over an array and display a list. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, html }) { return html`
    ${item.name}
    ${item.name}
      ${item.categories.map( (category) => html`
    • ${category.label}
    • `, )}
    `; }, }, }, ]; }, }); ``` > [!NOTE] > > Passing a unique `key` attribute is helpful when mapping over items. > It helps the virtual DOM keep track of each element when they change, and update the UI efficiently. To conditionally render a part of your UI, use a [short-circuit](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND) or a [ternary](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) operator. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, html }) { return html`
    ${item.name}
    ${item.name}
    ${item.rating !== null ? `Rating: ${item.rating}` : "Unrated"}
    `; }, }, }, ]; }, }); ``` ### HTML in variables **Only the HTML provided as a string literal is evaluated.** HTML in variables (for example, stored in Algolia records (A record is a searchable object in an Algolia index. Each record consists of named attributes.)) isn't supported and is rendered as-is. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, html }) { const name = "

    Jimmie Barninger

    "; // This returns a `div` with text "

    Jimmie Barninger

    " return html`
    ${name}
    `; }, }, }, ]; }, }); ``` ### Internet Explorer 11 support **You can't use `html` as a tagged template in Internet Explorer 11.** Depending on your project setup, you can work around this problem to still use `html` while providing compatible code to your Internet Explorer 11 users. #### With Babel If you have a build step and are using [Babel](https://babeljs.io/) to compile your code for legacy browsers, you can transform all `html` expressions into regular function calls. The recommended setup is to use [`@babel/preset-env`](https://babeljs.io/docs/babel-preset-env), which provides this transformation along with other common ones based on a list of browsers to support. ```json .babelrc icon=braces theme={"system"} { "presets": [["@babel/preset-env"]] } ``` > [!NOTE] > > This transform is available as a [Babel plugin](https://babeljs.io/docs/babel-plugin-transform-template-literals). #### With a shim If you don't have a build step in your project, write a shim (Piece of code to correct the behavior of existing code). Tagged templates are regular functions with a specific signature, so you can wrap their calls with a friendlier API to avoid using tagged template notation. This function takes templates as static strings or an array of interspersed chunks, splits them, and passes them to the `html` function. ```js Shim theme={"system"} function htmlShim(template, html) { if (typeof template === "string") { return html([template]); } const parts = template.reduce( (acc, part, index) => { const isEven = index % 2 === 0; acc[Math.abs(Number(!isEven))].push(part); return acc; }, [[], []], ); return html(parts[0], ...parts[1]); } ``` ```js Usage theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { header({ html }) { return htmlShim("
    Products
    ", html); }, item({ item, components, html }) { const template = [ "

    ", components.Highlight({ hit: item, attribute: "name", }), "

    ", item.price, "

    ", ]; return htmlShim(template, html); }, }, }, ]; }, }); ``` > [!NOTE] > > The documented shim assumes every even array entry is a template string, and every odd entry is a dynamic value. > Change the shim if you need it to do something different. ### Further optimizations The provided `html` function works in the browser without any build step, with a negligible impact on memory and bundle size (\< 600 bytes). > [!WARNING] > > To ensure optimal performance, > use [`babel-plugin-htm`](https://github.com/developit/htm/tree/master/packages/babel-plugin-htm) to compile HTML tagged templates. > This plugin replaces `html` calls, so your code must expose a consistent parameter name to the compiler. > Avoid destructuring directly in the function signature. > Instead, either name the parameter and destructure it inside the function body, > or pass the object as-is. > The parameter name (for example, `params`) must be consistent across all templates. ```diff JavaScript icon=code theme={"system"} autocomplete({ // ... getSources() { return [ { // ... templates: { - item({ item, html }) { + item(params) { + const { item, html } = params; return html`
    ${item.name}
    `; }, }, }, ]; }, - render({ children, render, html }, root) { + render(params, root) { + const { children, render, html } = params; render(html`
    ${children}
    `, root); }, }); ``` Set up the Babel plugin. ```js .babelrc theme={"system"} { "plugins": [ [ "htm", { "pragma": "params.createElement" } ] ] } ``` > [!NOTE] > > If you're destructuring objects, ensure you also transpile it using [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env). ## Return virtual nodes directly You can return virtual nodes with JSX or `createElement`. ### With JSX The JSX syntax compiles down to VNodes. If you're using JSX in your project, **you can directly return JSX templates**. ```jsx JSX icon=code theme={"system"} /** @jsx h */ import { h } from "preact"; import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item }) { return
    {item.name}
    ; }, }, }, ]; }, }); ``` > [!NOTE] > > By default, Autocomplete uses [Preact 10](https://preactjs.com/guide/v10/whats-new/) to render templates. > If you're using another virtual DOM implementation, you can [pass a custom `renderer`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-renderer). ### With `createElement` Each template function provides access to `createElement` and `Fragment` to create VNodes. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, createElement, Fragment }) { return createElement(Fragment, {}, item.name); }, }, }, ]; }, }); ``` > [!NOTE] > > By default, `createElement` and `Fragment` default to Preact's `preact.createElement` (or `h`) and `preact.Fragment`. > If you're using another virtual DOM implementation, you can [replace them](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-renderer). ## Render a header and footer In addition to rendering items, you can customize what to display before and after the list of items using the [`header`](#param-templates-header) and [`footer`](#param-templates-footer) templates. ```js JavaScript icon=code theme={"system"} autocomplete({ // ... getSources({ query }) { return [ { // ... templates: { header() { return "Suggestions"; }, item({ item }) { return `Result: ${item.name}`; }, footer() { return "Footer"; }, }, }, ]; }, }); ``` ## Components Autocomplete exposes [`components`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-components) to all templates to share them everywhere in the instance. ```js JavaScript theme={"system"} autocomplete({ // ... getSources({ query }) { return [ { getItems() { return [ /* ... */ ]; }, templates: { item({ item, components }) { return components.Highlight({ hit: item, attribute: "name" }); }, }, }, ]; }, }); ``` ```jsx JSX theme={"system"} autocomplete({ // ... getSources({ query }) { return [ { getItems() { return [ /* ... */ ]; }, templates: { item({ item, components }) { return ; }, }, }, ]; }, }); ``` [Four components](https://github.com/algolia/autocomplete/tree/next/packages/autocomplete-js/src/components) are registered by default: * [`Highlight`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#highlight) to highlight matches in Algolia results. * [`Snippet`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#snippet) to snippet matches in Algolia results. * [`ReverseHighlight`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#reversehighlight) to reverse highlight matches in Algolia results. * [`ReverseSnippet`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#reversesnippet) to reverse highlight and snippet matches in Algolia results. ## Highlight and snippet Templates expose a set of built-in [`components`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-components) to handle highlighting and snippeting. Use these components to highlight or snippet results from the [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaResults) function. ```js JavaScript theme={"system"} autocomplete({ // ... getSources({ query }) { return [ { // ... templates: { item({ item, components, html }) { return html``; }, }, }, ]; }, }); ``` ```jsx JSX theme={"system"} autocomplete({ // ... getSources({ query }) { return [ { // ... templates: { item({ item, components }) { return ( ); }, }, }, ]; }, }); ``` ## Render a no-results state If there are no results, you might want to display a message to inform users or let them know what to do next. Do this with the [`noResults`](#param-templates-no-results) template. ```js JavaScript icon=code theme={"system"} autocomplete({ // ... getSources({ query }) { return [ { // ... templates: { // ... noResults() { return "No results."; }, }, }, ]; }, }); ``` ## Style items Since you're fully controlling the rendered HTML, style it how you want using any class-based CSS library. For example, if you're using [Bootstrap](https://getbootstrap.com/): ```js JavaScript theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import "https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, html }) { return html`
    ${item.name}
    `; }, }, }, ]; }, }); ``` ```jsx JSX theme={"system"} /** @jsx h */ import { h } from "preact"; import { autocomplete } from "@algolia/autocomplete-js"; import "https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item }) { return
    {item.name}
    ; }, }, }, ]; }, }); ``` Or [Tailwind CSS](https://tailwindcss.com/): ```js JavaScript theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import "https://unpkg.com/tailwindcss@latest/dist/tailwind.min.css"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item, html }) { return html`
    ${item.name}
    `; }, }, }, ]; }, }); ``` ```jsx JSX theme={"system"} /** @jsx h */ import { h } from "preact"; import { autocomplete } from "@algolia/autocomplete-js"; import "https://unpkg.com/tailwindcss@latest/dist/tailwind.min.css"; autocomplete({ // ... getSources() { return [ { // ... templates: { item({ item }) { return (
    {item.name}
    ); }, }, }, ]; }, }); ``` ## Reference **templates** `AutocompleteTemplates` A set of templates to customize how items are displayed. You can also provide templates for header and footer elements around the list of items. You must define `templates` within your [sources](/doc/ui-libraries/autocomplete/core-concepts/sources). **templates.header** `function` ```ts Type definition theme={"system"} (params: { state: AutocompleteState; source: AutocompleteSource; items: TItem[]; createElement: Pragma; Fragment: PragmaFrag; components: AutocompleteComponents; }) => VNode | string; ``` A function that returns the template for the header (before the list of items). **templates.item** `function` ```ts Type definition theme={"system"} (params: { item: TItem; state: AutocompleteState; createElement: Pragma; Fragment: PragmaFrag; components: AutocompleteComponents; }) => VNode | string; ``` A function that returns the template for each item of the source. **templates.footer** `function` ```ts Type definition theme={"system"} (params: { state: AutocompleteState; source: AutocompleteSource; items: TItem[]; createElement: Pragma; Fragment: PragmaFrag; components: AutocompleteComponents; }) => VNode | string; ``` A function that returns the template for the footer (after the list of items). **templates.noResults** `function` ```ts Type definition theme={"system"} (params: { state: AutocompleteState; source: AutocompleteSource; createElement: Pragma; Fragment: PragmaFrag; components: AutocompleteComponents; }) => VNode | string; ``` A function that returns the template for when there are no items. # Add recent searches Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/adding-recent-searches Learn how to include recent searches using the recent searches plugin. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). Most autocomplete menus include [suggested searches](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches). You can make the experience more user-friendly by adding recent searches a user has made. This UX lets users easily recall their past searches in case they want to search for them again. ## Before you begin This guide assumes that you know HTML, CSS, and JavaScript and that you have existing HTML with an input element where you want to implement an autocomplete drop-down menu. **Using this plugin doesn't require an Algolia application**. If you plan on including other sections in your autocomplete such as [suggested searches](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches), you do need an 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.) with the [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) feature enabled. ## Get started Begin with the following code. Create a file called `index.js` in your `src` directory: ```js JavaScript icon=code theme={"system"} // index.js import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ container: "#autocomplete", plugins: [], openOnFocus: true, }); ``` This code adds the autocomplete to a DOM element with `autocomplete` as an `id`. Change the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) to [match your markup](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options). Setting [`openOnFocus`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) to `true` ensures that the drop-down menu appears as soon as a user focuses the input. For now, `plugins` is an empty array, but you'll learn how to add the Recent Searches [plugin](/doc/ui-libraries/autocomplete/core-concepts/plugins) next. ## Add recent searches The Autocomplete library provides the [`createLocalStorageRecentSearchesPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/createLocalStorageRecentSearchesPlugin) function for creating a recent searches [plugin](/doc/ui-libraries/autocomplete/core-concepts/plugins) out-of-the-box. To use it, you need to provide a `key` and `limit`. The `key` can be any string and is required to differentiate search histories if you have multiple Autocomplete UI elements on one page. The `limit` defines the maximum number of recent searches to display. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "RECENT_SEARCH", limit: 5, }); autocomplete({ container: "#autocomplete", plugins: [recentSearchesPlugin], openOnFocus: true, }); ``` Since the `recentSearchesPlugin` reads from [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), you won't see any recent searches in your implementation until you've made some searches. To submit a search, be sure to press `Enter` on the query. Once you do, you'll see it appear as a recent search. ## Customize recent searches The [`createLocalStorageRecentSearchesPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/createLocalStorageRecentSearchesPlugin) function creates a functional plugin out of the box. You may want to customize some aspects of it, depending on your use case. To change [`templates`](/doc/ui-libraries/autocomplete/core-concepts/templates) or other [source](/doc/ui-libraries/autocomplete/core-concepts/sources) configuration options, you can use [`transformSource`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/createLocalStorageRecentSearchesPlugin#param-transform-source). The function includes the original `source`, which you return along with any options you want to add or overwrite. For example, if you use Autocomplete as an entry point to a search results page, you can turn recent searches into links by modifying [`getItemUrl`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-item-url) and the [`item`](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-item) template. ```js JavaScript theme={"system"} const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ // ... transformSource({ source }) { return { ...source, getItemUrl({ item }) { return `/search?q=${item.query}`; }, templates: { item(params) { const { item, html } = params; return html` ${source.templates.item(params).props.children} `; }, }, }; }, }); ``` ```jsx JSX theme={"system"} const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ // ... transformSource({ source }) { return { ...source, getItemUrl({ item }) { return `/search?q=${item.query}`; }, templates: { item(params) { const { item } = params; return ( {source.templates.item(params).props.children} ); }, }, }; }, }); ``` If you use Autocomplete on the same page as your main search and want to avoid reloading the full page when an item is selected, you can modify your search query state when a user selects an item with [`onSelect`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-on-select): ```js JavaScript icon=code theme={"system"} const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ // ... transformSource({ source }) { return { ...source, onSelect({ item }) { // Assuming the refine function updates the search page state. refine(item.query); }, }; }, }); ``` ## Use your own storage Sometimes, you may not want to use [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) for your recent search data. You may want to use [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) or handle recent searches on your backend. If so, you can use the [`createRecentSearchesPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/createRecentSearchesPlugin) to implement your own storage: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; const recentSearchesPlugin = createRecentSearchesPlugin({ // Implement your own storage storage: { getAll() {}, onAdd() {}, onRemove() {}, }, }); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [recentSearchesPlugin], }); ``` ## Next steps This tutorial focuses on creating and adding recent searches to an autocomplete menu. Most autocomplete menus include recent searches in addition to suggested searches and possibly other items. * [Search suggestions](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) * [Multiple results types](/doc/ui-libraries/autocomplete/guides/including-multiple-result-types) * [Static sources](/doc/ui-libraries/autocomplete/core-concepts/sources#static-sources) # Build a Query Suggestions UI with Autocomplete Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/adding-suggested-searches Learn how to include suggested searches using the Query Suggestions plugin. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). The most common Autocomplete UX is one that displays a list of suggested searches that your users can select from as they type. Algolia provides a Query Suggestions feature that generates suggested search terms. It [bases these terms on what your users search for and the results in your data](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js#how-query-suggestions-work). This tutorial explains how to integrate Algolia Query Suggestions into an Autocomplete menu using the [`autocomplete-plugin-query-suggestions`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) package. ## Before you begin This guide assumes that you know HTML, CSS, and JavaScript. It also assumes you have existing HTML with an input element where you want to insert the autocomplete drop-down menu. It also assumes that you have an 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.) with a populated [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) 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.). > [!TIP] > > For learning purposes, use the demo application credentials and index provided in this tutorial. ## Get started Create an `index.js` file in your `src` directory, and add the following code: ```js JavaScript icon=code theme={"system"} // index.js import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ container: "#autocomplete", plugins: [], openOnFocus: true, }); ``` This code adds Autocomplete to a DOM element with `autocomplete` as `id`. Update the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) to [match your markup](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options) parameter to match your HTML. Setting [`openOnFocus`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) to `true` ensures that the drop-down menu appears as soon as a user focuses the input. For now, `plugins` is an empty array, but you'll learn how to add the Query Suggestions [plugin](/doc/ui-libraries/autocomplete/core-concepts/plugins) next. ## Add Query Suggestions The [`autocomplete-plugin-query-suggestions`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) package provides the [`createQuerySuggestionsPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin) function for creating a Query Suggestions [plugin](/doc/ui-libraries/autocomplete/core-concepts/plugins) out-of-the-box. It requires an [Algolia search client](/doc/libraries/sdk/install) initialized with an [Algolia application ID and API key](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api#required-credentials) and an `indexName`. The `indexName` is the name of your [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) index. ```js JavaScript icon=code theme={"system"} // index.js import { autocomplete } from "@algolia/autocomplete-js"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", }); autocomplete({ container: "#autocomplete", plugins: [querySuggestionsPlugin], openOnFocus: true, }); ``` You can optionally pass a `getSearchParams` function to apply [Algolia query parameters](/doc/api-reference/api-parameters) to the suggestions returned from the plugin. For example, you may choose to display 10 Query Suggestions if users haven't typed anything yet, but only five if they have: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams({ state }) { return { hitsPerPage: state.query ? 5 : 10 }; }, }); autocomplete({ container: "#autocomplete", plugins: [querySuggestionsPlugin], openOnFocus: true, }); ``` This option can be useful if you're [displaying other sources](/doc/ui-libraries/autocomplete/guides/including-multiple-result-types) along with Query Suggestions. Use it to always show the same total number of items, or to align your query parameters. ## Add categories Displaying relevant categories, along with suggestions, is helpful since it lets users limit their search scope. When a user selects a suggestion with a category, you can use both the suggestion and the associated category to show only the most relevant results. This lets users skip the additional task of selecting a category once they're on the results page. By including categories in your suggestions, you enable users to land on the most relevant set of results with as little friction as possible. With [some configuration](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js), the Algolia [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) feature adds relevant categories to suggestion records (A record is a searchable object in an Algolia index. Each record consists of named attributes.). Refer to the [index schema](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js#query-suggestions-index-schema) to see how the feature stores information about each suggestion record. To display categories with the suggestions, you need to define the attribute to retrieve category information from, using the `categoryAttribute` option when instantiating the plugin. In this example, the category data is stored in the nested attribute `instant_search.facets.exact_matches.categories`. With this structure, you need to provide the path `["instant_search", "facets", "exact_matches", "categories"]` as the `categoryAttribute`. You can also set the number of items that show categories with `itemsWithCategories`. Use `categoriesPerItem` to set the number of categories to show per item. Both default to `1`. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams({ state }) { return { hitsPerPage: state.query ? 5 : 10 }; }, categoryAttribute: [ "instant_search", "facets", "exact_matches", "categories", ], itemsWithCategories: 2, categoriesPerItem: 2, }); autocomplete({ container: "#autocomplete", plugins: [querySuggestionsPlugin], openOnFocus: true, }); ``` ### Apply categories Now that the autocomplete displays categories on suggestions, you need to apply them to the search results when a user selects a suggestion. #### On the same page If the search results are on the same page as the autocomplete, you can use the [`onSelect`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-on-select) hook to refine the search results. You can access this hook within [`transformSource`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin#param-transform-source). The function includes the original `source`. Return it along with any options you want to add or overwrite. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... transformSource({ source }) { return { ...source, onSelect({ item }) { // Assuming the `setSearchState` function updates the search page state. setSearchState({ query: item.query, category: item.__autocomplete_qsCategory, }); }, }; }, }); autocomplete({ container: "#autocomplete", plugins: [querySuggestionsPlugin], openOnFocus: true, }); ``` #### On a linked search results page If your autocomplete links to a separate search results page, you can apply selected categories there. To do this, change the results page URL with query parameters. This example writes a `createUrl` function within [`transformSource`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin#param-transform-source) to do so. It uses an item's `__autocomplete_qsCategory` property to construct the appropriate query parameters. It then uses the function within the [`item` template](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-item) and in [`getItemUrl`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-item-url). This way, whether users click a link or use keyboard navigation, they land on a search results page with categories applied. ```js JavaScript theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... transformSource({ source }) { function createUrl(item) { const urlParams = new URLSearchParams(); urlParams.set("q", item.query); if (item.__autocomplete_qsCategory) { urlParams.set("category", item.__autocomplete_qsCategory); } return `/search?${urlParams.toString()}`; } return { ...source, getItemUrl({ item }) { return createUrl(item); }, templates: { item(params) { const { item, html } = params; return html` ${source.templates.item(params).props.children} `; }, }, }; }, }); autocomplete({ container: "#autocomplete", plugins: [querySuggestionsPlugin], openOnFocus: true, }); ``` ```jsx JSX theme={"system"} /** @jsx h */ import { h } from "preact"; import { autocomplete } from "@algolia/autocomplete-js"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... transformSource({ source }) { function createUrl(item) { const urlParams = new URLSearchParams(); urlParams.set("q", item.query); if (item.__autocomplete_qsCategory) { urlParams.set("category", item.__autocomplete_qsCategory); } return `/search?${urlParams.toString()}`; } return { ...source, getItemUrl({ item }) { return createUrl(item); }, templates: { item(params) { const { item } = params; return ( {source.templates.item(params).props.children} ); }, }, }; }, }); autocomplete({ container: "#autocomplete", plugins: [querySuggestionsPlugin], openOnFocus: true, }); ``` ## Customize Query Suggestions The [`createQuerySuggestionsPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin) creates a functional plugin out of the box. You may want to customize some aspects of it, depending on your use case. To change [`templates`](/doc/ui-libraries/autocomplete/core-concepts/templates) or other [source](/doc/ui-libraries/autocomplete/core-concepts/sources) configuration options, you can use [`transformSource`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin#param-transform-source). The function includes the original `source`. Return it along with any options you want to add or overwrite. For example, if you use Autocomplete as an entry point to a search results page, you can turn Query Suggestions into links by modifying [`getItemUrl`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-item-url) and the [`item`](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-item) template. ```js JavaScript theme={"system"} const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... transformSource({ source }) { return { ...source, getItemUrl({ item }) { return `/search?q=${item.query}`; }, templates: { item(params) { const { item, html } = params; return html` ${source.templates.item(params).props.children} `; }, }, }; }, }); ``` ```jsx JSX theme={"system"} const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... transformSource({ source }) { return { ...source, getItemUrl({ item }) { return `/search?q=${item.query}`; }, templates: { item(params) { const { item } = params; return ( {source.templates.item(params).props.children} ); }, }, }; }, }); ``` If you use Autocomplete on the same page as your main search, you may want to avoid a full page reload when a user selects an item. To do this, update your search query state on selection with [`onSelect`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-on-select): ```js JavaScript icon=code theme={"system"} const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... transformSource({ source }) { return { ...source, onSelect({ item }) { // Assuming the `setSearchState` function updates the search page state. setSearchState({ query: item.query }); }, }; }, }); ``` ## Next steps This tutorial focuses on adding Query Suggestions to an autocomplete menu. Many autocomplete menus also include recent searches and possibly other items. * [Recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches) * [Multiple results types](/doc/ui-libraries/autocomplete/guides/including-multiple-result-types) * [Static sources](/doc/ui-libraries/autocomplete/core-concepts/sources#static-sources) # Create multi-column layouts Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/creating-a-multi-column-layout Learn how to create a multi-column layout with Autocomplete. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). Improving the search experience often means adding several sources of data, such as in a federated search. Doing this in an organized and efficient way can be challenging. To make the most of a larger display on devices such as desktops and tablets, display data in a multi-column layout. This lets users quickly browse search results and reduces scrolling. ![An experience using a two-column layout with Autocomplete](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/multi-column-layout.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=312cad785a7ae90072c8a1b605e179ab) ## Before you begin This tutorial assumes the following requirements: * An installation of the [`autocomplete-js`](/doc/ui-libraries/autocomplete/introduction/getting-started) package. * A populated Query Suggestions 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.). If you don't have one, [create it](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js): you can use the example application credentials and index provided in this tutorial. * Existing HTML with an input element where you want to insert the autocomplete drop-down menu. * Frontend development experience with HTML, CSS, and JavaScript. ## Starter code This tutorial builds an Autocomplete app composed of two columns: * A left column taking one third of the width which displays [recent searches and Query Suggestions](#add-recent-searches-and-query-suggestions) * A right column taking up the rest of the space which [displays products](#display-products). Create a file called `app.js` in your `src` directory and add this code to it: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; autocomplete({ container: "#autocomplete", openOnFocus: true, }); ``` This starter code assumes you want to insert the autocomplete into a DOM element with `autocomplete` as an [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id). Change the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) to [match your markup](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options). Setting [`openOnFocus`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) to `true` ensures that the drop-down menu appears as soon as a user focuses the input. ## Two-column layout With the [`render`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-render) function of [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js), you can customize the panel rendering to create two or more columns. ```js JavaScript theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [], render({ render, html }, root) { render( html`
    `, root, ); }, }); ``` ```jsx JSX theme={"system"} /** @jsx h */ import { autocomplete } from "@algolia/autocomplete-js"; import { h, render } from "preact"; import "@algolia/autocomplete-theme-classic"; autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [], render(params, root) { render(
    , root, ); }, }); ``` ## Add recent searches and Query Suggestions Add the [recent searches](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches) (with a limit of two recent searches) and [Query Suggestions](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) plugin (limiting the number of suggestions to seven) to the left column. This gives a total of nine items in the right column. Then, retrieve the recent searches and query suggestions plugins sources in the [`render`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-render) function using `elements` and add it to the left column. ```js JavaScript theme={"system"} // ... import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import "@algolia/autocomplete-theme-classic"; // You can use the example application credentials in case you don't have your own yet. // const appId = 'latency'; // const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "multi-column-layout-example", limit: 2, }); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "autocomplete_demo_products_query_suggestions", getSearchParams() { return { ...recentSearchesPlugin.data.getAlgoliaSearchParams(), hitsPerPage: 7, }; }, }); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [recentSearchesPlugin, querySuggestionsPlugin], render({ elements, render, html }, root) { const { recentSearchesPlugin, querySuggestionsPlugin } = elements; render( html`
    ${recentSearchesPlugin} ${querySuggestionsPlugin}
    `, root, ); }, }); ``` ```jsx JSX theme={"system"} // ... import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch("YourApplicationID", "YourSearchOnlyAPIKey"); const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "multi-column-layout-example", limit: 2, }); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "autocomplete_demo_products_query_suggestions", getSearchParams() { return { ...recentSearchesPlugin.data.getAlgoliaSearchParams(), hitsPerPage: 7, }; }, }); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [recentSearchesPlugin, querySuggestionsPlugin], render({ elements }, root) { const { recentSearchesPlugin, querySuggestionsPlugin } = elements; render(
    {recentSearchesPlugin} {querySuggestionsPlugin}
    , root, ); }, }); ``` ## Create a "products" source Add a source to retrieve and display the products in the right column. Create a [dynamic source](/doc/ui-libraries/autocomplete/core-concepts/sources#dynamic-sources) using the [`getSources`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-get-sources) function and retrieve the products from your Algolia index with the [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaResults) function. ```js JavaScript theme={"system"} // ... autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [recentSearchesPlugin, querySuggestionsPlugin], getSources({ query }) { return [ // ... { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "autocomplete_demo_products", params: { query, hitsPerPage: 4, }, }, ], }); }, // ... }, ]; }, render({ elements, render, html }, root) { const { recentSearchesPlugin, querySuggestionsPlugin } = elements; render( html`
    ${recentSearchesPlugin} ${querySuggestionsPlugin}
    `, root, ); }, }); ``` ```jsx JSX theme={"system"} // ... autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [recentSearchesPlugin, querySuggestionsPlugin], getSources({ query }) { return [ // ... { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "autocomplete_demo_products", params: { query, hitsPerPage: 4, }, }, ], }); }, // ... }, ]; }, render({ elements }, root) { const { recentSearchesPlugin, querySuggestionsPlugin } = elements; render(
    {recentSearchesPlugin} {querySuggestionsPlugin}
    , root, ); }, }); ``` ## Display products Now that you've set up your source, you can display products with [templates](/doc/ui-libraries/autocomplete/core-concepts/templates). ### Create a product item component Start by creating a file called `ProductItem.js` in your `src` directory, then copy and paste the following code (a component used to render each item of your source): ```js JavaScript theme={"system"} function cx(...classNames) { return classNames.filter(Boolean).join(" "); } function formatPrice(value, currency) { return value.toLocaleString("en-US", { style: "currency", currency }); } export function ProductItem({ html, hit, components }) { return html`
    ${hit.name} { const imgEl = document.querySelector( `.${hit.objectID} .aa-ItemPicture`, ); imgEl.classList.add("aa-ItemPicture--loaded"); }} />
    ${hit.brand && html`
    ${components.Highlight({ hit, attribute: "brand" })}
    `}
    ${components.Highlight({ hit, attribute: "name" })}
    ${formatPrice(hit.price.value, hit.price.currency)}
    ${hit.price.on_sales && html`
    ${formatPrice(hit.price.discounted_value, hit.price.currency)}
    `}
      ${Array(5) .fill(null) .map( (_, index) => html`
    • = hit.reviews.rating && "aa-StarIcon--muted", )}" >
    • `, )}
    (${hit.reviews.count})
    `; } ``` ```jsx JSX theme={"system"} // ... import { useState } from "preact/hooks"; function cx(...classNames) { return classNames.filter(Boolean).join(" "); } function formatPrice(value, currency) { return value.toLocaleString("en-US", { style: "currency", currency }); } export function ProductItem({ hit, components }) { const [loaded, setLoaded] = useState(false); return (
    {hit.name} setLoaded(true)} />
    {hit.brand && (
    )}
    {formatPrice(hit.price.value, hit.price.currency)}
    {hit.price.on_sales && (
    {formatPrice(hit.price.discounted_value, hit.price.currency)}
    )}
      {Array(5) .fill(null) .map((_, index) => (
    • = hit.reviews.rating && "aa-StarIcon--muted", )} >
    • ))}
    ({hit.reviews.count})
    ); } ``` ### Render each source item To render each source item, use the [`templates`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-templates) option in your source with the `item` function. ```js JavaScript theme={"system"} // ... autocomplete({ // ... getSources() { return [ // ... { sourceId: 'products', getItems({ /_..._/ }) { // ... }, templates: { item({ html, item, components }) { return ProductItem({ html, hit: item, components }); }, }, // ... }, ]; }, render({ elements, render, html }, root) { const { recentSearchesPlugin, querySuggestionsPlugin, products } = elements; render( html`
    ${recentSearchesPlugin} ${querySuggestionsPlugin}
    ${products}
    `, root ); }, }); ``` ```jsx JSX theme={"system"} // ... autocomplete({ // ... getSources() { return [ // ... { sourceId: "products", getItems( { /* ... */ }, ) { // ... }, templates: { item({ html, item, components }) { return ProductItem({ html, hit: item, components }); }, }, // ... }, ]; }, render({ elements }, root) { const { recentSearchesPlugin, querySuggestionsPlugin, products } = elements; render(
    {recentSearchesPlugin} {querySuggestionsPlugin}
    {products}
    , root, ); }, }); ``` ## Add styles Copy and paste the following CSS snippet in your `src` directory and call it `style.css`. ```css CSS icon=paintbrush theme={"system"} * { box-sizing: border-box; } body { background-color: rgb(244, 244, 249); color: rgb(65, 65, 65); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; padding: 1rem; } .container { margin: 0 auto; max-width: 1024px; width: 100%; } /* Panel section */ .aa-PanelSections { column-gap: var(--aa-spacing); display: flex; } .aa-PanelSection { display: flex; flex-direction: column; } .aa-PanelSection--left { width: 30%; } .aa-PanelSection--right { width: 70%; } /* Item */ .aa-ItemPicture { width: 100%; height: 100%; border-radius: 3px; overflow: hidden; background: rgba(var(--aa-muted-color-rgb), 0.2); } .aa-ItemPicture img { object-fit: cover; width: 100%; height: auto; opacity: 0; transition: opacity 0.2s ease-out; } .aa-ItemPicture--loaded img { opacity: 1; } /* Products */ /* --- Common */ .aa-Source[data-autocomplete-source-id="products"] .aa-List { display: grid; grid-template-columns: repeat(4, 1fr); } .aa-Source[data-autocomplete-source-id="products"] .aa-Item { padding: var(--aa-spacing-half); } .aa-Source[data-autocomplete-source-id="products"] .aa-ItemLink { justify-content: stretch; height: 100%; } /* --- Content */ .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContent { display: flex; flex-direction: column; height: 100%; } .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContent mark { color: rgb(var(--aa-primary-color-rgb)); } .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentBody { width: 100%; display: flex; flex-direction: column; justify-content: space-between; gap: var(--aa-spacing-half); } /* --- Brand */ .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentBrand { font-size: 0.7em; text-transform: uppercase; color: rgb(var(--aa-muted-color-rgb)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentBrand mark { font-weight: normal; } /* --- Title */ .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentTitleWrapper { height: calc(var(--aa-spacing) * 2.5); } .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentTitle { font-size: 0.9em; margin: 0; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; white-space: normal; } /* --- Price */ .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentPrice { display: flex; column-gap: var(--aa-spacing-half); } .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentPriceCurrent { font-weight: bold; } .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentPriceDiscounted { font-size: 0.9em; text-decoration: line-through; color: rgb(var(--aa-muted-color-rgb)); } /* --- Rating */ .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentRating ul { display: flex; list-style: none; padding: 0; } .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentRating { display: flex; align-items: center; column-gap: calc(var(--aa-spacing-half) / 2); margin-top: var(--aa-spacing-half); } .aa-Source[data-autocomplete-source-id="products"] .aa-StarIcon { width: 1.3em; height: 1.3em; color: #fdbc72; } .aa-Source[data-autocomplete-source-id="products"] .aa-StarIcon--muted { color: #d6d6e6; } .aa-Source[data-autocomplete-source-id="products"] .aa-ItemContentRatingReviews { font-size: 0.7em; color: #908eae; } /* Media queries */ @media screen and (max-width: 680px) { /* Panel section */ .aa-PanelSections { flex-direction: column; row-gap: var(--aa-spacing); } .aa-PanelSection--left, .aa-PanelSection--right { width: 100%; } /* Products */ .aa-Source[data-autocomplete-source-id="products"] .aa-List { display: flex; flex-wrap: wrap; gap: var(--aa-spacing-half); } .aa-Source[data-autocomplete-source-id="products"] .aa-Item { width: calc(50% - var(--aa-spacing-half) / 2); } } ``` Make sure to include it in your project's `app.js` file. ```js JavaScript icon=code theme={"system"} // ... import "./style.css"; autocomplete({ // ... }); ``` ## Next steps This tutorial focuses on building a multi-column layout but you can improve it by: * Adding more [sources](/doc/ui-libraries/autocomplete/core-concepts/sources) * [Reshaping](/doc/ui-libraries/autocomplete/guides/reshaping-sources) and combine your sources * Adding [no-result states](/doc/ui-libraries/autocomplete/guides/implementing-multiple-search-states) For an example, this [two-column layout](https://github.com/algolia/autocomplete/tree/next/examples/two-column-layout) implements these features. # Create a custom renderer in React Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/creating-a-renderer Learn how to build a full Autocomplete UI using React. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). The [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) package includes everything you need to render a search experience and bind it to [your framework](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-renderer). Use the [`autocomplete-core`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core) package to build a custom UI that differs from the `autocomplete-js` output. > [!NOTE] > > You might not need a custom renderer. > Building a custom renderer is an advanced pattern that uses the [`autocomplete-core`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core) package to fully control the rendered experience. > Don't use it unless you've reached limitations with [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) and its templating capabilities. ## Import the package Begin by importing [`createAutocomplete`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core/createAutocomplete) from the [core package](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core) and [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-preset-algolia/getAlgoliaResults) from the [Algolia preset](/doc/ui-libraries/autocomplete/api-reference/autocomplete-preset-algolia/getAlgoliaResults). The preset function [`autocomplete-preset-algolia`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-preset-algolia) retrieves items from an Algolia 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.). ```js JavaScript icon=code theme={"system"} import { liteClient as algoliasearch } from "algoliasearch/lite"; import { createAutocomplete } from "@algolia/autocomplete-core"; import { getAlgoliaResults } from "@algolia/autocomplete-preset-algolia"; // ... ``` ## Initialize Autocomplete The Autocomplete entry point is the [`createAutocomplete`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core/createAutocomplete) function, which returns the methods to create the search experience. ```js JavaScript icon=code theme={"system"} const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); function Autocomplete() { // (1) Create a React state. const [autocompleteState, setAutocompleteState] = React.useState({}); const autocomplete = React.useMemo( () => createAutocomplete({ onStateChange({ state }) { // (2) Synchronize the Autocomplete state with the React state. setAutocompleteState(state); }, getSources() { return [ // (3) Use an Algolia index source. { sourceId: "products", getItemInputValue({ item }) { return item.query; }, getItems({ query }) { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, hitsPerPage: 4, highlightPreTag: "", highlightPostTag: "", }, }, ], }); }, getItemUrl({ item }) { return item.url; }, }, ]; }, }), [], ); // ... } ``` * Use a React state for the Autocomplete component to re-render when the [Autocomplete state](/doc/ui-libraries/autocomplete/core-concepts/state) changes. * Listen to all Autocomplete state changes to synchronize them with the React state. * This example uses an Algolia index as a [source](/doc/ui-libraries/autocomplete/core-concepts/sources). This setup gives you access to all the methods you may want to use in the `autocomplete` variable in your React components. ## Use prop getters Prop getters are methods that return props to use in your components. These props contain accessibility features, event handlers, and so on. They help create a complete experience without exposing their underlying technical elements. The following snippet shows how to use the `getRootProps()`, `getInputProps()`, `getPanelProps()`, `getListProps()`, and `getItemProps()` [prop getters](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core/createAutocomplete#returns) in the appropriate elements. ```jsx JSX icon=code theme={"system"} function Autocomplete() { // ... return (
    {autocompleteState.isOpen && autocompleteState.collections.map((collection, index) => { const { source, items } = collection; return (
    {items.length > 0 && (
      {items.map((item) => (
    • {item.name}
    • ))}
    )}
    ); })}
    ); } ``` The preceding code demonstrates that you don't need to worry about keyboard events, or tracking which item is active. Autocomplete handles this under the hood with its prop getters. At this point, you have a usable search box: ![Screenshot of an autocomplete input showing the text 'am' with suggestions 'amazon', 'amazon fire', 'amazon echo', and 'amazing'.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/autocomplete-input.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=f12c7e56bc7899f4fcbb5c234e8a916f) ## Improve input accessibility To improve the `input` control, wrap it in a `form` and apply the form props given by Autocomplete: ```jsx JSX icon=code theme={"system"} function Autocomplete() { // ... const inputRef = React.useRef(null); return (
    {/* ... */}
    ); } ``` The `getFormProps` prop getter handles submit and reset events. It also respectively blurs and focuses the input when these events happen. Pass the `inputElement` when calling `getFormProps` to use these features. Add a label that represents the input and use the `getLabelProps` prop getter: ```jsx JSX icon=code theme={"system"} function Autocomplete() { // ... const inputRef = React.useRef(null); return (
    {/* ... */}
    ); } ``` Another good practice for search inputs is to display a reset button. Display it if there's a query. ```jsx JSX icon=code theme={"system"} function Autocomplete() { // ... return (
    {/* ... */}
    ); } ``` ## Check network status Displaying UI hints when the network is unstable helps users understand why results aren't updating in real-time. Examine the [`status`](/doc/ui-libraries/autocomplete/core-concepts/state#param-status) to determine this: ```jsx JSX icon=code theme={"system"} function Autocomplete() { // ... return (
    {/* ... */} {autocompleteState.isOpen && (
    {/* ... */}
    )} {/* ... */}
    ); } ``` You could, for example, create a `.aa-Panel--stalled` CSS class that lowers items' opacity to show that search is temporarily stuck or unavailable. ![Screenshot of a search input with a partial query typed, showing suggestions like television, telephone, telephoto, and telescopes.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/search-unavailable.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=f1d480e701e04653b3d9a28065eae8cd) For more information, see [Controlling behavior with state](/doc/ui-libraries/autocomplete/core-concepts/state). ## Mirror a native mobile experience Native platforms offer better primitives for mobile search experiences. Autocomplete helps provide these capabilities so that the web mobile experience is closer to the native mobile experience. A common feature in mobile native experiences is to close the virtual keyboard when users start scrolling. By automatically hiding the keyboard, you make it simpler for users to discover search results. The panel closes when users tap outside it. The `getEnvironmentProps` method returns event handlers that let you create this experience: ```jsx JSX icon=code theme={"system"} function Autocomplete() { // ... const inputRef = React.useRef(null); const formRef = React.useRef(null); const panelRef = React.useRef(null); const { getEnvironmentProps } = autocomplete; React.useEffect(() => { if (!(formRef.current && panelRef.current && inputRef.current)) { return; } const { onTouchStart, onTouchMove, onMouseDown } = getEnvironmentProps({ formElement: formRef.current, panelElement: panelRef.current, inputElement: inputRef.current, }); window.addEventListener("touchstart", onTouchStart); window.addEventListener("touchmove", onTouchMove); window.addEventListener("mousedown", onMouseDown); return () => { window.removeEventListener("touchstart", onTouchStart); window.removeEventListener("touchmove", onTouchMove); window.removeEventListener("mousedown", onMouseDown); }; }, [getEnvironmentProps, autocompleteState.isOpen]); return (
    {/* ... */}
    {autocompleteState.isOpen && (
    {/* ... */}
    )}
    ); } ``` This makes the app feel more familiar to mobile users. ## Help and discussion You now have enough knowledge to build your own experience based on Autocomplete. If you find that some topics weren't covered, feel free to [open an issue](https://github.com/algolia/autocomplete/issues/new). # Debounce sources Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/debouncing-sources Learn how to debounce your Autocomplete sources when as-you-type search isn't an option. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). By default, Autocomplete offers as-you-type search and processes [sources](/doc/ui-libraries/autocomplete/core-concepts/sources) for each keystroke: a real-time experience with instant feedback. However, there are situations when **as-you-type might not be desirable or even possible**. Debouncing helps prevent too many requests by waiting until the user stops typing for a short time before sending a search request (A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.). Debouncing can help in these cases: * **Avoid sending too many requests in a short period.** With rate-limited services like the GitHub API, frequent requests can cause errors. * **Accommodate users who prefer reduced UI motion.** As-you-type search results can change the UI rapidly. * **Anticipate typing speed.** By [selecting an appropriate debounce delay for your audience](#select-a-debounce-delay), you can reduce the number of requests while maintaining a good user experience. ## Write a debouncing function To tell Autocomplete to "wait" for a set time after typing stops before returning results, write a debouncing function that returns a promise. When the timeout elapses, the promise for the passed items is resolved. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; function debouncePromise(fn, time) { let timer = undefined; return function debounced(...args) { if (timer) { clearTimeout(timer); // Clear the timeout first if it's already defined. } return new Promise((resolve) => { timer = setTimeout(() => resolve(fn(...args)), time); }); }; } ``` The `debouncePromise` function lets you create a wrapper around any asynchronous function and delay it for a specified time: 200 ms in the example. ```js JavaScript icon=code theme={"system"} const DEBOUNCE_MS = 200; const debounced = debouncePromise( (items) => Promise.resolve(items), DEBOUNCE_MS, ); ``` ### Select a debounce delay The optimal debouncing delay should match your audience's typing speed (typically 30 words per minute (WPM) on mobile devices and 40 WPM on desktop devices). If the delay is too short, users still see flashes of content. If the delay is too long, the time between key presses and results becomes too long. 200 ms is the preferred debounce delay. Delays of over 300 ms will start degrading the user experience. Also increase the [`stallThreshold`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-stall-threshold) setting to avoid showing a "loading" spinner after the final keystroke. Set it to your debounce delay plus 300 ms. ## Debounce sources Use the `debounced` function for [static and dynamic sources](/doc/ui-libraries/autocomplete/core-concepts/sources). ```js JavaScript icon=code theme={"system"} // ... const STALL_THRESHOLD_MS = 500; // For example, 500ms for a 200ms debounce delay autocomplete({ container: "#autocomplete", stallThreshold: STALL_THRESHOLD_MS, getSources({ query }) { return debounced([ { sourceId: "algoliaItems", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "indexName", query, }, ], }); }, // ... }, { sourceId: "staticItems", getItems({ query }) { const items = [ { label: "Twitter", url: "https://twitter.com" }, { label: "GitHub", url: "https://github.com" }, ]; return items.filter(({ label }) => label.toLowerCase().includes(query.toLowerCase()), ); }, // ... }, { sourceId: "dynamicItems", getItems() { return fetch(`https://example.org/search/?q=${query}`) .then((response) => response.json()) .then(({ results }) => results) .catch(() => []); }, // ... }, ]); }, }); ``` # Debugging Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/debugging Learn about browser-based debugging strategies for Autocomplete. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). ## Use debug mode to inspect panel elements Set the [`debug`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-debug) option to `true` to keep the panel open. Use this while you inspect elements in your browser DevTools. This option defaults to `false`. Then the autocomplete panel closes when the [blur event](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) occurs. Only use this option during development. ## Emulate a focused page in Chrome DevTools You can enable the "Emulate a focused page" option from [Chrome DevTools](https://developer.chrome.com/docs/devtools/). This keeps the autocomplete panel open while you inspect the DOM. ![Screenshot of the 'Rendering' tab in Chrome DevTools with 'Emulate a focused page' checked.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/emulate-a-focused-page.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=be74c8e5eb14e5bdea28d338196847db) > [!TIP] > > You can also find this option by pressing `Cmd+Shift+P` or `Ctrl+Shift+P` and searching for it. ![Screenshot of the Chrome DevTools Rendering options menu showing 'Emulate a focused page' selected.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/emulate-a-focused-page-dropdown.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=d8a517ee9ea1fca73bc358b504b52dd0) ## Help and discussion If you're having trouble implementing Autocomplete, or have questions or feature requests, reach out on [GitHub Discussions](https://github.com/algolia/autocomplete/discussions/new) or join the community on Algolia's [Discord server](https://alg.li/discord). # Filter results with tags Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/filtering-results Learn how to filter your Algolia results with Autocomplete. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). As with any search experience, users who know what they're looking for might want to drill down and access a smaller, more manageable set of data based on meaningful categories. Instead of redirecting to a search page, **advanced autocomplete experiences let you refine results further without breaking the search flow.** In an autocomplete, users expect a flawless keyboard navigation experience, including with filters. Autocomplete provides the [`autocomplete-plugin-tags`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags) plugin to let you manage tags in your autocomplete. While there are many uses cases for tags, **they're particularly convenient to represent and apply refinements.** > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/tags-in-searchbox) > > Run and edit the Filter results with tags example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/autocomplete/tree/next/examples/tags-in-searchbox) > > Browse the source for the Filter results with tags example on GitHub. ## Get started Begin with some starter code. Create a file called `index.js` in your `src` directory, and add the code below: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [], }); ``` This starter code assumes you want to insert the autocomplete into a DOM element with `autocomplete` as an [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id). Change the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) to [match your markup](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options). Setting [`openOnFocus`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) to `true` ensures that the drop-down menu appears as soon as a user focuses the input. For now, `plugins` is an empty array, but you'll learn how to add the [tags plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags) next. ## Set up filters sources The [`autocomplete-plugin-tags`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags) package provides the [`createTagsPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createTagsPlugin) function to create a tags plugin out-of-the-box. Create an Autocomplete source to display available filters. To do so, use Algolia's [faceting feature](/doc/guides/managing-results/refine-results/faceting) and use the [`getAlgoliaFacets`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaFacets) function to retrieve available facet values for a given attribute. ```js JavaScript theme={"system"} import { liteClient as algoliasearch } from "algoliasearch/lite"; import { autocomplete, getAlgoliaFacets } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); autocomplete({ // ... getSources() { return [ { sourceId: "brands", getItems({ query }) { return getAlgoliaFacets({ searchClient, queries: [ { indexName: "instant_search", facet: "brand", params: { facetQuery: query, maxFacetHits: 5, }, }, ], transformResponse({ facetHits }) { return facetHits[0].map((hit) => ({ ...hit, facet: "brand" })); }, }); }, templates: { item({ item, components, html }) { return html`
    ${components.Highlight({ hit: item, attribute: "label" })}
    `; }, }, }, ]; }, }); ``` ```jsx JSX theme={"system"} import { liteClient as algoliasearch } from "algoliasearch/lite"; import { autocomplete, getAlgoliaFacets } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); autocomplete({ // ... getSources() { return [ { sourceId: "brands", getItems({ query }) { return getAlgoliaFacets({ searchClient, queries: [ { indexName: "instant_search", facet: "brand", params: { facetQuery: query, maxFacetHits: 5, }, }, ], transformResponse({ facetHits }) { return facetHits[0].map((hit) => ({ ...hit, facet: "brand" })); }, }); }, templates: { item({ item, components }) { return (
    ); }, }, }, ]; }, }); ``` In this example, you're displaying up to five facet values for the `brand` attribute. By using [`getAlgoliaFacets`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaFacets), you're also letting users search within these facet values to find meaningful filters. For now, nothing happens when selecting an item for this source. In the next step, you'll bind it to the tags plugin to apply tags on select. ## Add tags When selecting a filter from a source, you want to display them so that users knows what's affecting their search. The tags plugin renders applied tags as a source, letting users navigate through them with the keyboard and remove them on select. To automatically add a tag when selecting an item from your filters source, you can bind it to the plugin by `sourceId`. ```js JavaScript icon=code theme={"system"} // ... import { createTagsPlugin } from "@algolia/autocomplete-plugin-tags"; import "@algolia/autocomplete-plugin-tags/dist/theme.min.css"; // ... const tagsPlugin = createTagsPlugin({ getTagsSubscribers() { return [ { sourceId: "brands", getTag({ item }) { return item; }, }, ]; }, }); autocomplete({ // ... plugins: [tagsPlugin], }); ``` Whenever you select an item from source with the "brands" `sourceId`, the plugin adds it as a tag and displays it as a source. > [!NOTE] > > You can customize the default rendering for the tags source, or not use a source and render tags where and how you want. > For more information, see the [API reference](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createTagsPlugin). ## Apply filters from tags Once you've set tags in your autocomplete, you can use them to filter results. For example, suppose you've set the following tags. ```js JavaScript icon=code theme={"system"} const tags = [ { label: "Apple", facet: "brand" }, { label: "Samsung", facet: "brand" }, { label: "Cell Phones", facet: "categories" }, ]; ``` You might want to filter your Algolia results to only show Apple and Samsung phones. You can map tags into Algolia filters, and pass them to [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaResults). ```js JavaScript icon=code theme={"system"} // ... function mapToAlgoliaFilters(tagsByFacet, operator = "AND") { return Object.keys(tagsByFacet) .map((facet) => { return `(${tagsByFacet[facet] .map(({ label }) => `${facet}:"${label}"`) .join(" OR ")})`; }) .join(` ${operator} `); } function groupBy(items, predicate) { return items.reduce((acc, item) => { const key = predicate(item); if (!acc.hasOwnProperty(key)) { acc[key] = []; } acc[key].push(item); return acc; }, {}); } autocomplete({ // ... getSources({ query, state }) { const tagsByFacet = groupBy( state.context.tagsPlugin.tags, (tag) => tag.facet, ); return [ // ... { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, filters: mapToAlgoliaFilters(tagsByFacet), hitsPerPage: 5, }, }, ], }); }, // ... }, ]; }, }); ``` In this example, tags are turned into a conjunction (`AND`) of disjunctions (`OR`), and passed on to the [`filters`](/doc/api-reference/api-parameters/filters) search parameter. The tags would result in the following filter: ```txt theme={"system"} (categories:Phone) AND (brands:Apple OR brands:Samsung) ``` ### Exclude already applied tags from sources If you're displaying your filters as Autocomplete sources using [`getAlgoliaFacets`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaFacets), you might want to exclude already applied tags from the list. This way, users can discover more possible filters. To do so, you can derive negative filters from your tags. ```js JavaScript icon=code theme={"system"} // ... function mapToAlgoliaNegativeFilters(tags, facetsToNegate, operator = "AND") { return tags .map(({ label, facet }) => { const filter = `${facet}:"${label}"`; return facetsToNegate.includes(facet) && `NOT ${filter}`; }) .filter(Boolean) .join(` ${operator} `); } autocomplete({ // ... getSources({ query, state }) { // ... return [ { sourceId: "brands", getItems() { return getAlgoliaFacets({ searchClient, queries: [ { indexName: "instant_search", facet: "brand", params: { facetQuery: query, maxFacetHits: 3, filters: mapToAlgoliaNegativeFilters( state.context.tagsPlugin.tags, ["brand"], ), }, }, ], transformResponse({ facetHits }) { return facetHits[0].map((hit) => ({ ...hit, facet: "brand", })); }, }); }, // ... }, // ... ]; }, }); ``` > [!NOTE] > > The [`filters`](/doc/api-reference/api-parameters/filters) search parameter works for most cases, > but you can adjust the logic to generate [`facetFilters`](/doc/api-reference/api-parameters/facetFilters), > [`numericFilters`](/doc/api-reference/api-parameters/numericFilters), > [`tagFilters`](/doc/api-reference/api-parameters/tagFilters), > or [`optionalFilters`](/doc/api-reference/api-parameters/optionalFilters). ### Remove applied tag from the query Using [`getAlgoliaFacets`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaFacets) to populate your filters list lets you search within filters as well as products. For example, if you're looking for a specific brand, you can start typing it out, then select it from the refined filters. When you apply a tag you've found after typing, you might want to delete it from the query. For example, if a user types "app" then selects "Apple", you likely want to remove "app" from the query. For users, it feels like the autocomplete understands their intent, and it lets them type the rest of the query without having to clear it first. To do so, you can use [`onSelect`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-on-select) to clear the query after applying a tag. ```js JavaScript icon=code theme={"system"} // ... autocomplete({ // ... getSources({ query, state }) { // ... return [ { sourceId: "brands", onSelect({ item, setQuery }) { if (item.label.toLowerCase().includes(query.toLowerCase())) { setQuery(""); } }, // ... }, // ... ]; }, }); ``` ## Filter from external refinements A great way to help users make more meaningful searches is to **contextualize the autocomplete behavior based on intent**. * In an ecommerce site, a user searching from the "Video games" section might expect different results than if they were on the home page. * In a dashboard app, you might want to display different results on an empty query for users belonging to the "Billing" group than those in the "Technical" one. The [tags plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createTagsPlugin) lets you apply tags from external state by [passing initial tags](/doc/ui-libraries/autocomplete/guides/filtering-results#pass-initial-tags) and [manually updating them](/doc/ui-libraries/autocomplete/guides/filtering-results#update-tags-manually). ### Pass initial tags When starting your Autocomplete instance with the [tags plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createTagsPlugin), you can pass initial filters based on external state. For example, you could parse the current URL to retrieve the active category and turn it into a tag. ```js JavaScript icon=code theme={"system"} // Current URL: https://example.org/?categories=Video games const parameters = Array.from(new URLSearchParams(location.search)); const initialTags = parameters.map(([facet, label]) => ({ label, facet })); // [{ label: 'Video games', facet: 'categories' }] const tagsPlugin = createTagsPlugin({ initialTags }); autocomplete({ // ... plugins: [tagsPlugin], }); ``` You can then [apply filters based on these tags](/doc/ui-libraries/autocomplete/guides/filtering-results#apply-filters-from-tags). Note that in such a situation, since the applied tags don't come from an explicit user action, **you may want to split your results in two sources: first the ones in the current category, then all results in other categories.** This lets users find other results while acknowledging context. ```js JavaScript theme={"system"} // ... autocomplete({ // ... getSources({ query, state }) { const tagsByFacet = groupBy( state.context.tagsPlugin.tags, (tag) => tag.facet, ); return [ { sourceId: "productsInCurrentCategory", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, filters: mapToAlgoliaFilters(tagsByFacet), hitsPerPage: 5, }, }, ], }); }, templates: { header({ html }) { return html` In ${tagsByFacet.categories[0].label} category
    `; }, // ... }, // ... }, { sourceId: "productsInOtherCategories", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, filters: mapToAlgoliaNegativeFilters( state.context.tagsPlugin.tags, ["categories"], ), hitsPerPage: 5, }, }, ], }); }, templates: { header({ html }) { return html` In other categories
    `; }, // ... }, // ... }, ]; }, }); ``` ```jsx JSX theme={"system"} // ... autocomplete({ // ... getSources({ query, state }) { const tagsByFacet = groupBy( state.context.tagsPlugin.tags, (tag) => tag.facet, ); return [ { sourceId: "productsInCurrentCategory", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, filters: mapToAlgoliaFilters(tagsByFacet), hitsPerPage: 5, }, }, ], }); }, templates: { header() { return ( In {tagsByFacet.categories[0].label} category
    ); }, // ... }, // ... }, { sourceId: "productsInOtherCategories", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, filters: mapToAlgoliaNegativeFilters( state.context.tagsPlugin.tags, ["categories"], ), hitsPerPage: 5, }, }, ], }); }, templates: { header() { return ( In other categories
    ); }, // ... }, // ... }, ]; }, }); ``` ### Update tags manually If you're using a single-page app with client-side routing, or you're deriving tags from a local, dynamic state, **you might need to update tags manually after the instance has started**. A typical use case is when using Autocomplete along with InstantSearch. When applying new refinements with a [`refinementList`](/doc/api-reference/widgets/refinement-list/js), you might want to update the list of tags in your autocomplete to reflect the current refinements. You can imperatively update the list of tags outside the Autocomplete instance using the exposed API on the plugin. ```js JavaScript icon=code theme={"system"} const tagsPlugin = createTagsPlugin({ // ... }); const search = instantsearch({ // ... onStateChange({ uiState }) { const refinements = uiState.refinementList; const tags = Object.keys(refinements).flatMap((key) => refinements[key].map((refinement) => ({ label: refinement, facet: key, })), ); tagsPlugin.data.setTags(tags); }, }); ``` ## Display tags in the search box By default, the tags plugin displays tags as a source. This makes it straightforward to navigate through applied filters with the keyboard, as you would with any source. However, a common pattern is to display tags in the search box, near the input. It improves usability, especially when combined with a filters source using [`getAlgoliaFacets`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaFacets). As they type, the filters source suggests facets that can apply without leaving the keyboard. Applied tags display near the search input, which lets users remove them using the Backspace key. This approach makes tags behave like part of the query, making them more discoverable and simple to remove. ### Prevent the plugin from rendering tags as a source Do this with the [`transformSource`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createTagsPlugin#param-transform-source) option. ```js JavaScript icon=code theme={"system"} const tagsPlugin = createTagsPlugin({ // ... transformSource() { return undefined; }, }); ``` ### Display tags in the search box using the DOM API. [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) exposes the `.aa-InputWrapperPrefix` DOM element before the search input You can inject tags into this element. The plugin lets you perform custom logic with the [`onChange`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createTagsPlugin#param-on-change) option, which you can use to update the rendered tags. ```js JavaScript theme={"system"} // ... import { render, createElement } from "preact"; const tagsPlugin = createTagsPlugin({ // ... onChange({ tags, setIsOpen }) { requestAnimationFrame(() => { const container = document.querySelector(".aa-InputWrapperPrefix"); const oldTagsContainer = document.querySelector(".aa-Tags"); const tagsContainer = document.createElement("div"); tagsContainer.classList.add("aa-Tags"); render( createElement( "div", { className: "aa-TagsList" }, ...tags.map(({ label, remove }) => createElement( "div", { className: "aa-Tag", key: label }, createElement("span", { className: "aa-TagLabel" }, label), createElement( "button", { className: "aa-TagRemoveButton", onClick: () => { remove(); requestAnimationFrame(() => setIsOpen(true)); }, title: "Remove this tag", }, createElement( "svg", { fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, viewBox: "0 0 24 24", }, createElement("path", { d: "M18 6L6 18" }), createElement("path", { d: "M6 6L18 18" }), ), ), ), ), ), tagsContainer, ); if (oldTagsContainer) { container.removeChild(oldTagsContainer); } container.appendChild(tagsContainer); }); }, }); ``` ```jsx JSX theme={"system"} // ... import { render } from "preact"; const tagsPlugin = createTagsPlugin({ // ... onChange({ tags, setIsOpen }) { requestAnimationFrame(() => { const container = document.querySelector(".aa-InputWrapperPrefix"); const oldTagsContainer = document.querySelector(".aa-Tags"); const tagsContainer = document.createElement("div"); tagsContainer.classList.add("aa-Tags"); render(
    {tags.map(({ label, remove }) => ( { remove(); requestAnimationFrame(() => setIsOpen(true)); }} /> ))}
    , tagsContainer, ); if (oldTagsContainer) { container.removeChild(oldTagsContainer); } container.appendChild(tagsContainer); }); }, }); function TagItem({ label, onRemove }) { return (
    {label}
    ); } ``` > [!NOTE] > > This example uses Preact's [`render`](https://preactjs.com/guide/v10/api-reference#render) function with JSX to simplify injecting HTML and attaching event listeners. > You can use HTML template strings along with [`Element.innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) if you don't use JSX. When users press the backspace key, you can remove the last tag of the list. This feels as if they were removing them from the query. ```js JavaScript icon=code theme={"system"} // ... const searchInput = document.querySelector(".aa-Input"); searchInput.addEventListener("keydown", (event) => { if ( event.key === "Backspace" && searchInput.selectionStart === 0 && searchInput.selectionEnd === 0 ) { const newTags = tagsPlugin.data.tags.slice(0, -1); tagsPlugin.data.setTags(newTags); } }); ``` > [!NOTE] > > This solution doesn't work in [Detached mode](/doc/ui-libraries/autocomplete/core-concepts/detached-mode). > You can [turn it off manually](/doc/ui-libraries/autocomplete/core-concepts/detached-mode#manually-control-detached-mode). ## Next steps [Autocomplete tags](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags) are convenient to represent refinements and derive filters, but you can use them for a wide variety of use cases. For example, you can use them to represent navigation steps when using your autocomplete to browse nested hierarchies, or indicating a state or mode that makes your autocomplete behave differently. # Implement multiple search states Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/implementing-multiple-search-states Learn how to show different search states depending on the query or results. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). You may want to change which [sources](/doc/ui-libraries/autocomplete/core-concepts/sources) you use depending on the query or the number of results. A typical pattern is to display a different source when the query is empty and switch once users start typing. Or display a no results message when the current query doesn't match any results. This guide explains how to conditionally display specific sources when the query is empty or doesn't return any results. ## Before you begin This guide assumes that you know HTML, CSS, and JavaScript and that you have existing HTML with an input element where you want to insert the autocomplete drop-down menu. ## Get started First, begin with some code for the Autocomplete implementation. Create a file called `index.js` in your `src` directory, and add: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; autocomplete({ container: "#autocomplete", placeholder: "Search for products", openOnFocus: true, }); ``` This code assumes you want to insert Autocomplete into a DOM element with `autocomplete` as an [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id). Change the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) to [match your markup](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options). Setting [`openOnFocus`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) to `true` ensures that the drop-down menu appears as soon as a user focuses the input. ## Implement the empty query state ```js JavaScript icon=code theme={"system"} import { liteClient as algoliasearch } from "algoliasearch/lite"; import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); autocomplete({ container: "#autocomplete", placeholder: "Search for products", openOnFocus: true, getSources({ query }) { if (!query) { return [ { sourceId: "links", getItems() { return [ { label: "Shipping", url: "#shipping" }, { label: "Contact", url: "#contact" }, ]; }, getItemUrl({ item }) { return item.url; }, templates: { item({ item }) { return item.label; }, }, }, ]; } return [ { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", query, }, ], }); }, getItemUrl({ item }) { return item.url; }, templates: { item({ item }) { return item.name; }, }, }, ]; }, }); ``` The [`getSources`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-sources) function provides access to the current `query`, which you can use to return sources conditionally. You can use this pattern to display predefined items like these, [recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches), and [Query Suggestions](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) when the query is empty. When users begin to type, you can show search results. When there isn't a query, this Autocomplete instance returns useful links. When there is, it searches an Algolia 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 more information about using Algolia as a source, see [Get started with Autocomplete](/doc/ui-libraries/autocomplete/introduction/getting-started). ## Implement the no results search state Now that you can display a custom source when the query is empty, you can display a message if the query returned no results. ```js JavaScript icon=code theme={"system"} // ... autocomplete({ container: "#autocomplete", placeholder: "Search for products", openOnFocus: true, getSources({ query }) { // ... }, renderNoResults({ render, html, state }, root) { render( html`
    No results for "${state.query}".
    `, root, ); }, }); ``` The [`renderNoResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-render-no-results) function provides a way to render a no results section when there are no hits. You have access to the full Autocomplete state. It lets you compute sources based on [various parameters](/doc/ui-libraries/autocomplete/core-concepts/state), such as the query, Autocomplete status, whether the Autocomplete display panel is open or not, and the [context](/doc/ui-libraries/autocomplete/core-concepts/context). # Include multiple result types Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/including-multiple-result-types Learn how to show different types of results in one autocomplete. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). Query suggestions are one aspect of rich multi-category search experiences. For example, if you search for something in your email inbox, your results could contain not just email threads, but also contacts, attachments, and more. Ecommerce stores often show query suggestions, products, blog posts, brands, and categories all in one autocomplete. It's best to display different results types in different sections. This helps users understand what the items are and what happens when they choose one. The Autocomplete library lets you mix different item types in one autocomplete and customize their display. To do so you need to several sources in the [`getSources`](/doc/ui-libraries/autocomplete/core-concepts/sources) option. This tutorial outlines how to combine [static predefined items](/doc/ui-libraries/autocomplete/core-concepts/sources#static-sources), [recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches) and [Query Suggestions](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches). Using [plugins](/doc/ui-libraries/autocomplete/core-concepts/plugins) for each source makes the code modular, reusable, and sharable. However, instead of using plugins, you could add different sources directly in `getSources`. ## Before you begin This guide assumes that you know HTML, CSS, and JavaScript and that you have existing HTML with an input element where you want to insert the autocomplete drop-down menu. It also assumes that you have an 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.) with a populated [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) 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.). ## Starter code Begin by adding the following Autocomplete starter code. Create a file called `index.js` in your `src` directory. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ container: "#autocomplete", plugins: [], openOnFocus: true, }); ``` This starter code assumes you want to insert the Autocomplete menu into a DOM element with `autocomplete` as an [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id). Change the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) to [match your markup](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options). Setting [`openOnFocus`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) to `true` ensures that the drop-down menu appears as soon as a user focuses the input. For now, `plugins` is an empty array, but you'll learn how to create and add [plugins](/doc/ui-libraries/autocomplete/core-concepts/plugins) for predefined items, recent searches, and Query Suggestions next. ## Add predefined items A popular search pattern for autocomplete menus shows predefined search terms as soon as a user clicks on the search box and before they begin typing anything. This provides a guided experience and exposes users to helpful resources or other content you want them to see. This tutorial describes how to create a [plugin](/doc/ui-libraries/autocomplete/core-concepts/plugins) to show static, predefined items. In particular, it exposes helpful links users may want to refer to. ### Create a predefined items plugin Begin by creating a `predefinedItemsPlugin.js` file in your `src` directory, with the following code: ```js JavaScript icon=code theme={"system"} const predefinedItems = [ { label: "Documentation", url: "https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/", }, { label: "GitHub", url: "https://github.com/algolia/autocomplete", }, ]; export const predefinedItemsPlugin = { getSources() { return [ { sourceId: "predefinedItemsPlugin", getItems({ query }) { if (!query) { return predefinedItems; } return predefinedItems.filter((item) => item.label.toLowerCase().includes(query.toLowerCase()), ); }, getItemUrl({ item }) { return item.url; }, templates: { // ... }, }, ]; }, }; ``` The `predefinedItemsPlugin` plugin has a similar signature as any other autocomplete implementation: it uses the [`getSources`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-get-sources) option to return an array of items to display. Each object in the array defines where to get items using [`getItems`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-items). An Autocomplete plugin is an object that implements the `AutocompletePlugin` interface. For more information, see [Building your own plugin](/doc/ui-libraries/autocomplete/core-concepts/plugins#build-your-own-plugin). In this example, `getItems` returns a filtered array of `predefinedItems`. The code filters the array to return items that match the query, if it exists. If it doesn't, it returns the entire array. **You can return whatever predefined items you like and format them accordingly**. For example, suppose you want to show trending search items instead of helpful links. Use `getItems` to retrieve them from another source, [including an asynchronous API](/doc/ui-libraries/autocomplete/core-concepts/sources#dynamic-sources). The [`getItemUrl`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-item-url) function defines how to get the URL of an item. In this case, since it's an attribute on each object in the `predefinedItems` array, just return the attribute. Use `getItemUrl` to add [keyboard navigation](/doc/ui-libraries/autocomplete/core-concepts/keyboard-navigation) to the autocomplete menu. Users can scroll through items in the menu with the up and down keys. When they hit Enter on one of the `predefinedItems` or any source that includes `getItemUrl`, it opens the URL retrieved from `getItemUrl`. Templates define how to display each section of the autocomplete, including the [`header`](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-header), [`footer`](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-footer), and each [`item`](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-item). Templates can return any valid virtual DOM element. This example defines how to display each predefined item with the `item` template and gives a `header` for the entire section. ```js JavaScript theme={"system"} // ... export const predefinedItemsPlugin = { getSources() { return [ { sourceId: "predefinedItemsPlugin", getItems({ query }) { if (!query) { return predefinedItems; } return predefinedItems.filter((item) => item.label.toLowerCase().includes(query.toLowerCase()), ); }, getItemUrl({ item }) { return item.url; }, templates: { header({ items, html }) { if (items.length === 0) { return null; } return html`Links
    `; }, item({ item, html }) { return html`
    ${item.label}
    `; }, }, }, ]; }, }; ``` ```jsx JSX theme={"system"} // ... export const predefinedItemsPlugin = { getSources() { return [ { sourceId: "predefinedItemsPlugin", getItems({ query }) { if (!query) { return predefinedItems; } return predefinedItems.filter((item) => item.label.toLowerCase().includes(query.toLowerCase()), ); }, getItemUrl({ item }) { return item.url; }, templates: { header({ items }) { if (items.length === 0) { return null; } return ( <> Links
    ); }, item({ item }) { return (
    {item.label}
    ); }, }, }, ]; }, }; ``` ### Add the predefined items plugin to the autocomplete Import the newly created `predefinedItemsPlugin` and add it to`plugins` in `index.js`. Once you've done that, the file should look like this: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { predefinedItemsPlugin } from "./predefinedItemsPlugin"; autocomplete({ container: "#autocomplete", plugins: [predefinedItemsPlugin], openOnFocus: true, }); ``` Now, as soon as a user clicks on the search box, these predefined items appear. Once they begin typing, only predefined items that contain the query remain. ## Add recent searches and Query Suggestions You can add [recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches) and [Query Suggestions](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) using out-of-the-box plugins. ### Create a recent searches plugin Use the out-of-the-box [`createLocalStorageRecentSearchesPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/createLocalStorageRecentSearchesPlugin) function to create a recent searches plugin: ```js JavaScript icon=code theme={"system"} import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "RECENT_SEARCH", limit: 5, }); ``` The `key` can be any string and is required to differentiate search histories if you have several Autocomplete experiences on one page. The `limit` defines the maximum number of recent searches to display. ### Create a Query Suggestions plugin > [!NOTE] > > If you don't have a Query Suggestions index yet, [create one](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js). > Use the demo app credentials and index name provided in this tutorial. Use the out-of-the-box [`createQuerySuggestionsPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin) function to create a Query Suggestions plugin. It requires an [Algolia search client](/doc/libraries/sdk/install) initialized with an [Algolia application ID and API key](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api#required-credentials) and an `indexName`. The `indexName` is the name of your Query Suggestions index. ```js JavaScript icon=code theme={"system"} import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", }); ``` ### Coordinate Query Suggestions with other sources When instantiating your Query Suggestions plugin, you can optionally pass a [`getSearchParams`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin#param-get-search-params) function to apply [Algolia query parameters](/doc/api-reference/api-parameters) to the suggestions returned from the plugin. This is helpful if you need to coordinate your Query Suggestions with other sections displayed in the autocomplete, like [recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches). For example, if you'd like to show a combined total of 10 search terms (recent searches plus Query Suggestions), you can indicate this: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "RECENT_SEARCH", limit: 5, }); const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams() { return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 10, }); }, }); autocomplete({ // ... }); ``` This shows up to five recent searches (set by the [`limit`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/createLocalStorageRecentSearchesPlugin#param-limit) parameter) and up to 10 total search terms. If there's only one recent search in local storage, the autocomplete displays nine Query Suggestions, assuming that there are nine relevant suggestions. ### Separate result types When using sources other than just recent and suggested searches, it's best to label the different result types with [`headers`](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-header). For the `createLocalStorageRecentSearchesPlugin` and `createQuerySuggestionsPlugin` plugins, use the [`transformSource`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin#param-transform-source) option to do this. ```js JavaScript theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; // ... const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ // ... transformSource({ source }) { return { ...source, templates: { ...source.templates, header({ items, html }) { if (items.length === 0) { return null; } return html`Recent
    `; }, }, }; }, }); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... transformSource({ source }) { return { ...source, templates: { ...source.templates, header({ items, html }) { if (items.length === 0) { return null; } return html`Suggestions
    `; }, }, }; }, }); autocomplete({ // ... }); ``` ```jsx JSX theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; // ... const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ // ... transformSource({ source }) { return { ...source, templates: { ...source.templates, header({ items }) { if (items.length === 0) { return null; } return ( <> Recent
    ); }, }, }; }, }); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... transformSource({ source }) { return { ...source, templates: { ...source.templates, header({ items }) { if (items.length === 0) { return null; } return ( <> Suggestions
    ); }, }, }; }, }); autocomplete({ // ... }); ``` ## Add plugins All that's left to do is add all your plugins to your Autocomplete instance: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { predefinedItemsPlugin } from "./predefinedItemsPlugin"; import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "RECENT_SEARCH", limit: 5, }); const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams() { return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 10, }); }, }); autocomplete({ container: "#autocomplete", plugins: [ recentSearchesPlugin, querySuggestionsPlugin, predefinedItemsPlugin, ], openOnFocus: true, }); ``` This creates a basic multi-source autocomplete. ## Next steps This tutorial combined three sources in one autocomplete. Depending on your use case, you might want to add more or different ones than the ones included here. Regardless of what you use for your sections, the method is the same: provide a different a [source](/doc/ui-libraries/autocomplete/core-concepts/sources) for each. You may also choose to style your multi-source autocomplete differently by creating a horizontal layout or further differentiating how to display each source type. # Redirect searches to a URL Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/redirect Learn how to add redirects to your autocomplete experience with the redirect URL plugin. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). Redirects can enhance your autocomplete experience. For example: * Searching for "help" redirects users to a support page * Searching for a category, such as, "TV" redirects users to a category page * Searching for a specific product or brand redirects users to a seasonal promotional page for it * Searching for specific keywords related to an ongoing event, such as "masks", redirects users to a more specific landing page This guide explains how to add redirects to Autocomplete using the redirect URL plugin. You can use the plugin with Algolia and non-Algolia sources. ## Before you begin Before adding redirects, you need an [Algolia index](/doc/guides/sending-and-managing-data/send-and-update-your-data) with data and an HTML file with a container element for the Autocomplete menu. > [!NOTE] > > If you want to use redirects without further frontend development, > set up [rules for redirects](/doc/guides/managing-results/rules/merchandising-and-promoting/how-to/redirects) ## Create an autocomplete instance First, create an autocomplete instance. Create a new file `index.js` with the following code: ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ container: "#autocomplete", plugins: [], openOnFocus: true, }); ``` This code adds the autocomplete to a DOM element with the `id` attribute `autocomplete`. You can use a different selector by changing the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) parameter. Setting [`openOnFocus`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) to `true` ensures that the drop-down menu appears as soon as a user focuses the input. ## Add the redirect plugin to Algolia sources To add the redirect plugin to Autocomplete, install the [`autocomplete-plugin-redirect-url`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url) package. To create the plugin, call the `createRedirectUrlPlugin` function, and add it to the list of plugins. If you're using this plugin for an Algolia source with Rules with redirects as consequences, you must have a source defined with [`getSources`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-sources). The source must have these properties: * [`sourceId`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-source-id) * [`templates`](/doc/ui-libraries/autocomplete/core-concepts/templates) * [`getItemInputValue`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-item-input-value) * [`getItems`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-items) See the following code for a complete example: ```js JavaScript theme={"system"} import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import { createRedirectUrlPlugin } from "@algolia/autocomplete-plugin-redirect-url"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import "@algolia/autocomplete-theme-classic"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const redirectUrlPlugin = createRedirectUrlPlugin(); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [redirectUrlPlugin], getSources({ query }) { return [ { sourceId: "demo-source", templates: { item(params) { const { item, html } = params; return html`${item.name}`; }, }, getItemInputValue({ item }) { return item.name; }, getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, // the rules configured for this demo require this context to prevent triggering in other plugin demos ruleContexts: ["enable-redirect-url"], hitsPerPage: 10, }, }, ], }); }, }, ]; }, }); ``` ```jsx JSX theme={"system"} import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import { createRedirectUrlPlugin } from "@algolia/autocomplete-plugin-redirect-url"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import "@algolia/autocomplete-theme-classic"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const redirectUrlPlugin = createRedirectUrlPlugin(); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [redirectUrlPlugin], getSources({ query }) { return [ { sourceId: "demo-source", templates: { item(params) { const { item } = params; return ${item.name}; }, }, getItemInputValue({ item }) { return item.name; }, getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, // the rules configured for this demo require this context to prevent triggering in other plugin demos ruleContexts: ["enable-redirect-url"], hitsPerPage: 10, }, }, ], }); }, }, ]; }, }); ``` ## Set up redirects with non-Algolia sources If you want to use redirects that are defined in a non-Algolia source, you can configure [`createRedirectUrlPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url/createRedirectUrlPlugin) to customize the redirect response. Use the [`transformResponse`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url/createRedirectUrlPlugin#param-transform-response) parameter to map the response from the source to the redirect object that's used by the plugin. ```js JavaScript theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createRedirectUrlPlugin } from "@algolia/autocomplete-plugin-redirect-url"; import "@algolia/autocomplete-theme-classic"; function transformResponse(response) { // assumes your non-Algolia source provides redirect URLs this way return response.myRedirectData?.url; } const redirectUrlPlugin = createRedirectUrlPlugin({ transformResponse, }); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [redirectUrlPlugin], getSources({ query }) { return [ { sourceId: "demo-source", templates: { item(params) { const { item, html } = params; return html`${item.name}`; }, }, getItemInputValue({ item }) { return item.name; }, getItems() { return {}; // your custom results here... }, }, ]; }, }); ``` ```jsx JSX theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createRedirectUrlPlugin } from "@algolia/autocomplete-plugin-redirect-url"; import "@algolia/autocomplete-theme-classic"; function transformResponse(response) { // assumes your non-Algolia source provides redirect URLs this way return response.myRedirectData?.url; } const redirectUrlPlugin = createRedirectUrlPlugin({ transformResponse, }); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [redirectUrlPlugin], getSources({ query }) { return [ { sourceId: "demo-source", templates: { item(params) { const { item } = params; return ${item.name}; }, }, getItemInputValue({ item }) { return item.name; }, getItems() { return {}; // your custom results here... }, }, ]; }, }); ``` ## Control the redirect behavior It's possible to have multiple redirect URLs, since redirect criteria may occur for each source and their queries. In these cases, you might want to control which redirect URL is used. By default, the first source with a redirect with its first 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.) is the redirect. You can change this behavior with the [`onRedirect`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url/createRedirectUrlPlugin#param-on-redirect) callback, which receives the `redirects` array. This array contains every detected redirect for a given query, collected either automatically for Rules with redirects as consequences or from [`transformResponse`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url/createRedirectUrlPlugin#param-transform-response). For example: ```js JavaScript icon=code theme={"system"} [ { sourceId: "my-source-with-one-query", urls: ["https://www.algolia.com"], }, { sourceId: "my-source-with-two-queries", urls: ["https://www.algolia.com", "https://www.algolia.com/hello-world"], }, ]; ``` In this example, a query triggered three redirect URLs in the response which mapped into an array of URLs for each source. In this scenario, **you can implement [`onRedirect`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url/createRedirectUrlPlugin#param-on-redirect) to select the URL to redirect to.** The following example shows how to pass an object with options to the `onRedirect` callback. The options include a `navigator` object, which handles the redirection to the provided URL. For more information, see [Keyboard navigation](/doc/ui-libraries/autocomplete/core-concepts/keyboard-navigation). ```js JavaScript theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createRedirectUrlPlugin } from "@algolia/autocomplete-plugin-redirect-url"; import "@algolia/autocomplete-theme-classic"; function onRedirect(redirects, { event, navigator, state }) { const item = redirects.find( (r) => r.sourceId === "my-source-with-two-queries", ); const itemUrl = item?.urls?.find((url) => url.startsWith("https://www.algolia.com"), ); if (!itemUrl) { return; } if (event.metaKey || event.ctrlKey) { navigator.navigateNewTab({ itemUrl, item, state }); } else if (event.shiftKey) { navigator.navigateNewWindow({ itemUrl, item, state }); } else { navigator.navigate({ itemUrl, item, state }); } } const redirectUrlPlugin = createRedirectUrlPlugin({ onRedirect, }); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [redirectUrlPlugin], getSources({ query }) { return [ { sourceId: "demo-source", templates: { item(params) { const { item, html } = params; return html`${item.name}`; }, }, getItemInputValue({ item }) { return item.name; }, getItems() { return {}; // your custom results here... }, }, ]; }, }); ``` ```jsx JSX theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createRedirectUrlPlugin } from "@algolia/autocomplete-plugin-redirect-url"; import "@algolia/autocomplete-theme-classic"; function onRedirect(redirects, { event, navigator, state }) { const item = redirects.find( (r) => r.sourceId === "my-source-with-two-queries", ); const itemUrl = item?.urls?.find((url) => url.startsWith("https://www.algolia.com"), ); if (!itemUrl) { return; } if (event.metaKey || event.ctrlKey) { navigator.navigateNewTab({ itemUrl, item, state }); } else if (event.shiftKey) { navigator.navigateNewWindow({ itemUrl, item, state }); } else { navigator.navigate({ itemUrl, item, state }); } } const redirectUrlPlugin = createRedirectUrlPlugin({ onRedirect, }); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [redirectUrlPlugin], getSources({ query }) { return [ { sourceId: "demo-source", templates: { item(params) { const { item } = params; return ${item.name}; }, }, getItemInputValue({ item }) { return item.name; }, getItems() { return {}; // your custom results here... }, }, ]; }, }); ``` ## Customize rendering of redirect items You can use the [`templates`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url/createRedirectUrlPlugin#param-templates) parameter to change the default rendering of redirect items: ```js JavaScript theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createRedirectUrlPlugin } from "@algolia/autocomplete-plugin-redirect-url"; import "@algolia/autocomplete-theme-classic"; const templates = { item({ html, state }) { return html`${state.query}`; }, }; const redirectUrlPlugin = createRedirectUrlPlugin({ templates, }); autocomplete({ container: "#autocomplete", placeholder: "Search", openOnFocus: true, plugins: [redirectUrlPlugin], getSources({ query }) { return [ { sourceId: "demo-source", templates: { item(params) { const { item, html } = params; return html`${item.name}`; }, }, getItemInputValue({ item }) { return item.name; }, getItems() { return {}; // your custom results here... }, }, ]; }, }); ``` ```jsx JSX theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createRedirectUrlPlugin } from "@algolia/autocomplete-plugin-redirect-url"; import "@algolia/autocomplete-theme-classic"; const templates = { item({ state }) { return ${state.query}; }, }; const redirectUrlPlugin = createRedirectUrlPlugin({ templates, }); autocomplete({ container: "#autocomplete", placeholder: "Search", openOnFocus: true, plugins: [redirectUrlPlugin], getSources({ query }) { return [ { sourceId: "demo-source", templates: { item(params) { const { item } = params; return ${item.name}; }, }, getItemInputValue({ item }) { return item.name; }, getItems() { return {}; // your custom results here... }, }, ]; }, }); ``` # Reshape sources Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/reshaping-sources Learn how to reshape your Autocomplete sources to create richer search experiences. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). When you're browsing a website that fetches content from a database, the UI isn't fully representative of how that data is structured on the backend. This allows more human-friendly experiences and interactions. **A search UI doesn't have to be a one-to-one mapping with your search engine** either. Autocomplete lets you transform [static](/doc/ui-libraries/autocomplete/core-concepts/sources#static-sources), [dynamic](/doc/ui-libraries/autocomplete/core-concepts/sources#dynamic-sources), and [asynchronous sources](/doc/ui-libraries/autocomplete/core-concepts/sources#asynchronous-sources) into a friendlier search UI. Here are some examples of what you can do with the Reshape API: * **Apply a limit** of items for a group of sources * **Remove duplicates** in a group of sources * **Group** sources by an attribute * **Sort** sources In this guide, you'll learn how to use multiple reshape functions to remove duplicates and create a shared limit for your autocomplete suggestions. ## Create a reshape function To remove duplicates between sources, you can start by creating a `uniqBy` function. You can later apply this function to your [recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches) and [Query Suggestions](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) sources. ```js JavaScript icon=code theme={"system"} function uniqBy(predicate) { return function runUniqBy(...rawSources) { const sources = rawSources.flat().filter(Boolean); const seen = new Set(); return sources.map((source) => { const items = source.getItems().filter((item) => { const appliedItem = predicate({ source, item }); const hasSeen = seen.has(appliedItem); seen.add(appliedItem); return !hasSeen; }); return { ...source, getItems() { return items; }, }; }); }; } ``` Autocomplete supports [conditional sources](/doc/ui-libraries/autocomplete/guides/implementing-multiple-search-states#implement-the-empty-query-state): sources sometimes exist and sometimes don't. This can happen when you display a source on an empty but not when users start typing (for example, with a popular 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.)). Your function can support this by removing non-existent sources with `filter(Boolean)`. ## The reshape function Now that you've created the `uniqBy` function, you can specify the implementation for your sources and use it in the [`reshape`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-reshape) option: ```js JavaScript icon=code theme={"system"} const removeDuplicates = uniqBy(({ source, item }) => source.sourceId === "querySuggestionsPlugin" ? item.query : item.label, ); autocomplete({ container: "#autocomplete", plugins: [recentSearchesPlugin, querySuggestionsPlugin], reshape({ sourcesBySourceId }) { const { recentSearchesPlugin, querySuggestionsPlugin, ...rest } = sourcesBySourceId; return [ removeDuplicates(recentSearchesPlugin, querySuggestionsPlugin), Object.values(rest), ]; }, }); ``` The reshape option provides three arguments: * `sources`: the resolved sources provided by [`getSources`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-sources) * `sourcesBySourceId`: the resolved sources grouped by [`sourceId`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-source-id) * `state`: the Autocomplete [state](/doc/ui-libraries/autocomplete/core-concepts/state) The `uniqBy` function uses `item.query` as identifier for the [Query Suggestions plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) and `item.label` for the [recent searches plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches). These are the shape of the items that the plugins return. If you use custom sources, you can use a switch statement based on `source.sourceId`. Sources are retrieved from `sourcesBySourceId` with their `sourceId` using [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#object_destructuring). You can return sources that you didn't reshape with `Object.values(rest)`. ## Combine reshape functions You can create a function that combines results from two different sources. Use it with `uniqBy` to ensure there are no duplicates and there's a fixed number of combined items. In the video, four results are always displayed and the number of Query Suggestions varies depending on the number of recent searches. Here's a `limit` function: ```js JavaScript icon=code theme={"system"} function limit(value) { return function runLimit(...rawSources) { const sources = rawSources.flat().filter(Boolean); const limitPerSource = Math.ceil(value / sources.length); let sharedLimitRemaining = value; return sources.map((source, index) => { const isLastSource = index === sources.length - 1; const sourceLimit = isLastSource ? sharedLimitRemaining : Math.min(limitPerSource, sharedLimitRemaining); const items = source.getItems().slice(0, sourceLimit); sharedLimitRemaining = Math.max(sharedLimitRemaining - items.length, 0); return { ...source, getItems() { return items; }, }; }); }; } ``` Then combine the `uniqBy` and `limit` functions: ```js JavaScript icon=code theme={"system"} const removeDuplicates = uniqBy(({ source, item }) => source.sourceId === "querySuggestionsPlugin" ? item.query : item.label, ); const limitSuggestions = limit(4); autocomplete({ container: "#autocomplete", plugins: [recentSearchesPlugin, querySuggestionsPlugin], reshape({ sourcesBySourceId }) { const { recentSearchesPlugin, querySuggestionsPlugin, ...rest } = sourcesBySourceId; return [ limitSuggestions( removeDuplicates(recentSearchesPlugin, querySuggestionsPlugin), ), Object.values(rest), ]; }, }); ``` ## Pipe reshape functions Nested function calls can become cumbersome, so you can use functional libraries like [Ramda](https://ramdajs.com) to pipe reshape functions instead. ```js JavaScript icon=code theme={"system"} import { pipe } from "ramda"; const combineSuggestions = pipe( uniqBy(({ source, item }) => source.sourceId === "querySuggestionsPlugin" ? item.query : item.label, ), limit(4), ); autocomplete({ container: "#autocomplete", plugins: [recentSearchesPlugin, querySuggestionsPlugin], reshape({ sourcesBySourceId }) { const { recentSearchesPlugin, querySuggestionsPlugin, ...rest } = sourcesBySourceId; return [ combineSuggestions(recentSearchesPlugin, querySuggestionsPlugin), Object.values(rest), ]; }, }); ``` > [!NOTE] > > For your reshape functions to support `pipe`, > you need to make sure your sources are one level deep, > for example using [`Array.flat`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/flat). # Send Algolia Insights events Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/sending-algolia-insights-events Learn how to send Algolia Insights events from your autocomplete menu. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). With Autocomplete, you can automatically send click and view events with the [`insights`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-insights) parameter. [Sending events](/doc/guides/sending-events) lets Algolia [recommend](/doc/guides/algolia-recommend/overview) products, adjust the ranking dynamically ([Dynamic Re-Ranking](/doc/guides/algolia-ai/re-ranking) and [Personalization](/doc/guides/personalization/classic-personalization/what-is-personalization)), and lets you get better insights with [Analytics](/doc/guides/search-analytics/overview) and [A/B Testing](/doc/guides/ab-testing/what-is-ab-testing). > [!NOTE] > > You can see a complete implementation in the [Autocomplete playground](https://github.com/algolia/autocomplete/tree/v1.7.4/examples/playground). ## Before you begin Before you can send events, you need to add the Autocomplete library to your project. For more information, see [Get started with Autocomplete](/doc/ui-libraries/autocomplete/introduction/getting-started). ## Create an Autocomplete instance Get started by creating a new file `index.js`. Add the following code to create an Autocomplete instance that includes results from an Algolia 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.) and [Query Suggestions](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches). ```js JavaScript theme={"system"} import { h, Fragment } from "preact"; import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; import algoliasearch from "algoliasearch"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams: () => ({ clickAnalytics: true }), }); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [querySuggestionsPlugin], getSources({ query }) { return [ { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, clickAnalytics: true, }, }, ], }); }, templates: { item({ item, components, html }) { return html`
    ${item.name}
    ${components.Highlight({ hit: item, attribute: "name" })}
    ${components.Snippet({ hit: item, attribute: "description", })}
    `; }, }, }, ]; }, }); ``` ```jsx JSX theme={"system"} import { h, Fragment } from "preact"; import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; import algoliasearch from "algoliasearch"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams: () => ({ clickAnalytics: true }), }); autocomplete({ container: "#autocomplete", openOnFocus: true, plugins: [querySuggestionsPlugin], getSources({ query }) { return [ { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, clickAnalytics: true, }, }, ], }); }, templates: { item({ item, components }) { return ; }, }, }, ]; }, }); function ProductItem({ hit, components }) { return (
    {hit.name}
    ); } ``` This code adds the Autocomplete menu to an element with the `id` `autocomplete`. To use a different selector, adjust the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) parameter. The [`openOnFocus`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) parameter ensures that the Autocomplete menu appears as soon as users focus the input. This searches an Algolia index of [ecommerce products](https://github.com/algolia/datasets/tree/master/ecommerce) using the [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-preset-algolia/getAlgoliaResults) function. ## Enable the `insights` option To automatically send events, turn the [`insights` option](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-insights) to `true` in your autocomplete instance. ```js JavaScript icon=code theme={"system"} // ... autocomplete({ container: "#autocomplete", placeholder: "Search products", openOnFocus: true, insights: true, getSources({ query }) { // ... }, }); // ... ``` To identify users consistently, such as authenticated users, use your own user token (A user token is a pseudonymous ID that represents an individual user across Algolia searches and events. It links queries, clicks, and conversions to a user profile, enabling user-level analytics, personalization, and recommendations.) with `setUserToken`. The `search-insights` library can create anonymous user tokens and store them in cookie. To use cookie-based anonymous tokens, initialize the library with [`useCookie`](/doc/libraries/search-insights/init#param-use-cookie) set to `true`: ```js JavaScript icon="code" theme={"system"} insightsClient("init", { appId, apiKey }); insightsClient("setUserToken", yourUserToken); // or insightsClient("init", { appId, apiKey, useCookie: true }); ``` In older versions of the `search-insights` library (earlier than version 2.0), cookie-based anonymous tokens are on by default. ## Manage the Insights library Autocomplete loads the [`search-insights`](/doc/libraries/search-insights) library for you from [jsDelivr](https://www.jsdelivr.com/). You don't need to install it or set it up yourself. If you're using a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to protect your site and you want to let Autocomplete load `search-insights` for you, make sure to add `https://cdn.jsdelivr.net` in your list of trusted sources for JavaScript. ```txt theme={"system"} script-src https://cdn.jsdelivr.net/ ``` If you prefer hosting your own version of `search-insights`, you can add it to your project: 1. [Install the Insights client](/doc/libraries/search-insights) 2. [Initialize the Insights client](/doc/libraries/search-insights/init) Autocomplete doesn't load `search-insights` when it detects it on the page. ## Default events After enabling the [`insights` option](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-insights), Autocomplete automatically sends the following events: | Event | Description | | - | - | | `Items Viewed` | [View events](/doc/libraries/sdk/v1/methods/viewed-object-ids) for results shown in the autocomplete menu. | | `Items Selected` | [Click events](/doc/libraries/sdk/v1/methods/clicked-object-ids-after-search) when users selects an item in the menu. | By default, Autocomplete doesn't send any conversion events. You can [send events from templates](#send-events-from-templates) manually. ## Customize events You can customize the Insights plugin by using one of these hooks: * [`onItemsChange`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-algolia-insights/createAlgoliaInsightsPlugin#param-on-items-change) * [`onSelect`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-algolia-insights/createAlgoliaInsightsPlugin#param-on-select) * [`onActive`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-algolia-insights/createAlgoliaInsightsPlugin#param-on-active) For example, you might want to use a different `eventName`. For more information, see the [examples](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-algolia-insights/createAlgoliaInsightsPlugin) in the `createAlgoliaInsightsPlugin` documentation. ## Send events from templates By default, Autocomplete doesn't send any conversion events. If your [templates](/doc/ui-libraries/autocomplete/core-concepts/templates) include calls to action, such as an **Add to cart button**, you can track interactions with them as conversion events. ![Screenshot of a search autocomplete drop-down menu showing laptop queries and product results with a red arrow pointing to a shopping bag icon.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/add-to-cart.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=771eab11627d2a0ee8acd3ea96c5579d) To send events from your templates, pass the Insights client to your templates: ```js JavaScript theme={"system"} autocomplete({ // ... insights: true, templates: { item({ item, components, state, html }) { return createProductItemTemplate({ hit: item, components, insights: state.context.algoliaInsightsPlugin.insights, html, }); }, }, }); function createProductItemTemplate() { // ... } ``` ```jsx JSX theme={"system"} autocomplete({ // ... insights: true, templates: { item({ item, components, state }) { return ( ); }, }, }); ``` Now, you can send events from your templates by using the [Insights client's methods](/doc/libraries/search-insights). For example, to track a conversion event when users click the **Add to cart button**, use the [`convertedObjectIDsAfterSearch`](/doc/libraries/search-insights/converted-object-ids-after-search) method: ```js JavaScript theme={"system"} function createProductItemTemplate({ hit, insights, components, html }) { return html`
    ${hit.name}
    ${components.Highlight({ hit, attribute: "name" })}
    ${components.Snippet({ hit, attribute: "description" })}
    `; } ``` ```jsx JSX theme={"system"} function ProductItem({ hit, insights, components }) { return (
    {hit.name}
    ); } ``` To see a complete implementation, check the [Autocomplete playground](https://github.com/algolia/autocomplete/tree/v1.7.4/examples/playground). ### Add-to-cart events When your users add an item to their cart, send a special `conversion` event with the `addToCart` subtype. ```js JavaScript icon=code theme={"system"} insights.convertedObjectIDsAfterSearch({ eventName: "Added to cart", index: hit.__autocomplete_indexName, objectIDs: [hit.objectID], queryID: hit.__autocomplete_queryID, // Special subtype eventSubtype: "addToCart", // An array of objects representing each item added to the cart objectData: [ { // The discount value for this item, if applicable discount: item.discount || 0, // The price value for this item (minus the discount) price: item.price, // How many of this item were added quantity: 2, }, ], // The total value of all items value: item.price * 2, // The currency code currency: "USD", }); ``` > [!NOTE] > > Fields representing monetary values accept both numbers and strings, in major currency units (for example, `5.45` or `'5.45'`). > To prevent floating-point math issues, use strings, especially if you're performing calculations. ### Purchase events When your users purchase an item, send a special `conversion` event with the `purchase` subtype. ```js JavaScript icon=code theme={"system"} insights.convertedObjectIDsAfterSearch({ eventName: "Added to cart", index: hit.__autocomplete_indexName, objectIDs: [hit.objectID], queryID: hit.__autocomplete_queryID, // Special subtype eventSubtype: "purchase", // An array of objects representing each purchased item objectData: [ { // The discount value for this item, if applicable discount: item.discount || 0, // The price value for this item (minus the discount) price: item.price, // How many of this item were added quantity: 2, }, ], // The total value of all items value: item.price * 2, // The currency code currency: "USD", }); ``` > [!NOTE] > > Fields representing monetary values accept both numbers and strings, in major currency units (for example, `5.45` or `'5.45'`). > To prevent floating-point math issues, use strings, especially if you're performing calculations. ## Validate your events Use the [events debugger](/doc/guides/sending-events/guides/validate#debugger) to verify that events are being sent. # Upgrade Autocomplete to v1 Source: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/upgrading Learn how to upgrade from Autocomplete v0 to Autocomplete v1. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). Autocomplete v1 offers many new features that are explained in [Core concepts](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options). Read this documentation to understand the new capabilities. ## Import ```js JavaScript icon=code theme={"system"} // v0 import autocomplete from "autocomplete.js"; // [!code --] // v1 import { autocomplete } from "@algolia/autocomplete-js"; // [!code ++] ``` ## Container Autocomplete now takes a container (for example, a `div`), not an `input`. Autocomplete generates a fully accessible search box for you. ```html HTML icon=code-xml theme={"system"}
    ``` Read more about [installation options](/doc/ui-libraries/autocomplete/introduction/getting-started#installation) in the [Getting Started](/doc/ui-libraries/autocomplete/introduction/getting-started) guide. ## Parameters These parameters and how you use them have changed from [Autocomplete v0](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options): | v0 | v1 | | - | - | | [`autocomplete('#autocomplete', ...)`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | [`autocomplete({ container: '#autocomplete', ...})`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) | | [`autoselect: true`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | `defaultActiveItemId: 0` | | [`autoselectOnBlur: true`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | No longer needed. This is the default behavior | | [`tabAutocomplete: true`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | No longer supported. v1 implements the ARIA 1.1 form of the [combobox design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) | | [`hint`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | No longer supported | | [`clearOnSelected`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | This is now local to the [source](/doc/ui-libraries/autocomplete/core-concepts/sources): [`getItemInputValue: () => ''`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-item-input-value) | | [`dropdownMenuContainer`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | [`panelContainer`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-panel-container) | | [`templates`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) (top-level) | [`templates`](/doc/ui-libraries/autocomplete/core-concepts/templates) (see also [`render`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-render) and [`renderNoResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-render-no-results)) | | [`cssClasses`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | [`classNames`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-class-names) where properties have changed | | [`keyboardShortcuts`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | No longer supported as an option. Check out the [keyboard navigation docs](/doc/ui-libraries/autocomplete/core-concepts/keyboard-navigation) | | [`minLength: 0`](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#global-options) | [`openOnFocus: true`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) | ## Datasets [Datasets](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#datasets) are replaced by the [`getSources`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-get-sources) function. Learn more about [Sources concept](/doc/ui-libraries/autocomplete/core-concepts/sources). ## Sources [Sources](https://github.com/algolia/autocomplete/blob/45fa32d008620cf52bf4a90530be338543dfba7f/README.md#sources) are replaced by [`getItems`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-items). ## Templates Autocomplete v1 renamed some templates, and uses an agnostic virtual DOM implementation to render them. ### Return HTML Unlike in v0, you can no longer return plain HTML strings to inject into the DOM. Yet, you can still return HTML strings using the provided `html` tagged function. > [!NOTE] > > The `html` function is available in Autocomplete v1.6.0 and later. ```tsx v0 theme={"system"} autocomplete( // ... [ { // ... templates: { header: '
    Products
    ', suggestion: function (item) { const name = item._highlightResult.name.value; return ``; }, }, }, ] ); ``` ```tsx v1 theme={"system"} autocomplete({ // ... getSources() { return [ { // ... templates: { header({ html }) { // You can still return the same HTML strings using the provided // `html` tagged template. return html`
    Products
    `; }, item({ item, components, html }) { // Highlighting and snippeting now comes as a provided component to // which you can pass the full item and the attribute to highlight. return html``; }, }, }, ]; }, }); ``` See the [Returning HTML](/doc/ui-libraries/autocomplete/core-concepts/templates#return-html) section of the [Templates](/doc/ui-libraries/autocomplete/core-concepts/templates) guide for a full overview and to learn how to support Internet Explorer 11. > [!NOTE] > > If you're already using a virtual DOM in your app, > you can [provide it to Autocomplete](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-renderer) and [directly return JSX from templates](/doc/ui-libraries/autocomplete/core-concepts/templates#return-virtual-nodes-directly). ### Renamed templates Some templates from v0 were renamed in v1. * The `suggestion` template is now [`item`](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-item). * The `empty` template is now [`noResults`](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-no-results). For more information, see [Templates](/doc/ui-libraries/autocomplete/core-concepts/templates). ## New `renderer.render` option Starting from v1.6.0, Autocomplete introduces a new `render` property in the `renderer` option to pass a custom `render` function when using Autocomplete with a custom virtual DOM implementation (React, Vue). If you were using the `render` option to specify a custom `render` function, you can now remove it and pass the function to `renderer` instead. ```jsx JSX icon=code theme={"system"} import { createElement, Fragment } from "react"; import { render } from "react-dom"; autocomplete({ // ... renderer: { createElement, Fragment }, // [!code --] renderer: { createElement, Fragment, render }, // [!code ++] render({ children }, root) { // [!code --:2] render(children, root); }, }); ``` If you were using the `render` option to customize the layout, you can also use the new `renderer.render` option and access your custom `render` function in the provided `render` parameters. ```jsx JSX icon=code theme={"system"} import { createElement, Fragment } from "react"; import { render } from "react-dom"; // [!code --] import { render as reactRender } from "react-dom"; // [!code ++] autocomplete({ // ... renderer: { createElement, Fragment }, // [!code --] renderer: { createElement, Fragment, render: reactRender }, // [!code ++] render({ children }, root) { // [!code --] render({ children, render }, root) { // [!code ++] render(`
    ${children}
    `, root); }, }); ``` ## Top-level API ```js JavaScript icon=code theme={"system"} const search = autocomplete(/* ... */); search.autocomplete.open(); // [!code --:5] search.autocomplete.close(); search.autocomplete.getVal(); search.autocomplete.setVal("Query"); search.autocomplete.destroy(); search.setIsOpen(true); // [!code ++:4] search.setIsOpen(false); search.setQuery("Query"); search.destroy(); ``` ## Full example ```jsx v0 theme={"system"} import autocomplete from "autocomplete.js"; autocomplete( "#autocomplete", { cssClasses: { dropdownMenu: "search-suggestion-wrapper", }, }, [ { source: autocomplete.sources.hits(searchClient.initIndex("products"), { hitsPerPage: 3, distinct: true, }), templates: { header: '
    Products
    ', suggestion: function (item) { const name = item._highlightResult.name.value; return ``; }, }, }, ], ); ``` ```jsx v1 theme={"system"} import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; autocomplete({ container: "#autocomplete", classNames: { panel: "search-suggestion-wrapper", }, getSources({ query }) { return [ { sourceId: "products", getItems({ query }) { return getAlgoliaResults({ searchClient, queries: [ { indexName: "products", params: { query, hitsPerPage: 3, distinct: true, }, }, ], }); }, templates: { header({ createElement }) { return createElement("div", { class: "header" }, "Products"); }, item({ item, createElement, components }) { return createElement( "div", null, createElement( "img", { class: "thumbnail", src: item.image }, createElement( "a", { href: item.url }, components.Highlight({ hit: item, attribute: "name", tagName: "em", }), ), ), ); }, }, }, ]; }, }); ``` ## Upgrade the Algolia JavaScript API client from v4 to v5 Upgrading from `algoliasearch` v4 to v5 doesn't introduce any breaking changes. However, when you use the `query` in the search parameters, you need to use the `query` parameter instead of the `query` property. ```js JavaScript icon=code theme={"system"} getAlgoliaResults({ searchClient, queries: [ { indexName: "YOUR_INDEX_NAME", query, // [!code --] params: { // [!code ++:3] query, }, }, ], }); ``` # Use Autocomplete with React Source: https://www.algolia.com/doc/ui-libraries/autocomplete/integrations/using-react Learn how to create and use a React Autocomplete component. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). This guide uses the [`useRef`](https://react.dev/reference/react/useRef) and [`useEffect`](https://react.dev/reference/react/useEffect) Hooks to create and mount the component. It doesn't define specific [sources](/doc/ui-libraries/autocomplete/core-concepts/sources). Rather, you can pass [sources](/doc/ui-libraries/autocomplete/core-concepts/sources) and other [options](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options) as [props](https://react.dev/learn/passing-props-to-a-component). ## Before you begin The guide assumes you're familiar with the [basic Autocomplete configuration options](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options) and have an existing [React (v16.8+) app](https://reactjs.org/docs/getting-started.html) to which you want to add an Autocomplete menu. ## Create the component Add the following starter code for creating a React component. This component uses the [`useRef`](https://react.dev/reference/react/useRef) Hook to create a mutable ref object, `containerRef`, for mounting the autocomplete. All that you need to render is a `div` with the `containerRef` as the `ref`. ```jsx JSX icon=code theme={"system"} import React, { Fragment, useEffect, useRef } from "react"; export function Autocomplete(props) { const containerRef = useRef(null); useEffect(() => { if (!containerRef.current) { return undefined; } }, [props]); return
    ; } ``` ### Attach the autocomplete menu to the DOM Now that you have access to the DOM through the `containerRef` object, you can create and mount the Autocomplete instance. Upon instantiation, you can include any desired [Autocomplete options](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options) and rely on `props` to pass any options you want to remain configurable. The following examples specify where to mount your Autocomplete component with the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) option, but lets all other [options](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options) get configured through props. The default Autocomplete implementation uses [Preact](https://preactjs.com/) APIs to create and render elements, so you need to replace them with React APIs to properly render the views. **Depending on your React version, the implementation slightly differs**. In both cases, clean up the effect by returning a function that destroys the Autocomplete instance. #### With React 18 With [React 18](https://react.dev/blog/2022/03/29/react-v18), only pass React `createElement` and `Fragment` to the [`renderer`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-renderer) option. To silence the warning from Autocomplete, you can pass an empty function to `renderer.render`. Instead, use the [`render`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-render) option to create a `Root` object with the React `createRoot` function, then use this object to render. ```jsx JSX icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import React, { createElement, Fragment, useEffect, useRef } from "react"; import { createRoot } from "react-dom/client"; export function Autocomplete(props) { const containerRef = useRef(null); const panelRootRef = useRef(null); const rootRef = useRef(null); useEffect(() => { if (!containerRef.current) { return undefined; } const search = autocomplete({ container: containerRef.current, renderer: { createElement, Fragment, render: () => {} }, render({ children }, root) { if (!panelRootRef.current || rootRef.current !== root) { rootRef.current = root; panelRootRef.current?.unmount(); panelRootRef.current = createRoot(root); } panelRootRef.current.render(children); }, ...props, }); return () => { search.destroy(); }; }, [props]); return
    ; } ``` #### With React 16.8 or 17 With React 16.8 or 17, you need to import `createElement`, `Fragment`, and `render` from React and provide them to the Autocomplete [`renderer`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-renderer) option. ```jsx JSX icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import React, { createElement, Fragment, useEffect, useRef } from "react"; import { render } from "react-dom"; export function Autocomplete(props) { const containerRef = useRef(null); useEffect(() => { if (!containerRef.current) { return undefined; } const search = autocomplete({ container: containerRef.current, renderer: { createElement, Fragment, render }, ...props, }); return () => { search.destroy(); }; }, [props]); return
    ; } ``` ## Use the component Now that you've created an `` component, you can use it in your React application. The usage below sets [`openOnFocus`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-open-on-focus) and [sources](/doc/ui-libraries/autocomplete/core-concepts/sources) through props. This example uses an [Algolia index](https://support.algolia.com/hc/en-us/articles/4406981910289-What-is-an-index-) as a [source](/doc/ui-libraries/autocomplete/core-concepts/sources), but you could use anything else you want, including [plugins](/doc/ui-libraries/autocomplete/core-concepts/plugins). For more information about using Algolia as a source, see [Getting started](/doc/ui-libraries/autocomplete/introduction/getting-started). ```jsx JSX icon=code theme={"system"} import React, { createElement } from "react"; import { getAlgoliaResults } from "@algolia/autocomplete-js"; import algoliasearch from "algoliasearch"; import { Autocomplete } from "./components/Autocomplete"; import { ProductItem } from "./components/ProductItem"; const appId = "latency"; const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; const searchClient = algoliasearch(appId, apiKey); function App() { return (

    React Application

    [ { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", query, }, ], }); }, templates: { item({ item, components }) { return ; }, }, }, ]} />
    ); } export default App; ``` ### Create templates The example preceding passes ``, another React component, for the [`item` template](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-item). ```jsx JSX icon=code theme={"system"} import React, { createElement } from "react"; export function ProductItem({ hit, components }) { return (
    ); } ``` ## Further UI customization If you want to build a custom UI that differs from the `autocomplete-js` output, check out the [guide on creating a custom renderer](/doc/ui-libraries/autocomplete/guides/creating-a-renderer). This guide outlines how to create a custom React renderer specifically, but the underlying principles are the same for any other frontend framework. # Use Autocomplete with Vue Source: https://www.algolia.com/doc/ui-libraries/autocomplete/integrations/using-vue Learn how to embed Autocomplete into a Vue application. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). You can integrate an Autocomplete instance into a Vue application using [Vue's Composition API](https://vuejs.org/guide/extras/composition-api-faq.html#why-composition-api). Specifically you can instantiate an Autocomplete instance in the [`onMounted`](https://v3.vuejs.org/api/composition-api.html#lifecycle-hooks) lifecycle hook in the [`setup`](https://v3.vuejs.org/guide/composition-api-setup.html) function. This example uses an [Algolia index](https://support.algolia.com/hc/en-us/articles/4406981910289-What-is-an-index-) of [ecommerce products](https://github.com/algolia/datasets/tree/master/ecommerce) as a source. You could use any other source or sources you like. ## Before you begin The guide assumes that you're familiar with the [basic Autocomplete configuration options](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options) and have an existing [Vue 3 app](https://v3.vuejs.org/) to which you want to add an Autocomplete menu. ## Get started Start by adding a container for your autocomplete menu. This example adds a `div` with `autocomplete` as an [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id). ```jsx Vue icon=code theme={"system"} ; ``` Then, import the necessary packages for a basic implementation. Since the example queries an Algolia index, it imports the [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) package, [`autocomplete`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete) and [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaResults) from the [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) package. Finally, it imports [`autocomplete-theme-classic`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic) package for some out of the box styling. Depending on your desired [sources](/doc/ui-libraries/autocomplete/core-concepts/sources), you may need to import other packages including [plugins](/doc/ui-libraries/autocomplete/core-concepts/plugins). Include some starter code to insert the autocomplete menu into: ```jsx Vue icon=code theme={"system"} ``` ## Add an Algolia source The [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) package provides a built-in [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaResults) function for querying an Algolia index. It requires an [Algolia search client](/doc/libraries/sdk/install) initialized with an [Algolia application ID and API key](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api#required-credentials). It lets you search into your Algolia index using an array of `queries`, which defines one or more queries to send to the index. For more information how to use the [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaResults) function, see the [Getting Started guide](/doc/ui-libraries/autocomplete/introduction/getting-started). ## Attach the autocomplete menu to the DOM You can instantiate and mount your Autocomplete instance in the [`onMounted`](https://vuejs.org/api/composition-api-lifecycle.html#onmounted) lifecycle hook in the [`setup`](https://v3.vuejs.org/guide/composition-api-setup.html) function. Doing so requires passing the `renderer` and `render` parameters. This is because the default Autocomplete implementation uses [Preact's](https://preactjs.com/) version of `createElement`, `Fragment` and `render`. Without providing Vue's version of these, the Autocomplete instance won't render the views properly. ```jsx Vue icon=code theme={"system"} ``` ## Customize templates Next, to display the results from Algolia, you need to define an [`item` template](/doc/ui-libraries/autocomplete/core-concepts/templates#param-templates-item). ```jsx Vue icon=code theme={"system"} ``` Use JSX syntax for your templates. ## Further UI customization If you want to build a custom UI that differs from the `autocomplete-js` output, check out the [guide on creating a custom renderer](/doc/ui-libraries/autocomplete/guides/creating-a-renderer). This guide outlines how to create a custom React renderer, but the underlying principles are the same for any other frontend framework. # Integrate Autocomplete with InstantSearch.js Source: https://www.algolia.com/doc/ui-libraries/autocomplete/integrations/with-instantsearch Learn how to use Autocomplete with InstantSearch.js. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). When you think of search experiences on sites like Amazon (ecommerce) or YouTube (media), you may notice that both sites use an autocomplete experience. It's the autocomplete, and not just a search input, that powers the search page. If you have an existing InstantSearch implementation, **you can create a similar experience by adding Autocomplete to your InstantSearch application**. Adding Autocomplete to an existing InstantSearch implementation lets you enhance the search experience and create a richer, more contextual search. You can use context from the current user and how they interacted with your site, save their recent searches, provide suggested queries, and more. This autocomplete can work as a rich search box in a search page, and a portable all-in-one search experience anywhere else on your site. This guide shows you how to integrate Autocomplete with InstantSearch on your site. ![Screenshot of a search interface with 'air' entered, showing autocomplete suggestions and product results for Apple devices.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/autocomplete-with-instantsearch.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=db03bbff6fe7a511775e455e41f960cc) > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/instantsearch?file=/src/app.ts) > > Run and edit the Autocomplete with InstantSearch.js example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/autocomplete/tree/next/examples/instantsearch) > > Browse the source for the Autocomplete with InstantSearch.js example on GitHub. This guide starts from a brand new InstantSearch application, but you can adapt it to integrate Autocomplete in your existing implementation. ## Create a search page with InstantSearch First, begin with some starter code. In a new project, create an `index.html` file and add the following code: ```html HTML icon=code-xml theme={"system"} InstantSearch | Autocomplete
    ``` The search page uses a two-column layout with categories on the left, and a search box, hits, and a pagination widget on the right. Next, create the `app.js` file to instantiate InstantSearch. ```js JavaScript icon=code theme={"system"} // app.js import { liteClient as algoliasearch } from "algoliasearch/lite"; import instantsearch from "instantsearch.js"; import historyRouter from "instantsearch.js/es/lib/routers/history"; import { searchBox, hierarchicalMenu, hits, pagination, } from "instantsearch.js/es/widgets"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); const INSTANT_SEARCH_INDEX_NAME = "instant_search"; const instantSearchRouter = historyRouter(); const search = instantsearch({ searchClient, indexName: INSTANT_SEARCH_INDEX_NAME, routing: instantSearchRouter, }); search.addWidgets([ searchBox({ container: "#searchbox", placeholder: "Search for products", }), hierarchicalMenu({ container: "#categories", attributes: ["hierarchicalCategories.lvl0", "hierarchicalCategories.lvl1"], }), hits({ container: "#hits", }), pagination({ container: "#pagination", }), ]); search.start(); ``` This InstantSearch implementation imports the default `historyRouter` router which you want to reuse in your Autocomplete integration. This lets InstantSearch understand query parameters from the URL to derive its state. If your Algolia 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.) doesn't have hierarchical attributes, you can use a regular attribute with a `menu` widget. You now have a working InstantSearch application. ## Use Autocomplete as a search box InstantSearch ships with a `searchBox` component, but it doesn't provide autocomplete features like you see on YouTube and Amazon. Instead, you can replace the `searchBox` with an autocomplete experience, using Autocomplete. Start by removing the InstantSearch `#searchbox` container and adding the Autocomplete container, named `#autocomplete` here. ```html HTML icon=code-xml theme={"system"} InstantSearch | Autocomplete
    ``` You want the Autocomplete experience to be available on all the pages of the site, not only the search page, so you can move its container to a header or navigation bar instead of the page content. Next, you can replace the `searchBox` from your InstantSearch implementation with a virtual widget and instantiate Autocomplete in your `app.js` file. ```js JavaScript icon=code theme={"system"} // app.js import { autocomplete } from "@algolia/autocomplete-js"; import { liteClient as algoliasearch } from "algoliasearch/lite"; import instantsearch from "instantsearch.js"; import historyRouter from "instantsearch.js/es/lib/routers/history"; import { connectSearchBox } from "instantsearch.js/es/connectors"; import { hierarchicalMenu, hits, pagination, } from "instantsearch.js/es/widgets"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); const INSTANT_SEARCH_INDEX_NAME = "instant_search"; const instantSearchRouter = historyRouter(); const search = instantsearch({ searchClient, indexName: INSTANT_SEARCH_INDEX_NAME, routing: instantSearchRouter, }); // Mount a virtual search box to manipulate InstantSearch's `query` UI // state parameter. const virtualSearchBox = connectSearchBox(() => {}); search.addWidgets([ virtualSearchBox({}), hierarchicalMenu({ container: "#categories", attributes: ["hierarchicalCategories.lvl0", "hierarchicalCategories.lvl1"], }), hits({ container: "#hits", templates: { item(hit, { html, components }) { return html`
    ${components.Highlight({ attribute: "name", hit })}
    `; }, }, }), pagination({ container: "#pagination", }), ]); search.start(); // Set the InstantSearch index UI state from external events. function setInstantSearchUiState(indexUiState) { search.setUiState((uiState) => ({ ...uiState, [INSTANT_SEARCH_INDEX_NAME]: { ...uiState[INSTANT_SEARCH_INDEX_NAME], // We reset the page when the search state changes. page: 1, ...indexUiState, }, })); } // Return the InstantSearch index UI state. function getInstantSearchUiState() { const uiState = instantSearchRouter.read(); return (uiState && uiState[INSTANT_SEARCH_INDEX_NAME]) || {}; } const searchPageState = getInstantSearchUiState(); let skipInstantSearchUiStateUpdate = false; const { setQuery } = autocomplete({ container: "#autocomplete", placeholder: "Search for products", detachedMediaQuery: "none", initialState: { query: searchPageState.query || "", }, onSubmit({ state }) { setInstantSearchUiState({ query: state.query }); }, onReset() { setInstantSearchUiState({ query: "" }); }, onStateChange({ prevState, state }) { if (!skipInstantSearchUiStateUpdate && prevState.query !== state.query) { setInstantSearchUiState({ query: state.query }); } skipInstantSearchUiStateUpdate = false; }, }); // This keeps Autocomplete aware of state changes coming from routing // and updates its query accordingly window.addEventListener("popstate", () => { skipInstantSearchUiStateUpdate = true; setQuery(search.helper?.state.query || ""); }); ``` This replaces the InstantSearch search box with Autocomplete, and acts exactly like before. But you can now add many more interesting features. ## Add recent searches When you make a search on YouTube or Google and come back to the search box later on, the autocomplete displays your recent searches. This pattern lets users quickly access content by using the same path they took to find it in the first place. Autocomplete lets you add recent searches with the `@algolia/autocomplete-plugin-recent-searches` package. It exposes a [`createLocalStorageRecentSearchesPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/createLocalStorageRecentSearchesPlugin) function to let you create a recent searches plugin. ```js JavaScript theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; // ... // Build URLs that InstantSearch understands. function getInstantSearchUrl(indexUiState) { return search.createURL({ [INSTANT_SEARCH_INDEX_NAME]: indexUiState }); } // Detect when an event is modified with a special key to let the browser // trigger its default behavior. function isModifierEvent(event) { const isMiddleClick = event.button === 1; return ( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey ); } function onSelect({ setIsOpen, setQuery, event, query }) { // You want to trigger the default browser behavior if the event is modified. if (isModifierEvent(event)) { return; } setQuery(query); setIsOpen(false); setInstantSearchUiState({ query }); } function getItemUrl({ query }) { return getInstantSearchUrl({ query }); } function createItemWrapperTemplate({ children, query, html }) { const uiState = { query }; return html` ${children} `; } const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "instantsearch", limit: 3, transformSource({ source }) { return { ...source, getItemUrl({ item }) { return getItemUrl({ query: item.label, }); }, onSelect({ setIsOpen, setQuery, item, event }) { onSelect({ setQuery, setIsOpen, event, query: item.label, }); }, // Update the default `item` template to wrap it with a link // and plug it to the InstantSearch router. templates: { ...source.templates, item(params) { const { children } = source.templates.item(params).props; return createItemWrapperTemplate({ query: params.item.label, children, html: params.html, }); }, }, }; }, }); const { setQuery } = autocomplete({ // You want recent searches to appear with an empty query. openOnFocus: true, // Add the recent searches plugin. plugins: [recentSearchesPlugin], // ... }); ``` ```jsx JSX theme={"system"} /** @jsx h */ import { autocomplete } from "@algolia/autocomplete-js"; import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; import { h } from "preact"; // ... // Build URLs that InstantSearch understands. function getInstantSearchUrl(indexUiState) { return search.createURL({ [INSTANT_SEARCH_INDEX_NAME]: indexUiState }); } // Detect when an event is modified with a special key to let the browser // trigger its default behavior. function isModifierEvent(event) { const isMiddleClick = event.button === 1; return ( isMiddleClick || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey ); } function onSelect({ setIsOpen, setQuery, event, query }) { // You want to trigger the default browser behavior if the event is modified. if (isModifierEvent(event)) { return; } setQuery(query); setIsOpen(false); setInstantSearchUiState({ query }); } function getItemUrl({ query }) { return getInstantSearchUrl({ query }); } function ItemWrapper({ children, query }) { const uiState = { query }; return ( { if (!isModifierEvent(event)) { // Bypass the original link behavior if there's no event modifier // to set the InstantSearch UI state without reloading the page. event.preventDefault(); } }} > {children} ); } const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "instantsearch", limit: 3, transformSource({ source }) { return { ...source, getItemUrl({ item }) { return getItemUrl({ query: item.label, }); }, onSelect({ setIsOpen, setQuery, item, event }) { onSelect({ setQuery, setIsOpen, event, query: item.label, }); }, // Update the default `item` template to wrap it with a link // and plug it to the InstantSearch router. templates: { ...source.templates, item(params) { const { children } = source.templates.item(params).props; return ( {children} ); }, }, }; }, }); const { setQuery } = autocomplete({ // You want recent searches to appear with an empty query. openOnFocus: true, // Add the recent searches plugin. plugins: [recentSearchesPlugin], // ... }); ``` Some of this code is abstracted away in the following sections to simplify the examples. Since the `recentSearchesPlugin` reads from [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), you can't see any recent searches until you perform at least one query. To submit a search, make sure to press Enter on the query. Once you do, you'll see it appear as a recent search. See also: * [Recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches) * [Recent searches plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches) ## Add Query Suggestions **The most typical pattern you can see on every autocomplete is suggestions**. They're predictions of queries that match what users are currently typing, and that guarantee to return results. For example, when typing "how to" in Google, the search engine suggests matching suggestions for users to complete their query. It's especially useful on mobile, where typing is harder than on a physical keyboard. Autocomplete lets you add Query Suggestions with the `@algolia/autocomplete-plugin-query-suggestions` package. It exposes a [`createQuerySuggestionsPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin) function to let you create a Query Suggestions plugin. This plugin requires a [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) index. ```js JavaScript theme={"system"} // ... import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; // ... const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams() { // This creates a shared `hitsPerPage` value once the duplicates // between recent searches and Query Suggestions are removed. return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6, }); }, transformSource({ source }) { return { ...source, sourceId: "querySuggestionsPlugin", getItemUrl({ item }) { return getItemUrl({ query: item.query, }); }, onSelect({ setIsOpen, setQuery, event, item }) { onSelect({ setQuery, setIsOpen, event, query: item.query, }); }, getItems(params) { // We don't display Query Suggestions when there's no query. if (!params.state.query) { return []; } return source.getItems(params); }, templates: { ...source.templates, item(params) { const { children } = source.templates.item(params).props; return createItemWrapperTemplate({ query: params.item.label, children, html: params.html, }); }, }, }; }, }); const { setQuery } = autocomplete({ // ... // Add the recent searches and Query Suggestions plugins. plugins: [recentSearchesPlugin, querySuggestionsPlugin], }); ``` ```jsx JSX theme={"system"} // ... import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; // ... const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams() { // This creates a shared `hitsPerPage` value once the duplicates // between recent searches and Query Suggestions are removed. return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6, }); }, transformSource({ source }) { return { ...source, sourceId: "querySuggestionsPlugin", getItemUrl({ item }) { return getItemUrl({ query: item.query, }); }, onSelect({ setIsOpen, setQuery, event, item }) { onSelect({ setQuery, setIsOpen, event, query: item.query, }); }, getItems(params) { // We don't display Query Suggestions when there's no query. if (!params.state.query) { return []; } return source.getItems(params); }, templates: { ...source.templates, item(params) { const { children } = source.templates.item(params).props; return ( {children} ); }, }, }; }, }); const { setQuery } = autocomplete({ // ... // Add the recent searches and Query Suggestions plugins. plugins: [recentSearchesPlugin, querySuggestionsPlugin], }); ``` See also: * [Search suggestions](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) * [Autocomplete Query Suggestions plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) ## Debounce search results Having two sets of results update as you type generates many UI flashes. This is distracting for users, because two distinct sections of the page are competing for their attention. You can mitigate this problem by debouncing search results. ```js JavaScript icon=code theme={"system"} function debounce(fn, time) { let timerId = undefined; return function (...args) { if (timerId) { clearTimeout(timerId); } timerId = setTimeout(() => fn(...args), time); }; } const debouncedSetInstantSearchUiState = debounce(setInstantSearchUiState, 500); const { setQuery } = autocomplete({ // ... onStateChange({ prevState, state }) { if (!skipInstantSearchUiStateUpdate && prevState.query !== state.query) { debouncedSetInstantSearchUiState({ query: state.query }); } skipInstantSearchUiStateUpdate = false; }, }); ``` ## Support categories in Query Suggestions **A key feature to Autocomplete is to pre-configure your InstantSearch page**. The Query Suggestions plugin supports categories that you can use to refine both the query and the category in a single interaction This pattern brings users to the right category without interacting with the [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js) widget, only with the autocomplete. **1. Add category support to the helpers** You need to support categories in the helpers you created at the beginning: ```js JavaScript theme={"system"} function onSelect({ setIsOpen, setQuery, event, query, category }) { // You want to trigger the default browser behavior if the event is modified. if (isModifierEvent(event)) { return; } setQuery(query); setIsOpen(false); setInstantSearchUiState({ query, hierarchicalMenu: { [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category], }, }); } function getItemUrl({ query, category }) { return getInstantSearchUrl({ query, hierarchicalMenu: { [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category], }, }); } function createItemWrapperTemplate({ children, query, category, html }) { const uiState = { query, hierarchicalMenu: { [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category], }, }; return html` ${children} `; } ``` ```jsx JSX theme={"system"} function onSelect({ setIsOpen, setQuery, event, query, category }) { // You want to trigger the default browser behavior if the event is modified. if (isModifierEvent(event)) { return; } setQuery(query); setIsOpen(false); setInstantSearchUiState({ query, hierarchicalMenu: { [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category], }, }); } function getItemUrl({ query, category }) { return getInstantSearchUrl({ query, hierarchicalMenu: { [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category], }, }); } function ItemWrapper({ children, query, category }) { const uiState = { query, hierarchicalMenu: { [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category], }, }; return ( { if (!isModifierEvent(event)) { // Bypass the original link behavior if there's no event modifier // to set the InstantSearch UI state without reloading the page. event.preventDefault(); } }} > {children} ); } ``` **2. Send category to the helpers** Update the plugins to forward the category to the helpers: ```js JavaScript theme={"system"} const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "instantsearch", limit: 3, transformSource({ source }) { return { ...source, getItemUrl({ item }) { return getItemUrl({ query: item.label, category: item.category, }); }, onSelect({ setIsOpen, setQuery, item, event }) { onSelect({ setQuery, setIsOpen, event, query: item.label, category: item.category, }); }, templates: { ...source.templates, // Update the default `item` template to wrap it with a link // and plug it to the InstantSearch router. item(params) { const { children } = source.templates.item(params).props; return createItemWrapperTemplate({ query: params.item.label, category: params.item.category, children, html: params.html, }); }, }, }; }, }); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams() { // This creates a shared `hitsPerPage` value once the duplicates // between recent searches and Query Suggestions are removed. return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6, }); }, // Add categories to the suggestions. categoryAttribute: [ INSTANT_SEARCH_INDEX_NAME, "facets", "exact_matches", INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE, ], transformSource({ source }) { return { ...source, sourceId: "querySuggestionsPlugin", getItemUrl({ item }) { return getItemUrl({ query: item.query, category: item.__autocomplete_qsCategory, }); }, onSelect({ setIsOpen, setQuery, event, item }) { onSelect({ setQuery, setIsOpen, event, query: item.query, category: item.__autocomplete_qsCategory, }); }, getItems(params) { if (!params.state.query) { return []; } return source.getItems(params); }, templates: { ...source.templates, item(params) { const { children } = source.templates.item(params).props; return createItemWrapperTemplate({ query: params.item.label, category: params.item.__autocomplete_qsCategory, children, html: params.html, }); }, }, }; }, }); ``` ```jsx JSX theme={"system"} const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ key: "instantsearch", limit: 3, transformSource({ source }) { return { ...source, getItemUrl({ item }) { return getItemUrl({ query: item.label, category: item.category, }); }, onSelect({ setIsOpen, setQuery, item, event }) { onSelect({ setQuery, setIsOpen, event, query: item.label, category: item.category, }); }, templates: { ...source.templates, // Update the default `item` template to wrap it with a link // and plug it to the InstantSearch router. item(params) { const { children } = source.templates.item(params).props; return ( {children} ); }, }, }; }, }); const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams() { // This creates a shared `hitsPerPage` value once the duplicates // between recent searches and Query Suggestions are removed. return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6, }); }, // Add categories to the suggestions. categoryAttribute: [ INSTANT_SEARCH_INDEX_NAME, "facets", "exact_matches", INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE, ], transformSource({ source }) { return { ...source, sourceId: "querySuggestionsPlugin", getItemUrl({ item }) { return getItemUrl({ query: item.query, category: item.__autocomplete_qsCategory, }); }, onSelect({ setIsOpen, setQuery, event, item }) { onSelect({ setQuery, setIsOpen, event, query: item.query, category: item.__autocomplete_qsCategory, }); }, getItems(params) { if (!params.state.query) { return []; } return source.getItems(params); }, templates: { ...source.templates, item(params) { const { children } = source.templates.item(params).props; return ( {children} ); }, }, }; }, }); ``` **3. Reset the InstantSearch category** Implement the `onReset` function on your Autocomplete instance to also reset the InstantSearch category. ```js JavaScript icon=code theme={"system"} const { setQuery } = autocomplete({ // ... onReset() { setInstantSearchUiState({ query: "", hierarchicalMenu: { [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [], }, }); }, // ... }); ``` ## Add contextual Query Suggestions For an even richer Autocomplete experience, you can pick up the currently active InstantSearch category and provide suggestions for both this specific category and others. This pattern lets you reduce the scope of the search to the current category, like an actual department store, or broaden the suggestions to get out of the current category. ![Query Suggestions with current InstantSearch category](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/autocomplete-with-instantsearch-current-category.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=b160688d94624c0e32c46df57db17e58) First, make sure to set your category attribute as a facet in your Query Suggestions index. In this demo, the attribute to facet is `instant_search.facets.exact_matches.hierarchicalCategories.lvl0.value`. ```js JavaScript theme={"system"} // ... // Get the current category from InstantSearch. function getInstantSearchCurrentCategory() { const indexRenderState = search.renderState[INSTANT_SEARCH_INDEX_NAME]; const hierarchicalMenuUiState = indexRenderState && indexRenderState.hierarchicalMenu; const categories = (hierarchicalMenuUiState && hierarchicalMenuUiState[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE] && hierarchicalMenuUiState[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE].items) || []; const refinedCategory = categories.find((category) => category.isRefined); return refinedCategory && refinedCategory.value; } // Query Suggestions plugin for the current category. const querySuggestionsPluginInCategory = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams() { const currentCategory = getInstantSearchCurrentCategory(); return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 3, facetFilters: [ `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE}.value:${currentCategory}`, ], }); }, transformSource({ source }) { const currentCategory = getInstantSearchCurrentCategory(); return { ...source, sourceId: "querySuggestionsPluginInCategory", getItemUrl({ item }) { return getItemUrl({ query: item.query, category: currentCategory, }); }, onSelect({ setIsOpen, setQuery, event, item }) { onSelect({ setQuery, setIsOpen, event, query: item.query, category: currentCategory, }); }, getItems(params) { if (!currentCategory) { return []; } return source.getItems(params); }, templates: { ...source.templates, header({ items, html }) { if (items.length === 0) { return null; } return html`In ${currentCategory}
    `; }, item(params) { const { children, html } = source.templates.item(params).props; return createItemWrapperTemplate({ query: params.item.query, category: currentCategory, children, html, }); }, }, }; }, }); // Query Suggestions plugin for the other categories. const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... getSearchParams() { const currentCategory = getInstantSearchCurrentCategory(); if (!currentCategory) { return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6, }); } return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 3, facetFilters: [ `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE}.value:-${currentCategory}`, ], }); }, transformSource({ source }) { const currentCategory = getInstantSearchCurrentCategory(); return { ...source, // ... templates: { ...source.templates, header({ items, html }) { if (!currentCategory || items.length === 0) { return null; } return html`In other categories
    `; }, // ... }, }; }, }); ``` ```jsx JSX theme={"system"} /** @jsx h */ import { h, Fragment } from "preact"; // ... // Get the current category from InstantSearch. function getInstantSearchCurrentCategory() { const indexRenderState = search.renderState[INSTANT_SEARCH_INDEX_NAME]; const hierarchicalMenuUiState = indexRenderState && indexRenderState.hierarchicalMenu; const categories = (hierarchicalMenuUiState && hierarchicalMenuUiState[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE] && hierarchicalMenuUiState[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE].items) || []; const refinedCategory = categories.find((category) => category.isRefined); return refinedCategory && refinedCategory.value; } // Query Suggestions plugin for the current category. const querySuggestionsPluginInCategory = createQuerySuggestionsPlugin({ searchClient, indexName: "instant_search_demo_query_suggestions", getSearchParams() { const currentCategory = getInstantSearchCurrentCategory(); return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 3, facetFilters: [ `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE}.value:${currentCategory}`, ], }); }, transformSource({ source }) { const currentCategory = getInstantSearchCurrentCategory(); return { ...source, sourceId: "querySuggestionsPluginInCategory", getItemUrl({ item }) { return getItemUrl({ query: item.query, category: currentCategory, }); }, onSelect({ setIsOpen, setQuery, event, item }) { onSelect({ setQuery, setIsOpen, event, query: item.query, category: currentCategory, }); }, getItems(params) { if (!currentCategory) { return []; } return source.getItems(params); }, templates: { ...source.templates, header({ items }) { if (items.length === 0) { return null; } return ( In {currentCategory}
    ); }, item(params) { const { children } = source.templates.item(params).props; return ( {children} ); }, }, }; }, }); // Query Suggestions plugin for the other categories. const querySuggestionsPlugin = createQuerySuggestionsPlugin({ // ... getSearchParams() { const currentCategory = getInstantSearchCurrentCategory(); if (!currentCategory) { return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 6, }); } return recentSearchesPlugin.data.getAlgoliaSearchParams({ hitsPerPage: 3, facetFilters: [ `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE}.value:-${currentCategory}`, ], }); }, transformSource({ source }) { const currentCategory = getInstantSearchCurrentCategory(); return { ...source, // ... templates: { ...source.templates, header({ items }) { if (!currentCategory || items.length === 0) { return null; } return ( In other categories
    ); }, // ... }, }; }, }); ``` ## Next steps **Autocomplete is now the main user interaction point driving InstantSearch to refine search results**. From now on, you're leveraging the complete Autocomplete ecosystem to bring a state-of-the-art search experience for desktop and mobile. You can now add Autocomplete everywhere on your site and redirect users to the search page whenever they submit a search, or after they select a suggestion. You can also use context from the current page to personalize the autocomplete experience. For example, you could display a preview of matching results in a panel for each suggestion, and let InstantSearch provide these results once on the search page. Code examples: * [Autocomplete with InstantSearch.js](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/instantsearch?file=/src/app.ts) * [Autocomplete with React InstantSearch](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/react-instantsearch?file=/src/App.tsx) * [Autocomplete with Vue InstantSearch](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/vue-instantsearch?file=/src/App.vue) # InstantSearch.js (Quickstart) Source: https://www.algolia.com/doc/ui-libraries/autocomplete/integrations/with-instantsearch-quickstart Learn how to use Autocomplete with InstantSearch.js. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). This guide uses [`create-instantsearch-app`](https://github.com/algolia/instantsearch/tree/master/packages/create-instantsearch-app#create-instantsearch-app) to help you quickly build a JavaScript search app that integrates InstantSearch and Autocomplete. ## Before you begin This guide requires [Node.js version 8 or later](https://nodejs.org/en/download/package-manager) and [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). ## Integrate InstantSearch with Autocomplete Open the terminal and run: ```sh Command line icon=square-terminal theme={"system"} npx create-instantsearch-app my-app --template "InstantSearch.js" ``` Accept the default options throughout the installation process by pressing `Enter`. However, you can replace any of the default options with your own. To ensure that your app integrates Autocomplete with InstantSearch, pay particular attention to these configuration options: * Attributes to display * Attributes for faceting * Search input ### Attributes to display Select name, description, and categories when prompted. ![Select name, description, and categories when prompted](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/quickstart-attributes-display.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=4ca6a844673608c69fb780b1dbc8debe) ### Attributes for faceting Choose brand when prompted. ![Screenshot of a terminal interface showing 'Attributes for faceting' with 'brand' selected from a list including 'type', 'price', 'rating', 'categories', and 'popularity'.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/quickstart-attributes-faceting.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=337ababebc088231dc2627640075e49d) ### Search input Select [Autocomplete](/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete) as your search input and use the default index for [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js). ![Select Autocomplete as your search input and use the default index for Query Suggestions](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/quickstart-autocomplete-as-input.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=94a509fb7d89b16466b325bfd4a2cce9) Selecting `Y` when being prompted to "Enable user events" activates the [`insights`](/doc/api-reference/widgets/instantsearch/js#param-insights) option, which allows Algolia to process your user events. Events can unlock powerful aspects which enhance your app's effectiveness. > [!TIP] > > For more details about events collection and settings, > review the [API reference](/doc/api-reference/widgets/instantsearch/js#param-insights). When `create-instantsearch-app` has finished, it will have created a new app, in the directory `my-app`, with everything you need. Go to the `my-app` directory: ```sh Command line icon=square-terminal theme={"system"} cd my-app || exit ``` Run the app: ```sh Command line icon=square-terminal theme={"system"} npm start ``` Open your browser and go to [http://localhost:3000](http://localhost:3000). This is what you see ![Screenshot of a search interface with a search box, brand filters, and product results including streaming device listings.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/quickstart-result.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=b05c54ab530c6594675b69974a549dd7) ## Next steps * Read more about [styling](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js). * Get inspired to further customize your look and feel by taking a look at the [sandboxes](/doc/ui-libraries/autocomplete/introduction/sandboxes). * Start your own project with a [full installation of InstantSearch.js](/doc/guides/building-search-ui/installation/js). # Integrate Autocomplete with React InstantSearch Source: https://www.algolia.com/doc/ui-libraries/autocomplete/integrations/with-react-instantsearch Learn how to use Autocomplete with React InstantSearch. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). When you think of search experiences on sites like Amazon (ecommerce) or YouTube (media), you may notice that both sites use an autocomplete experience. It's the autocomplete, and not just a search input, that powers the search page. If you have an existing React InstantSearch implementation, **you can create a similar experience by adding Autocomplete to your React InstantSearch application**. Adding Autocomplete to an existing React InstantSearch implementation lets you enhance the search experience and create a richer, more contextual search. You can use context from the current user and how they interacted with your site, save their recent searches, provide suggested queries, and more. This autocomplete can work as a rich search box in a search page, and a portable all-in-one search experience anywhere else on your site. This guide shows you how to integrate Autocomplete with React InstantSearch on your site. ![Screenshot of a search box with 'air' entered, showing autocomplete suggestions and product results for tablets.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/autocomplete-with-vue-instantsearch.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=8f70ce2894d6b00419c8bef561c6b795) > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/react-instantsearch?file=/src/App.tsx) > > Run and edit the Autocomplete with React InstantSearch example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/autocomplete/tree/next/examples/react-instantsearch) > > Browse the source for the Autocomplete with React InstantSearch example on GitHub. This guide starts with a new React InstantSearch application, but you can adapt it to integrate Autocomplete into your existing implementation. ## Create a search page with React InstantSearch First, set up a starter template for the InstantSearch implementation with the `create-instantsearch-app` command-line utility. ```sh Command line icon=square-terminal theme={"system"} npx create-instantsearch-app@latest react-instantsearch \ --template "React InstantSearch" \ --app-id "latency" \ --api-key "6be0576ff61c053d5f9a3225e2a90f76" \ --index-name instant_search \ --attributes-to-display name,description \ --attributes-for-faceting categories ``` The template uses a two-column layout with categories on the left and a search box, hits, and a pagination widget on the right. Simplify it by replacing the content of App.tsx and App.css with the following: ```tsx App.tsx theme={"system"} import React from "react"; import algoliasearch from "algoliasearch/lite"; import { Hit as AlgoliaHit } from "instantsearch.js/es/types"; import { Highlight, Hits, InstantSearch, Pagination, RefinementList, SearchBox, } from "react-instantsearch"; import "./App.css"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); type HitProps = { hit: AlgoliaHit<{ name: string; image: string; brand: string; categories: string[]; }>; }; function Hit({ hit }: HitProps) { return (
    {hit.name}

    By {hit.brand} in{" "} {hit.categories[0]}
    ); } export function App() { return ( ); } ``` ```css App.css theme={"system"} * { box-sizing: border-box; } body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; background-color: rgb(244, 244, 249); color: rgb(65, 65, 65); } a { color: var(--aa-primary-color); text-decoration: none; } .header { background: rgb(252 252 255 / 92%); box-shadow: 0 0 0 1px rgba(35, 38, 59, 0.05), 0 1px 3px 0 rgba(35, 38, 59, 0.15); padding: 0.5rem 0; position: fixed; top: 0; width: 100%; } .header-wrapper { align-items: center; display: grid; grid-template-columns: 100px 1fr; } .header-nav { font-weight: 500; } .wrapper { margin: 0 auto; max-width: 1200px; padding: 0 1.5rem; width: 100%; } .container { margin-top: 3.5rem; padding: 1.5rem; display: grid; gap: 1rem; grid-template-columns: 1fr 3fr; } /* Autocomplete */ .aa-Panel { position: fixed; } /* InstantSearch */ .ais-Hits-list { display: grid; gap: 1rem; grid-template-columns: 1fr 1fr 1fr; } .ais-Hits-item { padding: 1rem !important; } .hit { align-items: center; display: grid; gap: 1rem; } .hit h1 { font-size: 1rem; } .hit p { font-size: 0.8rem; opacity: 0.8; } .hit-image { align-items: center; display: flex; height: 100px; justify-content: center; } .hit-image img { max-height: 100%; } .ais-HierarchicalMenu-item--selected.ais-HierarchicalMenu-item--parent > div:first-of-type .ais-HierarchicalMenu-label { font-weight: bold; } .ais-HierarchicalMenu-item--selected:not(.ais-HierarchicalMenu-item--parent) .ais-HierarchicalMenu-label { font-weight: bold; } .ais-Pagination { display: flex; justify-content: center; margin: 2rem 0; } ``` You now have a working React InstantSearch application. ## Use Autocomplete as a search box The React InstantSearch [`SearchBox`](/doc/api-reference/widgets/search-box/react) widget doesn't offer autocomplete features like those seen on YouTube and Amazon. To implement that, replace [`SearchBox`](/doc/api-reference/widgets/search-box/react) with a search box from Algolia's Autocomplete UI library. You can store all the logic of Autocomplete in a React component. By leveraging the Hooks offered in React InstantSearch, you can directly interface with InstantSearch in a simple but powerful way. ```tsx TypeScript icon=code theme={"system"} import React from "react"; import { createElement, Fragment, useEffect, useRef, useState } from "react"; import { createRoot, Root } from "react-dom/client"; import { usePagination, useSearchBox } from "react-instantsearch"; import { autocomplete, AutocompleteOptions } from "@algolia/autocomplete-js"; import { BaseItem } from "@algolia/autocomplete-core"; import "@algolia/autocomplete-theme-classic"; type AutocompleteProps = Partial> & { className?: string; }; type SetInstantSearchUiStateOptions = { query: string; }; export function Autocomplete({ className, ...autocompleteProps }: AutocompleteProps) { const autocompleteContainer = useRef(null); const panelRootRef = useRef(null); const rootRef = useRef(null); const { query, refine: setQuery } = useSearchBox(); const { refine: setPage } = usePagination(); const [instantSearchUiState, setInstantSearchUiState] = useState({ query }); useEffect(() => { setQuery(instantSearchUiState.query); setPage(0); }, [instantSearchUiState]); useEffect(() => { if (!autocompleteContainer.current) { return; } const autocompleteInstance = autocomplete({ ...autocompleteProps, container: autocompleteContainer.current, initialState: { query }, onReset() { setInstantSearchUiState({ query: "" }); }, onSubmit({ state }) { setInstantSearchUiState({ query: state.query }); }, onStateChange({ prevState, state }) { if (prevState.query !== state.query) { setInstantSearchUiState({ query: state.query, }); } }, renderer: { createElement, Fragment, render: () => {} }, render({ children }, root) { if (!panelRootRef.current || rootRef.current !== root) { rootRef.current = root; panelRootRef.current?.unmount(); panelRootRef.current = createRoot(root); } panelRootRef.current.render(children); }, }); return () => autocompleteInstance.destroy(); }, []); return
    ; } ``` You can now remove the original `` component from your React InstantSearch implementation and replace it with ``. ```tsx TypeScript icon=code theme={"system"} /* ... */ function App() { /* ... */ return ( /* ... */ ); } ``` This replaces the InstantSearch search box with Autocomplete, and acts exactly like before. But you can now add many more interesting features. ## Add recent searches When you search on YouTube or Google and come back to the search box later on, the autocomplete displays your recent searches. This pattern lets users quickly access content by using the same path they took to find it in the first place. Add recent searches to Autocomplete with the `@algolia/autocomplete-plugin-recent-searches` package. It exposes a [`createLocalStorageRecentSearchesPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/createLocalStorageRecentSearchesPlugin) function to let you create a recent searches plugin. ```tsx TypeScript icon=code theme={"system"} /* ... */ import { useMemo } from "react"; import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches"; export function Autocomplete({ className, ...autocompleteProps }: AutocompleteProps) { /* ... */ const plugins = useMemo(() => { const recentSearches = createLocalStorageRecentSearchesPlugin({ key: "instantsearch", limit: 3, transformSource({ source }) { return { ...source, onSelect({ item }) { setInstantSearchUiState({ query: item.label }); }, }; }, }); return [recentSearches]; }, []); useEffect(() => { if (!autocompleteContainer.current) { return; } const autocompleteInstance = autocomplete({ /* ... */ plugins, }); }, [plugins]); return
    ; } ``` > [!NOTE] > > Some of this code is abstracted in the following sections to simplify the examples. Since the `recentSearchesPlugin` reads from [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), you can't see any recent searches until you perform at least one query. To submit a search, make sure to press Enter on the query. Once you do, you'll see it appear as a recent search. See also: * [Recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches) * [Autocomplete recent searches plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches) ## Add Query Suggestions **The most typical pattern you can see on every autocomplete is suggestions**. They're predictions of queries that match what users are typing and are guaranteed to return results. For example, when typing "how to" in Google, the search engine suggests matching suggestions for users to complete their query. It's beneficial on mobile devices, where typing is more demanding than a physical keyboard. Autocomplete lets you add Query Suggestions with the `@algolia/autocomplete-plugin-query-suggestions` package. It exposes a [`createQuerySuggestionsPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin) function to let you create a Query Suggestions plugin. This plugin requires a [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) index. ```tsx App.tsx theme={"system"} // ... import { INSTANT_SEARCH_INDEX_NAME } from "./constants"; function App() { // ... return ( // ... {/* ... */} ); } ``` ```tsx Autocomplete.tsx theme={"system"} // ... import type { SearchClient } from "algoliasearch/lite"; import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions"; import { INSTANT_SEARCH_QUERY_SUGGESTIONS } from "./constants"; type AutocompleteProps = Partial> & { searchClient: SearchClient; className?: string; }; export function Autocomplete({ searchClient, className, ...autocompleteProps }: AutocompleteProps) { /* ... */ const plugins = useMemo(() => { /* ... */ const querySuggestions = createQuerySuggestionsPlugin({ searchClient, indexName: INSTANT_SEARCH_QUERY_SUGGESTIONS, getSearchParams() { return recentSearches.data!.getAlgoliaSearchParams({ hitsPerPage: 6, }); }, transformSource({ source }) { return { ...source, sourceId: "querySuggestionsPlugin", onSelect({ item }) { setInstantSearchUiState({ query: item.query }); }, getItems(params) { if (!params.state.query) { return []; } return source.getItems(params); }, }; }, }); return [recentSearches, querySuggestions]; }, []); } ``` ```ts constants.ts theme={"system"} export const INSTANT_SEARCH_INDEX_NAME = "instant_search"; export const INSTANT_SEARCH_QUERY_SUGGESTIONS = "instant_search_demo_query_suggestions"; ``` See also: * [Suggested searches](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) * [Autocomplete Query Suggestions plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) ## Debounce search results Having two sets of results update as you type generates many UI flashes. This is distracting for users because two distinct sections compete for their attention. You can mitigate this problem by debouncing search results. ```tsx TypeScript icon=code theme={"system"} // ... import { debounce } from "@algolia/autocomplete-shared"; export function Autocomplete({ searchClient, className, ...autocompleteProps }: AutocompleteProps) { // ... const debouncedSetInstantSearchUiState = debounce( setInstantSearchUiState, 500, ); // ... useEffect(() => { if (!autocompleteContainer.current) { return; } const autocompleteInstance = autocomplete({ // ... onStateChange({ prevState, state }) { if (prevState.query !== state.query) { debouncedSetInstantSearchUiState({ query: state.query, }); } }, }); return () => autocompleteInstance.destroy(); }, [plugins]); } ``` ## Support categories in Query Suggestions **A key feature of Algolia's Autocomplete is the ability to pre-configure your InstantSearch page**. The Query Suggestions plugin supports categories that you can use to refine the query and the category in a single interaction. This brings users to the correct category without interacting with the [`HierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/react) widget, but with Autocomplete instead. **1. Filter by category** You need to filter by category and support categories in the helpers you created at the beginning: ```tsx App.tsx theme={"system"} // ... ``` ```tsx Autocomplete.tsx theme={"system"} // ... type SetInstantSearchUiStateOptions = { query: string; category?: string; }; export function Autocomplete({ searchClient, className, ...autocompleteProps }: AutocompleteProps) { // ... const { items: categories, refine: setCategory } = useHierarchicalMenu({ attributes: INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES, }); useEffect(() => { setQuery(instantSearchUiState.query); instantSearchUiState.category && setCategory(instantSearchUiState.category); setPage(0); }, [instantSearchUiState]); } ``` ```ts constants.ts theme={"system"} // ... export const INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES = [ "hierarchicalCategories.lvl0", "hierarchicalCategories.lvl1", ]; ``` **2. Send category to the helpers** Update the plugins to forward the category to the helpers: ```tsx TypeScript icon=code theme={"system"} // ... export function Autocomplete({ searchClient, className, ...autocompleteProps }: AutocompleteProps) { /* ... */ const currentCategory = useMemo(() => { const category = categories.find(({ isRefined }) => isRefined); return category && category.value; }, [categories]); const plugins = useMemo(() => { const recentSearches = createLocalStorageRecentSearchesPlugin({ key: "instantsearch", limit: 3, transformSource({ source }) { return { ...source, onSelect({ item }) { setInstantSearchUiState({ query: item.label, category: item.category, }); }, }; }, }); const querySuggestions = createQuerySuggestionsPlugin({ searchClient, indexName: INSTANT_SEARCH_QUERY_SUGGESTIONS, getSearchParams() { return recentSearches.data!.getAlgoliaSearchParams({ hitsPerPage: 6, }); }, categoryAttribute: [ INSTANT_SEARCH_INDEX_NAME, "facets", "exact_matches", INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0], ], transformSource({ source }) { return { ...source, sourceId: "querySuggestionsPlugin", onSelect({ item }) { setInstantSearchUiState({ query: item.query, category: item.__autocomplete_qsCategory || "", }); }, getItems(params) { if (!params.state.query) { return []; } return source.getItems(params); }, }; }, }); return [recentSearches, querySuggestions]; }, []); } ``` **3. Reset the InstantSearch category** Implement the `onReset` function on your Autocomplete instance to also reset the InstantSearch category: ```tsx TypeScript icon=code theme={"system"} const autocompleteInstance = autocomplete({ // ... onReset() { setInstantSearchUiState({ query: "", category: currentCategory }); }, }); ``` ## Add contextual Query Suggestions For an even richer Autocomplete experience, you can pick up the currently active InstantSearch category and provide suggestions for both this specific category and others. This pattern lets you reduce the search scope to the current category, like an actual department store, or broaden the suggestions to get out of the current category. ![Query Suggestions with current InstantSearch category](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/autocomplete-with-instantsearch-current-category.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=b160688d94624c0e32c46df57db17e58) First, make sure to set your category attribute as a facet in your Query Suggestions index. In this demo, the attribute to facet is `instant_search.facets.exact_matches.hierarchicalCategories.lvl0.value`. ```tsx TypeScript icon=code theme={"system"} // ... const plugins = useMemo(() => { // ... const querySuggestionsInCategory = createQuerySuggestionsPlugin({ searchClient, indexName: INSTANT_SEARCH_QUERY_SUGGESTIONS, getSearchParams() { return recentSearches.data!.getAlgoliaSearchParams({ hitsPerPage: 3, facetFilters: [ `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]}.value:${currentCategory}`, ], }); }, transformSource({ source }) { return { ...source, sourceId: "querySuggestionsInCategoryPlugin", onSelect({ item }) { setInstantSearchUiState({ query: item.query, category: item.__autocomplete_qsCategory, }); }, getItems(params) { if (!currentCategory) { return []; } return source.getItems(params); }, templates: { ...source.templates, header({ items }) { if (items.length === 0) { return ; } return ( In {currentCategory} ); }, }, }; }, }); const querySuggestions = createQuerySuggestionsPlugin({ searchClient, indexName: INSTANT_SEARCH_QUERY_SUGGESTIONS, getSearchParams() { if (!currentCategory) { return recentSearches.data!.getAlgoliaSearchParams({ hitsPerPage: 6, }); } return recentSearches.data!.getAlgoliaSearchParams({ hitsPerPage: 3, facetFilters: [ `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]}.value:-${currentCategory}`, ], }); }, categoryAttribute: [ INSTANT_SEARCH_INDEX_NAME, "facets", "exact_matches", INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0], ], transformSource({ source }) { return { ...source, sourceId: "querySuggestionsPlugin", onSelect({ item }) { setInstantSearchUiState({ query: item.query, category: item.__autocomplete_qsCategory || "", }); }, getItems(params) { if (!params.state.query) { return []; } return source.getItems(params); }, templates: { ...source.templates, header({ items }) { if (!currentCategory || items.length === 0) { return ; } return ( In other categories ); }, }, }; }, }); return [recentSearches, querySuggestionsInCategory, querySuggestions]; }, [currentCategory]); ``` ## Next steps **Autocomplete is now the primary method for users to refine React InstantSearch results**. From now on, you're leveraging the complete Autocomplete ecosystem to bring a state-of-the-art search experience for desktop and mobile. You can now add Autocomplete everywhere on your site and redirect users to the search page whenever they submit a search or after they select a suggestion. You can also use context from the current page to personalize the autocomplete experience. For example, you could display a preview of matching results in a panel for each suggestion and let InstantSearch provide these results once on the search page. Code examples: * [Autocomplete with InstantSearch.js](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/instantsearch?file=/src/app.ts) * [Autocomplete with React InstantSearch](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/react-instantsearch?file=/src/App.tsx) * [Autocomplete with Vue InstantSearch](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/vue-instantsearch?file=/src/App.vue) # Integrate Autocomplete with Vue InstantSearch Source: https://www.algolia.com/doc/ui-libraries/autocomplete/integrations/with-vue-instantsearch Learn how to use Autocomplete with Vue InstantSearch. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). When you think of search experiences on sites like Amazon (ecommerce) or YouTube (media), you may notice that both sites use an autocomplete experience. It's the autocomplete, and not just a search input, that powers the search page. If you have an existing Vue InstantSearch implementation, **you can create a similar experience by adding Autocomplete to your Vue InstantSearch application**. Adding Autocomplete to an existing Vue InstantSearch implementation lets you enhance the search experience and create a richer, more contextual search. You can use context from the current user and how they interacted with your site, save their recent searches, provide suggested queries, and more. This autocomplete can work as a rich search box in a search page, and a portable all-in-one search experience anywhere else on your site. This guide shows you how to integrate Autocomplete with Vue InstantSearch on your site. ![Screenshot of a search interface with 'air' entered, showing autocomplete suggestions and iPad Air product results.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/autocomplete-with-vue-instantsearch.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=8f70ce2894d6b00419c8bef561c6b795) > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/vue-instantsearch?file=/src/App.vue) > > Run and edit the Autocomplete with Vue InstantSearch example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/autocomplete/tree/next/examples/vue-instantsearch) > > Browse the source for the Autocomplete with Vue InstantSearch example on GitHub. This guide starts from a brand new Vue InstantSearch application, but you can adapt it to integrate Autocomplete in your existing implementation. ## Create a search page with Vue InstantSearch First, use a starter template for the InstantSearch implementation. The easiest way to achieve that is by using `create-instantsearch-app`, a command-line utility that helps to quickly get your app started. ```sh Command line icon=square-terminal theme={"system"} npx create-instantsearch-app@latest autocomplete-vue-instantsearch \ --template "Vue InstantSearch with Vue 3" \ --app-id "latency" \ --api-key "6be0576ff61c053d5f9a3225e2a90f76" \ --index-name instant_search \ --attributes-to-display name,description \ --attributes-for-faceting categories ``` The template uses a two-column layout with categories on the left and a search box, hits, and a pagination widget on the right. Next, set up InstantSearch to enable routing by using the default [`history`](/doc/api-reference/widgets/history-router/vue) router, which you want to reuse in your Autocomplete integration. This lets InstantSearch understand query parameters from the URL to derive its state. ```jsx Vue icon=code theme={"system"} ``` You now have a working Vue InstantSearch application with routing. ## Use Autocomplete as a search box Vue InstantSearch ships with a [``](/doc/api-reference/widgets/search-box/vue) component, but it doesn't offer autocomplete features like those seen on YouTube and Amazon. Instead, you can replace the `` with Algolia's Autocomplete. With Vue, you can store all the logic of Autocomplete in a single file, and expose it as a component. Start by creating a file called `Autocomplete.vue`. As Autocomplete needs to interact with the InstantSearch state, you can add a mixin to transform it into a [custom Vue InstantSearch widget](/doc/guides/building-search-ui/widgets/create-your-own-widgets/vue). ```jsx Vue 3 theme={"system"} ``` ```jsx Vue 2 theme={"system"} ``` You can now add this component into your app. ```jsx Vue icon=code theme={"system"} ``` Next, you can initialize Autocomplete in the `mounted()` hook of the component's lifecycle. Since it behaves as a custom InstantSearch widget, you have direct access to the underlying InstantSearch instance. ```jsx Autocomplete.vue (Vue 3) theme={"system"} ``` ```jsx Autocomplete.vue (Vue 2) theme={"system"} ``` ```js constants.js theme={"system"} export const INSTANT_SEARCH_INDEX_NAME = "instant_search"; ``` You can now remove the original `` component from your Vue InstantSearch implementation. This replaces the InstantSearch search box with Autocomplete, and acts exactly like before. But you can now add many more interesting features. ## Add recent searches When you search on YouTube or Google and come back to the search box later on, the autocomplete displays your recent searches. This pattern lets users quickly access content by using the same path they took to find it in the first place. Add recent searches to Autocomplete with the `@algolia/autocomplete-plugin-recent-searches` package. It exposes a [`createLocalStorageRecentSearchesPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/createLocalStorageRecentSearchesPlugin) function to let you create a recent searches plugin. ```jsx Autocomplete.vue (Vue 3) theme={"system"} ``` ```jsx Autocomplete.vue (Vue 2) theme={"system"} ``` > [!NOTE] > > Some of this code is abstracted in the following sections to simplify the examples. Since the `recentSearchesPlugin` reads from [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), you can't see any recent searches until you perform at least one query. To submit a search, make sure to press Enter on the query. Once you do, you'll see it appear as a recent search. For more information, see [Recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches). ## Add Query Suggestions **The most typical pattern you can see on every autocomplete is suggestions.** They're predictions of queries that match what users are typing and are guaranteed to return results. For example, when typing "how to" in Google, the search engine suggests matching suggestions for users to complete their query. It's beneficial on mobile devices, where typing is more demanding than a physical keyboard. Autocomplete lets you add Query Suggestions with the `@algolia/autocomplete-plugin-query-suggestions` package. It exposes a [`createQuerySuggestionsPlugin`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin) function to let you create a Query Suggestions plugin. This plugin requires a [Query Suggestions](/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js) index. ```jsx App.vue theme={"system"} ``` ```jsx Autocomplete.vue (Vue 3) theme={"system"} ``` ```jsx Autocomplete.vue (Vue 2) theme={"system"} ``` ```js constants.js theme={"system"} export const INSTANT_SEARCH_INDEX_NAME = "instant_search"; export const INSTANT_SEARCH_QUERY_SUGGESTIONS = "instant_search_demo_query_suggestions"; ``` ```js searchClient.js theme={"system"} import { liteClient as algoliasearch } from "algoliasearch/lite"; export const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); ``` See also: * [Suggested searches](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) * [Autocomplete Query Suggestions plugin](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) ## Debounce search results Having two sets of results update as you type generates many UI flashes. This is distracting for users because two distinct sections compete for their attention. You can mitigate this problem by debouncing search results. ```jsx Vue icon=code theme={"system"} ``` ## Support categories in Query Suggestions **A key feature of Autocomplete is to pre-configure your InstantSearch page.** The Query Suggestions plugin supports categories that you can use to refine the query and the category in a single interaction. This pattern brings users to the right category without interacting with the [`ais-hierarchical-menu`](/doc/api-reference/widgets/hierarchical-menu/vue) widget but using Autocomplete. **1. Filter by category** You need to filter by category and support categories in the helpers you created at the beginning: ```jsx App.vue theme={"system"} ``` ```jsx Autocomplete.vue (Vue 3) theme={"system"} ``` ```jsx Autocomplete.vue (Vue 2) theme={"system"} ``` ```js constants.js theme={"system"} /* ... */ export const INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE = "hierarchicalCategories.lvl0"; ``` **2. Send category to the helpers** Update the plugins to forward the category to the helpers: ```jsx Autocomplete.vue (Vue 3) theme={"system"} ``` ```jsx Autocomplete.vue (Vue 2) theme={"system"} ``` **3. Reset the InstantSearch category** Implement the `onReset` function on your Autocomplete instance to also reset the InstantSearch category. ```jsx JSX icon=code theme={"system"} autocomplete({ /* ... */ onReset() { setInstantSearchUiState({ query: "", resetCategory: true }); }, }); ``` ## Add contextual Query Suggestions For an even richer Autocomplete experience, you can pick up the currently active InstantSearch category and provide suggestions for both this specific category and others. This pattern lets you reduce the search scope to the current category, like an actual department store, or broaden the suggestions to get out of the current category. ![Query Suggestions with current InstantSearch category](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/guides/autocomplete-with-instantsearch-current-category.png?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=b160688d94624c0e32c46df57db17e58) First, make sure to set your category attribute as a facet in your Query Suggestions index. In this demo, the attribute to facet is `instant_search.facets.exact_matches.hierarchicalCategories.lvl0.value`. ```jsx Autocomplete.vue (Vue 3) theme={"system"} ``` ```jsx Autocomplete.vue (Vue 2) theme={"system"} ``` ## Next steps **Autocomplete is now the primary method for users to refine Vue InstantSearch results.** From now on, you're leveraging the complete Autocomplete ecosystem to bring a state-of-the-art search experience for desktop and mobile. You can now add Autocomplete everywhere on your site and redirect users to the search page whenever they submit a search or after they select a suggestion. You can also use context from the current page to personalize the autocomplete experience. For example, you could display a preview of matching results in a panel for each suggestion and let InstantSearch provide these results once on the search page. Code examples: * [Autocomplete with InstantSearch.js](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/instantsearch?file=/src/app.ts) * [Autocomplete with React InstantSearch](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/react-instantsearch?file=/src/App.tsx) * [Autocomplete with Vue InstantSearch](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/vue-instantsearch?file=/src/App.vue) # Get started with Autocomplete Source: https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/getting-started Get started with Autocomplete by building an Algolia search experience. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). This documentation offers a few ways to learn about the Autocomplete library: * Read the [Core concepts](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options) to learn more about underlying principles, like [Sources](/doc/ui-libraries/autocomplete/core-concepts/sources) and [State](/doc/ui-libraries/autocomplete/core-concepts/state). * Follow the [Guides](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) to understand how to build common UX patterns. * Refer to the [API reference](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) for a comprehensive list of parameters and options. * Try out the [Playground](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/playground?file=/app.tsx) where you can fork a basic implementation and play around. Keep reading to see how to install Autocomplete and build a basic implementation with Algolia. ## Installation The recommended way to get started is with the [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) package. It includes everything you need to render a JavaScript autocomplete experience. Otherwise, you can install the [`autocomplete-core`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core) package if you want to [build a renderer](/doc/ui-libraries/autocomplete/guides/creating-a-renderer) from scratch. All Autocomplete packages are available on the [npm](https://www.npmjs.com/) registry. ```sh npm theme={"system"} npm install algoliasearch @algolia/autocomplete-js ``` ```sh yarn theme={"system"} yarn add algoliasearch @algolia/autocomplete-js ``` If you don't want to use a package manager, you can use a standalone endpoint: ```html HTML icon=code-xml theme={"system"} ``` Algolia recommends using [jsDelivr](https://www.jsdelivr.com) but [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) is also available through [unpkg](https://unpkg.com/@algolia/autocomplete-js). ### Install the Autocomplete classic theme The Autocomplete library provides the [`autocomplete-theme-classic`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic) package so that you can have sleek styling out of the box. If you want a custom theme, use this classic theme and customize it with [CSS variables](/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic). You can also create an entirely new theme using the classic theme as a starting point. This example uses the out of the box classic theme. You can import it like any other Autocomplete package. ```sh npm theme={"system"} npm install @algolia/autocomplete-theme-classic ``` ```sh yarn theme={"system"} yarn add @algolia/autocomplete-theme-classic ``` Then import it in your project: ```js JavaScript icon=code theme={"system"} import "@algolia/autocomplete-theme-classic"; ``` If you don't use a package manager, you can link the style sheet in your HTML: ```html HTML icon=code-xml theme={"system"} ``` > [!NOTE] > > Algolia doesn't provide support regarding third party services like jsDelivr or other CDNs. ## Define where to put your autocomplete To get started, you need a container for your autocomplete to go in. If you don't have one already, you can insert one into your markup: ```html HTML icon=code-xml theme={"system"}
    ``` Then, insert your autocomplete into it by calling the [`autocomplete`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete) function and providing the [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container). It can be a [CSS selector](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) or an [Element](https://developer.mozilla.org/docs/Web/API/HTMLElement). Make sure to provide a container (for example, a `div`), not an `input`. Autocomplete generates a fully accessible search box for you. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; autocomplete({ container: "#autocomplete", placeholder: "Search for products", getSources() { return []; }, }); ``` You may have noticed two options: [`placeholder`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-placeholder) and [`getSources`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-sources). The [`placeholder`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-placeholder) option defines the text to show until users start typing in the input. Autocomplete is now plugged in. But you won't see anything appear until you define your [sources](/doc/ui-libraries/autocomplete/core-concepts/sources). ## Define what items to display [Sources](/doc/ui-libraries/autocomplete/core-concepts/sources) define where to retrieve the items to display in your Autocomplete drop-down menu. You define your sources in the [`getSources`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-sources) function by returning an array of [source objects](/doc/ui-libraries/autocomplete/core-concepts/sources#param-source-id). Each source object needs to include a [`sourceId`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-source-id) and a [`getItems`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-items) function that returns the items to display. Sources can be static or dynamic. This example uses an [Algolia index](https://support.algolia.com/hc/en-us/articles/4406981910289-What-is-an-index-) of [ecommerce products](https://github.com/algolia/datasets/tree/master/ecommerce) as a source. The [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) package provides a built-in [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaResults) function for just this purpose. ```js JavaScript icon=code theme={"system"} import { liteClient as algoliasearch } from "algoliasearch/lite"; import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); autocomplete({ container: "#autocomplete", placeholder: "Search for products", getSources({ query }) { return [ { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, hitsPerPage: 5, }, }, ], }); }, // ... }, ]; }, }); ``` The [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaResults) function requires an [Algolia search client](/doc/libraries/sdk/install) initialized with an [Algolia application ID and API key](/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api#required-credentials). It lets you search into your Algolia index using an array of `queries`, which defines one or more queries to send to the index. This example makes just one query to the "autocomplete" index using the `query` from [`getSources`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-sources). For now, it passes one additional parameter, [`hitsPerPage`](/doc/api-reference/api-parameters/hitsPerPage) to define how many items to display, but you could pass any other [Algolia query parameters](/doc/api-reference/api-parameters). Although you've now declared what items to display using [`getSources`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-sources), you still won't see anything until you've defined how to display the items you've retrieved. ## Define how to display items [Sources](/doc/ui-libraries/autocomplete/core-concepts/sources) also define how to display items in your Autocomplete using [`templates`](/doc/ui-libraries/autocomplete/core-concepts/templates). Templates can return a string or anything that's a valid Virtual DOM element. The example creates a [Preact](https://preactjs.com/) component called `ProductItem` to use as the template for each item. The CSS classes correspond to the [classic theme](/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic) imported earlier. ```js JavaScript theme={"system"} import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import algoliasearch from "algoliasearch"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); autocomplete({ container: "#autocomplete", placeholder: "Search for products", insights: true, getSources({ query }) { return [ { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, hitsPerPage: 5, attributesToSnippet: ["name:10", "description:35"], snippetEllipsisText: "…", }, }, ], }); }, templates: { item({ item, components, html }) { return html`
    ${item.name}
    ${components.Highlight({ hit: item, attribute: "name", })}
    ${components.Snippet({ hit: item, attribute: "description", })}
    `; }, }, }, ]; }, }); ``` ```jsx JSX theme={"system"} /** @jsx h */ import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import algoliasearch from "algoliasearch"; import { h, Fragment } from "preact"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); autocomplete({ container: "#autocomplete", placeholder: "Search for products", insights: true, getSources({ query }) { return [ { sourceId: "products", getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: "instant_search", params: { query, hitsPerPage: 5, attributesToSnippet: ["name:10", "description:35"], snippetEllipsisText: "…", }, }, ], }); }, templates: { item({ item, components }) { return ; }, }, }, ]; }, }); function ProductItem({ hit, components }) { return (
    {hit.name}
    ); } ``` The `ProductItem` component uses the `Snippet` component to only display part of the item's name and description, if they go beyond a certain length. Each attribute's allowed length and the characters to show when truncated are defined in the [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet) and [`snippetEllipsisText`](/doc/api-reference/api-parameters/snippetEllipsisText) [Algolia query parameters](/doc/api-reference/api-parameters) in `params`. This is what the truncated JSON record looks like: ```json JSON icon=braces theme={"system"} { "name": "Apple - MacBook Air® (Latest Model) - 13.3\" Display - Intel Core i5 - 4GB Memory - 128GB Flash Storage - Silver", "description": "MacBook Air features fifth-generation Intel Core processors with stunning graphics, all-day battery life*, ultrafast flash storage, and great built-in apps. It's thin, light and durable enough to take everywhere you go — and powerful enough to do everything once you get there.", "image": "https://cdn-demo.algolia.com/bestbuy/1581921_sb.jpg", "url": "http://www.bestbuy.com/site/apple-macbook-air-latest-model-13-3-display-intel-core-i5-4gb-memory-128gb-flash-storage-silver/1581921.p?id=1219056464137&skuId=1581921&cmp=RMX&ky=1uWSHMdQqBeVJB9cXgEke60s5EjfS6M1W", "objectID": "1581921" } ``` Check out how the template displays items by searching in the input below: ## Keep the panel open during development When you're building, styling, or debugging your autocomplete experience, you might want to inspect it in your web developer tools. Set the [`debug`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-debug) option to `true` to keep the panel open when inspecting it. > [!NOTE] > > Only use the [`debug`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-debug) option during development. ## Send click and conversion events To send [click and conversion events](/doc/ui-libraries/autocomplete/guides/sending-algolia-insights-events) when users interact with your autocomplete experience, set the [`insights`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-insights) option to `true`. ```js JavaScript icon=code theme={"system"} import { autocomplete } from "@algolia/autocomplete-js"; autocomplete({ // ... insights: true, }); ``` ## Browser support Algolia supports the last two versions of the major browsers: Chrome, Edge, Firefox, Safari. ## Going further This is all you need for a basic implementation. To go further, you can use the [`getItemUrl`](/doc/ui-libraries/autocomplete/core-concepts/sources#param-get-item-url) to add [keyboard accessibility](/doc/ui-libraries/autocomplete/core-concepts/keyboard-navigation) features. It lets users open items directly from the autocomplete menu. ```js JavaScript icon=code theme={"system"} import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import algoliasearch from "algoliasearch"; import "@algolia/autocomplete-theme-classic"; const searchClient = algoliasearch( "latency", "6be0576ff61c053d5f9a3225e2a90f76", ); autocomplete({ container: "#autocomplete", placeholder: "Search for products", getSources({ query }) { return [ { sourceId: "products", getItems() { // ... }, templates: { // ... }, getItemUrl({ item }) { return item.url; }, }, ]; }, }); ``` This outlines a basic autocomplete implementation. There's a lot more you can do like: * Define [templates for headers, footers](/doc/ui-libraries/autocomplete/core-concepts/templates#render-a-header-and-footer), or when there's [no results](/doc/ui-libraries/autocomplete/core-concepts/templates#render-a-no-results-state) * [Add multiple sources](/doc/ui-libraries/autocomplete/guides/including-multiple-result-types), including [suggested searches](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) and [recent searches](/doc/ui-libraries/autocomplete/guides/adding-recent-searches) * [Send Algolia Insights events](/doc/ui-libraries/autocomplete/guides/sending-algolia-insights-events) when a user clicks on an item or adds it to their cart To learn about customization options, read the [core concepts](/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options) or follow one of the [Guides](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches). # Sandboxes Source: https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/sandboxes Explore Autocomplete code sandboxes. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). ## Query Suggestions These examples use the `autocomplete-plugin-query-suggestions` plugin. It shows suggested search queries in your autocomplete menu. For more information, see [Build a Query Suggestions UI with Autocomplete](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches). ### Search suggestions > [card] > > As users start typing, the autocomplete menu shows suggested search queries. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions?file=/app.tsx) ### Search suggestions with categories > [card] > > The top suggestions are shown with categories to help users search more precisely. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-categories?file=/app.tsx) ### Search suggestions with inline categories > [card] > > Categories are shown on the same line as the suggestion for a more compact experience. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-inline-categories?file=/app.tsx) ## Multiple sources These examples use multiple data sources for the autocomplete items. They use several plugins, such as `autocomplete-plugin-query-suggestions` and `autocomplete-plugin-recent-searches`. They also use the `getAlgoliaResults` function to retrieve search results. For more information, see [Sources](/doc/ui-libraries/autocomplete/core-concepts/sources). ### Search suggestions and results > [card] > > As users start typing, the autocomplete menu shows suggested searches and matching search results. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-hits?file=/app.tsx) ### Search suggestions and recent searches > [card] > > This autocomplete menu remembers previous searches and shows them on top of the suggested searches. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-recent-searches?file=/app.tsx) ### Search suggestions with categories and recent searches > [card] > > This autocomplete menu shows the top search suggestions with their categories, plus recent searches. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-recent-searches-and-categories?file=/app.tsx) ### Search suggestions with recent searches and search results > [card] > > This autocomplete shows recent searches and search suggestions. > It also shows matching search results grouped by category. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/reshape?file=/app.tsx) ### Federated ecommerce search experience > [card] > > This autocomplete menu uses a two-column layout. > The left column shows recent searches and suggestions, and the right column shows matching products in a grid. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/two-column-layout?file=/src/app.tsx) ## Integrate Autocomplete These examples show how to integrate Autocomplete with InstantSearch or popular frontend frameworks. ### Autocomplete and React InstantSearch > [card] [Autocomplete + React InstantSearch](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/react-instantsearch?file=/src/App.tsx) ### Autocomplete and InstantSearch.js > [card] [Autocomplete + InstantSearch.js](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/instantsearch?file=/src/app.ts) ### Autocomplete and Vue InstantSearch > [card] [Autocomplete + Vue InstantSearch](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/vue-instantsearch?file=/src/App.vue) ### Autocomplete and React > [card] [Custom React renderer](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/react-renderer?file=/src/Autocomplete.tsx) ## Other These examples highlight other autocomplete features and plugins. ### Static sources > [card] > > As users start typing, this autocomplete menu shows matching predefined links to GitHub repositories. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/github-repositories-custom-plugin?file=/app.tsx) ### Recently-viewed items > [card] > > This autocomplete menu remembers items that users have previously viewed and shows them *before* the user starts typing (empty query). > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/recently-viewed-items?file=/app.tsx) ### Redirect searches > [card] > > This autocomplete menu redirects users to other pages if the query matches predefined criteria. > > [Open](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/redirect-url?file=/app.tsx) # Autocomplete showcase Source: https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/showcase See the awesome experiences people built with Autocomplete. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). ## DocSearch The best search experience for docs, integrates in minutes, for free [Visit website](https://docsearch.algolia.com) ## Algolia documentation Algolia Search API Documentation, Guides, Tutorials, and FAQs. Read how Algolia [added Autocomplete to their documentation search](https://www.algolia.com/blog/ux/replicating-the-algolia-documentation-search-with-autocomplete/). [Visit website](https://algolia.com/doc/) # What is Autocomplete Source: https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete Autocomplete is an open source, production-ready JavaScript library for building autocomplete experiences. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). A user types into an input, and the autocomplete suggests full terms or results to complete their thought. This is the foundation of an autocomplete experience. **Autocomplete is an ubiquitous part of most search experiences.** Search providers like Google, ecommerce sites like Amazon, and messaging apps like Slack all use autocomplete experiences on mobile and desktop. Autocomplete can display completed search terms or direct search results. This reduces typing effort and is especially helpful on mobile devices. It can also surface relevant products, searches, or content. You've likely already encountered Autocomplete-powered interfaces. The Algolia documentation itself, as well as sites like [React Native](https://reactnative.dev) and [Tailwind CSS](https://tailwindcss.com), use Autocomplete through the [Algolia DocSearch](https://docsearch.algolia.com) project. This library is flexible enough to power much more than documentation search. **It's designed to help you build interactive and accessible autocomplete experiences, regardless of your use case.** ## What Autocomplete provides Autocomplete is a JavaScript library that lets you quickly build autocomplete experiences. All you need to get started is: * A container to inject the experience into * Data to fill the autocomplete with The data that fills the autocomplete is called [sources](/doc/ui-libraries/autocomplete/core-concepts/sources). You can use whatever you want in your sources: a static set of searches terms, search results from an external source like an Algolia 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.), recent searches, and more. You only need to configure two required parameters: [`container`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-container) and [`getSources`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete#param-get-sources). Once you do, Autocomplete creates the input and handles user interactions. **The library creates an input and provides the interactivity and accessibility attributes, but you're in full control of the DOM elements to output.** You aren't limited to suggested search terms. You can display links to individual results or even display "actions" that a user can take from within an autocomplete. For example, you could let your users turn dark mode on, directly from an autocomplete, if they begin to type "dark mode". You can also display different data types, such as suggested search terms, product results, and actions, in different ways. The format of each data type and layout is customizable. ## What Autocomplete doesn't provide Unlike [InstantSearch](/doc/guides/building-search-ui/what-is-instantsearch/js), Autocomplete doesn't provide a library of ready-made UI widgets. You control how the autocomplete interface is rendered, while the library handles its core behavior and accessibility. You're also in charge of providing the collection of items to display. You can easily plug Algolia results using [`getAlgoliaResults`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/getAlgoliaResults) if you want, but you're free to use Autocomplete with any data sources you want. To learn more, see [Getting started](/doc/ui-libraries/autocomplete/introduction/getting-started) for a basic example in action. # Advanced federated search experience for ecommerce Source: https://www.algolia.com/doc/ui-libraries/autocomplete/solutions/advanced-federated-search-experience-for-ecommerce Learn how to combine suggestions from multiple indices using a two-column layout. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). A federated search offers recent searches, popular searches, search results, and other content from multiple indices in a single interface. The following example shows how to combine multiple indices using Autocomplete with a [two-column layout](/doc/ui-libraries/autocomplete/guides/creating-a-multi-column-layout). > [tab] Desktop > > ![Advanced federated search experience for ecommerce (on desktop)](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/ecom-advanced-federated-experience-desktop.jpg?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=f9bb133af97a761703e561f5e6f7dbd8) > [tab] Mobile > > ![Advanced federated search experience for ecommerce (on mobile)](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/ecom-advanced-federated-experience-mobile.jpg?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=10df8367a4ce36997e8e3a346d386c7e) > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/two-column-layout) > > Run and edit the Advanced federated search experience for ecommerce example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/autocomplete/tree/next/examples/two-column-layout) > > Browse the source for the Advanced federated search experience for ecommerce example on GitHub. ## Understand the user interface This example's user interface consists of three search states, each one containing different plugins. | Search state | Plugins | | - | - | | **[Empty query](#empty-query-state)** | • [Recent searches](#recent-searches) • [Popular searches](#popular-searches) • [Quick access](#quick-access) | | **[Results](#results-search-state)** | • [Product Query Suggestions](#product-query-suggestions) • [Product category suggestions](#product-category-suggestions) • [Product brand suggestions](#product-brand-suggestions) • [FAQ suggestions](#faq-suggestions) • [Products preview](#products-preview) • [Articles preview](#articles-preview) | | **[No results](#no-results-search-state)** | • [No results message](#no-results-message) • [Popular category suggestions](#popular-category-suggestions) | ### Empty query state The empty query state describes what users see in the search box before typing a search query. This example provides some recent and popular searches on the left (1 and 2) and shortcut items on the right (3). ![Screenshot of a search interface with an empty query, showing recent searches, popular tags, and promotional banners.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/ecom-advanced-federated-experience-empty-query-state.jpg?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=1918a17c55bee1777326a6e82ac8a519) #### Recent searches The [`autocomplete-plugin-recent-searches`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches) plugin fetches and then saves recent searches on the user's local storage. If there aren't any recent searches, the recent searches section is hidden. A maximum of 4 recent searches are displayed by default. To customize the plugin options, edit the [`recentSearchesPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/recentSearchesPlugin.tsx) file. #### Popular searches The [`autocomplete-plugin-query-suggestions`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) plugin retrieves hits from the [Query Suggestions index](#product-query-suggestions-index). The first 6 most popular query suggestions are displayed. By default, query suggestions are sorted by the `popularity` attribute in descending order (most popular first). To customize the plugin options, edit the [`popularPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/popularPlugin.tsx) file. #### Quick access Quick access is a custom plugin that retrieves the JSON data from the response coming from a [Rule](/doc/guides/managing-results/rules/rules-overview) configured in the Algolia dashboard on the [products index](#products-index). The rule is triggered by a rule context defined in the [Autocomplete plugin](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/quickAccessPlugin.tsx#L32). The example uses four item templates: `sales-banner`, `sales-code`, `new-collection`, and `help`. ```json sales-banner theme={"system"} { "template": "sales-banner", "title": "Spring Sale", "subtitle": "up to 60% Off", "image": "https://res.cloudinary.com/hilnmyskv/image/upload/v1645453369/sales_banner_y1hsr8.jpg", "href": "#" } ``` ```json sales-code theme={"system"} { "template": "sales-code", "title": "Sale on\nSneakers", "subtitle": "with the code\nSALES_2131", "image": "https://res.cloudinary.com/hilnmyskv/image/upload/v1645453422/sales_code_vuatep.jpg", "href": "#", "date": "Till March 25th" } ``` ```json new-collection theme={"system"} { "template": "new-collection", "title": "New\ncollection", "subtitle": "Spring / summer\n2022", "image": "https://res.cloudinary.com/hilnmyskv/image/upload/v1645453466/new_collection_nloeb6.jpg", "href": "#" } ``` ```json help theme={"system"} { "template": "help", "title": "How can\nwe help?", "href": "#", "links": [ { "text": "Track my order", "href": "#" }, { "text": "Delivery & Returns", "href": "#" }, { "text": "FAQ", "href": "#" } ] } ``` Customize them or create a new template by using a new item's `template` value in the custom JSON data of the Rule and style it as you wish in the [CSS file](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/style.css#L450-L461). This is the rule's custom JSON data structure: ```jsonc JSON icon=braces theme={"system"} { "items": [/* Array of template items */] } ``` To customize the plugin, edit the [`quickAccessPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/quickAccessPlugin.tsx) file. The complete rule configuration is in the [`products_configuration.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/products_configuration.json) file. ### Results search state The results search state appears as soon as you start typing a query in the search box. It provides **multiple suggestions** on the left and a preview of relevant **products and articles** on the right. The suggestions are combined using the [Autocomplete Reshape API](/doc/ui-libraries/autocomplete/guides/reshaping-sources) following two rules: * Suggestions are **deduplicated** between sources. * A maximum of 1 category, 2 brands, and 1 FAQ are displayed. The rest is filled up with query suggestions to reach a total of 10 suggestions on desktop and 6 suggestions on mobile. This layout provides users with what they're looking for (however they may approach their search) while maximizing the display on any device. ![Screenshot of a search interface with suggestions, product results, and articles, numbered for reference.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/ecom-advanced-federated-experience-results-search-state.jpg?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=35c776a6a018b296fc40ee3333529fed) #### Key 1. [Product query suggestions](#product-query-suggestions) 2. [Product category suggestions](#product-category-suggestions) 3. [Product brand suggestions](#product-brand-suggestions) 4. [FAQ suggestions](#faq-suggestions) 5. [Products preview](#products-preview) 6. [Articles preview](#articles-preview) #### Product Query Suggestions The [`autocomplete-plugin-query-suggestions`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) plugin retrieves hits matching the query from the [product Query Suggestions index](#product-query-suggestions-index). A maximum of 10 product query suggestions are displayed by default. To customize the plugin options, edit the [`querySuggestionsPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/querySuggestionsPlugin.tsx) file. ##### Index records and configuration * [`products_query_suggestions.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/products_query_suggestions.json) * [`products_query_suggestions_configuration.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/products_query_suggestions_configuration.json) #### Product category suggestions **Product category suggestions** is a custom plugin that retrieves the category of the first matching product from the [products index](#products-index). A maximum of 1 product category is displayed by default. To customize the plugin, edit the [`categoriesPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/categoriesPlugin.tsx) file. #### Product brand suggestions **Product brand suggestions** is a custom plugin that retrieves the product brands facet (An attribute in your records that lets users filter or group results (for example, by color, brand, or price).) values matching the query from the [products index](#products-index). A maximum of two product brands are displayed by default. To customize the plugin, edit the [`brandsPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/brandsPlugin.tsx) file. #### FAQ suggestions **FAQ suggestions** is a custom plugin that retrieves an FAQ matching the query from the [FAQ index](#faq-index). A maximum of 1 FAQ is displayed by default. To customize the plugin, edit the [`faqPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/faqPlugin.tsx) file. The records (A record is a searchable object in an Algolia index. Each record consists of named attributes.) are in the [`faq.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/faq.json) file. ##### Sample FAQ record ```json JSON icon=braces theme={"system"} { "objectID": "5", "title": "Shipping methods/delays", "list_categories": ["shipping", "international"] } ``` #### Products preview Products preview is a custom plugin that retrieves products matching the query and displays a preview (including image, brand, name, price, and review rating/count) from the [products index](#products-index). A **See All** button that lists the number of other matching products (if any) is also displayed. A maximum of 4 product previews are displayed by default. To customize the plugin, edit the [`productsPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/productsPlugin.tsx) file. ##### Index records and configuration * [`products.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/products.json) * [`products_configuration.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/products_configuration.json) ##### Simplified sample record ```json JSON icon=braces theme={"system"} { "objectID": "M0E20000000DG2H", "name": "Dress Sun68 grey", "brand": "Sun 68", "image_blurred": "LbR:7=t7_4M{IUof%MWBxuWBRPt7", "image_urls": [ "https://res.cloudinary.com/hilnmyskv/image/upload/v1638371844/flagship_sunrise/M0E20000000DG2H_0.jpg", "https://res.cloudinary.com/hilnmyskv/image/upload/v1638371845/flagship_sunrise/M0E20000000DG2H_1.jpg", "https://res.cloudinary.com/hilnmyskv/image/upload/v1638371846/flagship_sunrise/M0E20000000DG2H_2.jpg" ], "price": { "currency": "EUR", "discount_level": -100, "discounted_value": 0, "on_sales": false, "value": 100 }, "reviews": { "bayesian_avg": 3.957894736842105, "count": 94, "rating": 4 } } ``` #### Articles preview **Articles preview** is a custom plugin that retrieves articles matching the query from the [articles index](#articles-index). A **See All** button that lists the number of other matching articles (if any) is also displayed. A maximum of 2 article previews are displayed by default. To customize the plugin, edit the [`articlesPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/articlesPlugin.tsx) file. ##### Index records * [`articles.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/articles.json). ##### Sample record ```json JSON icon=braces theme={"system"} { "objectID": "98", "title": "Victoria Beckham - New collection 2021", "image_url": "https://media.istockphoto.com/photos/stacked-and-ready-to-be-packed-picture-id1286024201?b=1&k=20&m=1286024201&s=170x170", "date": "2021-03-31T22:00:00.000Z" } ``` ### No results search state The no results search state is displayed when no results matching are found for the current query. It provides some advice on the left and popular categories on the right. ![Screenshot of a search interface showing 'No results for 'sd'' and a 'Popular categories' section with images and labels.](https://mintcdn.com/algolia/Gja8rtqcY6eJARlr/images/ui-libraries/autocomplete/ecom-advanced-federated-experience-no-results-search-state.jpg?fit=max\&auto=format\&n=Gja8rtqcY6eJARlr\&q=85\&s=201e2ae9f35ce4335325739d0291d34a) #### No results message To customize the no results message, edit the [`render`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/app.tsx#L144-L151) method in the `app.tsx` file. #### Popular category suggestions **Popular category suggestions** is a custom plugin that retrieves facet values for categories with the most number of products from the [products index](#products-index). Image URLs are hard-coded for the first 6 categories. To customize the plugin, edit the [`popularCategoriesPlugin`](https://github.com/algolia/autocomplete/blob/next/examples/two-column-layout/src/plugins/popularCategoriesPlugin.tsx) file. ## Technical requirements ### Dependencies The example has multiple dependencies: * [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) package with two plugins: * [`autocomplete-plugin-query-suggestions`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) to add [Query Suggestions](/doc/ui-libraries/autocomplete/guides/adding-suggested-searches). * [`autocomplete-plugin-recent-searches`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches) to display a list of the latest searches. * [`autocomplete-theme-classic`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic) package as a neutral and clean theme starter. * [Preact](https://preactjs.com/) to render the virtual DOM. * [Ramda](https://ramdajs.com/) to pipe reshape functions. * [Parcel](https://parceljs.org/) to bundle all the modules. * [BlurHash](https://blurha.sh/) to have a placeholder for loading images. * [TypeScript](https://www.typescriptlang.org/) for static type checking. ### Structure The folder structure of the example is as follows: * [`src/components`](https://github.com/algolia/autocomplete/tree/next/examples/two-column-layout/src/components): Preact components * [`src/functions`](https://github.com/algolia/autocomplete/tree/next/examples/two-column-layout/src/functions): Autocomplete reshape functions * [`src/plugins`](https://github.com/algolia/autocomplete/tree/next/examples/two-column-layout/src/plugins): Autocomplete custom plugins * [`src/types`](https://github.com/algolia/autocomplete/tree/next/examples/two-column-layout/src/types): Custom types * [`src/utils`](https://github.com/algolia/autocomplete/tree/next/examples/two-column-layout/src/utils): Miscellaneous utilities ### Combine different data sources This example uses multiple Algolia indices hosted on Algolia's `latency` 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.). If you intend to use your own Algolia indices or change the existing ones, the **datasets and configurations** are provided as JSON files in the [**Algolia datasets repository**](https://github.com/algolia/datasets/tree/master/ecommerce-federated) and described below. #### Products index This index contains **fashion products**. * [`products.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/products.json) * [`products_configuration.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/products_configuration.json) #### Product Query Suggestions index This index contains **Query Suggestions for products**. * [`products_query_suggestions.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/products_query_suggestions.json) * [`products_query_suggestions_configuration.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/products_query_suggestions_configuration.json) #### Articles index This index contains **articles related to some product brands.** * [`articles.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/articles.json). #### FAQ index This index contains **frequently asked questions.** * [`faq.json`](https://github.com/algolia/datasets/tree/master/ecommerce-federated/faq.json). # Rich text box with mentions and hashtags Source: https://www.algolia.com/doc/ui-libraries/autocomplete/solutions/rich-text-box-with-mentions-and-hashtags Learn how to create the mention or hashtag type-ahead feature from Twitter and Facebook with Autocomplete. Prefer using the Autocomplete widget from InstantSearch instead of the standalone Autocomplete library. It integrates more closely with your search experience. For more information, see the API reference for [InstantSearch.js](/doc/api-reference/widgets/autocomplete/js) or [React InstantSearch](/doc/api-reference/widgets/autocomplete/react). Beyond its typical usage for redirecting to a search page, **you can use an autocomplete as a secondary search pattern to improve the typing experience.** A great example is the mentions feature in Twitter. As you're typing your tweet, you can mention another user with the "@" character. This opens a panel with suggestions of matching users, allowing you to complete your message with the right username. The text box acts as a type-ahead: the suggestions panel isn't blocking, you can keep typing, ignoring the suggestions and letting them go away, or you can use them to complete your message without friction. ![An experience that replicates the Twitter autocomplete in the compose box](https://mintcdn.com/algolia/JoMDf1PtMFIqKpzx/images/ui-libraries/autocomplete/twitter-compose-box.png?fit=max\&auto=format\&n=JoMDf1PtMFIqKpzx\&q=85\&s=d5a1c4a019ca90f513e353fcbee54bcd) The Twitter compose box doesn't process a query from a search input. Instead, it parses the content of a text box and detects when users are trying to mention someone. To replicate such an experience, you need full control over how to render the text box, meaning you must use [`autocomplete-core`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core) instead of [`autocomplete-js`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-js). In this solution, you'll learn how to replicate the Twitter mentions feature. You can then reuse the same logic to implement hashtags. > [!NOTE] > > This solution uses [`autocomplete-core`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core) along with [React](https://react.dev/) and assumes familiarity with both. > [card] [Open CodeSandbox](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/twitter-compose-with-typeahead) > > Run and edit the Rich text box with mentions and hashtags example in CodeSandbox. > [card] [Explore source code](https://github.com/algolia/autocomplete/tree/next/examples/twitter-compose-with-typeahead) > > Browse the source for the Rich text box with mentions and hashtags example on GitHub. ## Get started First, you need to start a new React project. You can use [Create React App](https://github.com/facebook/create-react-app), [Vite](https://vite.dev) with a React template, or bootstrap the project yourself. Then, install the necessary dependencies to build your Autocomplete app: * [`@algolia/autocomplete-core`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core) to build the autocomplete * [`@algolia/autocomplete-preset-algolia`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-preset-algolia) to fetch Algolia results and highlight matches * [`algoliasearch`](/doc/libraries/sdk/install) to interact with the Algolia Search API ```sh npm theme={"system"} npm install @algolia/autocomplete-core @algolia/autocomplete-preset-algolia algoliasearch ``` ```sh yarn theme={"system"} yarn add @algolia/autocomplete-core @algolia/autocomplete-preset-algolia algoliasearch ``` ## Build the autocomplete text box When using [`autocomplete-core`](/doc/ui-libraries/autocomplete/api-reference/autocomplete-core), you're in control of rendering the entire experience using the Autocomplete state. **1. Create a custom hook** Create a custom Hook that instantiates Autocomplete with the passed options and returns the instance and state. ```jsx JSX icon=code theme={"system"} import React from "react"; import { createAutocomplete } from "@algolia/autocomplete-core"; function useAutocomplete(props) { const [state, setState] = React.useState(() => ({ collections: [], completion: null, context: {}, isOpen: false, query: "", activeItemId: null, status: "idle", })); const autocomplete = React.useMemo( () => createAutocomplete({ ...props, onStateChange(params) { props.onStateChange?.(params); setState(params.state); }, }), [], ); return { autocomplete, state }; } ``` **2. Build the Autocomplete component** Create an `Autocomplete` component which consumes the Hook and renders the experience. ```jsx JSX icon=code theme={"system"} function Autocomplete(props) { const { autocomplete, state } = useAutocomplete({ ...props, id: "twitter-autocomplete", defaultActiveItemId: 0, }); return null; } ``` **3. Use the Autocomplete component in your app** In your `App.jsx` file, render the `Autocomplete` component and pass Autocomplete options as props. ```jsx JSX icon=code theme={"system"} import React from "react"; import { Autocomplete } from "./Autocomplete"; export function App() { return (
    ); } ``` ### Render the text box For now, the autocomplete doesn't return anything. Use the `autocomplete` and `state` returned from the Hook to build the experience. ```jsx JSX icon=code theme={"system"} function Autocomplete(props) { // ... const inputRef = React.useRef(null); return (
    You