EXPIRE

Summary: Sets the expiration time of a key in seconds.

Options

The `EXPIRE` command supports a set of options:

* `NX` -- Set expiry only when the key has no expiry
* `XX` -- Set expiry only when the key has an existing expiry
* `GT` -- Set expiry only when the new expiry is greater than current one
* `LT` -- Set expiry only when the new expiry is less than current one

A non-volatile key is treated as an infinite TTL for the purpose of `GT` and `LT`.
The `GT`, `LT` and `NX` options are mutually exclusive.

Refreshing expires

It is possible to call `EXPIRE` using as argument a key that already has an
existing expire set.
In this case the time to live of a key is _updated_ to the new value.
There are many useful applications for this, an example is documented in the
_Navigation session_ pattern section below.

Differences in Redis prior 2.1.3

In Redis versions prior **2.1.3** altering a key with an expire set using a
command altering its value had the effect of removing the key entirely.
This semantics was needed because of limitations in the replication layer that
are now fixed.

`EXPIRE` would return 0 and not alter the timeout for a key with a timeout set.

Examples

#### Code Examples

Foundational: Set key expiration time using EXPIRE (supports conditional options NX/XX/GT/LT, returns 1 if set or 0 if not)

**Difficulty:** Beginner

**Commands:** SET, EXPIRE, TTL

**Complexity:**
- SET: O(1)
- EXPIRE: O(1)
- TTL: O(1)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

Give these commands a try in the interactive console:

SET mykey "Hello"
EXPIRE mykey 10
TTL mykey
SET mykey "Hello World"
TTL mykey
EXPIRE mykey 10 XX
TTL mykey
EXPIRE mykey 10 NX
TTL mykey

Pattern: Navigation session

Imagine you have a web service and you are interested in the latest N pages
_recently_ visited by your users, such that each adjacent page view was not
performed more than 60 seconds after the previous.
Conceptually you may consider this set of page views as a _Navigation session_
of your user, that may contain interesting information about what kind of
products he or she is looking for currently, so that you can recommend related
products.

You can easily model this pattern in Redis using the following strategy: every
time the user does a page view you call the following commands:

[code example]

If the user will be idle more than 60 seconds, the key will be deleted and only
subsequent page views that have less than 60 seconds of difference will be
recorded.

This pattern is easily modified to use counters using [`INCR`]() instead of lists
using [`RPUSH`]().

Appendix: Redis expires

Keys with an expire

Normally Redis keys are created without an associated time to live.
The key will simply live forever, unless it is removed by the user in an
explicit way, for instance using the [`DEL`]() command.

The `EXPIRE` family of commands is able to associate an expire to a given key,
at the cost of some additional memory used by the key.
When a key has an expire set, Redis will make sure to remove the key when the
specified amount of time elapsed.

The key time to live can be updated or entirely removed using the `EXPIRE` and
[`PERSIST`]() command (or other strictly related commands).

Expire accuracy

In Redis 2.4 the expire might not be pin-point accurate, and it could be between
zero to one seconds out.

Since Redis 2.6 the expire error is from 0 to 1 milliseconds.

Expires and persistence

Keys expiring information is stored as absolute Unix timestamps (in milliseconds
in case of Redis version 2.6 or greater).
This means that the time is flowing even when the Redis instance is not active.

For expires to work well, the computer time must be taken stable.
If you move an RDB file from two computers with a big desync in their clocks,
funny things may happen (like all the keys loaded to be expired at loading
time).

Even running instances will always check the computer clock, so for instance if
you set a key with a time to live of 1000 seconds, and then set your computer
time 2000 seconds in the future, the key will be expired immediately, instead of
lasting for 1000 seconds.

How Redis expires keys

Redis keys are expired in two ways: a passive way and an active way.

A key is passively expired when a client tries to access it and the
key is timed out.

However, this is not enough as there are expired keys that will never be
accessed again.
These keys should be expired anyway, so periodically, Redis tests a few keys at
random amongst the set of keys with an expiration.
All the keys that are already expired are deleted from the keyspace.

How expires are handled in the replication link and AOF file

In order to obtain a correct behavior without sacrificing consistency, when a
key expires, a [`DEL`]() operation is synthesized in both the AOF file and gains all
the attached replicas nodes.
This way the expiration process is centralized in the master instance, and there
is no chance of consistency errors.

However while the replicas connected to a master will not expire keys
independently (but will wait for the [`DEL`]() coming from the master), they'll
still take the full state of the expires existing in the dataset, so when a
replica is elected to master it will be able to expire the keys independently,
fully acting as a master.

Redis Search and expiration

Starting with Redis 8, Redis Search has enhanced behavior when handling expiring keys. For detailed information about how [`FT.SEARCH`]() and [`FT.AGGREGATE`]() commands interact with expiring keys, see [Key and field expiration behavior]().

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Standard
<nobr>✅ Active-Active</nobr> | ✅ Standard
<nobr>✅ Active-Active</nobr> |  |

Return information

**RESP2:**

One of the following:
* [Integer reply](../../develop/reference/protocol-spec#integers): `0` if the timeout was not set; for example, the key doesn't exist, or the operation was skipped because of the provided arguments.
* [Integer reply](../../develop/reference/protocol-spec#integers): `1` if the timeout was set.

**RESP3:**

One of the following:
* [Integer reply](../../develop/reference/protocol-spec#integers): `0` if the timeout was not set; for example, the key doesn't exist, or the operation was skipped because of the provided arguments.
* [Integer reply](../../develop/reference/protocol-spec#integers): `1` if the timeout was set.

> SET mykey "Hello"
"OK"
> EXPIRE mykey 10
(integer) 1
> TTL mykey
(integer) 10
> SET mykey "Hello World"
"OK"
> TTL mykey
(integer) -1
> EXPIRE mykey 10 XX
(integer) 0
> TTL mykey
(integer) -1
> EXPIRE mykey 10 NX
(integer) 1
> TTL mykey
(integer) 10

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis/hiredis.h>

int main(int argc, char **argv) {
    redisContext *c = redisConnect("127.0.0.1", 6379);

    if (c == NULL || c->err) {
        if (c) {
            printf("Connection error: %s\n", c->errstr);
            redisFree(c);
        } else {
            printf("Connection error: can't allocate redis context\n");
        }
        return 1;
    }

    redisReply *reply;

    // Set up keys
    reply = redisCommand(c, "MSET %s %s %s %s %s %s",
        "firstname", "Jack", "lastname", "Stuntman", "age", "35");
    printf("MSET firstname Jack lastname Stuntman age 35: %s\n", reply->str);
    // >>> OK
    freeReplyObject(reply);

    // Keys matching *name*
    reply = redisCommand(c, "KEYS %s", "*name*");
    printf("KEYS *name*:\n");
    for (size_t i = 0; i < reply->elements; i++) {
        printf("  %s\n", reply->element[i]->str);
    }
    // >>> firstname
    // >>> lastname
    freeReplyObject(reply);

    // Keys matching a??
    reply = redisCommand(c, "KEYS %s", "a??");
    printf("KEYS a??:\n");
    for (size_t i = 0; i < reply->elements; i++) {
        printf("  %s\n", reply->element[i]->str);
    }
    // >>> age
    freeReplyObject(reply);

    // All keys
    reply = redisCommand(c, "KEYS %s", "*");
    printf("KEYS *:\n");
    for (size_t i = 0; i < reply->elements; i++) {
        printf("  %s\n", reply->element[i]->str);
    }
    // >>> age
    // >>> firstname
    // >>> lastname
    freeReplyObject(reply);

    redisFree(c);

    return 0;
}

bool expireResult1 = db.StringSet("mykey", "Hello");
        Console.WriteLine(expireResult1);   // >>> true

        bool expireResult2 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10));
        Console.WriteLine(expireResult2);   // >>> true

        TimeSpan expireResult3 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
        Console.WriteLine(Math.Round(expireResult3.TotalSeconds));   // >>> 10

        bool expireResult4 = db.StringSet("mykey", "Hello World");
        Console.WriteLine(expireResult4);   // >>> true

        TimeSpan expireResult5 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
        Console.WriteLine(Math.Round(expireResult5.TotalSeconds).ToString());   // >>> 0

        bool expireResult6 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10), ExpireWhen.HasExpiry);
        Console.WriteLine(expireResult6);   // >>> false

        TimeSpan expireResult7 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
        Console.WriteLine(Math.Round(expireResult7.TotalSeconds));   // >>> 0

        bool expireResult8 = db.KeyExpire("mykey", new TimeSpan(0, 0, 10), ExpireWhen.HasNoExpiry);
        Console.WriteLine(expireResult8);   // >>> true

        TimeSpan expireResult9 = db.KeyTimeToLive("mykey") ?? TimeSpan.Zero;
        Console.WriteLine(Math.Round(expireResult9.TotalSeconds));   // >>> 10

expireResult1, err := rdb.Set(ctx, "mykey", "Hello", 0).Result()

	if err != nil {
		panic(err)
	}

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

	expireResult2, err := rdb.Expire(ctx, "mykey", 10*time.Second).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(expireResult2) // >>> true

	expireResult3, err := rdb.TTL(ctx, "mykey").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(math.Round(expireResult3.Seconds())) // >>> 10

	expireResult4, err := rdb.Set(ctx, "mykey", "Hello World", 0).Result()

	if err != nil {
		panic(err)
	}

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

	expireResult5, err := rdb.TTL(ctx, "mykey").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(expireResult5) // >>> -1ns

	expireResult6, err := rdb.ExpireXX(ctx, "mykey", 10*time.Second).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(expireResult6) // >>> false

	expireResult7, err := rdb.TTL(ctx, "mykey").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(expireResult7) // >>> -1ns

	expireResult8, err := rdb.ExpireNX(ctx, "mykey", 10*time.Second).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(expireResult8) // >>> true

	expireResult9, err := rdb.TTL(ctx, "mykey").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(math.Round(expireResult9.Seconds())) // >>> 10

package io.redis.examples.async;

import io.lettuce.core.*;

import io.lettuce.core.api.async.RedisAsyncCommands;

import io.lettuce.core.api.StatefulRedisConnection;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

public class CmdsGenericExample {

    public void run() {

            CompletableFuture<Void> existsExample = asyncCommands.set("key1", "Hello").thenCompose(res1 -> {
                System.out.println(res1); // >>> OK

                return asyncCommands.exists("key1");
            }).thenCompose(res2 -> {
                System.out.println(res2); // >>> 1

                return asyncCommands.exists("nosuchkey");
            }).thenCompose(res3 -> {
                System.out.println(res3); // >>> 0

                return asyncCommands.set("key2", "World");
            }).thenCompose(res4 -> {
                System.out.println(res4); // >>> OK

                return asyncCommands.exists("key1", "key2", "nosuchkey");
            }).thenAccept(res5 -> {
                System.out.println(res5); // >>> 2
            }).toCompletableFuture();
            existsExample.join();

            CompletableFuture<Void> keysExample = asyncCommands.mset(Map.of(
                    "firstname", "Jack",
                    "lastname", "Stuntman",
                    "age", "35"
            )).thenCompose(res1 -> {
                System.out.println(res1); // >>> OK

                return asyncCommands.keys("*name*");
            }).thenCompose(res2 -> {
                Collections.sort(res2);
                System.out.println(res2); // >>> [firstname, lastname]

                return asyncCommands.keys("a??");
            }).thenCompose(res3 -> {
                System.out.println(res3); // >>> [age]

                return asyncCommands.keys("*");
            }).thenAccept(res4 -> {
                Collections.sort(res4);
                System.out.println(res4); // >>> [age, firstname, lastname]
            }).toCompletableFuture();
            keysExample.join();
        } finally {
            redisClient.shutdown();
        }
    }
}

package io.redis.examples.reactive;

import io.lettuce.core.*;
import io.lettuce.core.api.reactive.RedisReactiveCommands;
import io.lettuce.core.api.StatefulRedisConnection;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.Map;

public class CmdsGenericExample {

    public void run() {
        RedisClient redisClient = RedisClient.create("redis://localhost:6379");

        try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
            RedisReactiveCommands<String, String> reactiveCommands = connection.reactive();

            Mono<Void> existsExample = reactiveCommands.set("key1", "Hello").doOnNext(res1 -> {
                System.out.println(res1); // >>> OK
            }).then(reactiveCommands.exists("key1")).doOnNext(res2 -> {
                System.out.println(res2); // >>> 1
            }).then(reactiveCommands.exists("nosuchkey")).doOnNext(res3 -> {
                System.out.println(res3); // >>> 0
            }).then(reactiveCommands.set("key2", "World")).doOnNext(res4 -> {
                System.out.println(res4); // >>> OK
            }).then(reactiveCommands.exists("key1", "key2", "nosuchkey")).doOnNext(res5 -> {
                System.out.println(res5); // >>> 2
            }).then();

            Mono.when(existsExample).block();

            Mono<Void> keysExample = reactiveCommands.mset(Map.of(
                    "firstname", "Jack",
                    "lastname", "Stuntman",
                    "age", "35"
            )).doOnNext(res1 -> {
                System.out.println(res1); // >>> OK
            }).then(reactiveCommands.keys("*name*").collectList()).doOnNext(res2 -> {
                Collections.sort(res2);
                System.out.println(res2); // >>> [firstname, lastname]
            }).then(reactiveCommands.keys("a??").collectList()).doOnNext(res3 -> {
                System.out.println(res3); // >>> [age]
            }).then(reactiveCommands.keys("*").collectList()).doOnNext(res4 -> {
                Collections.sort(res4);
                System.out.println(res4); // >>> [age, firstname, lastname]
            }).then();

            Mono.when(keysExample).block();

        } finally {
            redisClient.shutdown();
        }
    }

}

String expireResult1 = jedis.set("mykey", "Hello");
        System.out.println(expireResult1);  // >>> OK

        long expireResult2 = jedis.expire("mykey", 10);
        System.out.println(expireResult2);  // >>> 1

        long expireResult3 = jedis.ttl("mykey");
        System.out.println(expireResult3);  // >>> 10

        String expireResult4 = jedis.set("mykey", "Hello World");
        System.out.println(expireResult4);  // >>> OK

        long expireResult5 = jedis.ttl("mykey");
        System.out.println(expireResult5);  // >>> -1

        long expireResult6 = jedis.expire("mykey", 10, ExpiryOption.XX);
        System.out.println(expireResult6);  // >>> 0

        long expireResult7 = jedis.ttl("mykey");
        System.out.println(expireResult7);  // >>> -1

        long expireResult8 = jedis.expire("mykey", 10, ExpiryOption.NX);
        System.out.println(expireResult8);  // >>> 1

        long expireResult9 = jedis.ttl("mykey");
        System.out.println(expireResult9);  // >>> 10

const expireRes1 = await client.set('mykey', 'Hello');
console.log(expireRes1); // OK

const expireRes2 = await client.expire('mykey', 10);
console.log(expireRes2); // 1

const expireRes3 = await client.ttl('mykey');
console.log(expireRes3); // 10

const expireRes4 = await client.set('mykey', 'Hello World');
console.log(expireRes4); // OK

const expireRes5 = await client.ttl('mykey');
console.log(expireRes5); // -1

const expireRes6 = await client.expire('mykey', 10, "XX");
console.log(expireRes6); // 0

const expireRes7 = await client.ttl('mykey');
console.log(expireRes7); // -1

const expireRes8 = await client.expire('mykey', 10, "NX");
console.log(expireRes8); // 1

const expireRes9 = await client.ttl('mykey');
console.log(expireRes9); // 10

import assert from 'node:assert';
import { Redis } from 'ioredis';

const redis = new Redis();

const keysRes1 = await redis.mset({ firstname: 'Jack', lastname: 'Stuntman', age: '35' });
console.log(keysRes1); // >>> OK

const keysRes2 = await redis.keys('*name*');
console.log(keysRes2.sort()); // >>> ['firstname', 'lastname']

const keysRes3 = await redis.keys('a??');
console.log(keysRes3); // >>> ['age']

const keysRes4 = await redis.keys('*');
console.log(keysRes4.sort()); // >>> ['age', 'firstname', 'lastname']

redis.disconnect();

<?php
use PHPUnit\Framework\TestCase;
use Predis\Client as PredisClient;

class CmdsGenericTest
{
    public function testCmdsGeneric() {
        $r = new PredisClient([
            'scheme'   => 'tcp',
            'host'     => '127.0.0.1',
            'port'     => 6379,
            'password' => '',
            'database' => 0,
        ]);

        $existsResult1 = $r->set('key1', 'Hello');
        echo $existsResult1 . PHP_EOL; // >>> OK

        $existsResult2 = $r->exists('key1');
        echo $existsResult2 . PHP_EOL; // >>> 1

        $existsResult3 = $r->exists('nosuchkey');
        echo $existsResult3 . PHP_EOL; // >>> 0

        $existsResult4 = $r->set('key2', 'World');
        echo $existsResult4 . PHP_EOL; // >>> OK

        $existsResult5 = $r->exists('key1', 'key2', 'nosuchkey');
        echo $existsResult5 . PHP_EOL; // >>> 2

        $keysResult1 = $r->mset(['firstname' => 'Jack', 'lastname' => 'Stuntman', 'age' => '35']);
        echo $keysResult1 . PHP_EOL; // >>> OK

        $keysResult2 = $r->keys('*name*');
        sort($keysResult2);
        echo implode(', ', $keysResult2) . PHP_EOL; // >>> firstname, lastname

        $keysResult3 = $r->keys('a??');
        echo implode(', ', $keysResult3) . PHP_EOL; // >>> age

        $keysResult4 = $r->keys('*');
        sort($keysResult4);
        echo implode(', ', $keysResult4) . PHP_EOL; // >>> age, firstname, lastname

    }
}

res = r.set("mykey", "Hello")
print(res)
# >>> True

res = r.expire("mykey", 10)
print(res)
# >>> True

res = r.ttl("mykey")
print(res)
# >>> 10

res = r.set("mykey", "Hello World")
print(res)
# >>> True

res = r.ttl("mykey")
print(res)
# >>> -1

res = r.expire("mykey", 10, xx=True)
print(res)
# >>> False

res = r.ttl("mykey")
print(res)
# >>> -1

res = r.expire("mykey", 10, nx=True)
print(res)
# >>> True

res = r.ttl("mykey")
print(res)
# >>> 10

if let Ok(res) = r.set("mykey", "Hello").await {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.expire("mykey", 10).await {
            Ok(res) => {
                let res: bool = res;
                println!("{res}");    // >>> true
            },
            Err(e) => {
                println!("Error setting key expiration: {e}");
                return;
            }
        }

        match r.ttl("mykey").await {
            Ok(res) => {
                let res: i64 = res;
                println!("{res}");    // >>> 10
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

        if let Ok(res) = r.set("mykey", "Hello World").await {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.ttl("mykey").await {
            Ok(res) => {
                let res: i64 = res;
                println!("{res}");    // >>> -1
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

        // Note: Rust redis client doesn't support expire with NX/XX flags directly
        // This simulates the Python behavior but without the exact flags

        // Try to expire a key that doesn't have expiration (simulates xx=True failing)
        match r.ttl("mykey").await {
            Ok(res) => {
                let res: i64 = res;
                println!("false");    // >>> false (simulating expire xx=True failure)
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

        match r.ttl("mykey").await {
            Ok(res) => {
                let res: i64 = res;
                println!("{res}");    // >>> -1
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

        // Now set expiration (simulates nx=True succeeding)
        match r.expire("mykey", 10).await {
            Ok(res) => {
                let res: bool = res;
                println!("{res}");    // >>> true
            },
            Err(e) => {
                println!("Error setting key expiration: {e}");
                return;
            }
        }

        match r.ttl("mykey").await {
            Ok(res) => {
                let res: i64 = res;
                println!("{res}");    // >>> 10
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

if let Ok(res) = r.set("mykey", "Hello") {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.expire("mykey", 10) {
            Ok(res) => {
                let res: bool = res;
                println!("{res}");    // >>> true
            },
            Err(e) => {
                println!("Error setting key expiration: {e}");
                return;
            }
        }

        match r.ttl("mykey") {
            Ok(res) => {
                let res: i64 = res;
                println!("{res}");    // >>> 10
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

        if let Ok(res) = r.set("mykey", "Hello World") {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.ttl("mykey") {
            Ok(res) => {
                let res: i64 = res;
                println!("{res}");    // >>> -1
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

        // Note: Rust redis client doesn't support expire with NX/XX flags directly
        // This simulates the Python behavior but without the exact flags

        // Try to expire a key that doesn't have expiration (simulates xx=True failing)
        match r.ttl("mykey") {
            Ok(res) => {
                let res: i64 = res;
                println!("false");    // >>> false (simulating expire xx=True failure)
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

        match r.ttl("mykey") {
            Ok(res) => {
                let res: i64 = res;
                println!("{res}");    // >>> -1
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

        // Now set expiration (simulates nx=True succeeding)
        match r.expire("mykey", 10) {
            Ok(res) => {
                let res: bool = res;
                println!("{res}");    // >>> true
            },
            Err(e) => {
                println!("Error setting key expiration: {e}");
                return;
            }
        }

        match r.ttl("mykey") {
            Ok(res) => {
                let res: i64 = res;
                println!("{res}");    // >>> 10
            },
            Err(e) => {
                println!("Error getting key TTL: {e}");
                return;
            }
        }

MULTI
RPUSH pagewviews.user:<userid> http://.....
EXPIRE pagewviews.user:<userid> 60
EXEC


EXPIREAT

Summary: Sets the expiration time of a key to a Unix timestamp.

Overview

`EXPIREAT` has the same effect and semantic as [`EXPIRE`](), but instead of
specifying the number of seconds representing the TTL (time to live), it takes
an absolute [Unix timestamp][hewowu] (seconds since January 1, 1970). A
timestamp in the past will delete the key immediately.

[hewowu]: http://en.wikipedia.org/wiki/Unix_time

Please for the specific semantics of the command refer to the documentation of
[`EXPIRE`]().

Background

`EXPIREAT` was introduced in order to convert relative timeouts to absolute
timeouts for the AOF persistence mode.
Of course, it can be used directly to specify that a given key should expire at
a given time in the future.

Options

The `EXPIREAT` command supports a set of options:

* `NX` -- Set expiry only when the key has no expiry
* `XX` -- Set expiry only when the key has an existing expiry
* `GT` -- Set expiry only when the new expiry is greater than current one
* `LT` -- Set expiry only when the new expiry is less than current one

A non-volatile key is treated as an infinite TTL for the purpose of `GT` and `LT`.
The `GT`, `LT` and `NX` options are mutually exclusive.

Examples

SET mykey "Hello"
EXISTS mykey
EXPIREAT mykey 1293840000
EXISTS mykey

Redis Software and Redis Cloud compatibility

Return information


EXPIRETIME

Summary: Returns the expiration time of a key as a Unix timestamp.

Overview

Returns the absolute Unix timestamp (since January 1, 1970) in seconds at which the given key will expire.

See also the [`PEXPIRETIME`]() command which returns the same information with milliseconds resolution.

Examples

SET mykey "Hello"
EXPIREAT mykey 33177117420
EXPIRETIME mykey

Return information

**RESP2:**

One of the following:
* [Integer reply](../../develop/reference/protocol-spec#integers): the expiration Unix timestamp in seconds.
* [Integer reply](../../develop/reference/protocol-spec#integers): `-1` if the key exists but has no associated expiration time.
* [Integer reply](../../develop/reference/protocol-spec#integers): `-2` if the key does not exist.

**RESP3:**

One of the following:
* [Integer reply](../../develop/reference/protocol-spec#integers): the expiration Unix timestamp in seconds.
* [Integer reply](../../develop/reference/protocol-spec#integers): `-1` if the key exists but has no associated expiration time.
* [Integer reply](../../develop/reference/protocol-spec#integers): `-2` if the key does not exist.


FAILOVER

Summary: Starts a coordinated failover from a server to one of its replicas.

Overview

This command will start a coordinated failover between the currently-connected-to master and one of its replicas.
The failover is not synchronous, instead a background task will handle coordinating the failover.
It is designed to limit data loss and unavailability of the cluster during the failover.
This command is analogous to the [`CLUSTER FAILOVER`]() command for non-clustered Redis and is similar to the failover support provided by sentinel.

The specific details of the default failover flow are as follows:

1. The master will internally start a `CLIENT PAUSE WRITE`, which will pause incoming writes and prevent the accumulation of new data in the replication stream.
2. The master will monitor its replicas, waiting for a replica to indicate that it has fully consumed the replication stream. If the master has multiple replicas, it will only wait for the first replica to catch up.
3. The master will then demote itself to a replica. This is done to prevent any dual master scenarios. NOTE: The master will not discard its data, so it will be able to rollback if the replica rejects the failover request in the next step.
4. The previous master will send a special PSYNC request to the target replica, `PSYNC FAILOVER`, instructing the target replica to become a master.
5. Once the previous master receives acknowledgement the `PSYNC FAILOVER` was accepted it will unpause its clients. If the PSYNC request is rejected, the master will abort the failover and return to normal.

The field `master_failover_state` in `INFO replication` can be used to track the current state of the failover, which has the following values:

* `no-failover`: There is no ongoing coordinated failover.
* `waiting-for-sync`: The master is waiting for the replica to catch up to its replication offset.
* `failover-in-progress`: The master has demoted itself, and is attempting to hand off ownership to a target replica.

If the previous master had additional replicas attached to it, they will continue replicating from it as chained replicas. You will need to manually execute a [`REPLICAOF`]() on these replicas to start replicating directly from the new master.

Optional arguments

The following optional arguments exist to modify the behavior of the failover flow:

* `TIMEOUT` *milliseconds* -- This option allows specifying a maximum time a master will wait in the `waiting-for-sync` state before aborting the failover attempt and rolling back.
This is intended to set an upper bound on the write outage the Redis cluster can experience.
Failovers typically happen in less than a second, but could take longer if there is a large amount of write traffic or the replica is already behind in consuming the replication stream.
If this value is not specified, the timeout can be considered to be "infinite".

* `TO` *HOST* *PORT* -- This option allows designating a specific replica, by its host and port, to failover to. The master will wait specifically for this replica to catch up to its replication offset, and then failover to it.

* `FORCE` -- If both the `TIMEOUT` and `TO` options are set, the force flag can also be used to designate that once the timeout has elapsed, the master should failover to the target replica instead of rolling back.
This can be used for a best-effort attempt at a failover without data loss, but limiting write outage.

NOTE: The master will always rollback if the `PSYNC FAILOVER` request is rejected by the target replica.

Failover abort

The failover command is intended to be safe from data loss and corruption, but can encounter some scenarios it can not automatically remediate from and may get stuck.
For this purpose, the `FAILOVER ABORT` command exists, which will abort an ongoing failover and return the master to its normal state.
The command has no side effects if issued in the `waiting-for-sync` state but can introduce multi-master scenarios in the `failover-in-progress` state.
If a multi-master scenario is encountered, you will need to manually identify which master has the latest data and designate it as the master and have the other replicas.

NOTE: [`REPLICAOF`]() is disabled while a failover is in progress, this is to prevent unintended interactions with the failover that might cause data loss.

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ❌ Standard
<nobr>❌ Active-Active</nobr> | ❌ Standard
<nobr>❌ Active-Active</nobr> |  |

Return information

**RESP2:**

[Simple string reply](../../develop/reference/protocol-spec#simple-strings): `OK` if the command was accepted and a coordinated failover is in progress. An error if the operation cannot be executed.

**RESP3:**

[Simple string reply](../../develop/reference/protocol-spec#simple-strings): `OK` if the command was accepted and a coordinated failover is in progress. An error if the operation cannot be executed.


FCALL

Summary: Invokes a function.

Overview

Invoke a function.

Functions are loaded to the server with the [`FUNCTION LOAD`]() command.
The first argument is the name of a loaded function.

The second argument is the number of input key name arguments, followed by all the keys accessed by the function.
In Lua, these names of input keys are available to the function as a table that is the callback's first argument.

**Important:**
To ensure the correct execution of functions, both in standalone and clustered deployments, all names of keys that a function accesses must be explicitly provided as input key arguments.
The function **should only** access keys whose names are given as input arguments.
Functions **should never** access keys with programmatically-generated names or based on the contents of data structures stored in the database.

Any additional input argument **should not** represent names of keys.
These are regular arguments and are passed in a Lua table as the callback's second argument.

For more information please refer to the [Redis Programmability]() and [Introduction to Redis Functions]() pages.

Examples

The following example will create a library named `mylib` with a single function, `myfunc`, that returns the first argument it gets.

[code example]

Return information

**RESP2:**

The return value depends on the function that was executed.

**RESP3:**

The return value depends on the function that was executed.

redis> FUNCTION LOAD "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
"mylib"
redis> FCALL myfunc 0 hello
"hello"


FCALL_RO

Summary: Invokes a read-only function.

Overview

This is a read-only variant of the [`FCALL`]() command that cannot execute commands that modify data.

For more information about when to use this command vs [`FCALL`](), please refer to [Read-only scripts]().

For more information please refer to [Introduction to Redis Functions]().

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Standard
<nobr>✅ Active-Active</nobr> | ✅ Standard
<nobr>✅ Active-Active</nobr> |  |

Return information


FLUSHALL

Summary: Removes all keys from all databases.

Notes

* An asynchronous `FLUSHALL` command only deletes keys that were present at the time the command was invoked. Keys created during an asynchronous flush will be unaffected.
* This command does not delete functions.
* Other than emptying all databases (similar to `FLUSHDB`), this command clears the RDB persistence file, aborts any snapshot that is in progress, and, if the `save` config is enabled, saves an empty RDB file.

Behavior change history

*   `>= 6.2.0`: Default flush behavior now configurable by the **lazyfree-lazy-user-flush** configuration directive.

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Standard
<nobr>❌ Active-Active\*</nobr> | ✅ Standard
<nobr>❌ Active-Active</nobr> | \*Can use the [Active-Active flush API request](). |

Return information

**RESP2:**

[Simple string reply](../../develop/reference/protocol-spec#simple-strings): `OK`.

**RESP3:**

[Simple string reply](../../develop/reference/protocol-spec#simple-strings): `OK`.


FLUSHDB

Summary: Remove all keys from the current database.

Overview

This command's behavior varies in clustered Redis environments. See the [multi-key operations]() page for more information.

Delete all the keys of the currently selected DB.
This command never fails.

By default, `FLUSHDB` will synchronously flush all keys from the database.
Starting with Redis 6.2, setting the **lazyfree-lazy-user-flush** configuration directive to "yes" changes the default flush mode to asynchronous.

It is possible to use one of the following modifiers to dictate the flushing mode explicitly:

* `ASYNC`: flushes the database asynchronously
* `SYNC`: flushes the database synchronously

Notes

* An asynchronous `FLUSHDB` command only deletes keys that were present at the time the command was invoked. Keys created during an asynchronous flush will be unaffected.
* This command does not delete functions.
* When using Redis Cluster, this command is identical to `FLUSHALL` since a Redis Cluster supports only one database with an ID of zero.

Behavior change history

Return information


FT._LIST

Summary: Returns a list of all existing indexes

Overview

Returns a list of all existing indexes.

The prefix `_` in the command indicates, this is a temporary command.

In the future, a [`SCAN`]() type of command will be added, for use when a database
contains a large number of indices.

Examples

[code example]

| Redis
Software | Redis Cloud
Flexible & Annual | Redis Cloud
Free & Fixed | Notes |
|:----------------------|:-----------------|:-----------------|:------|
| ✅ Supported | ✅ Supported | ✅ Supported</nobr> |  |

Return information

**RESP2:**

[Array]() of index names as [simple strings]().

**RESP3:**

[Set]() of index names as [simple strings]().


FT.AGGREGATE

Overview

Run a search query on an index and perform aggregate transformations on the results.

This command will only return keys to which the user has read access.

[Examples](#examples)

Required arguments

index

is index name against which the query is executed. You must first create the index using [`FT.CREATE`]().

query

is base filtering query that retrieves the documents. It follows the exact same syntax as the search query, including filters, unions, not, optional, and so on.

Optional arguments

VERBATIM

if set, does not try to use stemming for query expansion but searches the query terms verbatim.

LOAD {nargs} {identifier} AS {property} …

loads document attributes from the source document.
 - `identifier` is either an attribute name for hashes and JSON or a JSON Path expression for JSON.
 - `property` is the optional name used in the result. If it is not provided, the `identifier` is used. This should be avoided.
 - If `*` is used as `nargs`, all attributes in a document are loaded.

Attributes needed for aggregations should be stored as `SORTABLE`, where they are available to the aggregation pipeline with very low latency. `LOAD` hurts the performance of aggregate queries considerably because every processed record needs to execute the equivalent of [`HMGET`]() against a Redis key, which when executed over millions of keys, amounts to high processing times.

GROUPBY {nargs} {property}

groups the results in the pipeline based on one or more properties. Each group should have at least one _reducer_, a function that handles the group entries,
  either counting them, or performing multiple aggregate operations (see below).

REDUCE {func} {nargs} {arg} … [AS {name}]

reduces the matching results in each group into a single record, using a reduction function. For example, `COUNT` counts the number of records in the group. The reducers can have their own property names using the `AS {name}` optional argument. If a name is not given, the resulting name will be the name of the reduce function and the group properties. For example, if a name is not given to `COUNT_DISTINCT` by property `@foo`, the resulting name will be `count_distinct(@foo)`.

See [Supported GROUPBY reducers]() for more details.

SORTBY {nargs} {property} {ASC|DESC} [MAX {num}]

sorts the pipeline up until the point of `SORTBY`, using a list of properties.

 - By default, sorting is ascending, but `ASC` or `DESC ` can be added for each property.
 - `nargs` is the number of sorting parameters, including `ASC` and `DESC`, for example, `SORTBY 4 @foo ASC @bar DESC`.
 - `MAX` is used to optimized sorting, by sorting only for the n-largest elements. Although it is not connected to `LIMIT`, you usually need just `SORTBY … MAX` for common queries.

Attributes needed for `SORTBY` should be stored as `SORTABLE` to be available with very low latency.

**Sorting Optimizations**: performance is optimized for sorting operations on `DIALECT 4` in different scenarios:
   - Skip Sorter - applied when there is no sort of any kind. The query can return after it reaches the `LIMIT` requested results.
   - Partial Range - applied when there is a `SORTBY` clause over a numeric field, with no filter or filter by the same numeric field, the query iterate on a range large enough to satisfy the `LIMIT` requested results.
   - Hybrid - applied when there is a `SORTBY` clause over a numeric field and another non-numeric filter. Some results will get filtered, and the initial range may not be large enough. The iterator is then rewinding with the following ranges, and an additional iteration takes place to collect the `LIMIT` requested results.
   - No optimization - If there is a sort by score or by non-numeric field, there is no other option but to retrieve all results and compare their values.

**Counts behavior**: optional `WITHCOUNT` argument returns accurate counts for the query results with sorting. This operation processes all results in order to get an accurate count, being less performant than the optimized option (default behavior on `DIALECT 4`)

You can also use `WITHOUTCOUNT` in place of `DIALECT 4` when used with either `FT.SEARCH` or `FT.AGGREGATE`.

APPLY {expr} AS {name}

applies a 1-to-1 transformation on one or more properties and either stores the result as a new property down the pipeline or replaces any property using this
  transformation.

`expr` is an expression that can be used to perform arithmetic operations on numeric properties, or functions that can be applied on properties depending on their types (see below), or any combination thereof. For example, `APPLY "sqrt(@foo)/log(@bar) + 5" AS baz` evaluates this expression dynamically for each record in the pipeline and store the result as a new property called `baz`, which can be referenced by further `APPLY`/`SORTBY`/`GROUPBY`/`REDUCE` operations down the
  pipeline.

See [APPLY expressions]() for details.

LIMIT {offset} {num}

limits the number of results to return just `num` results starting at index `offset` (zero-based). It is much more efficient to use `SORTBY … MAX` if you
  are interested in just limiting the output of a sort operation.
  If a key expires during the query, an attempt to `load` the key's value will return a null array.

However, limit can be used to limit results without sorting, or for paging the n-largest results as determined by `SORTBY MAX`. For example, getting results 50-100 of the top 100 results is most efficiently expressed as `SORTBY 1 @foo MAX 100 LIMIT 50 50`. Removing the `MAX` from `SORTBY` results in the pipeline sorting _all_ the records and then paging over results 50-100.

FILTER {expr}

filters the results using predicate expressions relating to values in each result.
  They are applied post query and relate to the current state of the pipeline.

WITHCURSOR {COUNT} {read_size} [MAXIDLE {idle_time}]

Scan part of the results with a quicker alternative than `LIMIT`.
See [Cursor API]() for more details.

TIMEOUT {milliseconds}

if set, overrides the timeout parameter of the module.

PARAMS {nargs} {name} {value}

defines one or more value parameters. Each parameter has a name and a value.

You can reference parameters in the `query` by a `$`, followed by the parameter name, for example, `$user`. Each such reference in the search query to a parameter name is substituted by the corresponding parameter value. For example, with parameter definition `PARAMS 4 lon 29.69465 lat 34.95126`, the expression `@loc:[$lon $lat 10 km]` is evaluated to `@loc:[29.69465 34.95126 10 km]`. You cannot reference parameters in the query string where concrete values are not allowed, such as in field names, for example, `@loc`. To use `PARAMS`, set `DIALECT` to `2` or greater than `2`.

SCORER {scorer}

uses a [built-in]() or a [user-provided]() scoring function.

ADDSCORES

The `ADDSCORES` option exposes the full-text score values to the aggregation pipeline.
You can use `@__score` in a pipeline as shown in the following example:

`FT.AGGREGATE idx 'hello' ADDSCORES SORTBY 2 @__score DESC`

DIALECT {dialect_version}

selects the dialect version under which to execute the query. If not specified, the query will execute under the default dialect version set during module initial loading or via [`FT.CONFIG SET`]() command.

Return multiple values

See [Return multiple values]() in [`FT.SEARCH`]()
The `DIALECT` can be specified as a parameter in the FT.AGGREGATE command. If it is not specified, the `DEFAULT_DIALECT` is used, which can be set using [`FT.CONFIG SET`]() or by passing it as an argument to the `redisearch` module when it is loaded.
For example, with the following document and index:

[code example]
Notice the different replies, with and without `DIALECT 3`:

[code example]

Complexity

Non-deterministic. Depends on the query and aggregations performed, but it is usually linear to the number of results returned.

Examples

Sort page visits by day

Find visits to the page `about.html`, group them by the day of the visit, count the number of visits, and sort them by day.

FT.AGGREGATE idx "@url:\"about.html\""
    APPLY "day(@timestamp)" AS day
    GROUPBY 2 @day @country
      REDUCE count 0 AS num_visits
    SORTBY 1 @day

Find most books ever published

Find most books ever published in a single year.

FT.AGGREGATE books-idx *
    GROUPBY 1 @published_year
      REDUCE COUNT 0 AS num_published
    GROUPBY 0
      REDUCE MAX 1 @num_published AS max_books_published_per_year

Reduce all results

The last example used `GROUPBY 0`. Use `GROUPBY 0` to apply a `REDUCE` function over all results from the last step of an aggregation pipeline -- this works on both the  initial query and subsequent `GROUPBY` operations.

Search for libraries within 10 kilometers of the longitude -73.982254 and latitude 40.753181 then annotate them with the distance between their location and those coordinates.

 FT.AGGREGATE libraries-idx "@location:[-73.982254 40.753181 10 km]"
    LOAD 1 @location
    APPLY "geodistance(@location, -73.982254, 40.753181)"

Here, notice the required use of `LOAD` to pre-load the `@location` attribute because it is a GEO attribute.

Next, count GitHub events by user (actor), to produce the most active users.

127.0.0.1:6379> FT.AGGREGATE gh "*" GROUPBY 1 @actor REDUCE COUNT 0 AS num SORTBY 2 @num DESC MAX 10
 1) (integer) 284784
 2) 1) "actor"
    2) "lombiqbot"
    3) "num"
    4) "22197"
 3) 1) "actor"
    2) "codepipeline-test"
    3) "num"
    4) "17746"
 4) 1) "actor"
    2) "direwolf-github"
    3) "num"
    4) "10683"
 5) 1) "actor"
    2) "ogate"
    3) "num"
    4) "6449"
 6) 1) "actor"
    2) "openlocalizationtest"
    3) "num"
    4) "4759"
 7) 1) "actor"
    2) "digimatic"
    3) "num"
    4) "3809"
 8) 1) "actor"
    2) "gugod"
    3) "num"
    4) "3512"
 9) 1) "actor"
    2) "xdzou"
    3) "num"
    4) "3216"
[10](10)) 1) "actor"
    2) "opstest"
    3) "num"
    4) "2863"
11) 1) "actor"
    2) "jikker"
    3) "num"
    4) "2794"
(0.59s)

Use the case function for conditional logic

//Simple mapping
FT.AGGREGATE products "*"
APPLY case(@price > 100, "premium", "standard") AS category

//Nested conditions where an error should be returned
FT.AGGREGATE orders "*"
APPLY case(@status == "pending",
           case(@priority == "high", 1, 2),
           case(@status == "completed", 3, 4)) AS status_code

//Mapped approach
FT.AGGREGATE orders "*"
APPLY case(@status == "pending", 1, 0) AS is_pending
APPLY case(@is_pending == 1 && @priority == "high", 1,2) AS status_high
APPLY case(@is_pending == 0 && @priority == "high", 3,4) AS status_completed

Return information

**RESP2:**

One of the following:
* [Array reply]() where each row is an array reply representing a single aggregate result. The [integer reply]() at position `1` does not represent a valid value.
* [Simple error reply]() in these cases: incorrect number of arguments, non-existent index, invalid query syntax.

**RESP3:**

One of the following:
* [Map]() with the following fields:
    - `attributes`: [Array]() of attribute names.
    - `format`: [Simple string]() - result format.
    - `results`: [Array]() of [maps]() containing aggregated data.
    - `total_results`: [Integer]() - total number of results.
    - `warning`: [Array]() of warning messages.
* [Simple error reply]() in these cases: incorrect number of arguments, non-existent index, invalid query syntax.

See also

[`FT.CONFIG SET`]() | [`FT.SEARCH`]()

Related topics

- [Aggregations]()
- [Key and field expiration behavior]()
- [RediSearch]()

127.0.0.1:6379> JSON.SET doc:1 $ '[{"arr": [1, 2, 3]}, {"val": "hello"}, {"val": "world"}]'
OK
127.0.0.1:6379> FT.CREATE idx ON JSON PREFIX 1 doc: SCHEMA $..arr AS arr NUMERIC $..val AS val TEXT
OK


FT.ALIASADD

Summary: Adds an alias to the index

Overview

Add an alias to an index

[Examples](#examples)

Required arguments

alias index

is alias to be added to an index.

Indexes can have more than one alias, but an alias cannot refer to another
alias.

FT.ALIASADD allows administrators to transparently redirect application queries to alternative indexes.

Examples

Add an alias to an index

Add an alias to an index.

127.0.0.1:6379> FT.ALIASADD alias idx
OK

Attempting to add the same alias returns a message that the alias already exists.

127.0.0.1:6379> FT.ALIASADD alias idx
(error) Alias already exists

Return information

**RESP2:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: alias already exists, index does not exist.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: alias already exists, index does not exist.

See also

[`FT.ALIASDEL`]() | [`FT.ALIASUPDATE`]()


FT.ALIASDEL

Summary: Deletes an alias from the index

Overview

Remove an alias from an index

[Examples](#examples)

Required arguments

alias

is index alias to be removed.

Examples

Remove an alias from an index

Remove an alias from an index.

127.0.0.1:6379> FT.ALIASDEL alias
OK

| Redis
Software | Redis Cloud
Flexible & Annual | Redis Cloud
Free & Fixed | Notes |
|:----------------------|:-----------------|:-----------------|:------|
| ✅ Supported | ✅ Supported | ✅ Supported</nobr> |  |

Return information

**RESP2:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: alias does not exist.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: alias does not exist.

See also

[`FT.ALIASADD`]() | [`FT.ALIASUPDATE`]()


FT.ALIASUPDATE

Summary: Adds or updates an alias to the index

Overview

Add an alias to an index. If the alias is already associated with another
index, FT.ALIASUPDATE removes the alias association with the previous index.

[Examples](#examples)

Required arguments

alias index

is alias to be added to an index.

Examples

Update an index alias

Update the alias of an index.

127.0.0.1:6379> FT.ALIASUPDATE alias idx
OK

Return information

**RESP2:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: index does not exist.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: index does not exist.

See also

[`FT.ALIASADD`]() | [`FT.ALIASDEL`]()

Related topics

[RediSearch]()


FT.ALTER

Summary: Adds a new field to the index

Overview

Add a new attribute to the index. Adding an attribute to the index causes any future document updates to use the new attribute when indexing and reindexing existing documents.

[Examples](#examples)

Required arguments

index

is index name to create.

SKIPINITIALSCAN

if set, does not scan and index.

SCHEMA ADD {attribute} {options} ...

after the SCHEMA keyword, declares which fields to add:

- `attribute` is attribute to add.
- `options` are attribute options. Refer to [`FT.CREATE`]() for more information.

<note>Note:

Depending on how the index was created, you may be limited by the number of additional text
attributes which can be added to an existing index. If the current index contains fewer than 32
text attributes, then `SCHEMA ADD` will only be able to add attributes up to 32 total attributes (meaning that the
index will only ever be able to contain 32 total text attributes). If you wish for the index to
contain more than 32 attributes, create it with the `MAXTEXTFIELDS` option.
</note>

Examples

Alter an index

127.0.0.1:6379> FT.ALTER idx SCHEMA ADD id2 NUMERIC SORTABLE
OK

Return information

**RESP2:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: no such index, invalid schema syntax.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: no such index, invalid schema syntax.

See also

[`FT.CREATE`]()

Related topics

- [RediSearch]()


FT.CONFIG GET

Summary: Retrieves runtime configuration options

Overview

Retrieve configuration options

[Examples](#examples)

Required arguments

option

is name of the configuration option, or '*' for all.

Examples

Retrieve configuration options

127.0.0.1:6379> FT.CONFIG GET TIMEOUT
1) 1) TIMEOUT
   2) 42

127.0.0.1:6379> FT.CONFIG GET *
 1) 1) EXTLOAD
    2) (nil)
 2) 1) SAFEMODE
    2) true
 3) 1) CONCURRENT_WRITE_MODE
    2) false
 4) 1) NOGC
    2) false
 5) 1) MINPREFIX
    2) 2
 6) 1) FORKGC_SLEEP_BEFORE_EXIT
    2) 0
 7) 1) MAXDOCTABLESIZE
    2) 1000000
 8) 1) MAXSEARCHRESULTS
    2) 1000000
 9) 1) MAXAGGREGATERESULTS
    2) unlimited
10) 1) MAXEXPANSIONS
    2) 200
11) 1) MAXPREFIXEXPANSIONS
    2) 200
12) 1) TIMEOUT
    2) 42
13) 1) INDEX_THREADS
    2) 8
14) 1) SEARCH_THREADS
    2) 20
15) 1) FRISOINI
    2) (nil)
16) 1) ON_TIMEOUT
    2) return
17) 1) GCSCANSIZE
    2) 100
18) 1) MIN_PHONETIC_TERM_LEN
    2) 3
19) 1) GC_POLICY
    2) fork
20) 1) FORK_GC_RUN_INTERVAL
    2) 30
21) 1) FORK_GC_CLEAN_THRESHOLD
    2) 100
22) 1) FORK_GC_RETRY_INTERVAL
    2) 5
23) 1) FORK_GC_CLEAN_NUMERIC_EMPTY_NODES
    2) true
24) 1) _FORK_GC_CLEAN_NUMERIC_EMPTY_NODES
    2) true
25) 1) _MAX_RESULTS_TO_UNSORTED_MODE
    2) 1000
26) 1) UNION_ITERATOR_HEAP
    2) 20
27) 1) CURSOR_MAX_IDLE
    2) 300000
28) 1) NO_MEM_POOLS
    2) false
29) 1) PARTIAL_INDEXED_DOCS
    2) false
30) 1) UPGRADE_INDEX
    2) Upgrade config for upgrading
31) 1) _NUMERIC_COMPRESS
    2) false
32) 1) _FREE_RESOURCE_ON_THREAD
    2) true
33) 1) _PRINT_PROFILE_CLOCK
    2) true
34) 1) RAW_DOCID_ENCODING
    2) false
35) 1) _NUMERIC_RANGES_PARENTS
    2) 0
36) 1) MINSTEMLEN
    2) 4

| Redis
Software | Redis Cloud
Flexible & Annual | Redis Cloud
Free & Fixed | Notes |
|:----------------------|:-----------------|:-----------------|:------|
| <nobr>❌ Not supported | <nobr>⚠ Not supported1 | <nobr>❌ Not supported</nobr> |  |

1. [Contact support](https://redis.com/company/support/) to view the current configuration values or request configuration changes for Flexible or Annual Redis Cloud subscriptions.

Return information

**RESP2:**

[Array]() of [arrays](), where each sub-array contains a configuration option name and its value.

**RESP3:**

[Map]() where keys are configuration option names and values are their corresponding values.

See also

[`FT.CONFIG SET`]() | [`FT.CONFIG HELP`]()

Related topics

[RediSearch]()


FT.CONFIG HELP

Summary: Help description of runtime configuration options

Overview

Describe configuration options

[Examples](#examples)

Required arguments

Examples

Get configuration details

127.0.0.1:6379> FT.CONFIG HELP TIMEOUT
1) 1) TIMEOUT
   2) Description
   3) Query (search) timeout
   4) Value
   5) "42"

| Redis
Software | Redis Cloud
Flexible & Annual | Redis Cloud
Free & Fixed | Notes |
|:----------------------|:-----------------|:-----------------|:------|
| <nobr>❌ Not supported | <nobr>❌ Not supported | <nobr>❌ Not supported</nobr> |  |

Return information

**RESP2:**

[Array]() of help information.

**RESP3:**

[Array]() of help information.

See also

[`FT.CONFIG SET`]() | [`FT.CONFIG GET`]()

Related topics

[RediSearch]()


FT.CONFIG SET

Summary: Sets runtime configuration options

Overview

Set the value of a RediSearch configuration parameter.

Values set using `FT.CONFIG SET` are not persisted after server restart.

RediSearch configuration parameters are detailed in [Configuration parameters]().

As detailed in the link above, not all RediSearch configuration parameters can be set at runtime.

[Examples](#examples)

Required arguments

option

is name of the configuration option, or '*' for all.

value

is value of the configuration option.

Examples

Set runtime configuration options

127.0.0.1:6379> FT.CONFIG SET TIMEOUT 42
OK

| Redis
Software | Redis Cloud
Flexible & Annual | Redis Cloud
Free & Fixed | Notes |
|:----------------------|:-----------------|:-----------------|:------|
| <nobr>⚠ Not supported1 | <nobr>⚠ Not supported2 | <nobr>❌ Not supported</nobr> |  |

1. Use [`rladmin`]() or the [REST API]() to change search and query configuration for Redis Software. See [search and query configuration compatibility with Redis Software]() for more information and examples.

2. [Contact support](https://redis.com/company/support/) to view the current configuration values or request configuration changes for Flexible or Annual Redis Cloud subscriptions.

Return information

**RESP2:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: invalid option, invalid value.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: invalid option, invalid value.

See also

[`FT.CONFIG GET`]() | [`FT.CONFIG HELP`]()

Related topics

[RediSearch]()


FT.CREATE

Summary: Creates an index with the given spec

Description

Create an index with the given specification. For usage, see [Examples](#examples).

Required arguments

index

is index name to create.
If such index already exists, returns an error reply `(error) Index already exists`.

SCHEMA {identifier} AS {attribute} {attribute type} {options...

after the SCHEMA keyword, declares which fields to index:

 - `{identifier}` for hashes, is a field name within the hash.
      For JSON, the identifier is a JSON Path expression.

 - `AS {attribute}` defines the attribute associated to the identifier. For example, you can use this feature to alias a complex JSONPath expression with more memorable (and easier to type) name.

 Field types are:

 - `TEXT` - Allows full-text search queries against the value in this attribute.

 - `TAG` - Allows exact-match queries, such as categories or primary keys, against the value in this attribute. For more information, see [Tag Fields]().

 - `NUMERIC` - Allows numeric range queries against the value in this attribute. See [query syntax docs]() for details on how to use numeric ranges.

 - `GEO` - Allows radius range queries against the value (point) in this attribute. The value of the attribute must be a string containing a longitude (first) and latitude separated by a comma.

 - `VECTOR` - Allows vector queries against the value in this attribute. This requires [query dialect 2]() or above (introduced in [RediSearch v2.4](https://github.com/RediSearch/RediSearch/releases/tag/v2.4.3)). For more information, see [Vector Fields]().

 - `GEOSHAPE`- Allows polygon queries against the value in this attribute. The value of the attribute must follow a [WKT notation](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) list of 2D points representing the polygon edges `POLYGON((x1 y1, x2 y2, ...)` separated by a comma. A `GEOSHAPE` field type can be followed by one of the following coordinate systems:
   - `SPHERICAL` for Geographic longitude and latitude coordinates
   - `FLAT` for Cartesian X Y coordinates

    The default coordinate system is `SPHERICAL`.

    Currently `GEOSHAPE` doesn't support JSON multi-value and `SORTABLE` option.

 Field options are:

 - `SORTABLE` - `NUMERIC`, `TAG`, `TEXT`, or `GEO` attributes can have an optional **SORTABLE** argument. As the user [sorts the results by the value of this attribute](), the results are available with very low latency. Note that his adds memory overhead, so consider not declaring it on large text attributes. You can sort an attribute without the `SORTABLE` option, but the latency is not as good as with `SORTABLE`.

 - `UNF` - By default, for hashes (not with JSON) `SORTABLE` applies a normalization to the indexed value (characters set to lowercase, removal of diacritics). When using the unnormalized form (UNF), you can disable the normalization and keep the original form of the value. With JSON, `UNF` is implicit with `SORTABLE` (normalization is disabled).

 - `NOSTEM` - Text attributes can have the NOSTEM argument that disables stemming when indexing its values. This may be ideal for things like proper names.

 - `NOINDEX` - Attributes can have the `NOINDEX` option, which means they will not be indexed. This is useful in conjunction with `SORTABLE`, to create attributes whose update using PARTIAL will not cause full reindexing of the document. If an attribute has NOINDEX and doesn't have SORTABLE, it will just be ignored by the index.

 - `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]().

  - `WEIGHT {weight}` for `TEXT` attributes, declares the importance of this attribute when calculating result accuracy. This is a multiplication factor, and defaults to 1 if not specified.

  - `SEPARATOR {sep}` for `TAG` attributes, indicates how the text contained in the attribute is to be split into individual tags. The default is `,`. The value must be a single character.

  - `CASESENSITIVE` for `TAG` attributes, keeps the original letter cases of the tags. If not specified, the characters are converted to lowercase.

  - `WITHSUFFIXTRIE` for `TEXT` and `TAG` attributes, keeps 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 suffix trie exists for some fields, these queries will be disabled for other fields.

  - `INDEXEMPTY` for `TEXT` and `TAG` attributes, introduced in v2.10, allows you to index and search for empty strings. By default, empty strings are not indexed.

  - `INDEXMISSING` for all field types, introduced in v2.10, allows you to search for missing values, that is, documents that do not contain a specific field. Note the difference between a field with an empty value and a document with a missing value. By default, missing values are not indexed.

Optional arguments

ON {data_type}

currently supports HASH (default) and JSON. To index JSON, you must have the [RedisJSON]() module installed.

PREFIX {count} {prefix}

tells the index which keys it should index. You can add several prefixes to index. Because the argument is optional, the default is `*` (all keys).

FILTER {filter}

is a filter expression with the full RediSearch aggregation expression language. It is possible to use `@__key` to access the key that was just added/changed. A field can be used to set field name by passing `'FILTER @indexName=="myindexname"'`.

LANGUAGE {default_lang}

if set, indicates the default language for documents in the index. Default is English.

LANGUAGE_FIELD {lang_attribute}

is a document attribute set as the document language.

A stemmer is used for the supplied language during indexing. If an unsupported language is sent, the command returns an error. The supported
languages are `arabic`, `armenian`, `basque`, `catalan`, `danish`, `dutch`, `english`, `finnish`, `french`, `german`, `greek`, `hindi`,
`hungarian`, `indonesian`, `irish`, `italian`, `lithuanian`, `nepali`, `norwegian`, `portuguese`, `romanian`, `russian`, `serbian`,
`spanish`, `swedish`, `tamil`, `turkish`, `yiddish`, and `chinese`.

When adding Chinese language documents, set `LANGUAGE chinese` for the indexer to properly tokenize the terms. If you use the default language, then search terms are extracted based on punctuation characters and whitespace. The Chinese language tokenizer makes use of a segmentation algorithm (via [Friso](https://github.com/lionsoul2014/friso)), which segments text and checks it against a predefined dictionary. See [Stemming]() for more information.

SCORE {default_score}

is default score for documents in the index. Default score is 1.0.

SCORE_FIELD {score_attribute}

is document attribute that you use as the document rank based on the user ranking. Ranking must be between 0.0 and 1.0. If not set, the default score is 1.

PAYLOAD_FIELD {payload_attribute}

is document attribute that you use as a binary safe payload string to the document that can be evaluated at query time by a custom scoring function or retrieved to the client.

MAXTEXTFIELDS

forces RediSearch to encode indexes as if there were more than 32 text attributes, which allows you to add additional attributes (beyond 32) using [`FT.ALTER`](). For efficiency, RediSearch encodes indexes differently if they are created with less than 32 text attributes.

NOOFFSETS

does not store term offsets for documents. It saves memory, but does not allow exact searches or highlighting. It implies `NOHL`.

TEMPORARY {seconds}

creates a lightweight temporary index that expires after a specified period of inactivity, in seconds. The internal idle timer is reset whenever the index is searched or added to. Because such indexes are lightweight, you can create thousands of such indexes without negative performance implications and, therefore, you should consider using `SKIPINITIALSCAN` to avoid costly scanning.

When temporary indexes expire, they drop all the records associated with them.
[`FT.DROPINDEX`]() was introduced with a default of not deleting docs and a `DD` flag that enforced deletion.
However, for temporary indexes, documents are deleted along with the index.
Historically, RediSearch used an FT.ADD command, which made a connection between the document and the index. Then, FT.DROP, also a hystoric command, deleted documents by default.
In version 2.x, RediSearch indexes hashes and JSONs, and the dependency between the index and documents no longer exists.

NOHL

conserves storage space and memory by disabling highlighting support. If set, the corresponding byte offsets for term positions are not stored. `NOHL` is also implied by `NOOFFSETS`.

NOFIELDS

does not store attribute bits for each term. It saves memory, but it does not allow
  filtering by specific attributes.

NOFREQS

avoids saving the term frequencies in the index. It saves memory, but does not allow sorting based on the frequencies of a given term within the document.

STOPWORDS {count}

sets the index with a custom stopword list, to be ignored during indexing and search time. `{count}` is the number of stopwords, followed by a list of stopword arguments exactly the length of `{count}`.

If not set, FT.CREATE takes the default list of stopwords. If `{count}` is set to 0, the index does not have stopwords.

SKIPINITIALSCAN

if set, does not scan and index.

INDEXALL {ENABLE | DISABLE}

introduced in v8.0, maintains a compact index of existing document IDs to optimize wildcard queries, for example, `FT.SEARCH idx *`. When disabled, wildcard queries scan all IDs ever assigned—including deleted ones—which can hurt performance in write-heavy workloads. The default is DISABLE.

<note>Notes:

 - **Attribute number limits:** RediSearch supports up to 1024 attributes per schema, out of which at most 128 can be TEXT attributes. On 32 bit builds, at most 64 attributes can be TEXT attributes. The more attributes you have, the larger your index, as each additional 8 attributes require one extra byte per index record to encode. You can always use the `NOFIELDS` option and not encode attribute information into the index, for saving space, if you do not need filtering by text attributes. This will still allow filtering by numeric and geo attributes.
 - **Running in clustered databases:** When having several indices in a clustered database, you need to make sure the documents you want to index reside on the same shard as the index. You can achieve this by having your documents tagged by the index name.

   127.0.0.1:6379> HSET doc:1{idx} ...
   127.0.0.1:6379> FT.CREATE idx ... PREFIX 1 doc: ...

   When Running RediSearch in a clustered database, you can span the index across shards using [RSCoordinator](https://github.com/RedisLabsModules/RSCoordinator). In this case the above does not apply.

</note>

Examples

Create an index

Create an index that stores the title, publication date, and categories of blog post hashes whose keys start with `blog:post:` (for example, `blog:post:1`).

127.0.0.1:6379> FT.CREATE idx ON HASH PREFIX 1 blog:post: SCHEMA title TEXT SORTABLE published_at NUMERIC SORTABLE category TAG SORTABLE
OK

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

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

Index two different hashes, one containing author data and one containing books, in the same index:

127.0.0.1:6379> 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

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

Index authors whose names start with G.

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

Index only books that have a subtitle.

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

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

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

Index a JSON document using a JSON Path expression

The following example uses data similar to the hash examples above but uses JSON instead.

127.0.0.1:6379> FT.CREATE idx ON JSON SCHEMA $.title AS title TEXT $.categories AS categories TAG

| Redis
Software | Redis Cloud
Flexible & Annual | Redis Cloud
Free & Fixed | Notes |
|:----------------------|:-----------------|:-----------------|:------|
| ✅ Supported | ✅ Supported | ✅ Supported</nobr> |  |

Return information

**RESP2:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: index already exists, invalid schema syntax.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: index already exists, invalid schema syntax.

See also

[`FT.ALTER`]() | [`FT.DROPINDEX`]()

Related topics

- [RediSearch]()
- [RedisJSON]()
- [Friso](https://github.com/lionsoul2014/friso)
- [Stemming]()
- [Phonetic Matching]()
- [RSCoordinator](https://github.com/RedisLabsModules/RSCoordinator)


FT.CURSOR DEL

Summary: Deletes a cursor

Overview

Delete a cursor

[Examples](#examples)

Required arguments

index

is index name.

cursor_id

is id of the cursor.

Examples

Delete a cursor

redis> FT.CURSOR DEL idx 342459320
OK

Check that the cursor is deleted.

127.0.0.1:6379> FT.CURSOR DEL idx 342459320
(error) Cursor does not exist

Return information

**RESP2:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: cursor does not exist.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: cursor does not exist.

See also

[`FT.CURSOR READ`]()

Related topics

[RediSearch]()


FT.CURSOR READ

Summary: Reads from a cursor

Overview

Read next results from an existing cursor

[Examples](#examples)

See [Cursor API]() for more details.

Required arguments

index

is index name.

cursor_id

is id of the cursor.

[COUNT read_size]

is number of results to read. This parameter overrides `COUNT` specified in [`FT.AGGREGATE`]().

Examples

Read next results from a cursor

127.0.0.1:6379> FT.CURSOR READ idx 342459320 COUNT 50

Return information

**RESP2:**

One of the following:
* [Array]() with search results and metadata.
* [Simple error reply]() in these cases: cursor not found.

**RESP3:**

One of the following:
* [Map]() with structured search results and metadata.
* [Simple error reply]() in these cases: cursor not found.

See also

[`FT.CURSOR DEL`]() | [`FT.AGGREGATE`]()

Related topics

[RediSearch]()


FT.DICTADD

Summary: Adds terms to a dictionary

Overview

Add terms to a dictionary

[Examples](#examples)

Required arguments

dict

is dictionary name.

term

term to add to the dictionary.

Examples

Add terms to a dictionary

127.0.0.1:6379> FT.DICTADD dict foo bar "hello world"
(integer) 3

Return information

**RESP2:**

[Integer reply](): the number of new terms added to the dictionary.

**RESP3:**

[Integer reply](): the number of new terms added to the dictionary.

See also

[`FT.DICTDEL`]() | [`FT.DICTDUMP`]()

Related topics

[RediSearch]()


FT.DICTDEL

Summary: Deletes terms from a dictionary

Overview

Delete terms from a dictionary

[Examples](#examples)

Required arguments

dict

is dictionary name.

term

term to delete from the dictionary.

Examples

Delete terms from a dictionary

127.0.0.1:6379> FT.DICTDEL dict foo bar "hello world"
(integer) 3

| Redis
Software | Redis Cloud
Flexible & Annual | Redis Cloud
Free & Fixed | Notes |
|:----------------------|:-----------------|:-----------------|:------|
| ✅ Supported | ✅ Supported | ✅ Supported</nobr> |  |

Return information

**RESP2:**

[Integer reply](): the number of terms deleted from the dictionary.

**RESP3:**

[Integer reply](): the number of terms deleted from the dictionary.

See also

[`FT.DICTADD`]() | [`FT.DICTDUMP`]()

Related topics

[RediSearch]()


FT.DICTDUMP

Summary: Dumps all terms in the given dictionary

Overview

Dump all terms in the given dictionary

[Examples](#examples)

Required argumemts

dict

is dictionary name.

Examples

Dump all terms in the dictionary

127.0.0.1:6379> FT.DICTDUMP dict
1) "foo"
2) "bar"
3) "hello world"

Return information

**RESP2:**

[Array]() of dictionary terms.

**RESP3:**

[Set]() of dictionary terms.

See also

[`FT.DICTADD`]() | [`FT.DICTDEL`]()

Related topics

[RediSearch]()


FT.DROPINDEX

Summary: Deletes the index

Overview

Delete an index

[Examples](#examples)

Required arguments

index

is full-text index name. You must first create the index using [`FT.CREATE`]().

Optional arguments

DD

drop index operation that, if set, deletes the actual document keys. `FT.DROPINDEX index DD` is an asynchronous operation.

By default, FT.DROPINDEX does not delete the documents associated with the index. Adding the `DD` option deletes the documents as well.
If an index creation is still running ([`FT.CREATE`]() is running asynchronously), only the document hashes that have already been indexed are deleted.
The document hashes left to be indexed remain in the database.
To check the completion of the indexing, use [`FT.INFO`]().

Examples

Delete an index

127.0.0.1:6379> FT.DROPINDEX idx DD
OK

Return information

**RESP2:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: no such index.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` if executed correctly.
* [Simple error reply]() in these cases: no such index.

See also

[`FT.CREATE`]() | [`FT.INFO`]()

Related topics

[RediSearch]()


FT.EXPLAIN

Summary: Returns the execution plan for a complex query

Overview

Return the execution plan for a complex query

[Examples](#examples)

Required arguments

index

is index name. You must first create the index using [`FT.CREATE`]().

query

is query string, as if sent to FT.SEARCH`.

Optional arguments

DIALECT {dialect_version}

is dialect version under which to execute the query. If not specified, the query executes under the default dialect version set during module initial loading or via [`FT.CONFIG SET`]() command.

- In the returned response, a `+` on a term is an indication of stemming.
- Use `redis-cli --raw` to properly read line-breaks in the returned response.

Examples

Return the execution plan for a complex query

$ redis-cli --raw

127.0.0.1:6379> FT.EXPLAIN rd "(foo bar)|(hello world) @date:[100 200]|@date:[500 +inf]"
INTERSECT {
  UNION {
    INTERSECT {
      foo
      bar
    }
    INTERSECT {
      hello
      world
    }
  }
  UNION {
    NUMERIC {100.000000 <= x <= 200.000000}
    NUMERIC {500.000000 <= x <= inf}
  }
}

Return information

**RESP2:**

One of the following:
* [Bulk string]() containing the query execution plan.
* [Simple error reply]() in these cases: no such index, syntax error in query.

**RESP3:**

One of the following:
* [Bulk string]() containing the query execution plan.
* [Simple error reply]() in these cases: no such index, syntax error in query.

See also

[`FT.CREATE`]() | [`FT.SEARCH`]() | [`FT.CONFIG SET`]()

Related topics

[RediSearch]()


FT.EXPLAINCLI

Overview

Return the execution plan for a complex query but formatted for easier reading without using `redis-cli --raw`

[Examples](#examples)

Required arguments

Optional arguments

DIALECT {dialect_version}

is dialect version under which to execute the query. If not specified, the query executes under the default dialect version set during module initial loading or via [`FT.CONFIG SET`]() command.

In the returned response, a `+` on a term is an indication of stemming.

Examples

Return the execution plan for a complex query

$ redis-cli

127.0.0.1:6379> FT.EXPLAINCLI rd "(foo bar)|(hello world) @date:[100 200]|@date:[500 +inf]"
 1) INTERSECT {
 2)   UNION {
 3)     INTERSECT {
 4)       UNION {
 5)         foo
 6)         +foo(expanded)
 7)       }
 8)       UNION {
 9)         bar
10)         +bar(expanded)
11)       }
12)     }
13)     INTERSECT {
14)       UNION {
15)         hello
16)         +hello(expanded)
17)       }
18)       UNION {
19)         world
20)         +world(expanded)
21)       }
22)     }
23)   }
24)   UNION {
25)     NUMERIC {100.000000 <= @date <= 200.000000}
26)     NUMERIC {500.000000 <= @date <= inf}
27)   }
28) }
29)

| Redis
Software | Redis Cloud
Flexible & Annual | Redis Cloud
Free & Fixed | Notes |
|:----------------------|:-----------------|:-----------------|:------|
| ✅ Supported | ✅ Supported | ✅ Supported</nobr> |  |

Return information

**RESP2:**

One of the following:
* [Array]() of [bulk string replies]() containing the query execution plan in CLI format.
* [Simple error reply]() in these cases: no such index, syntax error in query.

**RESP3:**

One of the following:
* [Array]() of [bulk string replies]() containing the query execution plan in CLI format.
* [Simple error reply]() in these cases: no such index, syntax error in query.

See also

Related topics

[RediSearch]()


FT.HYBRID

Summary: Performs hybrid search combining text search and vector similarity search

Overview

Performs hybrid search combining text search and vector similarity with configurable fusion methods.

`FT.HYBRID` provides a unified interface for combining traditional full-text and vector-based search within a single query. It supports hybrid retrieval use cases such as semantic search, Retrieval-Augmented Generation (RAG), and intelligent agent applications. The command builds on the familiar query syntax of `FT.SEARCH` and `FT.AGGREGATE`, simplifying hybrid query construction while enabling flexible post-processing through aggregation capabilities.

This command will only return document IDs (`keyid`) and scores to which the user has read access.
To retrieve entire documents, use projections with `LOAD *` or `LOAD <count> field...`.

[Examples](#examples)

Required arguments

index

is the name of the index. You must first create the index using [`FT.CREATE`]().

SEARCH "search-expression"

defines the text search component of the hybrid query. The search expression uses the same syntax as [`FT.SEARCH`]() queries, supporting all text search capabilities including field-specific searches, boolean operations, and phrase matching.

VSIM @vector_field "vector-data"

defines the vector similarity component of the hybrid query. The `@vector_field` specifies which vector field in the index to search against (for example, `$vector`), and `"vector-data"` contains the query vector for similarity comparison (for example, `PARAMS 2 $vector <vector-blob>`).

Optional arguments

SCORER algorithm params...

specifies the scoring algorithm and parameters for the text search component. Supports aliasing and follows the parameter count convention where the first number indicates the total count of following parameters.

Example: `SCORER 4 BM25 1.2 0.75` uses BM25 algorithm with parameters 1.2 and 0.75.

YIELD_SCORE_AS alias-search-score

assigns an alias to the search score for use in post-processing operations like `APPLY` or `SORTBY`.

KNN count K top-k [EF_RUNTIME ef-value] [YIELD_SCORE_AS name]

configures K-nearest neighbors search for vector similarity. The `count` parameter indicates the number of following parameters. `K` specifies the number of nearest neighbors to find. `EF_RUNTIME` controls the search accuracy vs. speed tradeoff. `YIELD_SCORE_AS` assigns an alias to the score value.

RANGE count RADIUS radius-value [EPSILON epsilon-value] [YIELD_SCORE_AS name]

configures range-based vector search within a specified radius. The `count` parameter indicates the number of following parameters. `RADIUS` defines the maximum distance for matches. `EPSILON` provides additional precision control.

FILTER "filter-expression"

applies pre-filtering to vector search results or post-filtering when used after the `COMBINE` step as post-processing. This filter affects which documents are considered for vector similarity but doesn't impact scoring. In contrast, the `SEARCH` component affects both filtering and scoring. The `FILTER` syntax uses a search expression with the same syntax as [`FT.SEARCH`](), supporting all text search capabilities including field-specific searches, boolean operations, and phrase matching

POLICY [ADHOC_BF|BATCHES] [BATCH_SIZE batch-size-value]

controls the pre-filtering policy for vector queries. `ADHOC_BF` processes filters on-demand and `BATCHES` processes in configurable batch sizes. See the [pre-filtering policy]() for more information.

COMBINE method params...

specifies how to fuse the text search and vector similarity results. Supports multiple fusion methods:

- **RRF (Reciprocal Rank Fusion)**: Default method. Parameters include `WINDOW` (default 20) and `CONSTANT` (default 60).
- **LINEAR**: Linear combination with `ALPHA` and `BETA` weights.
- **FUNCTION**: Custom fusion function (future support).

Example: `COMBINE RRF 4 WINDOW 40 CONSTANT 1.5`

YIELD_SCORE_AS alias-combined-score

assigns an alias to the combined fusion score for use in post-processing operations.

LOAD count field...

specifies which fields to return in the results. The `count` parameter indicates the number of fields that follow.

Example: `LOAD 3 @category @brand @price`

GROUPBY count field... REDUCE function...

groups results by specified fields and applies reduction functions. Follows the parameter count convention. The `count` parameter indicates the number of fields that follow.

Example: `GROUPBY 4 category brand REDUCE 2 COUNT 0`

APPLY expression AS field

applies transformations to create new fields. Can reference aliased scores and distances.

Example: `APPLY "@vector_distance+@score" AS final_score`

SORTBY count field [ASC|DESC]

sorts the final results by the specified field in ascending or descending order. The `count` parameter indicates the number of fields that follow.

LIMIT offset num

limits the final results. Default limit is 10 when not specified. The `offset` parameter is zero-indexed.

PARAMS count key value...

defines parameter substitution for the query. Parameters can be referenced in search expressions using `$parameter_name`.

Example: `PARAMS 4 min_price 50 max_price 200`

TIMEOUT timeout

sets a runtime timeout for the query execution in milliseconds.

Default values and behaviors

FT.HYBRID provides sensible defaults to ease onboarding:

- **Query count**: 2 (one SEARCH and one VSIM component required)
- **Default LIMIT**: 10 results
- **Default SCORER**: BM25STD for text search
- **Default KNN K**: 10 neighbors
- **Default RRF WINDOW**: 20
- **Default RRF CONSTANT**: 60
- **Default EF_RUNTIME**: 10 (as vector KNN [default](https://redis.io/docs/latest/develop/ai/search-and-query/vectors/#hnsw-index))
- **Default EPSILON**: 0.01 (as the vector RANGE [default]())

Parameter count convention

All multi-parameter options use a count prefix that contains ALL tokens that follow:

- `KNN 4 K 10 EF_RUNTIME 100` - 2 key-value pairs
- `PARAMS 4 min_price 50 max_price 200` - 2 key-value pairs
- `COMBINE RRF 4 WINDOW 40 CONSTANT 1.5` - RRF method with 2 key-value pairs

The only exception is alias usage with `AS`, which is not counted:
- `APPLY "@vector_distance+@score" AS final_score`
- `LOAD 3 @category AS cat @brand AS brd @price AS prc`

Reserved fields

The following fields are reserved for internal use:

- `@__key` - reserved for loading key IDs when required
- `@__score` - reserved for the combined score (can be aliased)
- `@vector_distance` - yields the vector distance (can be aliased)
- `@__combined_score` - fused score from the COMBINE step

Examples

Basic hybrid search

Perform a simple hybrid search combining text search for "laptop" with vector similarity:

127.0.0.1:6379> FT.HYBRID products-idx
  SEARCH "laptop"
  VSIM @description_vector $query_vec
  KNN 2 K 10
  PARAMS 2 query_vec <vector_blob>

Hybrid search with custom scoring and fusion

Search for electronics with custom BM25 parameters and RRF fusion:

127.0.0.1:6379> FT.HYBRID products-idx
  SEARCH "@category:electronics"
  SCORER 4 BM25 1.5 0.8
  YIELD_SCORE_AS text_score
  VSIM @features_vector $query_vec
  KNN 4 K 20 EF_RUNTIME 200
  YIELD_SCORE_AS vector_score
  COMBINE RRF 4 WINDOW 50 CONSTANT 80
  YIELD_SCORE_AS hybrid_score
  SORTBY 2 hybrid_score DESC
  LIMIT 0 20
  PARAMS 2 query_vec <vector_blob>

Hybrid search with pre-filtering

Search with vector pre-filtering and post-processing:

127.0.0.1:6379> FT.HYBRID products-idx
  SEARCH "smartphone"
  VSIM @image_vector $query_vec
  KNN 2 K 15
  FILTER "@price:[100 500]"
  COMBINE LINEAR 4 ALPHA 0.7 BETA 0.3
  LOAD 4 @title @price @category @rating
  APPLY "@price * 0.9" AS discounted_price
  SORTBY 2 rating DESC
  PARAMS 2 query_vec <vector_blob>

Hybrid search with parameters

Use parameter substitution for dynamic queries:

127.0.0.1:6379> FT.HYBRID products-idx
  SEARCH "@brand:$brand_name"
  VSIM @content_vector $query_vector
  RANGE 4 RADIUS 0.8 EPSILON 0.1
  FILTER "@availability:$stock_status"
  PARAMS 6 brand_name "Apple" query_vector <vector_blob> stock_status "in_stock"

Complexity

FT.HYBRID complexity depends on both the text search and vector similarity components:
- Text search: O(n) for simple term searches, where n is the number of matching documents. In multi-term queries with INTERSECT or UNION, or when using fuzzy or prefix matches, the complexity increases proportionally to the total number of entries scanned across all participating terms.
- Vector search: O(log n) for KNN with HNSW index, O(n) for range queries
- Fusion: O(k) where k is the number of results to combine
- Overall complexity is typically dominated by the more expensive component

| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Standard
<nobr>✅ Active-Active</nobr> | ✅ Standard
<nobr>✅ Active-Active</nobr> |  |

Return information

**RESP2:**

One of the following:
* [Array]() with the first element being the total number of results, followed by document IDs and their field-value pairs as [arrays]().
* [Simple error reply]() in these cases: no such index, syntax error in query.

**RESP3:**

One of the following:
* [Map]() with the following fields:
    - `total_results`: [Integer]() - total number of results
    - `execution_time`: [double]() containing hybrid query execution time
    - `warnings`: [Array]() of warning messages indicating partial results due to index errors or `MAXPREFIXEXPANSIONS`, out-of-memory conditions, and `TIMEOUT` reached
    - `results`: [Array]() of [maps]() containing document information
* [Simple error reply]() in these cases: no such index, syntax error in query.

See also

[`FT.CREATE`]() | [`FT.SEARCH`]() | [`FT.AGGREGATE`]()

Related topics

- [Vector search concepts]()
- [Combined search]()