Declares an explicit foreign-key path between the field’s parent table and the field’s target table. The generator threads the path through jOOQ’s catalog and emits a JOIN whenever the field is selected.
The directive is repeatable on output field definitions: repeated applications compose one table chain in the order they are written, and they compose with @routine so a database function can sit anywhere in the chain.
SDL signature
directive @reference(
path: [ReferenceElement!]!
) repeatable on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
input ReferenceElement {
table: String,
key: String,
condition: ExternalCodeReference
}
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
|
|
(required) |
Ordered list of hops from the parent table to the target table. Each element pins one hop by table name, foreign-key constraint name, or an external join condition. |
ReferenceElement fields:
| Name | Type | Default | Description |
|---|---|---|---|
|
|
inferred from the field’s target type |
The jOOQ table identifier for this hop’s destination. Overrides the type-level |
|
|
inferred when unique |
The foreign-key constraint name (matching jOOQ’s |
|
|
none |
An external Java method that returns a |
Canonical example
The tutorial’s Customer.address is a single-hop reference along a named foreign key:
type Customer @table(name: "customer") {
address: Address @reference(path: [{key: "customer_address_id_fkey"}])
}
customer_address_id_fkey is the FK constraint declared in init.sql as address_id int NOT NULL REFERENCES address(address_id). A query selecting customers { address { district } } emits:
SELECT customer.first_name AS "firstName",
address.district AS "district"
FROM customer
LEFT JOIN address ON customer.address_id = address.address_id
The JOIN is added only when the selection set traverses through address; selecting only firstName skips it.
@reference chains across hops. From the same Customer type:
storeAddress: Address @reference(path: [
{key: "customer_store_id_fkey"},
{key: "store_address_id_fkey"}
])
The path is customer.store_id → store.store_id → store.address_id → address.address_id: two FKs and three tables touched, but still a single SQL statement with the same projection-narrowing.
When exactly one foreign key exists between two tables, @reference is unnecessary and the generator picks the path automatically:
type Store @table(name: "store") {
customers: [Customer!]! @defaultOrder(primaryKey: true)
}
There is exactly one FK between customer and store (customer.store_id), so the join is implicit. Multiple-FK cases must declare the path explicitly: the generator does not guess.
Composing chains with repeated applications
On an output field definition, @reference may be applied more than once. The applications' path elements concatenate in the order they are written, over one running source, so the composed chain is exactly the longer path you could have written in a single application:
type Film @table(name: "film") {
castActors: [Actor!]
@reference(path: [{key: "film_actor_film_id_fkey"}])
@reference(path: [{key: "film_actor_actor_id_fkey"}])
@defaultOrder(primaryKey: true)
}
On its own this form is a stylistic choice. It exists for composition with @routine: the directives on a field, read left to right, describe the path your data travels, starting at the enclosing type’s table and ending at the field’s type, and a routine node can sit before, between, or after the @reference hops. See the @routine page for the chain rule and the routine-bearing shapes.
The order is load-bearing. Directive application order is semantically significant here, which the GraphQL spec sanctions and graphql-java preserves. Tooling that rewrites SDL and reorders repeatable directive applications (schema printers, federation composition pipelines) is out of contract: the terminus and keying checks catch most reorders, but two orderings can both terminate correctly and mean different join graphs, so an order-preserving round-trip is a stated requirement on any SDL tooling in your pipeline.
Constraints
-
In a chain of several applications, every application must carry at least one path element; an element-less application is rejected with
must carry at least one path element(element-less FK inference resolves between the field’s endpoints, and an application inside a chain has no endpoints of its own). -
Repetition is legal only on output field definitions. Repeated
@referenceon an argument or an input field is rejected, pointing at the field-level composition surface. -
When more than one foreign key exists between the source and destination tables for a given hop,
keyis required. The build fails with an ambiguous-reference error otherwise. -
When
keyis provided alone,tableis inferred from the FK target. Whentableis provided alone,keyis inferred when unique. Setting both is allowed but redundant. -
A
condition-only hop (notable, nokey) is treated as a joinONcondition; the generator joins the field’s target table and applies the condition. Use this when the join is non-FK or computed.
See also
-
Tutorial page 4: Joining tables introduces single-hop and multi-hop references.
-
How-to: Join with references for recipe-shaped variants.
-
@discriminate/@discriminatorfor the union/interface variant. See How-to: Polymorphic types for the supported shapes. -
@tableestablishes the table binding both endpoints rely on. -
@routinecontributes a database-function node to the same chain; the chain rule and the routine-bearing shapes live there.