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

# 0104 — Cross-module relations are @Link declarations, read through query.graph

> Accepted, amended by 0268 · 2026-10-19

> [!NOTE]
>
> Amended by [0268](/adr/0268-register-answers-thin-items-r1-to-r18): R10: `query.graph` returns at
> most 20 rows per relation by default and at most 100. Depth is at most 3, and fan-out is at most
> 1,000 rows per call.

## Context

Modules own their data ([0014](/adr/0014-data-ownership-and-orm)), so one module's model cannot hold a foreign key into
another's tables. Products still need "orders with their customer and product". Medusa solves this with `defineLink` (a pivot
owned by a link module) and `query.graph`, which fans out to each owning module and stitches the result; its index module is
a Postgres read model for the hot cases.

## Decision

1. **A relation across modules is a `@Link`** between two models, declared in a module that depends on both. The pivot table
   belongs to that link, not to either model.
2. **`query.graph({ entity, fields, filters })` reads across links**, calling each owning module's query and joining in
   memory, with batching through the DataLoader of [0097](/adr/0097-model-and-database-packages).
3. **Tenancy and iam apply per owning module**: each fan-out call goes through that module's `@Query` pipeline.
4. **Hot read paths use an index read model** fed by events, which is the job of `@stackra/search`
   ([0089](/adr/0089-search-fresh-with-pro-engine-drivers)), not a second index package.
5. **Within one module, ordinary relations** of 0097 apply; `@Link` is only for crossing a module boundary.

## Consequences

* Module boundaries hold without giving up joined reads.
* `query.graph` is slower than a SQL join; the read model is the answer when that matters.
* Links appear in the gen manifest, so the registry can show the relation graph.

## Enforcement

Planned: the gen validation pass fails a model relation that targets another module's model without a `@Link`. Nothing
checks this today.

## Supersedes and sources

Supersedes: none. Sources: owner decision in chat, 2026-10-19 (Round 11 W4). Informational: Medusa `defineLink`,
`query.graph` and index module.


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