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

**Boost** soft-ranks search results: it promotes or demotes matching documents without removing them from the result set. Matching documents move up. Non-matching documents stay in the results but rank lower.

Apply boost to vector, hybrid, BM25, near-text, near-vector, near-object, near-image, and near-media queries.

## How it works

A boost is a **post-retrieval rescorer**:

1. The primary search (vector, hybrid, BM25, ...) fetches `depth` candidate results. Set `depth` higher than `offset + limit` if you want boost to consider candidates beyond the first page.
2. The boost scorer rescores those candidates in memory by evaluating each condition per candidate, normalizing per result set, and blending with the primary score. There are no new index queries. The cost is per-candidate in-memory scoring, not extra shard fan-out. Both primary and boost scores are min-max normalized into `[0, 1]` before blending, and the final score is renormalized to `[0, 1]`.
3. The user's original `offset` and `limit` are applied **after** the re-sort.

## Condition types

A boost is one or more **conditions**, blended into a single rescore. Every condition is one of: filter, property value, time decay, or numeric decay.

### Filter condition (soft `WHERE`)

Score is `1` if the result matches the filter, `0` if not. Non-matching documents stay in the result set but rank lower than matching ones. Supported filter operators: `Equal`, `NotEqual`, `GreaterThan`, `GreaterThanEqual`, `LessThan`, `LessThanEqual`, `And`, `Or`, `Not`. (`Like`, `IsNull`, geo operators, and ref-path filters are not supported in boost conditions.)

:::code-group{sync="languages"}
```python title="Python" {5-8}
# Promote articles in the "research" category without filtering others out.
response = articles.query.near_text(
    query="transformer architectures",
    limit=5,
    boost=Boost.filter(
        Filter.by_property("category").equal("research"),
        weight=0.5,
    ),
    return_properties=["title", "category"],
)

for o in response.objects:
    print(o.properties["category"], "-", o.properties["title"])
```
:::

### Property-value condition

Continuous score proportional to a numeric property's value (`likes`, `downloads`, `popularity`, ...). The raw value is optionally modified, then min-max normalized to `[0, 1]` across the result set.

The `name` argument is required, only numeric properties (`int`, `number`) are supported.

| Modifier         | Effect                   | When to use                                                         |
| ---------------- | ------------------------ | ------------------------------------------------------------------- |
| `NONE` (default) | `score = value`          | Values in a narrow range.                                           |
| `LOG1P`          | `score = log(1 + value)` | Long-tail dampening (e.g. download counts from `5` to `5_000_000`). |
| `SQRT`           | `score = sqrt(value)`    | Milder long-tail dampening.                                         |

:::code-group{sync="languages"}
```python title="Python" {6-10}
# Bias toward articles with more `likes`. LOG1P dampens the long tail so a
# single 5-million-likes outlier doesn't dominate.
response = articles.query.near_text(
    query="transformer architectures",
    limit=5,
    boost=Boost.numeric_property(
        "likes",
        modifier=Boost.Modifier.LOG1P,
        weight=0.7,
    ),
    return_properties=["title", "likes"],
)

for o in response.objects:
    print(o.properties["likes"], "-", o.properties["title"])
```
:::

For "closer to a specific value is better" instead of "higher is better", use [numeric decay](#numeric-decay) below.

### Time decay

Continuous `[0, 1]` score that **decays with distance from an origin time**. The canonical use case is "boost more recent documents".

| Parameter  | Required | Notes                                                                                                           |
| ---------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| `property` | Yes      | Name of a `date` property.                                                                                      |
| `origin`   | No       | `"now"` (default), a `datetime`, or an ISO string.                                                              |
| `scale`    | Yes      | Distance at which the score equals `decay`. Accepts a `timedelta` or duration string (`"7d"`, `"6h"`, `"30m"`). |
| `offset`   | No       | Distance below which the score is exactly `1`. Default `0`.                                                     |
| `curve`    | No       | `EXPONENTIAL` (default), `GAUSSIAN`, or `LINEAR`. See [Curves](#curves) below.                                  |
| `decay`    | No       | Score at `scale` distance. Default `0.5`. Range `(0, 1]`.                                                       |

:::code-group{sync="languages"}
```python title="Python" {6-13}
# Score decays exponentially over time. "30d scale" + decay=0.5 means an
# article that's 30 days old gets half the score of one published "now".
response = articles.query.near_text(
    query="transformer architectures",
    limit=5,
    boost=Boost.time_decay(
        "published",
        origin="now",
        scale=timedelta(days=30),
        curve=Boost.Curve.EXPONENTIAL,
        decay=0.5,
        weight=0.6,
    ),
    return_properties=["title", "published"],
)
```
:::

### Numeric decay

Like time decay but for numeric (`int`, `number`) properties. Use this when "closer to X is better": prices near a target, distances near a coordinate, ages near a band. Same `scale` / `offset` / `decay` / `curve` semantics as time decay, with all values expressed as numbers.

`scale` must be `> 0` and `decay` (if set) must be in `(0, 1]`, same as time decay.

:::code-group{sync="languages"}
```python title="Python" {7-14}
# Score peaks at a target price and falls off symmetrically. Gauss gives a
# bell-shaped falloff: items within `offset` of $49.99 score 1.0, items at
# $59.99 (one scale away) score `decay`.
response = articles.query.near_text(
    query="transformer architectures",
    limit=5,
    boost=Boost.numeric_decay(
        "price",
        origin=49.99,
        scale=10.0,
        curve=Boost.Curve.GAUSSIAN,
        decay=0.5,
        weight=0.5,
    ),
    return_properties=["title", "price"],
)
```
:::

## Curves

The three decay curves shape how score falls off with distance. At `distance == 0` the score is always `1`. At `distance == scale` the score is always exactly `decay`. Past `scale` they behave differently:

| Curve                   | Shape                                                                 | When to use                                                                 |
| ----------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `EXPONENTIAL` (default) | Heavy tail: score halves geometrically every `scale` past the origin. | "Recency matters, but don't aggressively flatten older items to zero."      |
| `GAUSSIAN`              | Bell curve: sharp falloff past `scale`.                               | "Items close to the origin are great, items far away are nearly worthless." |
| `LINEAR`                | Straight line: score reaches zero at a finite distance past `scale`.  | "Predictable falloff with a clear cutoff."                                  |

Only these three values are accepted. Anything else is rejected at request time.

## Blending and weights

A boost must carry **at least one** and **at most 20** conditions. Use `Boost.blend(...)` to combine multiple conditions into one rescore. Each condition can carry its own `weight` (default `1.0`). The outer `weight` (default `0.5`) controls how much the combined boost affects the final score.

```
final_score = (1 − weight) · primary_norm + weight · boost_norm
```

- **`weight`**: the outer blending weight, in `[0, 1]`. Defaults to `0.5`.
- **`weight: 0`** is a no-op: the boost short-circuits and primary results are returned unchanged.
- **Per-condition `weight`**: a `float` defaulting to `1.0`. Use it to balance multiple boosts ("recency twice as important as popularity").
- **Negative per-condition `weight`**: _demotes_ matching documents. They stay in the result set but rank lower than non-matching ones.

:::code-group{sync="languages"}
```python title="Python" {8-15}
# Combine two soft signals: recency (weight 2) + popularity (weight 1).
# The outer weight=0.4 controls how much the blended rank affects the
# final score; the inner weights are *per-condition* and balance each
# other.
response = articles.query.near_text(
    query="transformer architectures",
    limit=5,
    boost=Boost.blend(
        [
            Boost.time_decay("published", origin="now", scale=timedelta(days=30), weight=2.0),
            Boost.numeric_property("likes", modifier=Boost.Modifier.LOG1P, weight=1.0),
        ],
        weight=0.4,
        depth=200,  # rescore the top 200 vector matches
    ),
    return_properties=["title", "likes", "published"],
)
```
:::

### Negative weights demote

A condition with `weight: -1.0` (or `-2.0`, etc.) reverses the effect: documents that match the condition rank below non-matching ones instead of above them. They are not removed. This is useful for deprioritizing (for example, surfacing drafts last without filtering them out).

:::code-group{sync="languages"}
```python title="Python" {7-10}
# A negative per-condition weight pushes matching documents DOWN — they
# stay in the result set but lose ground against everything else. Use
# this to deprioritize drafts without filtering them out entirely.
response = articles.query.bm25(
    query="transformer",
    limit=5,
    boost=Boost.blend(
        Boost.filter(Filter.by_property("draft").equal(True), weight=-2.0),
        weight=0.5,
    ),
    return_properties=["title", "draft"],
)

# The draft article is still in results, just no longer first.
all_titles = [o.properties["title"] for o in response.objects]
assert any("Draft" in t for t in all_titles)
assert response.objects[0].properties["draft"] is False
```
:::

## Depth and pagination

`depth` controls the **candidate pool**. The primary search fetches `depth` results before the boost rescorer runs. After rescoring, the user's `offset` and `limit` are applied.

| Property          | Value                                                                                                  |
| ----------------- | ------------------------------------------------------------------------------------------------------ |
| Default           | `100`                                                                                                  |
| Operator override | [`QUERY_BOOST_DEFAULT_DEPTH`](../database-configuration/overview.md#QUERY_BOOST_DEFAULT_DEPTH) env var |
| Hard cap          | `QUERY_MAXIMUM_RESULTS` (cluster-wide limit)                                                           |
| Lower bound       | At least `offset + limit`, so boost always sees enough to fill the page                                |
| Accepted range    | `≥ 0`, where `0` means "use the default"                                                               |

:::callout{intent="tip" title="Raise `depth` to reorder beyond the top page"}
Boost can only reorder what the primary search already retrieved. If your boost should reorder past the default top-100 (for example, to pull a popular older article back to page 1), pass a higher `depth` on the query.

Higher `depth` increases the primary search cost (BM25 / vector index work) and per-candidate scoring cost. Set it as tight as your use case allows.
:::

## Further resources

- [Rerank](rerank.md): second-stage reranking with an external model.
- [Hybrid search](hybrid.md): the BM25 / vector `alpha` blend.
- [Filters](filters.md): hard filters (remove non-matching docs).
- [BM25](bm25.md): keyword search.

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