Constructs a nested object type from columns on the parent’s table. The directive’s selection: argument lists the child type’s fields and the parent-table columns they read from, using a syntax close to a GraphQL selection set. The constructed type does not need its own @table, and its fields do not need individual @field directives, the directive supplies the mapping inline at the call site.
@experimental_constructType is a niche tool for two specific patterns: re-using a single small object type across multiple parents that map its fields to differently-named columns (so a per-parent @field axis would be more verbose), and shaping the keys for non-resolvable Federation entities where the GraphQL type can’t carry a @table because its data lives in another service. The "experimental" in the name signals limited support: the directive is shipped, but its guarantees are narrower than the rest of the directive surface, expect more rejections than @field would produce on the same field.
SDL signature
directive @experimental_constructType(selection: String) on FIELD_DEFINITION
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
|
|
(required in practice) |
A pseudo-selection string mapping the child type’s fields to columns on the parent’s table. |
Canonical examples
A re-used CustomerInfo type whose fields are projected from customer columns at the call site:
type Customer @table(name: "customer") {
info: CustomerInfo @experimental_constructType(selection: "label: FIRST_NAME, detail: EMAIL")
}
type CustomerInfo {
label: String
detail: String
}
A multiline selection (the directive accepts whitespace and triple-quoted strings unchanged):
type Customer @table(name: "customer") {
info: CustomerInfo @experimental_constructType(selection: """
label: FIRST_NAME,
detail: EMAIL
""")
}
Mapping a same-named column without an explicit colon, useful when the GraphQL field name already matches the column:
type Staff @table(name: "staff") {
summary: StaffSummary @experimental_constructType(selection: "first_name, last_name")
}
type StaffSummary {
first_name: String
last_name: String
}
Constraints
-
The containing type must have access to a table, either by carrying its own
@tabledirective or inheriting one through the call chain. Without a parent table, the column references inselection:have no surrounding source. -
The constructed type must not carry
@table. The whole point of the directive is to project a non-table-bound type from the parent’s columns, a@table-bound child has its own SQL source and would conflict with the inline projection. -
@experimental_constructTypedoes not compose with@fieldor@externalFieldon the same field. The three are mutually exclusive: pick a column-mapping axis (@field), a custom-method axis (@externalField), or an inline-projection axis (@experimental_constructType). -
Nested object types inside the constructed type are not supported. The
selection:syntax is column-list-only, no{ … }blocks, no further@experimental_constructTypeon inner fields. Compose@experimental_constructTypeonly at the leaf where columns project to scalars / enums. -
Every field name in
selection:must exist on the constructed type. Unknown field names are rejected at parse time. -
Fields on the constructed type that are not listed in
selection:resolve tonullat runtime, the directive does not require an exhaustive selection. (This is convenient when the constructed type is large and only a few fields are needed at this call site.) -
selection:does not accept GraphQL arguments, variables, or nested selection sets. The syntax is intentionally narrower than GraphQL’s, the goal is column projection, not query language. -
The directive is
experimental_for a reason: the constraints above are tighter than@field's, and the surface may shift in future Graphitron versions. Prefer@field(per-field on a@table-bound child) when both axes are available.
See also
-
@fieldis the per-field column-mapping axis, choose it when the constructed type has its own@tableor when you want individual mapping per field. -
@tableis the catalog-binding axis.@experimental_constructTypeexists precisely because the constructed type can’t carry@table. -
@externalFieldis the alternative when the projection needs custom Java logic rather than a column list. -
How-to: Federation key fields covers the non-resolvable-entity pattern that motivates
@experimental_constructType.