Abstract types in GraphQL.js
GraphQL includes two kinds of abstract types: interfaces and unions. These types let a single field return values of different object types, while keeping your schema type-safe.
This guide covers how to define and resolve abstract types using GraphQL.js. You can define
abstract types with code-first constructors or SDL. Runtime type resolution can come from
resolveType, isTypeOf, or from values that include __typename.
What are abstract types?
Most GraphQL types are concrete. They represent a specific kind of object, for example, a
Book or an Author. Abstract types let a field return different types of objects depending
on the data.
This is useful when the return type can vary but comes from a known set. For example, a search
field might return a book, an author, or a publisher. Abstract types let you model this kind of
flexibility while preserving validation, introspection, and tool support.
GraphQL provides two kinds of abstract types:
- Interfaces define a set of fields that multiple object types must implement.
- Use case: A
ContentIteminterface with fields likeid,title, andpublishedAt, implemented by types such asArticleandPodcastEpisode.
- Use case: A
- Unions group together unrelated types that don’t share any fields.
- Use case: A
SearchResultunion that includesBook,Author, andPublishertypes.
- Use case: A
Defining interfaces
An interface must include a name and shared fields. In SDL, object types implement the interface
with implements. The resolveType function is JavaScript behavior, so provide it through
supplementalConfig or through the GraphQLInterfaceType constructor. If your returned values
include __typename, GraphQL.js can use that value for runtime type resolution.
The following example defines a ContentItem interface for a publishing platform:
import { GraphQLInterfaceType, GraphQLString, GraphQLNonNull } from 'graphql';
const ContentItemInterface = new GraphQLInterfaceType({
name: 'ContentItem',
fields: {
id: { type: new GraphQLNonNull(GraphQLString) },
title: { type: GraphQLString },
publishedAt: { type: GraphQLString },
},
resolveType(value) {
if (value.audioUrl) {
return 'PodcastEpisode';
}
if (value.bodyText) {
return 'Article';
}
return null;
},
});
exports.ContentItemInterface = ContentItemInterface;The resolveType function must return either the string type name corresponding
to the GraphQLObjectType of the given value, or null if the type could not
be determined.
If returned values already include __typename, GraphQL.js can use that value
for default runtime type resolution instead of a schema-attached resolveType.
Implementing interfaces with object types
To implement an interface, declare the object type with implements in SDL or include the
interface in a GraphQLObjectType interfaces array. The object type must implement all fields
defined by the interface. isTypeOf is optional runtime behavior; it can be supplied through
supplementalConfig or in the object type constructor.
The following example implements the Article and PodcastEpisode types that
conform to the ContentItem interface:
import { GraphQLObjectType, GraphQLString, GraphQLNonNull } from 'graphql';
import { ContentItemInterface } from './ContentItemInterface.js';
const ArticleType = new GraphQLObjectType({
name: 'Article',
interfaces: [ContentItemInterface],
fields: {
id: { type: new GraphQLNonNull(GraphQLString) },
title: { type: GraphQLString },
publishedAt: { type: GraphQLString },
bodyText: { type: GraphQLString },
},
isTypeOf: (value) => value.bodyText !== undefined,
});
const PodcastEpisodeType = new GraphQLObjectType({
name: 'PodcastEpisode',
interfaces: [ContentItemInterface],
fields: {
id: { type: new GraphQLNonNull(GraphQLString) },
title: { type: GraphQLString },
publishedAt: { type: GraphQLString },
audioUrl: { type: GraphQLString },
},
isTypeOf: (value) => value.audioUrl !== undefined,
});The isTypeOf function is optional. It provides a fallback when resolveType isn’t defined, or
when runtime values could match multiple types. If both resolveType and isTypeOf are defined,
GraphQL uses resolveType.
In SDL, supply isTypeOf through supplementalConfig because it is schema configuration.
Defining union types
Use union in SDL or the GraphQLUnionType constructor to define a union. A union allows a field
to return one of several object types that don’t need to share fields.
A union requires a name and a list of object types. It should also be provided a resolveType
function the same as explained for interfaces above.
The following example defines a SearchResult union:
import { GraphQLUnionType } from 'graphql';
const SearchResultType = new GraphQLUnionType({
name: 'SearchResult',
types: [BookType, AuthorType, PublisherType],
resolveType(value) {
if (value.isbn) {
return 'Book';
}
if (value.bio) {
return 'Author';
}
if (value.catalogSize) {
return 'Publisher';
}
return null;
},
});Unlike interfaces, unions don’t declare any fields their members must implement. Clients use a fragment with a type condition to query fields from a concrete type.
Resolving abstract types at runtime
GraphQL resolves abstract types dynamically during execution using the resolveType function, if
present.
This function receives the following arguments:
resolveType(value, context, info);It can return:
- The name of a type as a string
nullif the type could not be determined- A
Promiseresolving to either of the above
If resolveType isn’t defined, GraphQL falls back to checking each possible type’s isTypeOf
function. This fallback is less efficient and makes type resolution harder to debug. For most cases,
explicitly defining resolveType is recommended.
Querying abstract types
To query a field that returns an abstract type, use fragments to select fields from the possible concrete types. GraphQL evaluates each fragment based on the runtime type of the result.
For example:
query Search($term: String! = "deep learning") {
search(term: $term) {
# Inline fragments with type condition:
... on Book {
title
isbn
}
... on Author {
name
bio
}
# Named fragment:
...publisherFrag
}
}
fragment publisherFrag on Publisher {
name
catalogSize
}GraphQL’s introspection system lists all possible types for each interface and union, which enables code generation and editor tooling to provide type-aware completions; however you should keep in mind the possibility that more types will implement the interface or be included in the union in future, and thus ensure that you have a default case to handle additional types.
Best practices
- Always implement
resolveTypefor interfaces and unions to handle runtime type resolution. - Keep
resolveTypelogic simple, using consistent field shapes or tags to distinguish types. - Test
resolveTypelogic carefully. Errors inresolveTypecan cause runtime errors that can be hard to trace. - Use interfaces when types share fields and unions when types are structurally unrelated.
Additional resources
- Constructing Types
- GraphQL Specification: