Skip to main content
Joins enable you to define relationships between documents in different indices, similar to foreign keys in relational databases. Instead of duplicating data across documents, you store only IDs and use joins to hydrate full documents at search time or filter by related data.

Why use joins?

Reduce data duplication

Store company details once; reference from many deals instead of embedding full objects in each deal document.

Faster indexing

Smaller documents mean faster indexing operations and reduced storage requirements.

Keep data normalized

Update company information once and it’s automatically reflected in all deal queries.

Flexible queries

Hydrate related data when needed and filter by related document properties without denormalization.

How relationships work

A relationship connects a source index to a target index using foreign key fields. For example, a document in the deals index references a document in the companies index through a company_id field:
Deals index
{
  "id": "deal_1",
  "title": "Contract X",
  "company_id": "company_1"
}
Companies index
{
  "id": "company_1",
  "name": "Acme Inc"
}
Once the relationship is configured, Meilisearch automatically hydrates the related document in search results, replacing company_id with the full company object.

Define a relationship

Configure foreign keys and filterable attributes in the source index settings:
curl \
  -X PATCH 'MEILISEARCH_URL/indexes/deals/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "foreignKeys": [
      {
        "fieldName": "company_id",
        "foreignIndexUid": "companies"
      }
    ],
    "filterableAttributes": [
      {
        "attributePatterns": ["company_id"],
        "features": {
          "facetSearch": false,
          "filter": {
            "equality": true,
            "comparison": false
          }
        }
      }
    ]
  }'

Configuration parameters

ParameterTypeDescriptionExample
fieldNamestringField in the source index containing the ID(s)"company_id" or "actor_ids"
foreignIndexUidstringUID of the target index"companies" or "actors"
attributePatternsarrayField patterns to make filterable["company_id"]
featuresobjectFilter capabilities (equality, comparison, facetSearch){"filter": {"equality": true}}

Relationship types

One-to-one

Each source document has exactly one related target document. Example: Users → Profiles (each user has one profile)
curl \
  -X PATCH 'MEILISEARCH_URL/indexes/users/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "foreignKeys": [
      {
        "fieldName": "profile_id",
        "foreignIndexUid": "profiles"
      }
    ],
    "filterableAttributes": [
      {
        "attributePatterns": ["profile_id"],
        "features": {
          "facetSearch": false,
          "filter": {
            "equality": true,
            "comparison": false
          }
        }
      }
    ]
  }'

One-to-many

Each source document has multiple related target documents, stored as an array of IDs. Example: Companies → Employees (one company has many employees)
curl \
  -X PATCH 'MEILISEARCH_URL/indexes/companies/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "foreignKeys": [
      {
        "fieldName": "employee_ids",
        "foreignIndexUid": "employees"
      }
    ],
    "filterableAttributes": [
      {
        "attributePatterns": ["employee_ids"],
        "features": {
          "facetSearch": false,
          "filter": {
            "equality": true,
            "comparison": false
          }
        }
      }
    ]
  }'

Many-to-many

Multiple source documents reference multiple target documents, typically using array fields. Example: Films → Actors (one film has many actors, many films feature the same actor)
curl \
  -X PATCH 'MEILISEARCH_URL/indexes/films/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "foreignKeys": [
      {
        "fieldName": "actor_ids",
        "foreignIndexUid": "actors"
      }
    ],
    "filterableAttributes": [
      {
        "attributePatterns": ["actor_ids"],
        "features": {
          "facetSearch": false,
          "filter": {
            "equality": true,
            "comparison": false
          }
        }
      }
    ]
  }'

Self-references

You can create relationships where a document references other documents in the same index. This is useful for modeling hierarchical or interconnected data. Example: Products frequently bought together
curl \
  -X PATCH 'MEILISEARCH_URL/indexes/products/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "foreignKeys": [
      {
        "fieldName": "frequently_bought_with",
        "foreignIndexUid": "products"
      }
    ],
    "filterableAttributes": [
      {
        "attributePatterns": ["frequently_bought_with"],
        "features": {
          "facetSearch": false,
          "filter": {
            "equality": true,
            "comparison": false
          }
        }
      }
    ]
  }'

Multiple relationships

Configure multiple foreign keys in a single settings update:
curl \
  -X PATCH 'MEILISEARCH_URL/indexes/deals/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "foreignKeys": [
      {
        "fieldName": "company_id",
        "foreignIndexUid": "companies"
      },
      {
        "fieldName": "owner_user_id",
        "foreignIndexUid": "users"
      }
    ],
    "filterableAttributes": [
      {
        "attributePatterns": ["company_id"],
        "features": {
          "facetSearch": false,
          "filter": {
            "equality": true,
            "comparison": false
          }
        }
      },
      {
        "attributePatterns": ["owner_user_id"],
        "features": {
          "facetSearch": false,
          "filter": {
            "equality": true,
            "comparison": false
          }
        }
      }
    ]
  }'

Update relationships

To replace an existing relationship, provide the updated configuration:
curl \
  -X PATCH 'MEILISEARCH_URL/indexes/deals/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "foreignKeys": [
      {
        "fieldName": "assigned_user_id",
        "foreignIndexUid": "users"
      }
    ],
    "filterableAttributes": [
      {
        "attributePatterns": ["assigned_user_id"],
        "features": {
          "facetSearch": false,
          "filter": {
            "equality": true,
            "comparison": false
          }
        }
      }
    ]
  }'
To remove all relationships:
curl \
  -X PATCH 'MEILISEARCH_URL/indexes/deals/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "foreignKeys": []
  }'

Depth limitation

Joins support hydration and filtering at one level only. You cannot create nested chains like deals → companies → industry_details. Each hydration or filter operates on a direct relationship between two indices.

Data integrity

Meilisearch does not enforce referential integrity. You can create foreign key references to non-existent documents, and deleting a target document does not affect documents that reference it. When a referenced document is deleted:
  • Hydration returns the document UID instead of the full object
  • Filtering ignores the reference in filter comparisons

Cleanup strategy

You can handle orphaned references in two ways: delete the source documents entirely, or remove the orphaned IDs from the foreign key fields.

Delete source documents

Use the delete by filter API to remove source documents that reference non-existent targets:
curl \
  -X POST 'MEILISEARCH_URL/indexes/deals/delete-by-filter' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "filter": "company_id = deleted_company_1 OR company_id = deleted_company_2"
  }'

Remove orphaned IDs with functions

Use edit documents by function to remove specific orphaned IDs from array foreign key fields without deleting the source documents. This is useful for many-to-many relationships where only some referenced IDs are orphaned. For example, remove a deleted actor from the actors array in all film documents:
curl \
  -X POST 'MEILISEARCH_URL/indexes/films/documents/edit' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "function": "doc.actors = doc.actors.filter(|id| id != context.deleted_id)",
    "context": {
      "deleted_id": "actor_42"
    },
    "filter": "actors = actor_42"
  }'

Next steps

Foreign filters

Filter documents by properties of related data across indices

Precise array filtering

Use AND logic to filter array relationships with joins

RBAC with joins

Implement role-based access control using joins and tenant tokens

Indexing best practices

Learn best practices including how joins reduce index size