1- # GraphQL Abstract Type Filter Specification
2-
3- _ Status: Strawman_ <br >
4- _ Version: 2026-01-08_
5-
6- This specification aims to provide a standardized way for clients to communicate
7- the exclusive set of types permitted in a resolver’s response when returning one
8- or more abstract types (i.e. an Interface or Union return type).
9-
10- Algorithms are provided for resolvers to enforce this contract at runtime.
11-
12- In the following example, ` allPets ` will return ** only** ` Cat ` or ` Dog ` types:
13-
14- ``` graphql example
15- {
16- allPets (only : ["Cat" , " Dog" ]) {
17- ... on Cat { name }
18- ... on Dog { name }
19- }
20- }
21- ```
22-
23- This is enforced on the server when using the ` @limitTypes ` type system
24- directive.
25-
26- This specification is intended to be used in conjunction with the
27- [ GraphQL @matches Directive Specification] ( ./MatchesSpec.html ) in order to avoid
28- duplicating the list of allowed types passed as a field argument.
29-
30- ** Use Cases**
31-
32- Applications may implement this specification to provide a filter for what
33- type(s) may be returned by a resolver. Notably, the filtering happens on the
34- server side, allowing clients to guarantee a fixed length of results.
35-
36- This may also be used a versioning scheme by applications that dynamically
37- render different parts of a user interface mapped from the return type(s) of a
38- resolver. Each version of the application can define the exclusive set of types
39- it supports displaying in the user interface.
1+ # Abstract Type Filter Argument
402
413## @limitTypes
424
@@ -45,8 +7,11 @@ directive @limitTypes on ARGUMENT_DEFINITION
457```
468
479`@limitTypes ` is a type system directive that may be applied to a field
48- argument in order to express that it defines the exclusive set of types that the
49- field may return .
10+ argument definition in order to express that it will define the exclusive set of
11+ types that the field is allowed to return .
12+
13+ The server must enforce and validate the allowed types according to this
14+ specification .
5015
5116**Example Usage **
5217
@@ -66,10 +31,6 @@ type Cat implements Pet {
6631type Dog implements Pet {
6732 name : String !
6833}
69-
70- interface Human {
71- name : String !
72- }
7334```
7435
7536`@limitTypes ` may also be applied to schema that implements the
@@ -93,10 +54,6 @@ type PetEdge {
9354 cursor : String !
9455 node : Pet
9556}
96-
97- interface Pet {
98- name : String !
99- }
10057```
10158
10259## Schema Validation
@@ -112,46 +69,27 @@ returns either:
11269
11370- an abstract type
11471- a list of an abstract type
115- - a connection type (conforming to
116- [the GraphQL Cursor Connections Specification](https ://relay .dev /graphql /connections .htm #sec-Connection-Types)) over an abstract type
72+ - a connection type (conforming to the
73+ [GraphQL Cursor Connections Specification](https ://relay .dev /graphql /connections .htm #sec-Connection-Types)
74+ over an abstract type )
11775
11876## Execution
11977
12078The `@limitTypes ` directive places requirements on the {resolver } used to
121- satisfy the field. Implementers of this specification must honour these
79+ satisfy the field . Implementers of this specification must honor these
12280requirements .
12381
124- ### Filter Argument Validation
125-
126- :: A *filter argument* is the coerced argument value of a field argument with
127- the `@limitTypes` directive applied.
128-
129- Each type referenced in the *filter argument* must exist in the type system,
130- and be a possible return type of the field to be considered a valid argument in
131- the context of this specification.
132-
133- This validation happens as part of {CoerceAllowedTypes ()}, defined below .
134-
135- ```graphql counter -example
136- {
137- allPets (only : ["Cat" , " Dog" , " LochNessMonster" ]) {
138- name
139- }
140- }
141- ```
142-
143- The example above must yield an execution error, since ` LochNessMonster ` is not
144- a type that exists in the type system.
82+ ### Coercing Allowed Types
14583
146- Note: Filter argument validation is necessary as schema-unaware clients are
147- otherwise unable to verify the correctness of this argument .
84+ :: A * filter argument * is a field argument which has the ` @limitTypes `
85+ directive applied .
14886
149- ### Coerce Allowed Types
87+ The input to the *filter argument * is a list of strings , however this must be
88+ made meaningful to the resolver such that it may perform its filtering - thus we
89+ must resolve it into a list of valid concrete object types that are possible in
90+ this position .
15091
151- The input to the filter argument is a list of strings, however this must be made
152- meaningful to the resolver such that it may perform its filtering - thus we must
153- resolve it into a list of valid concrete object types that are possible in this
154- position.
92+ :: The coerced list of valid concrete object types are the *allowed types *.
15593
15694CoerceAllowedTypes (abstractType, typeNames):
15795
@@ -162,7 +100,7 @@ CoerceAllowedTypes(abstractType, typeNames):
162100 - If {type } does not exist , raise an execution error .
163101 - If {type } is an object type :
164102 - If {type } is a member of {possibleTypes }, add {type } to {allowedTypes }.
165- - Otherwise, raise an exection error.
103+ - Otherwise , raise an execution error .
166104 - Otherwise , if {type } is a union type :
167105 - For each {concreteType } in {type }:
168106 - If {concreteType } is a member of {possibleTypes }, add {concreteType } to
@@ -171,23 +109,76 @@ CoerceAllowedTypes(abstractType, typeNames):
171109 - For each {concreteType } that implements {type }:
172110 - If {concreteType } is a member of {possibleTypes }, add {concreteType } to
173111 {allowedTypes }.
174- - Otherwise continue to the next {typeName}.
112+ - Otherwise , raise an execution error (scalars, enums, and input types are not
113+ valid filter argument values).
175114- Return {allowedTypes }.
176115
177- ### Enforcing Allowed Types
116+ **Explanatory Text **
117+
118+ The input to the *filter argument * may include both concrete and abstract types .
119+ {CoerceAllowedTypes } expands *allowed types * to include the possible and valid
120+ concrete types for each abstract type .
121+
122+ To see why this is needed , we will expand our example schema above to include
123+ the following types:
124+
125+ ```graphql example
126+ interface Fish {
127+ swimSpeed : Int !
128+ }
129+
130+ type Goldfish implements Pet & Fish {
131+ name : String !
132+ swimSpeed : Int !
133+ }
134+
135+ type Haddock implements Fish {
136+ swimSpeed : Int !
137+ }
138+ ```
178139
179- Enforcement of the allowed types is the responsibility of the {resolver} called
180- in
140+ It is possible for types to implement multiple interfaces . It therefore must be
141+ possible to select concrete types of another interface in the *filter argument *:
142+
143+ ```graphql example
144+ {
145+ allPets (only : ["Fish" ]) {
146+ ... on Goldfish {
147+ swimSpeed
148+ }
149+ }
150+ }
151+ ```
152+
153+ The below example must fail, since ` Haddock ` does not implement the ` Pet `
154+ interface, and is therefore not a possible return type.
155+
156+ ``` graphql counter-example
157+ {
158+ allPets (only : ["Haddock" ]) {
159+ ... on Fish {
160+ swimSpeed
161+ }
162+ }
163+ }
164+ ```
165+
166+ ### Allowed Types Restriction
167+
168+ Enforcement of the * allowed types* is the responsibility of the {resolver}
169+ called in
181170[ ` ResolveFieldValue() ` ] ( < https://spec.graphql.org/draft/#ResolveFieldValue() > )
182171during the [ ` ExecuteField() ` ] ( < https://spec.graphql.org/draft/#ExecuteField() > )
183- algorithm. This is because the filtering must be applied to the {collection}
184- prior to applying the pagination arguments.
172+ algorithm.
185173
186- When the field returns an abstract type, the {collection} is a list containing
187- a single element which is that type. When the field returns a list of an
188- abstract type, the {collection} is this list. When the field returns a
189- connection type over an abstract type, the {collection} is the list of abstract
190- type the connection represents.
174+ :: When the field returns an abstract type, the * collection* is this type.
175+ When the field returns a list of an abstract type, the * collection* is this
176+ list. When the field returns a connection type over an abstract type, the
177+ * collection* is the list of abstract type the connection represents.
178+
179+ The resolver must apply this restriction when fetching or generating the source
180+ data to produce the * collection* . This is because the filtering must occur prior
181+ to applying pagination logic in order to produce the correct number of results.
191182
192183When a field with a ` @limitTypes ` argument is being resolved:
193184
@@ -201,19 +192,71 @@ When a field with a `@limitTypes` argument is being resolved:
201192- Let {abstractType} be the abstract type the {collection} represents.
202193- Let {allowedTypes} be {CoerceAllowedTypes(abstractType, limitTypes)}.
203194
204- The resolver must ensure that the result of
205- [ ` ResolveAbstractType() ` ] ( < https://spec.graphql.org/draft/#ExecuteField() > ) for
206- each entry in {collection} is a type within {allowedTypes}. The resolver must
207- apply this restriction before applying any pagination arguments.
208-
209195Note: The restriction must be applied before pagination arguments so that
210- non-terminal pages in the {collection} get full representation - i.e. there are
211- no gaps.
196+ non-terminal pages in the collection get full representation - i.e. there
197+ are no gaps.
198+
199+ ## Validation Algorithms
212200
213- ### Field Collection Validation
201+ ` @limitTypes ` fields must implement the algorithms listed in the
202+ [ Execution] ( #Execution ) section above to be spec-compliant. However, it may be
203+ impossible or extremely difficult for GraphQL servers to statically verify the
204+ correctness of the runtime and prevent non-compliant implementations.
214205
215- TODO: the following should raise an error since ` Mouse ` does not appear as a
216- value in {allowedTypes}
206+ To this end, this section specifies a set of algorithms in order for the server
207+ to validate that the * filter argument* value and the field response are valid.
208+
209+ Usage of these algorithms is ** optional** , but highly recommended to guard
210+ against programmer error.
211+
212+ All algorithms in this section run either before or after
213+ [ ` ResolveFieldValue() ` ] ( < https://spec.graphql.org/draft/#ResolveFieldValue() > ) ,
214+ and must be run automatically by the server when executing fields for which
215+ the ` @limitTypes ` directive is applied,
216+
217+ ### Filter Argument Value Validation
218+
219+ Each member of the * filter argument* value must exist in the type system and be
220+ a possible return type of the field.
221+
222+ For example, the query below must yield an execution error - since
223+ ` LochNessMonster ` is not a type that exists in the example schema.
224+
225+ ``` graphql counter-example
226+ {
227+ allPets (only : ["Cat" , " Dog" , " LochNessMonster" ]) {
228+ name
229+ }
230+ }
231+ ```
232+
233+ When used, this algorithm must be applied before
234+ [ ` ResolveFieldValue() ` ] ( < https://spec.graphql.org/draft/#ResolveFieldValue() > ) .
235+
236+ ValidateFilterArgument(filterArgumentValue):
237+
238+ - Let {abstractType} be the abstract type the {collection} represents.
239+ - Let {possibleTypes} be a set of the possible types of {abstractType}.
240+ - For each {typeName} in {filterArgumentValue}:
241+ - Let {type} be the type in the schema named {typeName}.
242+ - If {type} does not exist, raise an execution error.
243+ - If {type} is an object type:
244+ - If {type} is not a member of {possibleTypes} raise an execution error.
245+ - Otherwise, if {type} is a union type:
246+ - ??? todo
247+ - Otherwise, if {type} is an interface type:
248+ - ??? todo
249+ - Otherwise, raise an execution error (scalars, enums, and input types are not
250+ valid filter argument values).
251+ - Return {allowedTypes}.
252+
253+ Note: Schema-aware clients or linting tools are encouraged to implement this
254+ validation locally.
255+
256+ ### Field Collection Validation (wip)
257+
258+ For example, the following query must raise an execution error since ` Mouse `
259+ does not appear as a value in {allowedTypes}
217260
218261``` graphql counter-example
219262{
@@ -225,6 +268,8 @@ value in {allowedTypes}
225268}
226269```
227270
271+ TODO: implement algorithm
272+
228273### Field Response Validation (wip)
229274
230275TODO: if the response array of the field contains a type that did not appear in
@@ -256,3 +301,5 @@ likely is not possible since this logic is intended to be run generically as a
256301middleware - i.e _ after_ the field has completed, and the in-memory object
257302representation has been converted into json blob (potentially without
258303` __typename ` )
304+
305+ or can we look at using \_\_ resolveType()?
0 commit comments