CONCEPT Cited by 1 source
Component-scoped field access¶
Definition¶
Component-scoped field access is a GraphQL schema discipline that restricts a field to a named allowlist of UI components (or equivalently, named queries/operations). At persist time, any query that references the field but is not tagged with a permitted component name is rejected.
The purpose is not runtime security — the field may still be returned correctly to any caller that managed to persist — but deliberately-reduced blast radius: the operator knows, by construction, which UI surfaces use the field, so a subsequent breaking change has a known, small migration surface.
At Zalando, it is the second stage in a three-stage
directive-based field lifecycle: fields go from @draft
(unpersistable) to @allowedFor(components: [...]) (component-
scoped) to no annotation (stable)
(Source: sources/2022-02-16-zalando-graphql-persisted-queries-and-schema-stability).
Mechanism (Zalando)¶
Two paired directives:
directive @component(name: String!) on QUERY
directive @allowedFor(componentNames: [String!]!) on FIELD_DEFINITION
Schema side — the field declares its whitelist:
Query side — the query declares which component is making it:
At persist time, the validator resolves every field in the
query. If the field has @allowedFor(componentNames: [...])
and the query's @component(name: ...) is not in that list,
persistence fails. Any other query that attempts to use
fancyProp would be rejected
(Source: sources/2022-02-16-zalando-graphql-persisted-queries-and-schema-stability).
The post notes that instead of a component name, the
query's operation name (here productCard) can serve as
the scope identifier — the directive pair is agnostic to
the choice of identifier
(Source: sources/2022-02-16-zalando-graphql-persisted-queries-and-schema-stability).
Why it exists¶
The post motivates it with a concrete failure mode: once a
field is promoted out of @draft, removing the draft
annotation lets any UI component persist a query that
uses it. The platform team cannot tell (or control) which
teams pick up the new field; an experimental field can
accidentally lock in because "some other parts of the UI
may start persisting those experimental fields, and we
might not notice it until we inspect the queries. We
certainly cannot break the schema once it is in
production." @allowedFor prevents the accidental lock-in
by making the second stage of the lifecycle deliberately
narrow
(Source: sources/2022-02-16-zalando-graphql-persisted-queries-and-schema-stability).
Not runtime authorization¶
Component-scoped field access is not a runtime auth
mechanism. Zalando does not propose evaluating @allowedFor
per-request — it's a persist-time gate. Once a query is
persisted, the field is returned to that query ID regardless
of the requester.
Runtime-grade authorization in GraphQL uses different
mechanisms (role-based resolver guards, directive-based
@auth(requires: Role) runtime hooks, etc.). Component-
scoped access is about the set of queries that exist,
not about who can run them.
Blast radius sizing¶
By sizing the componentNames list, the operator trades
breaking-change cost against adoption surface:
- One component. Minimum blast radius. Canonical experimental-field shape. A breaking change forces migration of exactly one UI surface.
- Handful of components. Multi-surface experiment (e.g. the field is tested on product card and wishlist card simultaneously).
- No
@allowedFor. Stable field; any persisted query may use it. This is the end state of a successful lifecycle transition.
The discipline is to keep the list narrow during the
experimental window, measure, decide on the field's shape,
and either (a) stabilise by removing @allowedFor entirely
or (b) break the field and roll the change through the
small named whitelist
(Source: sources/2022-02-16-zalando-graphql-persisted-queries-and-schema-stability).
Lifecycle placement¶
@draft → @allowedFor(components: [...]) → (no directive)
(unpersistable) (this page: component-scoped) (stable, unrestricted)
See concepts/draft-schema-field for stage 1 and patterns/directive-based-field-lifecycle for the umbrella.
Seen in¶
- Zalando UBFF — the canonical implementation (sources/2022-02-16-zalando-graphql-persisted-queries-and-schema-stability, systems/zalando-graphql-ubff).
Related¶
- concepts/draft-schema-field — the preceding lifecycle stage.
- concepts/graphql-persisted-queries — the mechanism that makes this a persist-time gate.
- concepts/blast-radius — the design concept the
componentNameslist is sizing. - patterns/directive-based-field-lifecycle — umbrella pattern.
- patterns/automatic-persisted-queries — pattern class.
- systems/graphql
- systems/zalando-graphql-ubff