1- # Abstract Filter Argument
1+ # GraphQL Abstract Type Filter Specification
22
3- Status: Strawman
3+ _ Status: Strawman_ <br >
4+ _ Version: 2026-01-08_
45
5- ## Directive
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.
40+
41+ ## @limitTypes
642
743``` graphql
844directive @limitTypes on ARGUMENT_DEFINITION
945```
1046
11- ### Examples
47+ `@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 .
1250
13- ```graphql
51+ **Example Usage **
52+
53+ ```graphql example
1454type Query {
15- allPets (first : Int , only : [String ] @limitTypes ): [Pet ]
55+ allPets (only : [String ] @limitTypes ): [Pet ]
1656}
1757
1858interface Pet {
1959 name : String !
2060}
61+
62+ type Cat implements Pet {
63+ name : String !
64+ }
65+
66+ type Dog implements Pet {
67+ name : String !
68+ }
69+
70+ interface Human {
71+ name : String !
72+ }
2173```
2274
23- ```graphql
75+ `@limitTypes ` may also be applied to schema that implements the
76+ [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm#sec-Connection-Types):
77+
78+ ```graphql example
2479type Query {
2580 allPetsConnection (
2681 first : Int
@@ -29,7 +84,15 @@ type Query {
2984 ): PetConnection
3085}
3186
32- # TODO: connection types
87+ type PetConnection {
88+ edges : [PetEdge ]
89+ pageInfo : PageInfo !
90+ }
91+
92+ type PetEdge {
93+ cursor : String !
94+ node : Pet
95+ }
3396
3497interface Pet {
3598 name : String !
@@ -44,18 +107,45 @@ same field.
44107The `@limitTypes ` directive may only appear on an argument that accepts a
45108(possibly non-nullable) list of (possibly non-nullable) String .
46109
47- The `@limitTypes ` directive may only appear on an argument to a field whose
48- named return type is a connection type (that is to say, conforming to
49- [the GraphQL Cursor Connections Specification's Connection Type](https ://relay .dev /graphql /connections .htm #sec-Connection-Types))
50- over an abstract type , or to a field whose return type is a list and the named
51- return type is an abstract type.
110+ The `@limitTypes ` directive may only appear on an field argument where the field
111+ returns either :
112+
113+ - an abstract type
114+ - 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
52117
53118## Execution
54119
55120The `@limitTypes ` directive places requirements on the {resolver } used to
56121satisfy the field. Implementers of this specification must honour these
57122requirements.
58123
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.
145+
146+ Note: Filter argument validation is necessary as schema-unaware clients are
147+ otherwise unable to verify the correctness of this argument.
148+
59149### Coerce Allowed Types
60150
61151The input to the filter argument is a list of strings, however this must be made
@@ -69,9 +159,10 @@ CoerceAllowedTypes(abstractType, typeNames):
69159- Let {allowedTypes} be an empty unordered set of object types.
70160- For each {typeName} in {typeNames}:
71161 - Let {type} be the type in the schema named {typeName}.
72- - If {type } does not exist , continue to the next { typeName } .
162+ - If {type} does not exist, raise an execution error .
73163 - If {type} is an object type:
74164 - If {type} is a member of {possibleTypes}, add {type} to {allowedTypes}.
165+ - Otherwise, raise an exection error.
75166 - Otherwise, if {type} is a union type:
76167 - For each {concreteType} in {type}:
77168 - If {concreteType} is a member of {possibleTypes}, add {concreteType} to
@@ -92,9 +183,11 @@ during the [`ExecuteField()`](<https://spec.graphql.org/draft/#ExecuteField()>)
92183algorithm. This is because the filtering must be applied to the {collection}
93184prior to applying the pagination arguments.
94185
95- When the field returns a list of an abstract type, the {collection} is this
96- list. When the field returns a connection type over an abstract type, the
97- {collection} is the list of nodes the connection represents.
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.
98191
99192When a field with a ` @limitTypes ` argument is being resolved:
100193
@@ -114,5 +207,52 @@ each entry in {collection} is a type within {allowedTypes}. The resolver must
114207apply this restriction before applying any pagination arguments.
115208
116209Note: The restriction must be applied before pagination arguments so that
117- non -terminal pages in the collection get full representation - i .e . there are no
118- gaps .
210+ non-terminal pages in the {collection} get full representation - i.e. there are
211+ no gaps.
212+
213+ ### Field Collection Validation
214+
215+ TODO: the following should raise an error since ` Mouse ` does not appear as a
216+ value in {allowedTypes}
217+
218+ ``` graphql counter-example
219+ {
220+ allPets (only : ["Cat" , " Dog" ]) {
221+ ... on Cat { name }
222+ ... on Dog { name }
223+ ... on Mouse { name }
224+ }
225+ }
226+ ```
227+
228+ ### Field Response Validation (wip)
229+
230+ TODO: if the response array of the field contains a type that did not appear in
231+ {CoerceAllowedTypes()}, raise an execution error<br ><br >
232+ yes, if a resolver already correctly implements the "Enforcing Allowed Types"
233+ logic then this isn't necessary - but - I think this is worth speccing out as a
234+ dedicated step because this is likely something tooling will want to be able to
235+ automatically apply to all @limitTypes 'd fields as a middleware. This is to
236+ provide an extra layer of safety (otherwise we're trusting that human
237+ implementers got it right inside the resolver)
238+
239+ For example, given a * filter argument* of ` ["Cat", "Dog"] ` , the following would
240+ be invalid since {allPets} contains ` Mouse ` :
241+
242+ ``` json counter-example
243+ {
244+ "data" : {
245+ "allPets" : [
246+ { "__typename" : " Cat" , "name" : " Tom" },
247+ { "__typename" : " Mouse" , "name" : " Jerry" }
248+ ]
249+ }
250+ }
251+ ```
252+
253+ ...is this even possible? this assumes that client asks for ` __typename `
254+ which isn't guaranteed. https://spec.graphql.org/draft/#ResolveAbstractType()
255+ likely is not possible since this logic is intended to be run generically as a
256+ middleware - i.e _ after_ the field has completed, and the in-memory object
257+ representation has been converted into json blob (potentially without
258+ ` __typename ` )
0 commit comments