Skip to main content

Creating an Index

The SEARCH.CREATE command creates a new search index that automatically tracks keys matching specified prefixes. An index is identified by its name, which must be a unique key in Redis. Each index works only with a specified key type (JSON, hash, or string) and tracks changes to keys matching the prefixes defined during index creation.
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

// Basic index on JSON data
const users = await redis.search.createIndex({
  name: "users",
  dataType: "json",
  prefix: "user:",
  schema: s.object({
    name: s.string(),
    email: s.string(),
    age: s.number("U64"),
  }),
});
For JSON indexes, an index field can be specified for fields on various nested levels. For hash indexes, an index field can be specified for fields. As hash fields cannot have nesting on their own, for this kind of indexes, only top-level schema fields can be used. For string indexes, indexed keys must be valid JSON strings. A field on any nesting level can be indexed, similar to JSON indexes.
import { Redis, s } from "@upstash/redis";
const redis = Redis.fromEnv();

// String index with nested schema fields
const comments = await redis.search.createIndex({
  name: "comments",
  dataType: "string",
  prefix: "comment:",
  schema: s.object({
    user: s.object({
      name: s.string(),
      email: s.string().noTokenize(),
    }),
    comment: s.string(),
    upvotes: s.number("U64"),
    commentedAt: s.date().fast(),
  }),
});
It is possible to define an index for more than one prefix. However, there are some rules concerning the usage of multiple prefixes:
  • Prefixes must not contain duplicates.
  • No prefix should cover another prefix (e.g., user: and user:admin: are not allowed together).
  • Multiple distinct prefixes are allowed (e.g., article: and blog: are valid together).
import { Redis, s } from "@upstash/redis";
const redis = Redis.fromEnv();

// JSON index with multiple prefixes
const articles = await redis.search.createIndex({
  name: "articles",
  dataType: "json",
  prefix: ["article:", "blog:", "news:"],
  schema: s.object({
    title: s.string(),
    body: s.string(),
    author: s.string().noStem(),
    publishedAt: s.date().fast(),
    viewCount: s.number("U64"),
  }),
});
By default, when an index is created, all existing keys matching the specified type and prefixes are scanned and indexed. Use SKIPINITIALSCAN to defer indexing, which is useful for large datasets where you want to start fresh or handle existing data differently.
// Skipping initial scan and indexing of keys with SKIPINITIALSCAN
// TODO: TS SDK does not support SKIPINITIALSCAN for now
It is possible to specify the language of the text fields, so that an appropriate tokenizer and stemmer can be used. For more on tokenization and stemming, see the Text Field Options section. When not specified, language defaults to english. Currently, the following languages are supported:
  • english
  • arabic
  • danish
  • dutch
  • finnish
  • french
  • german
  • greek
  • hungarian
  • italian
  • norwegian
  • portuguese
  • romanian
  • russian
  • spanish
  • swedish
  • tamil
  • turkish
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

// Turkish language index
const addresses = await redis.search.createIndex({
  name: "addresses",
  dataType: "json",
  prefix: "address:",
  language: "turkish",
  schema: s.object({
    address: s.string().noStem(),
    description: s.string(),
  }),
});
Finally, it is possible safely create an index only if it does not exist, using the EXISTOK option.
// Safe creation with EXISTOK
// TODO: TS SDK does not support EXISTSOK for now
For the schema definition of the index, see the Schema Definition section.

Getting an Index Client

The redis.search.index() method creates a client for an existing index without making a Redis call. This is useful when you want to query or manage an index that already exists, without the overhead of creating it.
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

// Get a client for an existing index
const users = redis.search.index("users");

// Query the index
const results = await users.query({
  filter: { name: "John" },
});

// With schema for type safety
const schema = s.object({
  name: s.string(),
  email: s.string(),
  age: s.number("U64"),
});

const typedUsers = redis.search.index("users", schema);

// Now queries are type-safe
const typedResults = await typedUsers.query({
  filter: { name: "John" },
});
This method is different from redis.search.createIndex() which:
  • Creates a new index if it doesn’t exist
  • Makes a Redis call to create the index
  • Returns an error if the index already exists (unless EXISTOK is used)
Use redis.search.index() when:
  • The index already exists
  • You want to avoid unnecessary Redis calls
  • You’re querying or managing an existing index
Use redis.search.createIndex() when:
  • You need to create a new index
  • You’re setting up your application for the first time

Describing an Index

The SEARCH.DESCRIBE command returns detailed information about an index.
let description = await index.describe();
console.log(description);
On response, the following information is returned:
FieldDescription
nameIndex name
typeData type (STRING, HASH, or JSON)
prefixesList of tracked key prefixes
languageStemming language
schemaField definitions with types and options

Dropping an Index

The SEARCH.DROP command removes an index and stops tracking associated keys.
await index.drop();
Note that, dropping an index only removes the search index. The underlying Redis keys are not affected.

Waiting for Indexing

For adequate performance, index updates are batched and committed periodically. This means recent writes may not immediately appear in search results. Use SEARCH.WAITINDEXING when you need to ensure queries reflect recent changes. The SEARCH.WAITINDEXING command blocks until all pending index updates are processed and visible to queries. We recommend not to call this command each time you perform a write operation on the index. For optimal indexing and query performance, batch updates are necessary.
// Add new document
await redis.json.set("product:new", "$", { name: "New Product", price: 49.99 });

// Ensure it's searchable before querying
await index.waitIndexing();

// Now the query will include the new product
const products = await index.query({
  filter: { name: "new" },
});

for (const product of products) {
  console.log(product);
}