> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mixpeek.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Document Enrich

> Join documents across collections for cross-reference enrichment

![Document Enrich stage showing collection joins and cross-reference lookups](https://mintcdn.com/mixpeek/TwtTrae3Fi3EFJ72/assets/retrievers/document-enrich.svg?fit=max\&auto=format\&n=TwtTrae3Fi3EFJ72\&q=85\&s=16b0bb36191f390695226d70e4622221)

The Document Enrich stage performs collection joins by looking up related documents from other Mixpeek collections. This enables cross-reference enrichment without external database calls.

> [!NOTE]
>
> **Stage Category**: ENRICH (Enriches documents)
>
> **Transformation**: N documents → N documents (with joined data added)

## When to Use

| Use Case | Description |
| - | - |
| **Cross-collection joins** | Link products to reviews, users to profiles |
| **Reference resolution** | Expand foreign keys to full documents |
| **Denormalization** | Flatten related data for display |
| **Multi-index search** | Combine results from different collections |

## When NOT to Use

| Scenario | Recommended Alternative |
| - | - |
| External database joins | `sql_lookup` |
| Single collection search | No enrichment needed |
| Real-time external data | `api_call` |

## Parameters

| Parameter | Type | Default | Description |
| - | - | - | - |
| `target_collection_id` | string | `{{COLLECTION_ID}}` | Collection to join from. Replace the placeholder with a real collection ID. With an inline retriever it also scopes the collections that retriever searches |
| `source_field` | string | `source_object_id` | Direct join: field in the current document whose value is the join key |
| `target_field` | string | `document_id` | Direct join: field in the target collection matched against the join key. The stage creates a keyword index on it when one is missing. Ignored for retriever joins |
| `retriever_id` | string | `null` | Retriever join: ID of an existing retriever to run for each document. Cannot be combined with `retriever_config` |
| `retriever_config` | object | `null` | Retriever join: inline retriever definition with a non-empty `stages` array. Cannot be combined with `retriever_id` |
| `retriever_inputs` | object | `null` | Retriever join: maps retriever input names to values, using `{{DOC.field_name}}` to read fields from the current document |
| `fields_to_merge` | array | `null` | Fields to copy from the matched document. `null` merges the entire matched payload |
| `output_field` | string | `null` | Field on the current document that receives the matched data. `null` merges the data into the document root |
| `strategy` | string | `enrich` | How the match is merged: `enrich`, `replace` or `append` (see below) |
| `when` | object | `null` | Condition that selects which documents are enriched. Documents that do not match pass through unchanged |
| `allow_missing` | boolean | `true` | `true` passes documents with no match through unchanged. `false` removes them from the results |
| `cache_behavior` | string | `auto` | Internal stage caching: `auto`, `disabled` or `aggressive` |
| `cache_ttl_seconds` | integer | `null` | Cache entry lifetime in seconds. `null` leaves eviction to the cache |

### Join modes

The stage runs in one of two modes, chosen by whether `retriever_id` or `retriever_config` is set.

**Direct join** (the default). The stage reads the value at `source_field` from each document and looks for a document in `target_collection_id` whose `target_field` has the same value. A document with no value at `source_field` is not looked up and passes through unchanged when `allow_missing` is `true`.

**Retriever join.** The stage evaluates `retriever_inputs` for each document and runs the retriever named by `retriever_id`, or the inline `retriever_config`, with those inputs. Documents that produce identical inputs share a single retriever run. In an inline `retriever_config`, string stage parameters that contain `{{DOC.field_name}}` are filled in from the current document. `source_field` and `target_field` are not used in this mode.

In both modes each document receives at most one match: the first document found, or the top result of the retriever. `fields_to_merge`, `output_field`, `strategy`, `when` and `allow_missing` apply the same way in both.

### Merge strategies

* `enrich` places the match under `output_field`. Without `output_field`, it merges the match into the document root and keeps existing fields unless the match has a field of the same name.
* `replace` merges the match into the document root and overwrites fields of the same name. `output_field` is not used.
* `append` adds the match to an array at `output_field`, or at `enrichments` when `output_field` is unset.

## Configuration Examples

```json Basic Collection Join theme={null}
{
  "stage_name": "document_enrich",
  "stage_type": "enrich",
  "config": {
    "stage_id": "document_enrich",
    "parameters": {
      "target_collection_id": "user_profiles",
      "target_field": "user_id",
      "source_field": "metadata.author_id",
      "output_field": "author"
    }
  }
}
```

```json Select Specific Fields theme={null}
{
  "stage_name": "document_enrich",
  "stage_type": "enrich",
  "config": {
    "stage_id": "document_enrich",
    "parameters": {
      "target_collection_id": "products",
      "target_field": "product_id",
      "source_field": "metadata.product_id",
      "output_field": "product_details",
      "fields_to_merge": ["name", "price", "category", "image_url"]
    }
  }
}
```

```json Conditional Enrichment theme={null}
{
  "stage_name": "document_enrich",
  "stage_type": "enrich",
  "config": {
    "stage_id": "document_enrich",
    "parameters": {
      "target_collection_id": "reviews",
      "target_field": "product_id",
      "source_field": "document_id",
      "output_field": "reviews"
    }
  }
}
```

```json Retriever Join theme={null}
{
  "stage_name": "document_enrich",
  "stage_type": "enrich",
  "config": {
    "stage_id": "document_enrich",
    "parameters": {
      "retriever_id": "ret_find_similar_products",
      "retriever_inputs": {
        "query": "{{DOC.description}}"
      },
      "output_field": "similar_product",
      "fields_to_merge": ["name", "price"]
    }
  }
}
```

```json Nested Field Lookup theme={null}
{
  "stage_name": "document_enrich",
  "stage_type": "enrich",
  "config": {
    "stage_id": "document_enrich",
    "parameters": {
      "target_collection_id": "categories",
      "target_field": "category_code",
      "source_field": "metadata.taxonomy.primary_category",
      "output_field": "category_info"
    }
  }
}
```

## Output Schema

### Single Document Join

```json theme={null}
{
  "document_id": "doc_123",
  "content": "Product review content...",
  "metadata": {
    "product_id": "prod_456"
  },
  "product_details": {
    "name": "Wireless Headphones",
    "price": 199.99,
    "category": "Electronics",
    "image_url": "https://..."
  }
}
```

### Append Strategy

With `strategy: "append"`, `output_field` holds an array and the match is added to it.

```json theme={null}
{
  "document_id": "prod_456",
  "content": "Product description...",
  "reviews": [
    {
      "document_id": "rev_1",
      "rating": 5,
      "text": "Great product!"
    }
  ]
}
```

### No Match Found

With `allow_missing: true` (the default) the document passes through unchanged and no `output_field` is added. With `allow_missing: false` the document is removed from the results.

```json theme={null}
{
  "document_id": "doc_123",
  "content": "..."
}
```

When none of the checked documents match, the response also carries a warning that names the join. The stage metadata reports `documents_checked`, `documents_enriched` and `documents_skipped`.

## Performance

| Metric | Value |
| - | - |
| **Latency** | 5-20ms per document |
| **Batch processing** | Automatic batching |
| **Index usage** | Creates a keyword index on `target_field` if missing |
| **Lookup strategy** | One exact-match lookup per key for up to 50 keys, then batched lookups of 100 keys |

> [!TIP]
>
> Use `fields_to_merge` to reduce payload size. Documents that share the same retriever inputs run the retriever once, so keep `retriever_inputs` to the fields that matter.

## Common Pipeline Patterns

### Search + Author Enrichment

```json theme={null}
[
  {
    "stage_name": "semantic_search",
    "stage_type": "filter",
    "config": {
      "stage_id": "feature_search",
      "parameters": {
        "searches": [
          { "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1", "query": { "input_mode": "text", "value": "{{INPUT.query}}" }, "top_k": 20 }
        ],
        "final_top_k": 20
      }
    }
  },
  {
    "stage_name": "document_enrich",
    "stage_type": "enrich",
    "config": {
      "stage_id": "document_enrich",
      "parameters": {
        "target_collection_id": "users",
        "target_field": "user_id",
        "source_field": "metadata.author_id",
        "output_field": "author",
        "fields_to_merge": ["name", "avatar", "bio"]
      }
    }
  }
]
```

### Product Search with Reviews

```json theme={null}
[
  {
    "stage_name": "hybrid_search",
    "stage_type": "filter",
    "config": {
      "stage_id": "feature_search",
      "parameters": {
        "searches": [
          { "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1", "query": { "input_mode": "text", "value": "{{INPUT.query}}" }, "top_k": 10 }
        ],
        "final_top_k": 10
      }
    }
  },
  {
    "stage_name": "document_enrich",
    "stage_type": "enrich",
    "config": {
      "stage_id": "document_enrich",
      "parameters": {
        "target_collection_id": "reviews",
        "target_field": "product_id",
        "source_field": "document_id",
        "output_field": "recent_reviews",
        "fields_to_merge": ["rating", "text", "author_name", "created_at"]
      }
    }
  }
]
```

### Hierarchical Category Enrichment

```json theme={null}
[
  {
    "stage_name": "semantic_search",
    "stage_type": "filter",
    "config": {
      "stage_id": "feature_search",
      "parameters": {
        "searches": [
          { "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1", "query": { "input_mode": "text", "value": "{{INPUT.query}}" }, "top_k": 50 }
        ],
        "final_top_k": 50
      }
    }
  },
  {
    "stage_name": "document_enrich",
    "stage_type": "enrich",
    "config": {
      "stage_id": "document_enrich",
      "parameters": {
        "target_collection_id": "categories",
        "target_field": "category_id",
        "source_field": "metadata.category_id",
        "output_field": "category"
      }
    }
  },
  {
    "stage_name": "document_enrich",
    "stage_type": "enrich",
    "config": {
      "stage_id": "document_enrich",
      "parameters": {
        "target_collection_id": "categories",
        "target_field": "category_id",
        "source_field": "category.parent_id",
        "output_field": "parent_category"
      }
    }
  }
]
```

## Error Handling

| Condition | Behavior |
| - | - |
| No matching document, or target collection has no match | Document passes through unchanged (`allow_missing: true`) or is removed (`false`). A warning is added when nothing matched |
| Document has no value at `source_field` | Not looked up. Passes through unchanged when `allow_missing` is `true` |
| Retriever execution fails | Affected documents follow `allow_missing`, and the warning names the error |
| `retriever_id` not found in the namespace | Stage fails with a not-found error |
| Both `retriever_id` and `retriever_config`, or `retriever_config` without stages | Configuration is rejected as invalid |

## vs Other Enrichment Stages

| Feature | document\_enrich | sql\_lookup | api\_call |
| - | - | - | - |
| Data source | Mixpeek collections | SQL database | External API |
| Latency | 5-20ms | 10-100ms | 50-500ms |
| Best for | Cross-collection | External relational | REST APIs |
| Setup | None | Connection config | Endpoint config |

## Related

* [SQL Lookup](https://docs.mixpeek.com/docs/retrieval/stages/sql-lookup.md) - External database joins
* [API Call](https://docs.mixpeek.com/docs/retrieval/stages/api-call.md) - REST API enrichment
* [Taxonomy Enrich](https://docs.mixpeek.com/docs/retrieval/stages/taxonomy-enrich.md) - Classification enrichment


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.