title: Index management best practices for Redis Search

title: Introduction to managing Redis Search indexes

text:
Redis Search is a powerful tool for executing complex search and query operations on structured, semi-structured, and unstructured data. Indexes are the backbone of this functionality, enabling fast and efficient data retrieval.
Proper management of these indexes is essential for optimal performance, scalability, and resource utilization.

This guide outlines best practices for managing Redis Search indexes throughout their lifecycle. It provides recommendations on:

- Planning and creating indexes to suit your query patterns.
- Using index aliasing to manage schema updates and minimize downtime.
- Monitoring and verifying index population to ensure query readiness.
- Optimizing performance through query profiling and memory management.
- Maintaining and scaling indexes in both standalone and clustered Redis environments.
- Versioning, testing, and automating index management.

title: Why index management matters

text:
Indexes directly impact query speed and resource consumption.
Poorly managed indexes can lead to increased memory usage, slower query times, and challenges in maintaining data consistency.
By following the strategies outlined in this guide, you can:

- Reduce operational overhead.
- Improve application performance.
- Ensure smooth transitions during schema changes.
- Scale efficiently with your growing datasets.

title: Plan your indexes strategically

text:
Planning your indexes strategically requires understanding your application’s query patterns and tailoring indexes to match.
Begin by identifying the types of searches your application performs—such as full-text search, range queries, or geospatial lookups—and the fields involved.
Categorize fields based on their purpose: searchable fields (e.g., [`TEXT`]() for full-text searches), filterable fields (e.g., [`TAG`]() for exact match searches), and sortable fields (e.g., [`NUMERIC`]() for range queries or sorting).
Match field types to their intended use and avoid indexing fields that are rarely queried to conserve resources. Here's the list of index types:

- [`TEXT`](): use `TEXT` for free-text searches and set weights if some fields are more important.
- [`TAG`](): use `TAG` for categorical data (e.g., product categories) that benefit from exact matching and filtering.
- [`NUMERIC`](): use `NUMERIC` for numeric ranges (e.g., prices, timestamps).
- [`GEO`](): use `GEO` for geospatial coordinates (e.g., latitude/longitude).
- [`GEOSHAPE`](): use `GEOSHAPE` to represent locations as points, but also to define shapes and query the interactions between points and shapes (e.g., to find all points that are contained within an enclosing shape).
- [`VECTOR`](): use `VECTOR` for high-dimensional similarity searches.

See [these pages]() for discussions and examples on how best to use these index types.

Next, simulate queries on a sample dataset to identify potential bottlenecks.
Use tools like [`FT.PROFILE`]() to analyze query execution and refine your schema if needed.
For example, assign weights to `TEXT` fields for prioritizing results or use the `PREFIX` option of [`FT.CREATE`]() to limit indexing to specific key patterns. Note that you can use multiple `PREFIX` clauses when you create an index (see [below](#index-creation))
After creating the index, validate its performance with real queries and monitor usage with the available tools:

- [`FT.EXPLAIN`]() and [`FT.EXPLAINCLI`]() allow you to see how Redis Search parses a given search query. `FT.EXPLAIN` returns a structured breakdown of the query execution plan, while `FT.EXPLAINCLI` presents a more readable, tree-like format for easier interpretation. These commands are useful for diagnosing query structure and ensuring it aligns with the intended logic.
- [`FT.INFO`]() provides detailed statistics about an index, including the number of indexed documents, memory usage, and configuration settings. It helps in monitoring index growth, assessing memory consumption, and verifying index structure to detect potential inefficiencies.
- [`FT.PROFILE`]() runs a query while capturing execution details, which helps to reveal query performance bottlenecks. It provides insights into processing time, key accesses, and filter application, making it a crucial tool for fine-tuning complex queries and optimizing search efficiency.

Avoid over-indexing. Indexing every field increases memory usage and can slow down updates.
Only index the fields that are essential for your planned queries.

title: Index creation {#index-creation}

text:
- Use the [`FT.CREATE`]() command to define an index schema.
   - Assign weights to `TEXT` fields to prioritize certain fields in full-text search results.
   - Use the `PREFIX` option to restrict indexing to keys with specific patterns.
      Using multiple PREFIX clauses when creating an index allows you to index multiple key patterns under a single index. This is useful in several scenarios:
      - If your Redis database stores different types of entities under distinct key prefixes (e.g., `user:123`, `order:456`), a single index can cover both by specifying multiple prefixes. For example:

          [code example]

          This approach enables searching across multiple entity types without needing separate indexes.

      - Instead of querying multiple indexes separately, you can search across related data structures using a single query. This is particularly helpful when data structures share common fields, such as searching both customer and vendor records under a unified contacts index.

      - Maintaining multiple indexes for similar data types can be inefficient in terms of memory and query performance. By consolidating data under one index with multiple prefixes, you reduce overhead while still allowing for distinct key organization.

      - If your data model evolves and new key patterns are introduced, using multiple `PREFIX` clauses from the start ensures future compatibility without requiring a full reindexing.
   - Data loading strategy: load data into Redis before creating an index when working with large datasets. Use the `ON HASH` or `ON JSON` options to match the data structure.

title: Index aliasing

text:
Index aliases act as abstracted names for the underlying indexes, enabling applications to reference the alias instead of the actual index name. This approach simplifies schema updates and index management.

There are several use cases for index aliasing, including:

- Schema updates: when updating an index schema, create a new index and associate the same alias with it. This allows a seamless transition without requiring application-level changes.
- Version control: use aliases to manage different versions of an index. For example, assign the alias products to `products_v1` initially and later to `products_v2` when the schema evolves.
- Testing and rollback: assign an alias to a test index during staged deployments. If issues arise, quickly switch the alias back to the stable index.

Best practices for aliasing:

- Always create an alias for your indexes during initial setup, even if you don’t anticipate immediate schema changes.
- Use clear and descriptive alias names to avoid confusion (e.g., `users_current` or `orders_live`).
- Make sure that an alias points to only one index at a time to maintain predictable query results.
- Use aliases to provide tenant-specific access. For example, assign tenant-specific aliases like `tenant1_products` and `tenant2_products` to different indexes for isolated query performance.

Tools for managing aliases:

- Assign an alias: [`FT.ALIASADD`]() `my_alias my_index`
- Update an alias: [`FT.ALIASUPDATE`]() `my_alias new_index`
- Remove an alias: [`FT.ALIASDEL`]() `my_alias`

Monitoring and troubleshooting aliases:

- Use the `FT.INFO` command to check which aliases are associated with an index.
- Make sure your aliases always points to valid indexes and are correctly updated during schema changes.

title: Monitor index population

text:
- Use the `FT.INFO` command to monitor the `num_docs` and `indexing` fields, to check that all expected documents are indexed.
    [code example]
- Validate data with sample queries to ensure proper indexing:
    [code example]
- Use `FT.PROFILE` to analyze query plans and validate performance:

    [code example]
 - Implement scripts to periodically verify document counts and query results. For example, in Python:

    [code example]

title: Monitoring index performance

text:
- Use the `FT.PROFILE` command to analyze query performance and identify bottlenecks.
- Regularly monitor memory usage with the [`INFO`]() `memory` and `FT.INFO` commands to detect growth patterns and optimize resource allocation.

title: Index maintenance

text:
- If schema changes are required, create a new index with the updated schema and reassign the alias once the index is ready.
- Use [Redis key expiration]() to automatically remove outdated records and keep indexes lean.

title: FT.ALTER vs. aliasing

text:
Use [`FT.ALTER`]() when you need to add new fields to an existing index without rebuilding it, minimizing downtime and resource usage. However, `FT.ALTER` cannot remove or modify existing fields, limiting its flexibility.

Use index aliasing when making schema changes that require reindexing, such as modifying field types or removing fields. In this case, create a new index with the updated schema, populate it, and then use `FT.ALIASUPDATE` to seamlessly switch queries to the new index without disrupting application functionality.

title: Scaling and high availability

text:
- In a clustered Redis setup, make sure indexes are designed with key distribution in mind to prevent query inefficiencies.
- Test how indexes behave under replica promotion to ensure consistent query behavior across nodes.

title: Versioning and testing

text:
- When changing schemas, create a new version of the index alongside the old one and migrate data progressively.
- Test index changes in a staging environment before deploying them to production.

title: Cleaning up

text:
- Use the [`FT.DROPINDEX`]() command to remove unused indexes and free up memory. Be cautious with the `DD` (Delete Documents) flag to avoid unintended data deletion.
- Make sure no keys remain that were previously associated with dropped indexes if the data is no longer relevant.

title: Documentation and automation

text:
- Document your index configurations to facilitate future maintenance.
- Use scripts or orchestration tools to automate index creation, monitoring, and cleanup.

code: FT.CREATE my_index ON HASH PREFIX 2 "user:" "order:" SCHEMA name TEXT age NUMERIC status TAG

section_id: index-creation-index-creation

code: FT.INFO my_new_index

section_id: monitor-index-population

code: FT.SEARCH my_new_index "*"

code: FT.PROFILE my_new_index SEARCH QUERY "your_query"

code:
import re
    def check_index_readiness(index_name, expected_docs):
        r = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)
        info = r.execute_command('FT.INFO', index_name)
        num_docs = int(info[info.index('num_docs') + 1])
        return num_docs >= expected_d
    if check_index_readiness('my_new_index', 100000):
        print("Index is fully populated!")
    else:
        print("Index is still populating...")


title: Best practices

summary: Redis Search best practices

title: Best practices for Redis Search performance

title: Move from Development to Production with Redis Search

page_type: index


page_type: content

title: Overview

role: overview

text: If you're using Redis Software or Redis Cloud, see the [best practices for scalable Redis Search]() page.

title: Checklist

role: content

text:
Below are some basic steps to ensure good performance of Redis Search .

* Create a Redis data model with your query patterns in mind.
* Ensure the Redis architecture has been sized for the expected load using the [sizing calculator](https://redis.io/redisearch-sizing-calculator/).
* Provision Redis nodes with sufficient resources (RAM, CPU, network) to support the expected maximum load.
* Review [`FT.INFO`]() and [`FT.PROFILE`]() outputs for anomalies and/or errors.
* Conduct load testing in a test environment with real-world queries and a load generated by either [memtier_benchmark](https://github.com/redislabs/memtier_benchmark) or a custom load application.

title: Indexing considerations

title: General

text:
- Favor [`TAG`]() over [`NUMERIC`]() for use cases that only require matching.
- Favor [`TAG`]() over [`TEXT`]() for use cases that don’t require full-text capabilities (pure match).

title: Non-threaded search

text:
- Put only those fields used in your queries in the index.
- Only make fields [`SORTABLE`]() if they are used in [`SORTBY`]()
queries.
- Use [`DIALECT 2`]().

title: Threaded (query performance factor or QPF) search

text:
- Put both query fields and any projected fields (`RETURN` or `LOAD`) in the index.
- Set all fields to `SORTABLE`.
- Set TAG fields to [UNF]().
- Optional: Set `TEXT` fields to `NOSTEM` if the use case will support it.
- Use [`DIALECT 2`]().

title: Query optimization

text:
- Avoid returning large result sets.  Use `CURSOR` or `LIMIT`.
- Avoid wildcard searches.
- Avoid projecting all fields (e.g., `LOAD *`). Project only those fields that are part of the index schema.
- If queries are long-running, enable threading (query performance factor) to reduce contention for the main Redis thread.

title: Validate performance (`FT.PROFILE`)

text:
You can analyze [`FT.PROFILE`]() output to gain insights about query execution.
The following informational items are available for analysis:

- Total execution time
- Execution time per shard
- Coordination time (for multi-sharded environments)
- Breakdown of the query into fundamental components, such as `UNION` and `INTERSECT`
- Warnings, such as `TIMEOUT`

title: Anti-patterns

text:
When designing and querying indexes in Redis Search, certain practices can hinder performance, scalability, and maintainability. Below are some common anti-patterns to avoid:

- **Large documents**: storing excessively large documents in Redis makes data retrieval slower and increases memory usage. Break data into smaller, focused records whenever possible.
- **Deeply-nested fields**: retrieving or indexing deeply-nested JSON fields is computationally expensive. Use a flatter schema for better performance.
- **Large result sets**: fetching unnecessarily large result sets puts a strain on memory and network resources. Limit results to only what is needed.
- **Wildcarding**: using wildcard patterns indiscriminately in queries can lead to large and inefficient scans, especially if the index size is significant.
- **Large projections**: including excessive fields in query results increases memory overhead and slows down query execution. Limit projections to essential fields.

The following examples depict an anti-pattern index schema and query, followed by corrected versions designed for scalability with Redis Search.

title: Anti-pattern index schema

text:
The following schema introduces challenges for scalability and performance:

[code example]

Issues:

- Minimal schema definition: the schema is sparse and lacks fields like `lastName`, `id`, and `version` that might be frequently queried. This results in additional operations to fetch these fields separately, reducing efficiency.
- Missing `SORTABLE` flag for text fields: sorting operations on unsortable fields require full-text processing, which is slow.
- Wildcard indexing: `$.tags.*` creates a broad index that can lead to excessive memory usage and reduced query performance.

title: Anti-pattern query

text:
The following query is inefficient and not optimized for vertical scaling:

[code example]
Issues:

- Wildcard projection (`LOAD *`): retrieving all fields in the result set is inefficient and increases memory usage, especially if the documents are large.
- Unnecessary fields: fields that aren't required for the current operation are still fetched, slowing down execution.
- Lack of advanced query syntax: without specifying a query dialect or leveraging features like tagging, the query may perform unnecessary computations.

title: Improved index schema

text:
Here’s an optimized schema that adheres to best practices for vertical scaling:

[code example]

Improvements:

- `NOSTEM` for text fields: prevents stemming on fields like `firstName` and `lastName` to allow for exact matches (e.g., "Smith" stays "Smith").
- Expanded schema: adds commonly queried fields like `lastName`, `id`, and `version`, making queries more efficient by reducing the need for post-query data retrieval.
- `TAG` fields: `id` and `ver` are defined as `TAG` fields to support fast filtering with exact matches.
- `SORTABLE` for all relevant fields: ensures that sorting operations are efficient without requiring full-text scanning.

You might be wondering why `$.tags.* as t NUMERIC SORTABLE` is acceptable in the improved schema and it wasn't previously.
The inclusion of `$.tags.*` is acceptable when:

- It has a clear purpose: it is actively used in queries, such as filtering on numeric ranges or matching specific values.
- Other fields in the schema complement it: these fields reduce over-reliance on `$.tags.*` for all query operations, distributing the load more evenly.
- Projections and limits are managed carefully: queries that use `$.tags.*` should avoid loading unnecessary fields or returning excessively large result sets.

title: Improved query

text:
The following query is better suited for vertical scaling:

[code example]

Improvements:

- Targeted projection: the `LOAD` clause specifies only essential fields (`id, t, name, lastname, loc, ver`), reducing memory and network overhead.
- Limited results: the `LIMIT` clause ensures the query retrieves only the first 10 results, avoiding large result sets.
- [`DIALECT 2`](): enables the latest Redis Search syntax and features, ensuring compatibility with modern capabilities.

code:
FT.CREATE jsonidx:profiles ON JSON PREFIX 1 profiles:
          SCHEMA $.tags.* as t NUMERIC SORTABLE
                 $.firstName as name TEXT
                 $.location as loc GEO

section_id: anti-pattern-index-schema

code: FT.AGGREGATE jsonidx:profiles '@t:[1299 1299]' LOAD * LIMIT 0 10

section_id: anti-pattern-query

code:
FT.CREATE jsonidx:profiles ON JSON PREFIX 1 profiles:
          SCHEMA $.tags.* as t NUMERIC SORTABLE
                 $.firstName as name TEXT NOSTEM SORTABLE
                 $.lastName as lastname TEXT NOSTEM SORTABLE
                 $.location as loc GEO SORTABLE
                 $.id as id TAG SORTABLE UNF
                 $.ver as ver TAG SORTABLE UNF

section_id: improved-index-schema

code:
FT.AGGREGATE jsonidx:profiles '@t:[1299 1299]'
                LOAD 6 id t name lastname loc ver
                LIMIT 0 10
                DIALECT 2

section_id: improved-query


title: Developer notes

summary: Notes on RediSearch debugging, testing, and documentation

title: Overview

text: Developing RediSearch features involves setting up a development environment (which can be either Linux-based or macOS-based), building the module, running tests and benchmarks, and debugging both the module and its tests.

title: Cloning the git repository

text:
Run the following command to clone the RediSearch module and its submodules:

[code example]

title: Working in an isolated environment

text:
There are several reasons to develop in an isolated environment, like keeping your workstation clean, and developing for a different Linux distribution.
The most general option for an isolated environment is a virtual machine. It's very easy to set one up using [Vagrant](https://www.vagrantup.com)).
Docker is even more agile, as it offers an almost instant solution:

[code example]

Then, from within the container, `cd /build` and go on as usual.

In this mode, all installations remain in the scope of the Docker container.
Upon exiting the container, you can either re-invoke it with the above `docker exec` or commit the state of the container to an image and re-invoke it at a later stage:

[code example]

You can replace `debian:bullseye` with your choice of OS, with the host OS being the best choice allowing you to run the RediSearch binary on your host after it is built.

title: Installing prerequisites

role: setup

text:
To build and test RediSearch you need to install several packages, depending on the underlying OS. The following OSes are supported:
- Ubuntu 18.04
- Ubuntu 20.04
- Ubuntu 22.04
- Debian linux 11
- Rocky linux 8
- Rocky linux 9
- Amazon linux 2
- Mariner 2.0
- MacOS

To install the prerequisites on your system using a setup script, first enter the `RediSearch` directory and then run:

[code example]

Note that this will install various packages on your system using the native package manager (`sudo` is not required in a Docker environment).

If you prefer to avoid that, you can:

* Review the relevant setup scripts under the `./install` directory and install packages manually.
* Use an isolated environment as explained above.

title: Installing Redis

text:
As a rule of thumb, you should run the latest Redis version.

If your OS has a Redis 7.x package, you can install it using the OS package manager.

Otherwise, you can build it from source and install it as described in [redis GitHub page](https://github.com/redis/redis).

title: Getting help

text:
[code example]
make fetch         # download and prepare dependant modules

make build          # compile and link
  COORD=1             # build coordinator
  DEBUG=1             # build for debugging
  NO_TESTS=1          # disable unit tests
  WHY=1               # explain CMake decisions (in /tmp/cmake-why)
  FORCE=1             # Force CMake rerun (default)
  CMAKE_ARGS=...      # extra arguments to CMake
  VG=1                # build for Valgrind
  SAN=type            # build with LLVM sanitizer (type=address|memory|leak|thread)
  SLOW=1              # do not parallelize build (for diagnostics)
  GCC=1               # build with GCC (default unless Sanitizer)
  CLANG=1             # build with CLang
  STATIC_LIBSTDCXX=0  # link libstdc++ dynamically (default: 1)
make parsers       # build parsers code (required after chaging files under query_parser dir)
make clean         # remove build artifacts
  ALL=1              # remove entire artifacts directory

make run           # run redis with RediSearch
  COORD=1            # run three local shards with coordinator (assuming the module was built with coordinator support)
  GDB=1              # invoke using gdb

make test          # run all tests
  COORD=1            # test coordinator
  TEST=name          # run specified test
make pytest        # run python tests (tests/pytests)
  COORD=1            # test coordinator
  TEST=name          # e.g. TEST=test:testSearch
  RLTEST_ARGS=...    # pass args to RLTest
  REJSON=1|0|get     # also load JSON module (default: 1)
  REJSON_PATH=path   # use JSON module at `path`
  EXT=1              # External (existing) environment
  GDB=1              # RLTest interactive debugging
  VG=1               # use Valgrind
  VG_LEAKS=0         # do not search leaks with Valgrind
  SAN=type           # use LLVM sanitizer (type=address|memory|leak|thread)
make unit-tests    # run unit tests (C and C++)
  TEST=name          # e.g. TEST=FGCTest.testRemoveLastBlock
make c_tests       # run C tests (from tests/ctests)
make cpp_tests     # run C++ tests (from tests/cpptests)

make callgrind     # produce a call graph
  REDIS_ARGS="args"

make sanbox        # create container with CLang Sanitizer
```

title: Building from source

text:
Run the following from the project root dir:

[code example]make build[code example]make clean``` to remove build artifacts. ```make clean ALL=1``` will remove the entire `bin` subdirectory.

title: Diagnosing the build process

text:
`make build` will build in parallel by default.

For the purposes of build diagnosis, `make build SLOW=1 VERBOSE=1` can be used to examine compilation commands.

title: Running Redis with RediSearch

text:
The following will run [code example]
make run
[code example]redis-cli``` in another terminal to interact with it.

title: Running tests

text:
There are several sets of unit tests:
* C tests, located in [code example]tests/cpptests[code example]tests/pytests[code example]make test[code example]TEST``` parameter, e.g., ```make test TEST=regex```.

title: Debugging

text: To build for debugging (enabling symbolic information and disabling optimization), run [code example]make run DEBUG=1[code example]gdb[code example]BB()``` function inside a test.

code: git clone --recursive https://github.com/RediSearch/RediSearch.git

section_id: cloning-the-git-repository

code:
search=$(docker run -d -it -v $PWD:/build debian:bullseye bash)
docker exec -it $search bash

section_id: working-in-an-isolated-environment

code:
docker commit $search redisearch1
docker stop $search
search=$(docker run -d -it -v $PWD:/build rediseatch1 bash)
docker exec -it $search bash

code:
cd ./install
./install_script.sh sudo
./install_boost.sh 1.83.0

section_id: installing-prerequisites

code:
`make build COORD=1` will build Redis Open Source RediSearch Coordinator.

`make build STATIC=1` will build as a static library.

Notes:

* Binary files are placed under `bin`, according to platform and build variant.
* RediSearch uses [CMake](https://cmake.org) as its build system.

section_id: building-from-source

language: will invoke both CMake and the subsequent make command that's required to complete the build.

code: Use

code: You can open

section_id: running-redis-with-redisearch

language: tests/ctests```, run by ```make c-tests```.

code: * C++ tests (enabled by GTest), located in

section_id: running-tests

code: * Python tests (enabled by RLTest), located in

code: You can run all tests by invoking

code: A single test can be run using the

code: You can then use

section_id: debugging

code: In addition to the usual way to set breakpoints in

language: , it is possible to use the ```BB``` macro to set a breakpoint inside the RediSearch code. It will only have an effect when running under ```gdb```.

code: Similarly, Python tests in a single-test mode, you can set a breakpoint by using the


title: Deprecated

summary: Deprecated features

title: Developer notes

summary: Payload support(deprecated)

title: Document payloads


title: Document payloads

summary: Payload support(deprecated)

title: Overview

text:
The payload feature is deprecated in 2.0

Usually, Redis Open Source stores documents as hashes or JSON. But if you want to access some data for aggregation or scoring functions, Redis can store that data as an inline payload. This will allow us to evaluate the properties of a document for scoring purposes at a very low cost.

Since the scoring functions already have access to the DocumentMetaData, which contains document flags and score, Redis can add custom payloads that can be evaluated in run-time.

Payloads are NOT indexed and are not treated by the engine in any way. They are simply there for the purpose of evaluating them in query time, and optionally retrieving them. They can be JSON objects, strings, or preferably, if you are interested in fast evaluation, some sort of binary encoded data which is fast to decode.

title: Evaluating payloads in query time

text:
When implementing a scoring function, the signature of the function exposed is:

[code example]

Currently, scoring functions cannot be dynamically added, and forking the engine and replacing them is required.

DocumentMetaData includes a few fields, one of them being the payload. It wraps a simple byte array with arbitrary length:

[code example]

If no payload was set to the document, it is simply NULL. If it is not, you can go ahead and decode it. It is recommended to encode some metadata about the payload inside it, like a leading version number, etc.

title: Retrieving payloads from documents

text:
When searching, it is possible to request the document payloads from the engine.

This is done by adding the keyword `WITHPAYLOADS` to [`FT.SEARCH`]().

If `WITHPAYLOADS` is set, the payloads follow the document id in the returned result.
If `WITHSCORES` is set as well, the payloads follow the scores.

code: double (*ScoringFunction)(DocumentMetadata *dmd, IndexResult *h);

section_id: evaluating-payloads-in-query-time

code:
typedef struct  {
    char *data,
    uint32_t len;
} DocumentPayload;


title: Redis Search

summary: Searching and querying Redis data using Redis Search

summary: How to index and search JSON documents

title: Indexing

summary: Understand how to query, search, and aggregate Redis data

title: Querying data

summary: Details about query syntax, aggregation, scoring, and other search and query options

title: Search concepts

summary: Learn how to use vector fields and perform vector searches in Redis

title: Vector search concepts

summary: Redis Search Administration

title: Administration

summary: Redis Search best practices

title: Best practices

summary: Deprecated features

title: Deprecated

title: Overview

text:
Redis Search offers an enhanced Redis experience via the following search and query features:

- A rich query language
- Incremental indexing on JSON and hash documents
- Vector search
- Full-text search
- Geospatial queries
- Aggregations

You can find a complete list of features in the [reference documentation]().

Redis Search features allow you to use Redis as a:

- Document database
- Vector database
- Secondary index
- Search engine

Here are the next steps to get you started:

1. Follow our [quick start guide]() to get some initial hands-on experience.
1. Learn how to [create an index]().
1. Learn how to [query your data]().
1. [Install Redis Insight](), connect it to your Redis database, and then use [Redis Copilot](#redis-copilot) to help you learn how to execute complex queries against your own data using simple, plain language prompts.

title: Enable Redis Search

text:
Redis Search is available in Redis Open Source, Redis Software, and Redis Cloud.
See
[Install Redis Open Source]() or
[Install Redis Software]()
for full installation instructions.

title: License and source code

text:
The Redis Search features of Redis are available under the Source Available License 2.0 (RSALv2), the Server Side Public License v1 (SSPLv1), or the GNU Affero General Public License version 3 (AGPLv3). Please read the [license file](https://raw.githubusercontent.com/RediSearch/RediSearch/master/LICENSE.txt) for further details. The source code and the [detailed release notes](https://github.com/RediSearch/RediSearch/releases) are available on [GitHub](https://github.com/RediSearch/RediSearch).

Do you have questions? Feel free to ask at the [RediSearch forum](https://forum.redis.com/c/modules/redisearch/).

<br/>


title: Field and type options

summary: Available field types and options.

title: Overview

text: Redis Open Source provides various field types that allow you to store and search different kinds of data in your indexes. This page explains the available field types, their characteristics, and how they can be used effectively.

title: Numeric fields

text:
Numeric fields are used to store non-textual, countable values. They can hold integer or floating-point values. Numeric fields are sortable, meaning you can perform range-based queries and retrieve documents based on specific numeric conditions. For example, you can search for documents with a price between a certain range or retrieve documents with a specific rating value.

You can add number fields to a schema in [`FT.CREATE`]() using this syntax:

[code example]

where:

- `SORTABLE` indicates that the field can be sorted. This is useful for performing range queries and sorting search results based on numeric values.
- `NOINDEX` indicates that the field is not indexed. This is useful for storing numeric values that you don't want to search for, but that you want to retrieve in search results.

You can search for documents with specific numeric values using the `@<field_name>:[<min> <max>]` query syntax. For example, this query finds documents with a price between 200 and 300:

[code example]

You can also use the following query syntax to perform more complex numeric queries:

| **Comparison operator** | **Query string**              | **Comment**              |
|-------------------------|-------------------------------|--------------------------|
| min <= x <= max   | @field:[min max]              | Fully inclusive range    |
|                         | "@field>=min @field<=max"     | Fully inclusive range \* |
| min < x < max     | @field:[(min (max]            | Fully exclusive range    |
|                         | "@field>min @field<max"       | Fully exclusive range \* |
|                         |                               |   grouping with a space denotes AND relationship |
| x >= min                | @field:[min +inf]             | Upper open range         |
|                         | @field>=min                   | Upper open range \*      |
| x <= max             | @field:[-inf max]             | Lower open range         |
|                         | @field<=max                   | Lower open range \*      |
| x == val                | @field:[val val]              | Equal                    |
|                         | @field:[val]                  | Equal \*                 |
|                         | @field==val                   | Equal \*                 |
| x != val                | -@field:[val val]             | Not equal                |
|                         | @field!=val                   | Not equal \*             |
| x == val1 or x == val2  | "@field==val1 \| @field==val2" | Grouping with a bar denotes OR relationship \* |

\* New syntax as of RediSearch v2.10. Requires [`DIALECT 2`](#dialect-2).

title: Geo fields

text:
Geo fields are used to store geographical coordinates such as longitude and latitude. They enable geospatial radius queries, which allow you to implement location-based search functionality in your applications such as finding nearby restaurants, stores, or any other points of interest.

Redis Search also supports [geoshape fields](#geoshape-fields) for more advanced
geospatial queries. See the
[Geospatial]()
reference page for an introduction to the format and usage of both schema types.

You can add geo fields to the schema in [`FT.CREATE`]() using this syntax:

[code example]

Where:
- `SORTABLE` indicates that the field can be sorted. This is useful for performing range queries and sorting search results based on coordinates.
- `NOINDEX` indicates that the field is not indexed. This is useful for storing coordinates that you don't want to search for, but that you still want to retrieve in search results.

You can query geo fields using the `@<field_name>:[<lon> <lat> <radius> <unit>]` query syntax. For example, this query finds documents within 1000 kilometers from the point `2.34, 48.86`:

[code example]

See
[Geospatial queries]()
for more information and code examples.

title: Geoshape fields

text:
Geoshape fields provide more advanced functionality than [Geo](#geo-fields).
You can use them to represent locations as points but also to define
shapes and query the interactions between points and shapes (for example,
to find all points that are contained within an enclosing shape). You can
also choose between geographical coordinates (on the surface of a sphere)
or standard Cartesian coordinates. Use geoshape fields for spatial queries
such as finding all office locations in a specified region or finding
all rooms in a building that fall within range of a wi-fi router.

See the
[Geospatial]()
reference page for an introduction to the format and usage of both the
geoshape and geo schema types.

Add geoshape fields to the schema in
[`FT.CREATE`]() using the following syntax:

[code example]

Where:
-   `FLAT` indicates Cartesian (planar) coordinates.
-   `SPHERICAL` indicates spherical (geographical) coordinates. This is the
    default option if you don't specify one explicitly.
-   `NOINDEX` indicates that the field is not indexed. This is useful for storing
    coordinates that you don't want to search for, but that you still want to retrieve
    in search results.

Note that unlike geo fields, geoshape fields don't support the `SORTABLE` option.

Query geoshape fields using the syntax `@<field_name>:[<OPERATION> <shape>]`
where `<operation>` is one of `WITHIN`, `CONTAINS`, `INTERSECTS`, or `DISJOINT`,
and `<shape>` is the shape of interest, specified in the
[Well-known text](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry)
format. For example, the query below finds shapes that contain the point (2, 2):

[code example]

See
[Geospatial queries]()
for more information and code examples.

title: Vector fields

text:
Vector fields are floating-point vectors that are typically generated by external machine learning models. These vectors represent unstructured data such as text, images, or other complex features. Redis allows you to search for similar vectors using vector search algorithms like cosine similarity, Euclidean distance, and inner product. This enables you to build advanced search applications, recommendation systems, or content similarity analysis.

You can add vector fields to the schema in [`FT.CREATE`]() using this syntax:

[code example]

Where:

* `{algorithm}` must be specified and be a supported vector similarity index algorithm. The supported algorithms are:

    - `FLAT`: brute force algorithm.
    - `HNSW`: hierarchical, navigable, small world algorithm.
    - `SVS-VAMANA`: a graph-based nearest neighbor search algorithm, which is optimized for use with compression methods to reduce its memory footprint.

    The `{algorithm}` attribute specifies the algorithm to use when searching `k` most similar vectors in the index or filtering vectors by range.

* `{count}` specifies the number of attributes for the index and it must be present.
Notice that `{count}` represents the total number of attribute pairs passed in the command. Algorithm parameters should be submitted as named arguments.

    For example:

    [code example]

    Here, three parameters are passed for the index ([`TYPE`](), `DIM`, `DISTANCE_METRIC`), and `count` is the total number of attributes (6).

* `{attribute_name} {attribute_value}` are algorithm attributes for the creation of the vector index. Every algorithm has its own mandatory and optional attributes.

For more information about vector fields, see [vector fields]().

title: Tag fields

text:
Tag fields are used to store textual data that represents a collection of data tags or labels. Tag fields are characterized by their low cardinality, meaning they typically have a limited number of distinct values. Unlike text fields, tag fields are stored as-is without tokenization or stemming. They are useful for organizing and categorizing data, making it easier to filter and retrieve documents based on specific tags.

Tag fields can be added to the schema with the following syntax:

[code example]

where

- `SEPARATOR` defaults to a comma (`,`), and can be any printable ASCII character. It is used to separate tags in the field value. For example, if the field value is `hello,world`, the tags are `hello` and `world`.

- `CASESENSITIVE` indicates that the field is case-sensitive. By default, tag fields are case-insensitive.

You can search for documents with specific tags using the `@<field_name>:{<tag>}` query syntax. For example, this query finds documents with the tag `blue`:

[code example]

For more information about tag fields, see [Tag Fields]().

title: Text fields

text:
Text fields are specifically designed for storing human language text. When indexing text fields, Redis performs several transformations to optimize search capabilities. The text is transformed to lowercase, allowing case-insensitive searches. The data is tokenized, meaning it is split into individual words or tokens, which enables efficient full-text search functionality. Text fields can be weighted to assign different levels of importance to specific fields during search operations. Additionally, text fields can be sorted based on their values, enabling the sorting of search results by relevance or other criteria.

Text fields can be added to the schema with the following syntax:

[code example]

where

- `WEIGHT` indicates that the field is weighted. This is useful for assigning different levels of importance to specific fields during search operations.
- `NOSTEM` indicates that the field is not stemmed. This is useful for storing text that you don't want to be tokenized, such as URLs or email addresses.
- `PHONETIC {matcher}` Declaring a text attribute as `PHONETIC` will perform phonetic matching on it in searches by default. The obligatory matcher argument specifies the phonetic algorithm and language used. The following matchers are supported:

   - `dm:en` - double metaphone for English
   - `dm:fr` - double metaphone for French
   - `dm:pt` - double metaphone for Portuguese
   - `dm:es` - double metaphone for Spanish

    For more information, see [Phonetic Matching]().
- `SORTABLE` indicates that the field can be sorted. This is useful for performing range queries and sorting search results based on text values.
- `NOINDEX` indicates that the field is not indexed. This is useful for storing text that you don't want to search for, but that you still want to retrieve in search results.
- `WITHSUFFIXTRIE` indicates that the field will be indexed with a suffix trie. The index will keep a suffix trie with all terms which match the suffix. It is used to optimize `contains (*foo*)` and `suffix (*foo)` queries. Otherwise, a brute-force search on the trie is performed. If a suffix trie exists for some fields, these queries will be disabled for other fields.

You can search for documents with specific text values using the `<term>` or the `@<field_name>:{<term>}` query syntax. Here are a couple of examples:

- Search for a term in every text attribute:
    [code example]

- Search for a term only in the `title` attribute
    [code example]

title: Unicode considerations

text:
Redis Search only supports Unicode characters in the [basic multilingual plane](https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane); U+0000 to U+FFFF. Unicode characters beyond U+FFFF, such as Emojis, are not supported and would not be retrieved by queries including such characters in the following use cases:

* Querying TEXT fields with Prefix/Suffix/Infix
* Querying TEXT fields with fuzzy

Examples:

[code example]

code: FT.CREATE ... SCHEMA ... {field_name} NUMERIC [SORTABLE] [NOINDEX]

section_id: numeric-fields

code: FT.SEARCH products "@price:[200 300]"

code: FT.CREATE ... SCHEMA ... {field_name} GEO [SORTABLE] [NOINDEX]

section_id: geo-fields

code: FT.SEARCH cities "@coords:[2.34 48.86 1000 km]"

code: FT.CREATE ... SCHEMA ... {field_name} GEOSHAPE [FLAT|SPHERICAL] [NOINDEX]

section_id: geoshape-fields

code: FT.SEARCH idx "(@geom:[CONTAINS $qshape])" PARAMS 2 qshape "POINT (2 2)" RETURN 1 name DIALECT 2

code: FT.CREATE ... SCHEMA ... {field_name} VECTOR {algorithm} {count} [{attribute_name} {attribute_value} ...]

section_id: vector-fields

code: FT.CREATE my_idx SCHEMA vec_field VECTOR FLAT 6 TYPE FLOAT32 DIM 128 DISTANCE_METRIC L2

code: FT.CREATE ... SCHEMA ... {field_name} TAG [SEPARATOR {sep}] [CASESENSITIVE]

section_id: tag-fields

code: FT.SEARCH idx "@tags:{blue}"

code: FT.CREATE ... SCHEMA ... {field_name} TEXT [WEIGHT] [NOSTEM] [PHONETIC {matcher}] [SORTABLE] [NOINDEX] [WITHSUFFIXTRIE]

section_id: text-fields

code: FT.SEARCH books-idx "wizard"

code: FT.SEARCH books-idx "@title:dogs"

code:
redis> FT.CREATE idx SCHEMA tag TAG text TEXT
OK
redis> HSET doc:1 tag '😀😁🙂' text '😀😁🙂'
(integer) 2
redis> HSET doc:2 tag '😀😁🙂abc' text '😀😁🙂abc'
(integer) 2
redis> FT.SEARCH idx '@text:(*😀😁🙂)' NOCONTENT
1) (integer) 0
redis> FT.SEARCH idx '@text:(*😀😁🙂*)' NOCONTENT
1) (integer) 0
redis> FT.SEARCH idx '@text:(😀😁🙂*)' NOCONTENT
1) (integer) 0

redis> FT.SEARCH idx '@text:(%😀😁🙃%)' NOCONTENT
1) (integer) 0

section_id: unicode-considerations


title: Geospatial indexing

summary: Options for indexing geospatial data

title: `GEO`

text:
The following command creates a `GEO` index for JSON objects that contain
the geospatial data in a field called `location`:

#### Code Examples

Foundational: Create an index for geospatial point data using the GEO field type when you need to store and query longitude-latitude coordinates

**Difficulty:** Beginner

**Commands:** FT.CREATE

**Complexity:**
- FT.CREATE: O(K)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

If you now add JSON objects with the `product:` prefix and a `location` field,
they will be added to the index automatically:

#### Code Examples

Foundational: Add JSON documents with geospatial point data to an indexed field when you need to populate the index with location information

**Difficulty:** Beginner

**Commands:** JSON.SET

**Complexity:**
- JSON.SET: O(M+N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

The query below finds products within a 100 mile radius of Colorado Springs
(Longitude=-104.800644, Latitude=38.846127). This returns only the location in
Denver, but a radius of 200 miles would also include the location in Fort Collins:

#### Code Examples

GEO radius query: Query geospatial points within a radius using center coordinates and distance when you need to find nearby locations

**Difficulty:** Beginner

**Commands:** FT.SEARCH

**Complexity:**
- FT.SEARCH: O(N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

See [Geospatial queries]()
for more information about the available options.

title: `GEOSHAPE`

text:
The following command creates an index for JSON objects that include
geospatial data in a field called `geom`. The `FLAT` option at the end
of the field definition specifies Cartesian coordinates instead of
the default spherical geographical coordinates. Use `SPHERICAL` in
place of `FLAT` to choose the coordinate space explicitly.

#### Code Examples

Foundational: Create an index for geometric shapes using the GEOSHAPE field type when you need to store and query polygons and points

**Difficulty:** Intermediate

**Commands:** FT.CREATE

**Complexity:**
- FT.CREATE: O(K)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

Use the `shape:` prefix for the JSON objects to add them to the index:

#### Code Examples

Foundational: Add JSON documents with geometric shape data to an indexed field when you need to populate the index with polygon and point information

**Difficulty:** Intermediate

**Commands:** JSON.SET

**Complexity:**
- JSON.SET: O(M+N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

You can now run various geospatial queries against the index. For
example, the query below returns any shapes within the boundary
of the green square but omits the green square itself:

#### Code Examples

GEOSHAPE query: Query geometric shapes using spatial operators like WITHIN to find shapes with specific geometric relationships when you need to test shape containment

**Difficulty:** Intermediate

**Commands:** FT.SEARCH

**Complexity:**
- FT.SEARCH: O(N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

You can also run queries to find whether shapes in the index completely contain
or overlap each other. See
[Geospatial queries]()
for more information.

code:
> FT.CREATE productidx ON JSON PREFIX 1 product: SCHEMA $.location AS location GEO
OK

section_id: geo

code:
Schema geoSchema = new Schema()
            .AddGeoField(new FieldName("$.location", "location"));

        bool geoCreateResult = db.FT().Create(
            "productidx",
            new FTCreateParams()
                .On(IndexDataType.JSON)
                .Prefix("product:"),
            geoSchema
        );
        Console.WriteLine(geoCreateResult); // >>> True

code:
geoCreateResult, err := rdb.FTCreate(ctx,
		"productidx",
		&redis.FTCreateOptions{
			OnJSON: true,
			Prefix: []interface{}{"product:"},
		},
		&redis.FieldSchema{
			FieldName: "$.location",
			As:        "location",
			FieldType: redis.SearchFieldTypeGeo,
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(geoCreateResult) // >>> OK

code:
SchemaField[] geoSchema = {
            GeoField.of("$.location").as("location")
        };

        String geoIdxCreateResult = jedis.ftCreate("productidx",
            FTCreateParams.createParams()
                    .on(IndexDataType.JSON)
                    .addPrefix("product:"),
            geoSchema
        );

code:
geo_schema = (
    GeoField("$.location", as_name="location")
)

geo_index_create_result = r.ft("productidx").create_index(
    geo_schema,
    definition=IndexDefinition(
        prefix=["product:"], index_type=IndexType.JSON
    )
)
print(geo_index_create_result)  # >>> True

code:
> JSON.SET product:46885 $ '{"description": "Navy Blue Slippers","price": 45.99,"city": "Denver","location": "-104.991531, 39.742043"}'
OK
> JSON.SET product:46886 $ '{"description": "Bright Green Socks","price": 25.50,"city": "Fort Collins","location": "-105.0618814,40.5150098"}'
OK

code:
var product46885 = new
        {
            description = "Navy Blue Slippers",
            price = 45.99,
            city = "Denver",
            location = "-104.991531, 39.742043"
        };

        bool gjAddResult1 = db.JSON().Set("product:46885", "$", product46885);
        Console.WriteLine(gjAddResult1); // >>> True

        var product46886 = new
        {
            description = "Bright Green Socks",
            price = 25.50,
            city = "Fort Collins",
            location = "-105.0618814,40.5150098"
        };

        bool gjAddResult2 = db.JSON().Set("product:46886", "$", product46886);
        Console.WriteLine(gjAddResult2); // >>> True

code:
prd46885 := map[string]interface{}{
		"description": "Navy Blue Slippers",
		"price":       45.99,
		"city":        "Denver",
		"location":    "-104.991531, 39.742043",
	}

	gjResult1, err := rdb.JSONSet(ctx, "product:46885", "$", prd46885).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gjResult1) // >>> OK

	prd46886 := map[string]interface{}{
		"description": "Bright Green Socks",
		"price":       25.50,
		"city":        "Fort Collins",
		"location":    "-105.0618814,40.5150098",
	}

	gjResult2, err := rdb.JSONSet(ctx, "product:46886", "$", prd46886).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gjResult2) // >>> OK

code:
JSONObject prd46885 = new JSONObject()
                .put("description", "Navy Blue Slippers")
                .put("price", 45.99)
                .put("city", "Denver")
                .put("location", "-104.991531, 39.742043");

        String jsonAddResult1 = jedis.jsonSet("product:46885", new Path2("$"), prd46885);
        System.out.println(jsonAddResult1); // >>> OK

        JSONObject prd46886 = new JSONObject()
                .put("description", "Bright Green Socks")
                .put("price", 25.50)
                .put("city", "Fort Collins")
                .put("location", "-105.0618814,40.5150098");

        String jsonAddResult2 = jedis.jsonSet("product:46886", new Path2("$"), prd46886);
        System.out.println(jsonAddResult2); // >>> OK

code:
prd46885 = {
    "description": "Navy Blue Slippers",
    "price": 45.99,
    "city": "Denver",
    "location": "-104.991531, 39.742043"
}

json_add_result_1 = r.json().set("product:46885", Path.root_path(), prd46885)
print(json_add_result_1)  # >>> True

prd46886 = {
    "description": "Bright Green Socks",
    "price": 25.50,
    "city": "Fort Collins",
    "location": "-105.0618814,40.5150098"
}

json_add_result_2 = r.json().set("product:46886", Path.root_path(), prd46886)
print(json_add_result_2)  # >>> True

code:
SearchResult geoQueryResult = db.FT().Search(
            "productidx",
            new Query("@location:[-104.800644 38.846127 100 mi]")
        );
        Console.WriteLine(geoQueryResult.Documents.Count); // >>> 1

        Console.WriteLine(
            string.Join(", ", geoQueryResult.Documents.Select(x => x["json"]))
        );
        // >>> {"description":"Navy Blue Slippers","price":45.99,"city":"Denver"...

code:
geoQueryResult, err := rdb.FTSearch(ctx, "productidx",
		"@location:[-104.800644 38.846127 100 mi]",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(geoQueryResult)
	// >>> {1 [{product:46885...

code:
SearchResult geoResult = jedis.ftSearch("productidx",
            "@location:[-104.800644 38.846127 100 mi]"
        );

        System.out.println(geoResult.getTotalResults()); // >>> 1

        for (Document doc: geoResult.getDocuments()) {
            System.out.println(doc.getId());
        }
        // >>> product:46885

code:
geo_result = r.ft("productidx").search(
    "@location:[-104.800644 38.846127 100 mi]"
)
print(geo_result)
# >>> Result{1 total, docs: [Document {'id': 'product:46885'...

code:
> FT.CREATE geomidx ON JSON PREFIX 1 shape: SCHEMA $.name AS name TEXT $.geom AS geom GEOSHAPE FLAT
OK

section_id: geoshape

code:
Version version = muxer.GetServer("localhost:6379").Version;
        if (version.Major >= 7)
        {
            Schema geomSchema = new Schema()
                .AddGeoShapeField(
                    new FieldName("$.geom", "geom"),
                    Schema.GeoShapeField.CoordinateSystem.FLAT
                )
                .AddTextField(new FieldName("$.name", "name"));

            bool geomCreateResult = db.FT().Create(
                "geomidx",
                new FTCreateParams()
                    .On(IndexDataType.JSON)
                    .Prefix("shape:"),
                    geomSchema
            );
            Console.WriteLine(geomCreateResult); // >>> True
        }

code:
geomCreateResult, err := rdb.FTCreate(ctx, "geomidx",
		&redis.FTCreateOptions{
			OnJSON: true,
			Prefix: []interface{}{"shape:"},
		},
		&redis.FieldSchema{
			FieldName: "$.name",
			As:        "name",
			FieldType: redis.SearchFieldTypeText,
		},
		&redis.FieldSchema{
			FieldName:         "$.geom",
			As:                "geom",
			FieldType:         redis.SearchFieldTypeGeoShape,
			GeoShapeFieldType: "FLAT",
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(geomCreateResult) // >>> OK

code:
SchemaField[] geomSchema = {
            TextField.of("$.name").as("name"),
            GeoShapeField.of("$.geom", CoordinateSystem.FLAT).as("geom")
        };

        String geomIndexCreateResult = jedis.ftCreate("geomidx",
            FTCreateParams.createParams()
                    .on(IndexDataType.JSON)
                    .addPrefix("shape"),
            geomSchema
        );
        System.out.println(geomIndexCreateResult); // >>> OK

code:
geom_schema = (
    TextField("$.name", as_name="name"),
    GeoShapeField(
        "$.geom", as_name="geom", coord_system=GeoShapeField.FLAT
    )
)

geom_index_create_result = r.ft("geomidx").create_index(
    geom_schema,
    definition=IndexDefinition(
        prefix=["shape:"], index_type=IndexType.JSON
    )
)
print(geom_index_create_result)  # True

code:
> JSON.SET shape:1 $ '{"name": "Green Square", "geom": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"}'
OK
> JSON.SET shape:2 $ '{"name": "Red Rectangle", "geom": "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))"}'
OK
> JSON.SET shape:3 $ '{"name": "Blue Triangle", "geom": "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))"}'
OK
> JSON.SET shape:4 $ '{"name": "Purple Point", "geom": "POINT (2 2)"}'
OK

code:
var shape1 = new
        {
            name = "Green Square",
            geom = "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"
        };

        bool gmJsonRes1 = db.JSON().Set("shape:1", "$", shape1);
        Console.WriteLine(gmJsonRes1); // >>> True

        var shape2 = new
        {
            name = "Red Rectangle",
            geom = "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))"
        };

        bool gmJsonRes2 = db.JSON().Set("shape:2", "$", shape2);
        Console.WriteLine(gmJsonRes1); // >>> True

        var shape3 = new
        {
            name = "Blue Triangle",
            geom = "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))"
        };

        bool gmJsonRes3 = db.JSON().Set("shape:3", "$", shape3);
        Console.WriteLine(gmJsonRes3); // >>> True

        var shape4 = new
        {
            name = "Purple Point",
            geom = "POINT (2 2)"
        };

        bool gmJsonRes4 = db.JSON().Set("shape:4", "$", shape4);
        Console.WriteLine(gmJsonRes3); // >>> True

code:
shape1 := map[string]interface{}{
		"name": "Green Square",
		"geom": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
	}

	gmjResult1, err := rdb.JSONSet(ctx, "shape:1", "$", shape1).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gmjResult1) // >>> OK

	shape2 := map[string]interface{}{
		"name": "Red Rectangle",
		"geom": "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))",
	}

	gmjResult2, err := rdb.JSONSet(ctx, "shape:2", "$", shape2).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gmjResult2) // >>> OK

	shape3 := map[string]interface{}{
		"name": "Blue Triangle",
		"geom": "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))",
	}

	gmjResult3, err := rdb.JSONSet(ctx, "shape:3", "$", shape3).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gmjResult3) // >>> OK

	shape4 := map[string]interface{}{
		"name": "Purple Point",
		"geom": "POINT (2 2)",
	}

	gmjResult4, err := rdb.JSONSet(ctx, "shape:4", "$", shape4).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gmjResult4) // >>> OK

code:
JSONObject shape1 = new JSONObject()
                .put("name", "Green Square")
                .put("geom", "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))");

        String gmJsonRes1 = jedis.jsonSet("shape:1", new Path2("$"), shape1);
        System.out.println(gmJsonRes1); // >>> OK

        JSONObject shape2 = new JSONObject()
                .put("name", "Red Rectangle")
                .put("geom", "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))");

        String gmJsonRes2 = jedis.jsonSet("shape:2", new Path2("$"), shape2);
        System.out.println(gmJsonRes2); // >>> OK

        JSONObject shape3 = new JSONObject()
                .put("name", "Blue Triangle")
                .put("geom", "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))");

        String gmJsonRes3 = jedis.jsonSet("shape:3", new Path2("$"), shape3);
        System.out.println(gmJsonRes3); // >>> OK

        JSONObject shape4 = new JSONObject()
                .put("name", "Purple Point")
                .put("geom", "POINT (2 2)");

        String gmJsonRes4 = jedis.jsonSet("shape:4", new Path2("$"), shape4);
        System.out.println(gmJsonRes4); // >>> OK

code:
shape1 = {
    "name": "Green Square",
    "geom": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"
}

gm_json_res_1 = r.json().set("shape:1", Path.root_path(), shape1)
print(gm_json_res_1)  # >>> True

shape2 = {
    "name": "Red Rectangle",
    "geom": "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))"
}

gm_json_res_2 = r.json().set("shape:2", Path.root_path(), shape2)
print(gm_json_res_2)  # >>> True

shape3 = {
    "name": "Blue Triangle",
    "geom": "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))"
}

gm_json_res_3 = r.json().set("shape:3", Path.root_path(), shape3)
print(gm_json_res_3)  # >>> True

shape4 = {
    "name": "Purple Point",
    "geom": "POINT (2 2)"
}

gm_json_res_4 = r.json().set("shape:4", Path.root_path(), shape4)
print(gm_json_res_4)  # >>> True

code:
> FT.SEARCH geomidx "(-@name:(Green Square) @geom:[WITHIN $qshape])" PARAMS 2 qshape "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))" RETURN 1 name DIALECT 2

1) (integer) 1
2) "shape:4"
3) 1) "name"
   2) "[\"Purple Point\"]"

code:
if (version.Major >= 7)
        {
            SearchResult geomQueryResult = db.FT().Search(
                "geomidx",
                new Query("(-@name:(Green Square) @geom:[WITHIN $qshape])")
                    .AddParam("qshape", "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))")
                    .Limit(0, 1)
            );

            Console.WriteLine(geomQueryResult.Documents.Count); // >>> 1
            var res = string.Join(", ", geomQueryResult.Documents.Select(x => x["json"]));

            Console.WriteLine(
                string.Join(", ", geomQueryResult.Documents.Select(x => x["json"]))
            );
            // >>> {"name":"Purple Point","geom":"POINT (2 2)"}
        }

code:
geomQueryResult, err := rdb.FTSearchWithArgs(ctx, "geomidx",
		"(-@name:(Green Square) @geom:[WITHIN $qshape])",
		&redis.FTSearchOptions{
			Params: map[string]interface{}{
				"qshape": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
			},
			DialectVersion: 4,
			Limit:          1,
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(geomQueryResult)
	// >>> {1 [{shape:4...

code:
SearchResult geomResult = jedis.ftSearch("geomidx",
            "(-@name:(Green Square) @geom:[WITHIN $qshape])",
            FTSearchParams.searchParams()
                    .addParam("qshape", "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))")
                    .dialect(4)
                    .limit(0, 1)
        );
        System.out.println(geomResult.getTotalResults()); // >>> 1

        for (Document doc: geomResult.getDocuments()) {
            System.out.println(doc.getId());
        }
        // shape:4

code:
geom_result = r.ft("geomidx").search(
    Query(
        "(-@name:(Green Square) @geom:[WITHIN $qshape])"
    ).dialect(4).paging(0, 1),
    query_params={
        "qshape": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"
    }
)
print(geom_result)
# >>> Result{1 total, docs: [Document {'id': 'shape:4'...


title: Indexing

summary: How to index and search JSON documents

summary: How to define the schema of an index.

title: Schema definition

summary: Available field types and options.

title: Field and type options

summary: Options for indexing geospatial data

title: Geospatial indexing

title: Overview

text: In addition to indexing Redis hashes, Redis Open Source can also index JSON documents.

title: Create index with JSON schema

text:
When you create an index with the [`FT.CREATE`]() command, include the `ON JSON` keyword to index any existing and future JSON documents stored in the database.

To define the `SCHEMA`, you can provide [JSONPath]() expressions.
The result of each JSONPath expression is indexed and associated with a logical name called an `attribute` (previously known as a `field`).
You can use these attributes in queries.

`attribute` is optional for [`FT.CREATE`]().

Use the following syntax to create a JSON index:

[code example]

For example, this command creates an index that indexes the name, description, price, and image vector embedding of each JSON document that represents an inventory item:

[code example]

See [Index limitations](#index-limitations) for more details about JSON index `SCHEMA` restrictions.

title: Add JSON documents

text:
After you create an index, Redis automatically indexes any existing, modified, or newly created JSON documents stored in the database. For existing documents, indexing runs asynchronously in the background, so it can take some time before the document is available. Modified and newly created documents are indexed synchronously, so the document will be available by the time the add or modify command finishes.

You can use any JSON write command, such as [`JSON.SET`]() and [`JSON.ARRAPPEND`](), to create or modify JSON documents.

The following examples use these JSON documents to represent individual inventory items.

Item 1 JSON document:

[code example]

Item 2 JSON document:

[code example]

Use [`JSON.SET`]() to store these documents in the database:

[code example]

Because indexing is synchronous in this case, the documents will be available on the index as soon as the [`JSON.SET`]() command returns.
Any subsequent queries that match the indexed content will return the document.

title: Search the index

text:
To search the index for JSON documents, use the [`FT.SEARCH`]() command.
You can search any attribute defined in the `SCHEMA`.

For example, use this query to search for items with the word "earbuds" in the name:

[code example]

This query searches for all items that include "bluetooth" and "headphones" in the description:

[code example]

Now search for Bluetooth headphones with a price less than 70:

[code example]

And lastly, search for the Bluetooth headphones that are most similar to an image whose embedding is [1.0, 1.0, 1.0, 1.0]:

[code example]

For more information about search queries, see [Search query syntax]().

[`FT.SEARCH`]() queries require `attribute` modifiers. Don't use JSONPath expressions in queries because the query parser doesn't fully support them.

title: Understanding TAG field behavior: hash versus JSON

text: TAG fields behave differently depending on whether you're indexing hash or JSON documents. This difference is a common source of confusion.

title: Hash documents

text: [code example]

title: JSON documents

text: [code example]

title: Making JSON documents behave like hash documents

text:
To get hash-like behavior in JSON, explicitly add `SEPARATOR ","`:

[code example]

title: Recommended approach for JSON

text:
Instead of comma-separated strings, use JSON arrays:

[code example]

title: Index JSON arrays as TAG

text: For JSON documents, you have two approaches to create TAG fields with multiple values:

title: Approach 1: JSON arrays (recommended)

text:
The preferred method for indexing multiple tag values is using JSON arrays. Each array element becomes a separate tag value. Use the [JSONPath]() wildcard operator `[*]` to index array elements.

[code example]

Now you can search for silver headphones:

[code example]

title: Approach 2: strings with explicit separators

text:
You can also use comma-separated strings, but you must explicitly specify the `SEPARATOR`:

[code example]

- **JSON without SEPARATOR**: `"black,silver"` becomes one tag: `"black,silver"`.
- **JSON with SEPARATOR ","**: `"black,silver"` becomes two tags: `"black"` and `"silver"`.
- **Hash (default)**: `"black,silver"` becomes two tags: `"black"` and `"silver"`.

For JSON, always specify `SEPARATOR ","` if you want to split comma-separated strings, or use arrays instead.

title: Which approach to choose?

text:
Use JSON arrays when:

- You control the data structure.
- You want clean, structured data.
- You need to store complex values (strings with spaces, punctuation).

Use strings with separators when:

- You're migrating from hashes to JSON.
- You receive data as delimited strings.
- You need compatibility with existing systems.

title: Index JSON arrays as TEXT

text:
Starting with RediSearch v2.6.0, full text search can be done on an array of strings or on a JSONPath leading to multiple strings.

If you want to index multiple string values as TEXT, use either a JSONPath leading to a single array of strings, or a JSONPath leading to multiple string values, using JSONPath operators such as wildcard, filter, union, array slice, and/or recursive descent.

To index an item's list of available colors, specify the JSONPath `$.colors` in the `SCHEMA` definition during index creation:

[code example]

[code example]

Now you can do full text search for light colored headphones:

[code example]

title: Limitations

role: limits

text:
- When a JSONPath may lead to multiple values and not only to a single array, e.g., when a JSONPath contains wildcards, etc., specifying `SLOP` or `INORDER` in [`FT.SEARCH`]() will return an error, since the order of the values matching the JSONPath is not well defined, leading to potentially inconsistent results.

   For example, using a JSONPath such as `$..b[*]` on a JSON value such as
   [code example]
   may match values in various orderings, depending on the specific implementation of the JSONPath library being used.

   Since `SLOP` and `INORDER` consider relative ordering among the indexed values, and results may change in future releases, an error will be returned.

- When JSONPath leads to multiple values:
  - String values are indexed
  - `null` values are skipped
  - Any other value type will cause an indexing failure

title: Handling phrases in different array slots:

text:
When indexing, a predefined delta is used to increase positional offsets between array slots for multiple text values. This delta controls the level of separation between phrases in different array slots (related to the `SLOP` parameter of [`FT.SEARCH`]()).
This predefined value is set by the configuration parameter `MULTI_TEXT_SLOP` (at module load-time). The default value is 100.

title: Index JSON arrays as NUMERIC

text:
Starting with RediSearch v2.6.1, search can be done on an array of numerical values or on a JSONPath leading to multiple numerical values.

If you want to index multiple numerical values as NUMERIC, use either a JSONPath leading to a single array of numbers, or a JSONPath leading to multiple numbers, using JSONPath operators such as wildcard, filter, union, array slice, and/or recursive descent.

For example, add to the item's list the available `max_level` of volume (in decibels):

[code example]

To index the `max_level` array, specify the JSONPath `$.max_level` in the `SCHEMA` definition during index creation:

[code example]

You can now search for headphones with specific max volume levels, for example, between 70 and 80 (inclusive), returning items with at least one value in their `max_level` array, which is in the requested range:

[code example]

You can also search for items with all values in a specific range. For example, all values are in the range [90, 120] (inclusive):

[code example]

title: Limitations

text:
When JSONPath leads to multiple numerical values:
  - Numerical values are indexed
  - `null` values are skipped
  - Any other value type will cause an indexing failure

title: Index JSON arrays as GEO and GEOSHAPE

text:
You can use `GEO` and `GEOSHAPE` fields to store geospatial data,
such as geographical locations and geometric shapes. See
[Geospatial indexing]()
to learn how to use these schema types and see the
[Geospatial]()
reference page for an introduction to their format and usage.

title: Index JSON arrays as VECTOR

text:
Starting with RediSearch 2.6.0, you can index a JSONPath leading to an array of numeric values as a VECTOR type in the index schema.

For example, assume that your JSON items include an array of vector embeddings, where each vector represents an image of a product. To index these vectors, specify the JSONPath `$.embedding` in the schema definition during index creation:

[code example]

Now you can search for the two headphones that are most similar to the image embedding by using vector search KNN query. (Note that the vector queries are supported as of dialect 2.) For example:

[code example]

If you want to index multiple numeric arrays as VECTOR, use a [JSONPath]() leading to multiple numeric arrays using JSONPath operators such as wildcard, filter, union, array slice, and/or recursive descent.

For example, assume that your JSON items include an array of vector embeddings, where each vector represents a different image of the same product. To index these vectors, specify the JSONPath `$.embeddings[*]` in the schema definition during index creation:

[code example]

Unlike the case with the NUMERIC type, setting a static path such as `$.embedding` in the schema for the VECTOR type does not allow you to index multiple vectors stored under that field. Hence, if you set `$.embedding` as the path to the index schema, specifying an array of vectors in the `embedding` field in your JSON will cause an indexing failure.

Now you can search for the two headphones that are most similar to an image embedding by using vector search KNN query. (Note that the vector queries are supported as of dialect 2.) The distance between a document to the query vector is defined as the minimum distance between the query vector to a vector that matches the JSONPath specified in the schema. For example:

[code example]
Note that `0.771500051022` is the L2 distance between the query vector and `[-0.8,-0.15,0.33,-0.01]`, which is the second element in the embedding array, and it is lower than the L2 distance between the query vector and `[-0.7,-0.51,0.88,0.14]`, which is the first element in the embedding array.

For more information on vector similarity syntax, see [Vector fields]().

title: Index JSON objects

text:
You cannot index JSON objects. FT.CREATE will return an error if the JSONPath expression returns an object.

To index the contents of a JSON object, you need to index the individual elements within the object in separate attributes.

For example, to index the `connection` JSON object, define the `$.connection.wireless` and `$.connection.type` fields as separate attributes when you create the index:

[code example]

After you create the new index, you can search for items with the wireless TAG set to `true`:

[code example]

You can also search for items with a Bluetooth connection type:

[code example]

title: Field projection

text: [`FT.SEARCH`]() returns the entire JSON document by default. If you want to limit the returned search results to specific attributes, you can use field projection.

title: Return specific attributes

role: returns

text:
When you run a search query, you can use the `RETURN` keyword to specify which attributes you want to include in the search results. You also need to specify the number of fields to return.

For example, this query only returns the `name` and `price` of each set of headphones:

[code example]

title: Project with JSONPath

text:
You can use [JSONPath]() expressions in a `RETURN` statement to extract any part of the JSON document, even fields that were not defined in the index `SCHEMA`.

For example, the following query uses the JSONPath expression `$.stock` to return each item's stock in addition to the name and price attributes.

[code example]

Note that the returned property name is the JSONPath expression itself: `"$.stock"`.

You can use the `AS` option to specify an alias for the returned property:

[code example]

This query returns the field as the alias `"stock"` instead of the JSONPath expression `"$.stock"`.

title: Highlight search terms

text:
You can [highlight]() relevant search terms in any indexed `TEXT` attribute.

For JSON documents, you must use the `RETURN` parameter to specify the attributes, followed by `HIGHLIGHT` to indicate which of those attributes to highlight.

Use the optional `TAGS` keyword to specify the strings that will surround (or highlight) the matching search terms.

`HIGHLIGHT` and `SUMMARIZE` are not supported when the JSONPath leads to multiple values (such as arrays indexed as `TEXT`). See [Index limitations](#index-limitations) for details.

For example, highlight the word "bluetooth" with bold HTML tags in item names and descriptions:

[code example]

title: Aggregate with JSONPath

text:
You can use [aggregation]() to generate statistics or build facet queries.

The `LOAD` option accepts [JSONPath]() expressions. You can use any value in the pipeline, even if the value is not indexed.

This example uses aggregation to calculate a 10% price discount for each item and sorts the items from least expensive to most expensive:

[code example]

[`FT.AGGREGATE`]() queries require `attribute` modifiers. Don't use JSONPath expressions in queries, except with the `LOAD` option, because the query parser doesn't fully support them.

title: Index missing or empty values

text:
As of v2.10, you can search for missing properties, that is, properties that do not exist in a given document, using the `INDEXMISSING` option to `FT.CREATE` in conjunction with the `ismissing` query function with `FT.SEARCH`. You can also search for existing properties with no value (i.e., empty) using the `INDEXEMPTY` option with `FT.CREATE`. Both query types require DIALECT 2. Examples below:

[code example]

[code example]

title: Index limitations

title: Schema mapping

text:
During index creation, you need to map the JSON elements to `SCHEMA` fields as follows:

- Strings as `TEXT`, `TAG`, or `GEO`.
- Numbers as `NUMERIC`.
- Booleans as `TAG`.
- JSON array
  - Array of strings as `TAG` or `TEXT`.
  - Array of numbers as `NUMERIC` or `VECTOR`.
  - Array of geo coordinates as `GEO`.
  - `null` values in such arrays are ignored.
- You cannot index JSON objects. Index the individual elements as separate attributes instead.
- `null` values are ignored.

title: JSONPath with multiple values

text:
When a JSONPath leads to an array or to multiple values:

- No `HIGHLIGHT` and `SUMMARIZE` support.
- `SORTBY` only sorts by the first value.
- `RETURN` of a schema attribute returns the values as a JSON string.
- If a JSONPath is specified by the `RETURN`, instead of a schema attribute, all values are returned (as a JSON string).

code: FT.CREATE {index_name} ON JSON SCHEMA {json_path} AS {attribute} {type}

section_id: create-index-with-json-schema

code: 127.0.0.1:6379> FT.CREATE itemIdx ON JSON PREFIX 1 item: SCHEMA $.name AS name TEXT $.description as description TEXT $.price AS price NUMERIC $.embedding AS embedding VECTOR FLAT 6 DIM 4 DISTANCE_METRIC L2 TYPE FLOAT32

Noise-cancelling Bluetooth headphones

type: Bluetooth

colors: black silver

section_id: add-json-documents

Wireless earbuds

description: Wireless Bluetooth in-ear headphones

colors: black white

code:
127.0.0.1:6379> JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","connection":{"wireless":true,"type":"Bluetooth"},"price":99.98,"stock":25,"colors":["black","silver"],"embedding":[0.87,-0.15,0.55,0.03]}'
"OK"
127.0.0.1:6379> JSON.SET item:2 $ '{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","connection":{"wireless":true,"type":"Bluetooth"},"price":64.99,"stock":17,"colors":["black","white"],"embedding":[-0.7,-0.51,0.88,0.14]}'
"OK"

code:
127.0.0.1:6379> FT.SEARCH itemIdx '@name:(earbuds)'
1) "1"
2) "item:2"
3) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

section_id: search-the-index

code:
127.0.0.1:6379> FT.SEARCH itemIdx '@description:(bluetooth headphones)'
1) "2"
2) "item:1"
3) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"], \"embedding\":[0.87,-0.15,0.55,0.03]}"
4) "item:2"
5) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

code:
127.0.0.1:6379> FT.SEARCH itemIdx '@description:(bluetooth headphones) @price:[0 70]'
1) "1"
2) "item:2"
3) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

code:
127.0.0.1:6379> FT.SEARCH itemIdx '@description:(bluetooth headphones)=>[KNN 2 @embedding $blob]' PARAMS 2 blob \x01\x01\x01\x01 DIALECT 2
1) "2"
2) "item:1"
3) 1) "__embedding_score"
   2) "1.08280003071"
   1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"],\"embedding\":[0.87,-0.15,0.55,0.03]}"
2) "item:2"
3) 1) "__embedding_score"
   2) "1.54409992695"
   3) "$"
   4) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

code:
# HASH: Comma is the default separator
HSET product:1 category "Electronics,Gaming,PC"
FT.CREATE products ON HASH PREFIX 1 product: SCHEMA category TAG

# Result: Creates 3 separate tags: "Electronics", "Gaming", "PC"
FT.SEARCH products '@category:{Gaming}'  # ✅ Finds the document

section_id: hash-documents

code:
# JSON: No default separator - the entire string becomes one tag
JSON.SET product:1 $ '{"category": "Electronics,Gaming,PC"}'
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category AS category TAG

# Result: Creates 1 tag: "Electronics,Gaming,PC"
FT.SEARCH products '@category:{Gaming}'           # ❌ Does NOT find the document
FT.SEARCH products '@category:{Electronics,Gaming,PC}'  # ✅ Finds the document

section_id: json-documents

code:
JSON.SET product:1 $ '{"category": "Electronics,Gaming,PC"}'
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category AS category TAG SEPARATOR ","

# Result: Creates 3 separate tags: "Electronics", "Gaming", "PC"
FT.SEARCH products '@category:{Gaming}'  # ✅ Now finds the document

code:
JSON.SET product:1 $ '{"category": ["Electronics", "Gaming", "PC"]}'
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category[*] AS category TAG

# Result: Creates 3 separate tags: "Electronics", "Gaming", "PC"
FT.SEARCH products '@category:{Gaming}'  # ✅ Finds the document

section_id: recommended-approach-for-json

code:
# Create index with array indexing
127.0.0.1:6379> FT.CREATE itemIdx2 ON JSON PREFIX 1 item: SCHEMA $.colors[*] AS colors TAG $.name AS name TEXT $.description as description TEXT

# The JSON data uses arrays
# Each array element ("black", "silver") becomes a separate tag

code:
127.0.0.1:6379> FT.SEARCH itemIdx2 "@colors:{silver} (@name:(headphones)|@description:(headphones))"
1) "1"
2) "item:1"
3) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"]}"

code:
# JSON with comma-separated string
JSON.SET item:1 $ '{"colors": "black,silver,gold"}'

# Index with explicit separator
FT.CREATE itemIdx3 ON JSON PREFIX 1 item: SCHEMA $.colors AS colors TAG SEPARATOR ","

# Now you can search individual colors
FT.SEARCH itemIdx3 "@colors:{silver}"

code: 127.0.0.1:6379> FT.CREATE itemIdx3 ON JSON PREFIX 1 item: SCHEMA $.colors AS colors TEXT $.name AS name TEXT $.description as description TEXT

section_id: index-json-arrays-as-text

code:
127.0.0.1:6379> JSON.SET item:3 $ '{"name":"True Wireless earbuds","description":"True Wireless Bluetooth in-ear headphones","connection":{"wireless":true,"type":"Bluetooth"},"price":74.99,"stock":20,"colors":["red","light blue"]}'
"OK"

code:
127.0.0.1:6379> FT.SEARCH itemIdx3 '@colors:(white|light) (@name|description:(headphones))' RETURN 1 $.colors
1) (integer) 2
2) "item:2"
3) 1) "$.colors"
   2) "[\"black\",\"white\"]"
4) "item:3"
5) 1) "$.colors"
   2) "[\"red\",\"light blue\"]"

b:
first first
first second

b:
second first
second second

b:
third first
third second

section_id: limitations

code:
127.0.0.1:6379> JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","connection":{"wireless":true,"type":"Bluetooth"},"price":99.98,"stock":25,"colors":["black","silver"], "max_level":[60, 70, 80, 90, 100]}'
OK

127.0.0.1:6379> JSON.SET item:2 $ '{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","connection":{"wireless":true,"type":"Bluetooth"},"price":64.99,"stock":17,"colors":["black","white"], "max_level":[80, 100, 120]}'
OK

127.0.0.1:6379> JSON.SET item:3 $ '{"name":"True Wireless earbuds","description":"True Wireless Bluetooth in-ear headphones","connection":{"wireless":true,"type":"Bluetooth"},"price":74.99,"stock":20,"colors":["red","light blue"], "max_level":[90, 100, 110, 120]}'
OK

section_id: index-json-arrays-as-numeric

code:
127.0.0.1:6379> FT.CREATE itemIdx4 ON JSON PREFIX 1 item: SCHEMA $.max_level AS dB NUMERIC
OK

code:
127.0.0.1:6379> FT.SEARCH itemIdx4 '@dB:[70 80]'
1) (integer) 2
2) "item:1"
3) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"],\"max_level\":[60,70,80,90,100]}"
4) "item:2"
5) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"max_level\":[80,100,120]}"

code:
127.0.0.1:6379> FT.SEARCH itemIdx4 '-@dB:[-inf (90] -@dB:[(120 +inf]'
1) (integer) 1
2) "item:3"
3) 1) "$"
   2) "{\"name\":\"True Wireless earbuds\",\"description\":\"True Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":74.99,\"stock\":20,\"colors\":[\"red\",\"light blue\"],\"max_level\":[90,100,110,120]}"

code:
127.0.0.1:6379> FT.CREATE itemIdx5 ON JSON PREFIX 1 item: SCHEMA $.embedding AS embedding VECTOR FLAT 6 DIM 4 DISTANCE_METRIC L2 TYPE FLOAT32
OK
127.0.0.1:6379> JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","price":99.98,"stock":25,"colors":["black","silver"],"embedding":[0.87,-0.15,0.55,0.03]}'
OK
127.0.0.1:6379> JSON.SET item:2 $ '{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","price":64.99,"stock":17,"colors":["black","white"],"embedding":[-0.7,-0.51,0.88,0.14]}'
OK

section_id: index-json-arrays-as-vector

code:
127.0.0.1:6379> FT.SEARCH itemIdx5 '*=>[KNN 2 @embedding $blob AS dist]' SORTBY dist PARAMS 2 blob \x01\x01\x01\x01 DIALECT 2
1) (integer) 2
2) "item:1"
3) 1) "dist"
   2) "1.08280003071"
   3) "$"
   4) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"],\"embedding\":[0.87,-0.15,0.55,0.03]}"
4) "item:2"
5) 1) "dist"
   2) "1.54409992695"
   3) "$"
   4) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

code:
127.0.0.1:6379> FT.CREATE itemIdx5 ON JSON PREFIX 1 item: SCHEMA $.embeddings[*] AS embeddings VECTOR FLAT 6 DIM 4 DISTANCE_METRIC L2 TYPE FLOAT32
OK
127.0.0.1:6379> JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","price":99.98,"stock":25,"colors":["black","silver"],"embeddings":[[0.87,-0.15,0.55,0.03]]}'
OK
127.0.0.1:6379> JSON.SET item:2 $ '{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","price":64.99,"stock":17,"colors":["black","white"],"embeddings":[[-0.7,-0.51,0.88,0.14],[-0.8,-0.15,0.33,-0.01]]}'
OK

code:
127.0.0.1:6379> FT.SEARCH itemIdx5 '*=>[KNN 2 @embeddings $blob AS dist]' SORTBY dist PARAMS 2 blob \x01\x01\x01\x01 DIALECT 2
1) (integer) 2
2) "item:2"
3) 1) "dist"
   2) "0.771500051022"
   3) "$"
   4) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embeddings\":[[-0.7,-0.51,0.88,0.14],[-0.8,-0.15,0.33,-0.01]]}"
4) "item:1"
5) 1) "dist"
   2) "1.08280003071"
   3) "$"
   4) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"],\"embeddings\":[[0.87,-0.15,0.55,0.03]]}"

code:
127.0.0.1:6379> FT.CREATE itemIdx3 ON JSON SCHEMA $.connection.wireless AS wireless TAG $.connection.type AS connectionType TEXT
"OK"

section_id: index-json-objects

code:
127.0.0.1:6379> FT.SEARCH itemIdx3 '@wireless:{true}'
1) "2"
2) "item:2"
3) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"]}"
4) "item:1"
5) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"]}"

code:
127.0.0.1:6379> FT.SEARCH itemIdx3 '@connectionType:(bluetooth)'
1) "2"
2) "item:1"
3) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"]}"
4) "item:2"
5) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"]}"

code:
127.0.0.1:6379> FT.SEARCH itemIdx '@description:(headphones)' RETURN 2 name price
1) "2"
2) "item:1"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "price"
   4) "99.98"
4) "item:2"
5) 1) "name"
   2) "Wireless earbuds"
   3) "price"
   4) "64.99"

section_id: return-specific-attributes

code:
127.0.0.1:6379> FT.SEARCH itemIdx '@description:(headphones)' RETURN 3 name price $.stock
1) "2"
2) "item:1"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "price"
   4) "99.98"
   5) "$.stock"
   6) "25"
4) "item:2"
5) 1) "name"
   2) "Wireless earbuds"
   3) "price"
   4) "64.99"
   5) "$.stock"
   6) "17"

section_id: project-with-jsonpath

code:
127.0.0.1:6379> FT.SEARCH itemIdx '@description:(headphones)' RETURN 5 name price $.stock AS stock
1) "2"
2) "item:1"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "price"
   4) "99.98"
   5) "stock"
   6) "25"
4) "item:2"
5) 1) "name"
   2) "Wireless earbuds"
   3) "price"
   4) "64.99"
   5) "stock"
   6) "17"

code:
127.0.0.1:6379> FT.SEARCH itemIdx '(@name:(bluetooth))|(@description:(bluetooth))' RETURN 3 name description price HIGHLIGHT FIELDS 2 name description TAGS '' ''
1) "2"
2) "item:1"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "description"
   4) "Wireless Bluetooth headphones with noise-cancelling technology"
   5) "price"
   6) "99.98"
4) "item:2"
5) 1) "name"
   2) "Wireless earbuds"
   3) "description"
   4) "Wireless Bluetooth in-ear headphones"
   5) "price"
   6) "64.99"

section_id: highlight-search-terms

code:
127.0.0.1:6379> FT.AGGREGATE itemIdx '*' LOAD 4 name $.price AS originalPrice APPLY '@originalPrice - (@originalPrice * 0.10)' AS salePrice SORTBY 2 @salePrice ASC
1) "2"
2) 1) "name"
   2) "Wireless earbuds"
   3) "originalPrice"
   4) "64.99"
   5) "salePrice"
   6) "58.491"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "originalPrice"
   4) "99.98"
   5) "salePrice"
   6) "89.982"

section_id: aggregate-with-jsonpath

code:
JSON.SET key:1 $ '{"propA": "foo"}'
JSON.SET key:2 $ '{"propA": "bar", "propB":"abc"}'
FT.CREATE idx ON JSON PREFIX 1 key: SCHEMA $.propA AS propA TAG $.propB AS propB TAG INDEXMISSING

> FT.SEARCH idx 'ismissing(@propB)' DIALECT 2
1) "1"
2) "key:1"
3) 1) "$"
   2) "{\"propA\":\"foo\"}"

section_id: index-missing-or-empty-values

code:
JSON.SET key:1 $ '{"propA": "foo", "propB":""}'
JSON.SET key:2 $ '{"propA": "bar", "propB":"abc"}'
FT.CREATE idx ON JSON PREFIX 1 key: SCHEMA $.propA AS propA TAG $.propB AS propB TAG INDEXEMPTY

> FT.SEARCH idx '@propB:{""}' DIALECT 2
1) "1"
2) "key:1"
3) 1) "$"
   2) "{\"propA\":\"foo\",\"propB\":\"\"}"


title: Schema definition

summary: How to define the schema of an index.

title: Overview

text:
An index structure is defined by a schema. The schema specifies the fields, their types, whether they should be indexed or stored, and other additional configuration options. By properly configuring the schema, you can optimize search performance and control the storage requirements of your index.

[code example]

In this example, a schema is defined for an index named `idx` that will index all hash documents whose keyname starts with `blog:post:`.
The schema includes the fields `title`, `content`, `author`, `created_date`, and `views`. The `TEXT` type indicates that the `title` and `content` fields are text-based, the `TAG` type is used for the `author` field, and the `NUMERIC` type is used for the `created_date` and `views` fields. Additionally, a weight of 5.0 is assigned to the `title` field to give it more relevance in search results, and `created_date` is marked as `SORTABLE` to enable sorting based on this field.

You can learn more about the available field types and options on the [`FT.CREATE`]() page.

title: More schema definition examples

text:
##### Index tags with a separator

Index books that have a `categories` attribute, where each category is separated by a `;` character.

[code example]

##### Index a single field in multiple ways

Index the `sku` attribute from a hash as both a `TAG` and as `TEXT`:

[code example]

##### Index documents with multiple prefixes

Index two different hashes, one containing author data and one containing book data:
[code example]

In this example, keys for author data use the key pattern `author:details:<id>`, while keys for book data use the pattern `book:details:<id>`.

##### Only index documents if a field specifies a certain value using `FILTER`

Index authors whose names start with G:

[code example]

Index only books that have a subtitle:

[code example]

##### Index a JSON document using a JSONPath expression

Index a JSON document that has `title` and `categories` fields. The `title` field is indexed as `TEXT` and the `categories` field is indexed as `TAG`.

[code example]

You can learn more about the available field types and options on the [`FT.CREATE`]() page.

code:
FT.CREATE idx
    ON HASH
    PREFIX 1 blog:post:
SCHEMA
    title TEXT WEIGHT 5.0
    content TEXT
    author TAG
    created_date NUMERIC SORTABLE
    views NUMERIC

section_id: overview

code:
FT.CREATE books-idx
    ON HASH
    PREFIX 1 book:details
SCHEMA
    title TEXT
    categories TAG SEPARATOR ";"

section_id: more-schema-definition-examples

code:
FT.CREATE idx
    ON HASH
    PREFIX 1 blog:post:
SCHEMA
    sku AS sku_text TEXT
    sku AS sku_tag TAG SORTABLE

code:
FT.CREATE author-books-idx
    ON HASH
    PREFIX 2 author:details: book:details:
SCHEMA
    author_id TAG SORTABLE
    author_ids TAG
    title TEXT name TEXT

code:
FT.CREATE g-authors-idx
    ON HASH
    PREFIX 1 author:details
    FILTER 'startswith(@name, "G")'
SCHEMA
    name TEXT

code:
FT.CREATE subtitled-books-idx
    ON HASH
    PREFIX 1 book:details
    FILTER '@subtitle != ""'
SCHEMA
    title TEXT

code:
FT.CREATE idx
    ON JSON
SCHEMA
    $.title AS title TEXT
    $.categories AS categories TAG