Skip to main content
Weaviate Docs (migrated from docs.weaviate.io) Docs

Search documentation

Type to search this documentation.

On this pageOverview

Keyword search

Keyword search, also called "BM25 (Best match 25)" or "sparse vector" search, returns objects that have the highest BM25F scores.

To use BM25 keyword search, define a search string.

Python
jeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="food",    limit=3)for o in response.objects:    print(o.properties)
JavaScript/TypeScript
const jeopardy = client.collections.use('JeopardyQuestion');
Go
ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("food")
limit := int(3)

result, err := client.GraphQL().Get().
  WithClassName(className).
  WithFields(
    graphql.Field{Name: "question"},
    graphql.Field{Name: "answer"},
  ).
  WithBM25(query).
  WithLimit(limit).
  Do(ctx)
Java
CollectionHandle<Map<String, Object>> jeopardy =    client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25(    "food",    q -> q.limit(3));for (var o : response.objects()) {  System.out.println(o.properties());}
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25(    "food",    limit: 3);foreach (var o in response.Objects){    Console.WriteLine(JsonSerializer.Serialize(o.Properties));}
GraphQL
{  Get {    JeopardyQuestion(      limit: 3      bm25: {        query: "food"      }    ) {      question      answer    }  }}
Example response

The response is like this:

JSON
{
  "data": {
    "Get": {
      "JeopardyQuestion": [
        {
          "answer": "food stores (supermarkets)",
          "question": "This type of retail store sells more shampoo & makeup than any other"
        },
        {
          "answer": "cake",
          "question": "Devil's food & angel food are types of this dessert"
        },
        {
          "answer": "a closer grocer",
          "question": "A nearer food merchant"
        }
      ]
    }
  }
}

Search operators define how many of the query tokens must match, and whether they must all match within a single searched property. The options are or (default), and, and and_cross.

With the or operator, the search returns objects that contain at least minimumOrTokensMatch of the tokens in the search string.

Python
from weaviate.classes.query import BM25Operatorjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="African desert wind",    operator=BM25Operator.or_(minimum_match=1),    limit=3,)for o in response.objects:    print(o.properties)
Java
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25(    query: "Australian mammal cute",    searchOperator: new BM25Operator.Or(MinimumMatch: 1),    limit: 3);foreach (var o in response.Objects){    Console.WriteLine(JsonSerializer.Serialize(o.Properties));}
GraphQL
{  Get {    JeopardyQuestion(      limit: 3      bm25: {        query: "Australian mammal cute"        searchOperator: {          operator: Or,          minimumOrTokensMatch: 2        }      }    ) {      question      answer    }  }}

With the and operator, the search returns objects where all tokens in the search string appear together within a single searched property.

Python
from weaviate.classes.query import BM25Operatorjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="African desert wind",    operator=BM25Operator.and_(),  # Each result must include all tokens (e.g. "african", "desert", "wind")    limit=3,)for o in response.objects:    print(o.properties)
Java
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25(    query: "Australian mammal cute",    searchOperator: new BM25Operator.And(), // Each result must include all tokens (e.g. "australian", "mammal", "cute")    limit: 3);foreach (var o in response.Objects){    Console.WriteLine(JsonSerializer.Serialize(o.Properties));}
GraphQL
{  Get {    JeopardyQuestion(      limit: 3      bm25: {        query: "Australian mammal cute"        searchOperator: {          operator: And,        }      }    ) {      question      answer    }  }}

With the and_cross operator, every token in the search string must be matched by at least one of the searched properties, but the tokens do not all have to occur in the same property. An object whose title matches one token and whose body matches the rest is a match for and_cross, and is not a match for and.

Because it relaxes the single-property requirement, and_cross returns every object that and returns, and usually more.

The examples below use Python and GraphQL: and_cross is available in the Python client and, from version 1.2.0, the C# client, but not yet in the TypeScript, Go, or Java clients. Both examples restrict the search to question and answer, because the JeopardyQuestion collection mixes tokenizations across its properties.

Python
from weaviate.classes.query import BM25Operatorjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="African desert wind",    # and_cross errors unless every searched property shares tokenization settings    query_properties=["question", "answer"],    # Each token must be matched by at least one searched property,    # but not necessarily all by the same property    operator=BM25Operator.and_cross(),    limit=3,)for o in response.objects:    print(o.properties)
GraphQL
{  Get {    JeopardyQuestion(      limit: 3      bm25: {        query: "Australian mammal cute"        properties: ["question", "answer"]        searchOperator: {          operator: AndCross,        }      }    ) {      question      answer    }  }}

You can retrieve the BM25F score values for each returned object.

Python
from weaviate.classes.query import MetadataQueryjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="food",    return_metadata=MetadataQuery(score=True),    limit=3)for o in response.objects:    print(o.properties)    print(o.metadata.score)
JavaScript/TypeScript
const jeopardy = client.collections.use('JeopardyQuestion');
Go
ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("food")
limit := int(3)

result, err := client.GraphQL().Get().
  WithClassName(className).
  WithFields(
    graphql.Field{Name: "question"},
    graphql.Field{Name: "answer"},
    graphql.Field{
      Name: "_additional",
      Fields: []graphql.Field{
        {Name: "score"},
      },
    },
  ).
  WithBM25(query).
  WithLimit(limit).
  Do(ctx)
Java
CollectionHandle<Map<String, Object>> jeopardy =    client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("food",    q -> q.returnMetadata(Metadata.SCORE).limit(3));for (var o : response.objects()) {  System.out.println(o.properties());  System.out.println(o.queryMetadata().score());}
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25(    "food",    returnMetadata: MetadataOptions.Score,    limit: 3);foreach (var o in response.Objects){    Console.WriteLine(JsonSerializer.Serialize(o.Properties));    Console.WriteLine(o.Metadata.Score);}
GraphQL
{  Get {    JeopardyQuestion(      limit: 3      bm25: {        query: "food"      }    ) {      question      answer      _additional {        score      }    }  }}
Example response

The response is like this:

JSON
{
  "data": {
    "Get": {
      "JeopardyQuestion": [
        {
          "_additional": {
            "score": "3.0140665"
          },
          "answer": "food stores (supermarkets)",
          "question": "This type of retail store sells more shampoo & makeup than any other"
        },
        {
          "_additional": {
            "score": "2.8725255"
          },
          "answer": "cake",
          "question": "Devil's food & angel food are types of this dessert"
        },
        {
          "_additional": {
            "score": "2.7672548"
          },
          "answer": "a closer grocer",
          "question": "A nearer food merchant"
        }
      ]
    }
  }
}

A keyword search can be directed to only search a subset of object properties. In this example, the BM25 search only uses the question property to produce the BM25F score.

Python
from weaviate.classes.query import MetadataQueryjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="safety",    query_properties=["question"],    return_metadata=MetadataQuery(score=True),    limit=3)for o in response.objects:    print(o.properties)    print(o.metadata.score)
JavaScript/TypeScript
const jeopardy = client.collections.use('JeopardyQuestion');
Go
ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("safety").WithProperties("question")
limit := int(3)

result, err := client.GraphQL().Get().
  WithClassName(className).
  WithFields(
    graphql.Field{Name: "question"},
    graphql.Field{
      Name: "_additional",
      Fields: []graphql.Field{
        {Name: "score"},
      },
    },
  ).
  WithBM25(query).
  WithLimit(limit).
  Do(ctx)
Java
CollectionHandle<Map<String, Object>> jeopardy =    client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("safety", q -> q    .queryProperties("question")    .returnMetadata(Metadata.SCORE)    .limit(3));for (var o : response.objects()) {  System.out.println(o.properties());  System.out.println(o.queryMetadata().score());}
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25(    "safety",    searchFields: ["question"],    returnMetadata: MetadataOptions.Score,    limit: 3);foreach (var o in response.Objects){    Console.WriteLine(JsonSerializer.Serialize(o.Properties));    Console.WriteLine(o.Metadata.Score);}
GraphQL
{  Get {    JeopardyQuestion(      limit: 3      bm25: {        query: "food"        properties: ["question"]      }    ) {      question      answer      _additional {        score      }    }  }}
Example response

The response is like this:

JSON
{
  "data": {
    "Get": {
      "JeopardyQuestion": [
        {
          "_additional": {
            "score": "3.7079012"
          },
          "answer": "cake",
          "question": "Devil's food & angel food are types of this dessert"
        },
        {
          "_additional": {
            "score": "3.4311616"
          },
          "answer": "a closer grocer",
          "question": "A nearer food merchant"
        },
        {
          "_additional": {
            "score": "2.8312314"
          },
          "answer": "honey",
          "question": "The primary source of this food is the Apis mellifera"
        }
      ]
    }
  }
}

You can weight how much each property affects the overall BM25F score. This example boosts the question property by a factor of 2 while the answer property remains static.

Python
jeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="food",    query_properties=["question^2", "answer"],    limit=3)for o in response.objects:    print(o.properties)
JavaScript/TypeScript
const jeopardy = client.collections.use('JeopardyQuestion');
Go
ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("food").WithProperties("question^2", "answer")
limit := int(3)

result, err := client.GraphQL().Get().
  WithClassName(className).
  WithFields(
    graphql.Field{Name: "question"},
    graphql.Field{Name: "answer"},
  ).
  WithBM25(query).
  WithLimit(limit).
  Do(ctx)
Java
CollectionHandle<Map<String, Object>> jeopardy =    client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("food", q -> q    .queryProperties("question^2", "answer")    .limit(3));for (var o : response.objects()) {  System.out.println(o.properties());}
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25(    "food",    searchFields: ["question^2", "answer"],    limit: 3);foreach (var o in response.Objects){    Console.WriteLine(JsonSerializer.Serialize(o.Properties));}
GraphQL
{  Get {    JeopardyQuestion(      limit: 3      bm25: {        query: "food"        properties: ["question^2", "answer"]      }    ) {      question      answer      _additional {        score      }    }  }}
Example response

The response is like this:

JSON
{
  "data": {
    "Get": {
      "JeopardyQuestion": [
        {
          "_additional": {
            "score": "4.0038033"
          },
          "answer": "cake",
          "question": "Devil's food & angel food are types of this dessert"
        },
        {
          "_additional": {
            "score": "3.8706005"
          },
          "answer": "a closer grocer",
          "question": "A nearer food merchant"
        },
        {
          "_additional": {
            "score": "3.2457707"
          },
          "answer": "food stores (supermarkets)",
          "question": "This type of retail store sells more shampoo & makeup than any other"
        }
      ]
    }
  }
}

The BM25 query string is tokenized before it is used to search for objects using the inverted index.

You must specify the tokenization method in the collection definition for each property.

Python
from weaviate.classes.config import Configure, Property, DataType, Tokenizationclient.collections.create(    "Article",    vector_config=Configure.Vectors.text2vec_cohere(),    properties=[        Property(            name="title",            data_type=DataType.TEXT,            vectorize_property_name=True,  # Use "title" as part of the value to vectorize            tokenization=Tokenization.LOWERCASE,  # Use "lowercase" tokenization            description="The title of the article.",  # Optional description        ),        Property(            name="body",            data_type=DataType.TEXT,            skip_vectorization=True,  # Don't vectorize this property            tokenization=Tokenization.WHITESPACE,  # Use "whitespace" tokenization        ),    ],)
JavaScript/TypeScript
import { vectors, dataType, tokenization } from 'weaviate-client';
Java
client.collections.create("Article",
    col -> col.properties(
        Property.text("title",
            p -> p.description("The title of the article.")
                .tokenization(Tokenization.LOWERCASE)
                .vectorizePropertyName(false)),
        Property.text("body", p -> p.skipVectorization(true)
            .tokenization(Tokenization.WHITESPACE))));
C#

Text properties can enable accent folding via textAnalyzer.asciiFold to normalize accented characters to their ASCII equivalents during both indexing and querying. For example, "Café Crème" becomes searchable as "cafe creme" and vice versa. This improves BM25 recall for multilingual content without requiring users to type exact accented characters.

See Inverted index: Accent folding for configuration details.

By default, Weaviate filters out common English stopwords (like "a", "the", "is") from BM25 scoring. You can customize this behavior:

  • Custom presets: Define named stopword lists per collection via invertedIndexConfig.stopwordPresets, useful for non-English languages or domain-specific terms.
  • Per-property overrides: Assign different stopword presets to individual properties via textAnalyzer.stopwordPreset, useful for multilingual collections where each property contains text in a different language.

Stopwords are still indexed and only filtered at query time, so changing the configuration does not require reindexing.

See Inverted index: Custom stopword presets and the stopwords configuration reference for details.

Use limit to set a fixed maximum number of objects to return.

Optionally, use offset to paginate the results.

Python
jeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="safety",    limit=3,    offset=1)for o in response.objects:    print(o.properties)
JavaScript/TypeScript
const jeopardy = client.collections.use('JeopardyQuestion');
Go
ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("safety")
limit := int(3)
offset := int(1)

result, err := client.GraphQL().Get().
  WithClassName(className).
  WithFields(
    graphql.Field{Name: "question"},
    graphql.Field{Name: "answer"},
  ).
  WithBM25(query).
  WithLimit(limit).
  WithOffset(offset).
  Do(ctx)
Java
CollectionHandle<Map<String, Object>> jeopardy =    client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("safety", q -> q    .limit(3)    .offset(1));for (var o : response.objects()) {  System.out.println(o.properties());}
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25(    "safety",    limit: 3,    offset: 1);foreach (var o in response.Objects){    Console.WriteLine(JsonSerializer.Serialize(o.Properties));}
GraphQL
{  Get {    JeopardyQuestion(      bm25: {        query: "safety"      }      limit: 3    ) {      question      answer      _additional {        score      }    }  }}

To limit results to groups of similar distances to the query, use the autocut filter to set the number of groups to return.

Python
jeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="safety",    auto_limit=1)for o in response.objects:    print(o.properties)
JavaScript/TypeScript
const jeopardy = client.collections.use('JeopardyQuestion');
Go
ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("safety")
autoLimit := int(1)

result, err := client.GraphQL().Get().
  WithClassName(className).
  WithFields(
    graphql.Field{Name: "question"},
    graphql.Field{Name: "answer"},
  ).
  WithBM25(query).
  WithAutocut(autoLimit).
  Do(ctx)
Java
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25(    "safety",    autoLimit: 1);foreach (var o in response.Objects){    Console.WriteLine(JsonSerializer.Serialize(o.Properties));}
GraphQL
{  Get {    JeopardyQuestion(      bm25: {        query: "safety"      }      autocut: 1    ) {      question      answer      _additional {        score      }    }  }}
Example response

The response is like this:

JSON
{
  "data": {
    "Get": {
      "JeopardyQuestion": [
        {
          "_additional": {
            "score": "2.6768136"
          },
          "answer": "OSHA (Occupational Safety and Health Administration)",
          "question": "The government admin. was created in 1971 to ensure occupational health & safety standards"
        }
      ]
    }
  }
}

Define criteria to group search results.

Python
from weaviate.classes.query import GroupBy

jeopardy = client.collections.use("JeopardyQuestion")

# Grouping parameters
group_by = GroupBy(
    prop="round",  # group by this property
    objects_per_group=3,  # maximum objects per group
    number_of_groups=2,  # maximum number of groups
)

# Query
response = jeopardy.query.bm25(
    query="California",
    group_by=group_by
)

for grp_name, grp_content in response.groups.items():
    print(grp_name, grp_content.objects)
JS/TS
const jeopardy = client.collections.use('JeopardyQuestion');
Java
CollectionHandle<Map<String, Object>> jeopardy =
    client.collections.use("JeopardyQuestion");

var response = jeopardy.query.bm25("California", q -> q, // No query options needed for this example
    GroupBy.property("round", // group by this property
        2, // maximum number of groups
        3 // maximum objects per group
    ));

response.groups().forEach((groupName, group) -> {
  System.out.println(group.name() + " " + group.objects());
});
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");

var response = await jeopardy.Query.BM25(
    "California",
    groupBy: new GroupByRequest("round") // group by this property
    {
        NumberOfGroups = 2, // maximum number of groups
        ObjectsPerGroup = 3, // maximum objects per group
    }
);

foreach (var group in response.Groups.Values)
{
    Console.WriteLine($"{group.Name} {JsonSerializer.Serialize(group.Objects)}");
}
Example response

The response is like this:

'Jeopardy!'
'Double Jeopardy!'

For more specific results, use a filter to narrow your search.

Python
from weaviate.classes.query import Filterjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25(    query="food",    filters=Filter.by_property("round").equal("Double Jeopardy!"),    return_properties=["answer", "question", "round"], # return these properties    limit=3)for o in response.objects:    print(o.properties)
JavaScript/TypeScript
const jeopardy = client.collections.use('JeopardyQuestion');
Go
ctx := context.Background()
className := "JeopardyQuestion"

query := (&graphql.BM25ArgumentBuilder{}).WithQuery("food")
limit := int(3)

filter := filters.Where().
  WithPath([]string{"round"}).
  WithOperator(filters.Equal).
  WithValueString("Double Jeopardy!")

result, err := client.GraphQL().Get().
  WithClassName(className).
  WithFields(
    graphql.Field{Name: "answer"},
    graphql.Field{Name: "question"},
    graphql.Field{Name: "round"},
  ).
  WithBM25(query).
  WithWhere(filter).
  WithLimit(limit).
  Do(ctx)
Java
CollectionHandle<Map<String, Object>> jeopardy =    client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("food", q -> q    .filters(Filter.property("round").eq("Double Jeopardy!"))    .returnProperties("answer", "question", "round") // return these properties    .limit(3));for (var o : response.objects()) {  System.out.println(o.properties());}
C#
var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25(    "food",    filters: Filter.Property("round").IsEqual("Double Jeopardy!"),    returnProperties: ["answer", "question", "round"], // return these properties    limit: 3);foreach (var o in response.Objects){    Console.WriteLine(JsonSerializer.Serialize(o.Properties));}
GraphQL
{  Get {    JeopardyQuestion(      limit: 3      bm25: {        query: "food"      }      where: {        path: ["round"]        operator: Equal        valueText: "Double Jeopardy!"      }    ) {      question      answer      _additional {        score      }    }  }}
Example response

The response is like this:

JSON
{
  "data": {
    "Get": {
      "JeopardyQuestion": [
        {
          "_additional": {
            "score": "3.0140665"
          },
          "answer": "food stores (supermarkets)",
          "question": "This type of retail store sells more shampoo & makeup than any other",
          "round": "Double Jeopardy!"
        },
        {
          "_additional": {
            "score": "1.9633813"
          },
          "answer": "honey",
          "question": "The primary source of this food is the Apis mellifera",
          "round": "Double Jeopardy!"
        },
        {
          "_additional": {
            "score": "1.6719631"
          },
          "answer": "pseudopods",
          "question": "Amoebas use temporary extensions called these to move or to surround & engulf food",
          "round": "Double Jeopardy!"
        }
      ]
    }
  }
}

Weaviate converts filter terms into tokens. The default tokenization is word. The word tokenizer keeps alphanumeric characters, lowercase them and splits on whitespace. It converts a string like "Test_domain_weaviate" into "test", "domain", and "weaviate".

For details and additional tokenization methods, see Tokenization.

You can enable fuzzy matching and typo tolerance in BM25 searches by using trigram tokenization. This technique breaks text into overlapping 3-character sequences, allowing BM25 to find matches even when there are spelling errors or variations.

This enables matching between similar but not identical strings because they share many trigrams:

  • "Morgn" and "Morgan" share trigrams like "org", "rga", "gan"

Set the tokenization method to trigram at the property level when creating your collection:

Python
from weaviate.classes.config import Configure, Property, DataType, Tokenizationclient.collections.create(    "Article",    vector_config=Configure.Vectors.text2vec_cohere(),    properties=[        Property(            name="title",            data_type=DataType.TEXT,            tokenization=Tokenization.TRIGRAM,  # Use "trigram" tokenization        ),    ],)
JavaScript/TypeScript
import { vectors, dataType, tokenization } from 'weaviate-client';
Java
client.collections.create("Article", col -> col
    .vectorConfig(VectorConfig.text2vecTransformers())
    .properties(
        Property.text("title", p -> p.tokenization(Tokenization.TRIGRAM))));
C#

Keyword (BM25) queries accept an optional boost argument that promotes or demotes matching documents without removing them. This is useful for biasing results by recency, popularity, a soft filter, or another property. Matching documents move up. Everything else stays in the results but ranks lower.

See Boost for the supported condition types (filter, property value, time decay, numeric decay), curve choices, blending semantics, and depth tuning.

Have a question or feedback? Here's how to reach us.

Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu