Skip to content

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:

type Product {
  fancyProp: String @allowedFor(componentNames: ["web-product-card"])
}

Query side — the query declares which component is making it:

query productCard @component(name: "web-product-card") {
  product {
    fancyProp
  }
}

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

Last updated · 501 distilled / 1,218 read