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

:::callout{intent="tip" title="Prefer natural language queries?"}
The [Query Agent](query-agent.md) translates plain English questions into optimized Weaviate queries automatically - no manual query construction needed.
Cloud only
:::

## Basic BM25 search

To use BM25 keyword search, define a search string.

:::code-group{sync="languages"}
```python title="Python" {2}
jeopardy = client.collections.use("JeopardyQuestion")
response = jeopardy.query.bm25(
    query="food",
    limit=3
)

for o in response.objects:
    print(o.properties)
```

```typescript title="JavaScript/TypeScript"
const jeopardy = client.collections.use('JeopardyQuestion');
```

```go title="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 title="Java" {4}
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());
}
```

```csharp title="C#" {3}
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 title="GraphQL" {5-7}
{
  Get {
    JeopardyQuestion(
      limit: 3
      bm25: {
        query: "food"
      }
    ) {
      question
      answer
    }
  }
}
```
:::

:::accordion{title="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

:::callout{intent="info" title="Added in `v1.31`"}
:::

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

### `or`

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

:::code-group{sync="languages"}
```python title="Python" {1,5-6}
from weaviate.classes.query import BM25Operator

jeopardy = 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 title="Java"
```

```csharp title="C#" {3-4}
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));
}
```

```python title="GraphQL" {7-10}
{
  Get {
    JeopardyQuestion(
      limit: 3
      bm25: {
        query: "Australian mammal cute"
        searchOperator: {
          operator: Or,
          minimumOrTokensMatch: 2
        }
      }
    ) {
      question
      answer
    }
  }
}
```
:::

### `and`

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

:::code-group{sync="languages"}
```python title="Python" {1,5-6}
from weaviate.classes.query import BM25Operator

jeopardy = 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 title="Java"
```

```csharp title="C#" {3-4}
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));
}
```

```python title="GraphQL" {7-9}
{
  Get {
    JeopardyQuestion(
      limit: 3
      bm25: {
        query: "Australian mammal cute"
        searchOperator: {
          operator: And,
        }
      }
    ) {
      question
      answer
    }
  }
}
```
:::

### `and_cross`

:::callout{intent="info" title="Added in `v1.38.8`"}
:::

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.

:::callout{intent="warning" title="All searched properties must be configured alike"}
`and_cross` requires every searched property to share the same tokenization and the same analyzer settings, which means the tokenizer, accent folding and its exceptions, and the stopword preset. If they differ, the query fails with an error instead of returning fewer results:

```
OPERATOR_AND_CROSS requires all searched properties to share the same tokenization and analyzer settings
```

If a collection mixes tokenizations, restrict the search to a compatible set of properties. See [Search on selected properties only](#search-on-selected-properties-only).
:::

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.

:::code-group{sync="languages"}
```python title="Python" {1,5-10}
from weaviate.classes.query import BM25Operator

jeopardy = 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)
```

```python title="GraphQL" {8-10}
{
  Get {
    JeopardyQuestion(
      limit: 3
      bm25: {
        query: "Australian mammal cute"
        properties: ["question", "answer"]
        searchOperator: {
          operator: AndCross,
        }
      }
    ) {
      question
      answer
    }
  }
}
```
:::

## Retrieve BM25F scores

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

:::code-group{sync="languages"}
```python title="Python" {12}
from weaviate.classes.query import MetadataQuery

jeopardy = 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)
```

```typescript title="JavaScript/TypeScript"
const jeopardy = client.collections.use('JeopardyQuestion');
```

```go title="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 title="Java" {8}
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());
}
```

```csharp title="C#" {11}
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 title="GraphQL" {11-13}
{
  Get {
    JeopardyQuestion(
      limit: 3
      bm25: {
        query: "food"
      }
    ) {
      question
      answer
      _additional {
        score
      }
    }
  }
}
```
:::

:::accordion{title="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"
        }
      ]
    }
  }
}
```
:::

## Search on selected properties only

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.

:::code-group{sync="languages"}
```python title="Python" {6}
from weaviate.classes.query import MetadataQuery

jeopardy = 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)
```

```typescript title="JavaScript/TypeScript"
const jeopardy = client.collections.use('JeopardyQuestion');
```

```go title="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 title="Java" {4}
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());
}
```

```csharp title="C#" {4}
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 title="GraphQL" {7}
{
  Get {
    JeopardyQuestion(
      limit: 3
      bm25: {
        query: "food"
        properties: ["question"]
      }
    ) {
      question
      answer
      _additional {
        score
      }
    }
  }
}
```
:::

:::accordion{title="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"
        }
      ]
    }
  }
}
```
:::

## Use weights to boost properties

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.

:::code-group{sync="languages"}
```python title="Python" {4}
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)
```

```typescript title="JavaScript/TypeScript"
const jeopardy = client.collections.use('JeopardyQuestion');
```

```go title="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 title="Java" {4}
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());
}
```

```csharp title="C#" {4}
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 title="GraphQL" {7}
{
  Get {
    JeopardyQuestion(
      limit: 3
      bm25: {
        query: "food"
        properties: ["question^2", "answer"]
      }
    ) {
      question
      answer
      _additional {
        score
      }
    }
  }
}
```
:::

:::accordion{title="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"
        }
      ]
    }
  }
}
```
:::

## Set tokenization

The BM25 query string is [tokenized](../reference-configuration/collections.md#tokenization) 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](../how-to-manage-collections/vector-config.md#property-level-settings).

:::code-group{sync="languages"}
```python title="Python" {10-12,17-18}
from weaviate.classes.config import Configure, Property, DataType, Tokenization

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

```typescript title="JavaScript/TypeScript"
import { vectors, dataType, tokenization } from 'weaviate-client';
```

```java title="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))));
```

```csharp title="C#"
```
:::

:::callout{intent="tip" title="Tokenization and fuzzy matching"}
For fuzzy matching and typo tolerance, use `trigram` tokenization. See the [fuzzy matching section](#fuzzy-matching) above for details.
:::

### Accent folding

:::callout{intent="warning" title="Preview — added in `v1.37`"}
This is a preview feature. The API may change in future releases.
:::

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](../indexing/inverted-index.md#accent-folding) for configuration details.

### Stopwords

:::callout{intent="warning" title="Preview — added in `v1.37`"}
This is a preview feature. The API may change in future releases.
:::

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](../indexing/inverted-index.md#custom-stopword-presets) and the [stopwords configuration reference](../reference-configuration/indexing-inverted-index.md#stopwords) for details.

## `limit` & `offset`

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

Optionally, use `offset` to paginate the results.

:::code-group{sync="languages"}
```python title="Python" {4-5}
jeopardy = client.collections.use("JeopardyQuestion")
response = jeopardy.query.bm25(
    query="safety",
    limit=3,
    offset=1
)

for o in response.objects:
    print(o.properties)
```

```typescript title="JavaScript/TypeScript"
const jeopardy = client.collections.use('JeopardyQuestion');
```

```go title="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 title="Java" {4-5}
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());
}
```

```csharp title="C#" {4-5}
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 title="GraphQL" {7}
{
  Get {
    JeopardyQuestion(
      bm25: {
        query: "safety"
      }
      limit: 3
    ) {
      question
      answer
      _additional {
        score
      }
    }
  }
}
```
:::

## Limit result groups

To limit results to groups of similar distances to the query, use the [`autocut`](../apis/graphql-additional-operators.md#autocut) filter to set the number of groups to return.

:::code-group{sync="languages"}
```python title="Python" {4}
jeopardy = client.collections.use("JeopardyQuestion")
response = jeopardy.query.bm25(
    query="safety",
    auto_limit=1
)

for o in response.objects:
    print(o.properties)
```

```typescript title="JavaScript/TypeScript"
const jeopardy = client.collections.use('JeopardyQuestion');
```

```go title="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 title="Java"
```

```csharp title="C#" {4}
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 title="GraphQL" {7}
{
  Get {
    JeopardyQuestion(
      bm25: {
        query: "safety"
      }
      autocut: 1
    ) {
      question
      answer
      _additional {
        score
      }
    }
  }
}
```
:::

:::accordion{title="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"
        }
      ]
    }
  }
}
```
:::

## Group results

Define criteria to group search results.

:::code-group{sync="languages"}
```python title="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)
```

```typescript title="JS/TS"
const jeopardy = client.collections.use('JeopardyQuestion');
```

```java title="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());
});
```

```csharp title="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)}");
}
```
:::

:::accordion{title="Example response"}
The response is like this:

```
'Jeopardy!'
'Double Jeopardy!'
```
:::

## Filter results

For more specific results, use a [`filter`](../apis/graphql-filters.md) to narrow your search.

:::code-group{sync="languages"}
```python title="Python" {6}
from weaviate.classes.query import Filter

jeopardy = 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)
```

```typescript title="JavaScript/TypeScript"
const jeopardy = client.collections.use('JeopardyQuestion');
```

```go title="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 title="Java" {4}
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());
}
```

```csharp title="C#" {4}
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 title="GraphQL" {8-12}
{
  Get {
    JeopardyQuestion(
      limit: 3
      bm25: {
        query: "food"
      }
      where: {
        path: ["round"]
        operator: Equal
        valueText: "Double Jeopardy!"
      }
    ) {
      question
      answer
      _additional {
        score
      }
    }
  }
}
```
:::

:::accordion{title="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!"
        }
      ]
    }
  }
}
```
:::

### Tokenization

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](../reference-configuration/collections.md#tokenization).

## Fuzzy matching

You can enable fuzzy matching and typo tolerance in BM25 searches by using [`trigram` tokenization](../reference-configuration/collections.md#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:

:::code-group{sync="languages"}
```python title="Python" {10}
from weaviate.classes.config import Configure, Property, DataType, Tokenization

client.collections.create(
    "Article",
    vector_config=Configure.Vectors.text2vec_cohere(),
    properties=[
        Property(
            name="title",
            data_type=DataType.TEXT,
            tokenization=Tokenization.TRIGRAM,  # Use "trigram" tokenization
        ),
    ],
)
```

```typescript title="JavaScript/TypeScript"
import { vectors, dataType, tokenization } from 'weaviate-client';
```

```java title="Java"
client.collections.create("Article", col -> col
    .vectorConfig(VectorConfig.text2vecTransformers())
    .properties(
        Property.text("title", p -> p.tokenization(Tokenization.TRIGRAM))));
```

```csharp title="C#"
```
:::

:::callout{intent="tip" title="Best practices"}
- Use trigram tokenization selectively on fields that need fuzzy matching. Filtering behavior will change significantly, as text filtering will be done based on trigram-tokenized text, instead of whole words
- Keep exact-match fields with `word` or `field` tokenization for precision.
:::

## Soft-rank with Boost

:::callout{intent="info" title="Added in `v1.38`"}
:::

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](boost.md) for the supported condition types (filter, property value, time decay, numeric decay), curve choices, blending semantics, and depth tuning.

## Further resources

- [Connect to Weaviate](../connect-to-weaviate/index.md)
- [API References: Search operators # BM25](../apis/graphql-search-operators.md#bm25)
- [Reference: Tokenization options](../reference-configuration/collections.md#tokenization)

## Questions and feedback

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

::::card-grid
:::card{title="Community Forum" href="https://forum.weaviate.io/c/support" icon="messages-square"}
Ask questions and connect with other developers on our **Community forum**.
:::

:::card{title="Support" href="/guides/support-overview" icon="life-buoy"}
Weaviate Cloud user or customer? Find the right channel on the **Support page**.
:::
::::

## Related pages

- [Agents](./agents-index.md)
- [AI-assisted Weaviate code generation](./ai-assisted-vibe-coding-index.md)
- [APIs](./apis-index.md)
- [Authorization and authentication](./authorization-and-authentication-index.md)
- [Benchmarks](./benchmarks-index.md)
- [Best practices](./best-practices-index.md)
- [Client libraries](./clients-index.md)
- [Client Libraries / SDKs](./client-libraries-index.md)
- [Cloud](./cloud-index.md)
- [Cloud account management](./cloud-account-management-index.md)

# Agent Instructions

This portal answers questions programmatically. To receive a synthesized,
source-cited answer instead of crawling page by page, append the `?ask=`
query parameter to any page URL on this site:

    /guides/quickstart?ask=how+do+I+authenticate

Optional parameters:

- `&goal=<what-you-are-trying-to-do>` steers the answer toward your
  objective (e.g. `&goal=write+a+python+client`).
- `&version=<label>` scopes the answer to a mounted version when the
  portal publishes more than one.

The response is `text/markdown`: the answer followed by a `# Sources` list
of the portal pages it was grounded in. Status codes are the contract:

- `200` — the answer; `402` — the portal owner’s plan or answer credits are
  exhausted (surface this to your operator; do NOT retry); `429` — you are
  rate-limited; back off for the `Retry-After` seconds; `503` — the answer
  lane is temporarily unavailable; fall back to crawling the `.md` pages.

For the full corpus map read `llms.txt` at the site root; for the tool
surface (search + page fetch as MCP tools) see `/mcp`.
