Extending Types

GraphQL allows us to extend existing types with new functionality.

SDL
type Query {
foo: String
}
extend type Query {
bar: String
}

In Hot Chocolate type extensions work a little different. Instead of generating the extend syntax in the schema, the original type definition and its type extensions are merged at runtime into a combined type.

Type extensions make most sense, if

  • we want to split up types into separate classes.

  • we can not modify the original type.

Note: If we just want to organize the fields of one of our types in different files, we can use partial classes in the Annotation-based approach.

Object Types

Consider we have the following entity that we want to extend with functionality.

C#
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public int AuthorId { get; set; }
}

Adding fields

We can easily add new fields to our existing Book type.

C#
[ExtendObjectType(typeof(Book))]
public class BookExtensions
{
public IEnumerable<string> GetGenres([Parent] Book book)
{
// Omitted code for brevity
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddGraphQLServer()
.AddTypeExtension<BookExtensions>();
}
}

One of the most common use-cases for this would be adding new resolvers to one of our root types.

C#
[ExtendObjectType(typeof(Query))]
public class QueryBookResolvers
{
public IEnumerable<Book> GetBooks()
{
// Omitted code for brevity
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddGraphQLServer()
.AddTypeExtension<QueryBookResolvers>();
}
}

Removing fields

We can also ignore fields of the type we are extending.

C#
[ExtendObjectType(typeof(Book),
IgnoreProperties = new[] { nameof(Book.AuthorId) })]
public class BookExtensions
{
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddGraphQLServer()
.AddTypeExtension<BookExtensions>();
}
}

Replacing fields

We might have an Id field, which we want to replace with a field that resolves the actual type the Id is pointing to.

In this example we replace the authorId field with an author field.

C#
[ExtendObjectType(typeof(Book))]
public class BookExtensions
{
[BindMember(nameof(Book.AuthorId))]
public Author GetAuthor([Parent] Book book)
{
// Omitted code for brevity
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddGraphQLServer()
.AddTypeExtension<BookExtensions>();
}
}

Extending base types

We can also extend multiple types at once, but still dedicate specific resolvers to specific types.

C#
// this extends every type that inherits from object (essentially every type)
[ExtendObjectType(typeof(object))]
public class ObjectExtensions
{
// this field is added to every object type
public string NewField()
{
// Omitted code for brevity
}
// this field is only added to the Book type
public Author GetAuthor([Parent] Book book)
{
// Omitted code for brevity
}
// this field is only added to the Author type
public IEnumerable<Book> GetBooks([Parent] Author author)
{
// Omitted code for brevity
}
}

We can also modify all object types that are connected by a base type, like an interface.

C#
[InterfaceType]
public interface IPost
{
string Title { get; set; }
}
// this extends every type that implements the IPost interface
// note: the interface itself is not extended in the schema
[ExtendObjectType(typeof(IPost))]
public class PostExtensions
{
public string NewField([Parent] IPost post)
{
// Omitted code for brevity
}
}

Note: The IPost is annotated with [InterfaceType] to include it in the GraphQL schema, but that isn't necessary for the type extension to work. We can use any base type, like object or an abstract base class, as an extension point without necessarily exposing the base type in our GraphQL schema.