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

path

[ReferenceElement!]!

(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

table

String

inferred from the field’s target type

The jOOQ table identifier for this hop’s destination. Overrides the type-level @table if both are present.

key

String

inferred when unique

The foreign-key constraint name (matching jOOQ’s Keys class). Required when more than one foreign key exists between the source and destination tables for a hop.

condition

ExternalCodeReference

none

An external Java method that returns a org.jooq.Condition to add to the join. Acts as an ON condition when neither table nor key is set, otherwise as an extra WHERE constraint on the joined hop.

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 @reference on 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, key is required. The build fails with an ambiguous-reference error otherwise.

  • When key is provided alone, table is inferred from the FK target. When table is provided alone, key is inferred when unique. Setting both is allowed but redundant.

  • A condition-only hop (no table, no key) is treated as a join ON condition; the generator joins the field’s target table and applies the condition. Use this when the join is non-FK or computed.

See also