Overview

A GraphQL schema defines the contract between your server and its clients. It declares what data clients can query, what mutations they can perform, and what events they can subscribe to. In Hot Chocolate, you build the schema from C# code, and the framework translates your types and methods into a GraphQL schema at build time.

This page explains how that translation works and how to choose between the two supported approaches. The sub-pages in this section cover each schema element in detail.

How Hot Chocolate Maps C# to GraphQL

Hot Chocolate inspects your C# types and produces GraphQL equivalents. Understanding the mapping rules makes the rest of the documentation predictable.

Types. A C# class or record becomes a GraphQL object type. Each public property or method becomes a field. The GraphQL type name matches the C# type name.

C#
// Types/Book.cs
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public Author Author { get; set; }
}
GraphQL
type Book {
id: Int!
title: String!
author: Author!
}

Nullability. Non-nullable C# types (like int and string with nullable reference types enabled) become non-null GraphQL types (Int!, String!). Nullable C# types (int?, string?) become nullable GraphQL types.

Naming. Method names are converted to camelCase. The Get prefix and Async suffix are stripped. GetBookByIdAsync becomes bookById.

Scalars. C# primitives map to GraphQL scalars: string to String, int to Int, float/double to Float, bool to Boolean. DateTime, Guid, Uri, and other .NET types have built-in scalar mappings. See Scalars for the full list.

Collections. List<T>, IEnumerable<T>, arrays, and other collection types become GraphQL list types ([T]).

Two Approaches

Hot Chocolate supports two approaches to defining a schema. Both produce the same GraphQL output. They differ in how much control you want over the mapping.

You write standard C# classes and decorate them with attributes. A source generator handles the rest. This is the approach used throughout this documentation.

C#
// Types/ProductQueries.cs
[QueryType]
public static partial class ProductQueries
{
public static async Task<Product?> GetProductByIdAsync(
int id,
CatalogContext db,
CancellationToken ct)
=> await db.Products.FindAsync([id], ct);
}

The source generator produces a productById field on the Query type, infers the return type as Product, and registers everything at build time. You do not write type descriptors or SDL.

When to use: Most of the time. It keeps your schema close to your domain code and lets the tooling handle the translation. The source generator catches errors at compile time rather than at startup.

Code-first

You create classes that inherit from ObjectType<T> and configure each field explicitly through a descriptor API. This decouples the GraphQL schema shape from your C# model.

C#
// Types/ProductType.cs
public class ProductType : ObjectType<Product>
{
protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
{
descriptor
.Field(p => p.Id)
.Type<NonNullType<IdType>>();
descriptor
.Field(p => p.Name)
.Type<NonNullType<StringType>>();
}
}

When to use: When you need the GraphQL schema to differ significantly from your C# model, when you are building shared infrastructure that generates schemas programmatically, or when you need access to descriptor APIs that do not have attribute equivalents.

Both approaches can coexist in the same project. You can use implementation-first for most types and switch to code-first for specific cases.

Schema Elements

A GraphQL schema is built from a small set of elements. Each has its own page with full examples in both approaches.

Root Types

Every schema has up to three root types that serve as entry points for operations.

Root TypePurposeAttributePage
QueryRead data. Runs fields in parallel.[QueryType]Queries
MutationWrite data. Runs fields serially.[MutationType]Mutations
SubscriptionStream real-time events.[SubscriptionType]Subscriptions

Only the Query type is required. Add Mutation and Subscription types as needed.

Output Types

These types describe the shape of data returned to clients.

TypeC# MappingPage
Object typeClass or record with public properties/methodsObject Types
InterfaceC# interface or abstract classInterfaces
UnionMultiple object types grouped togetherUnions
EnumC# enumEnums
ScalarPrimitive or custom type (String, Int, DateTime, etc.)Scalars

Input Types

These types describe the shape of data sent by clients.

TypePurposePage
ArgumentsParameters on a fieldArguments
Input object typeComplex argument payloads (commonly used in mutations)Input Object Types

Type Modifiers

Modifiers wrap other types to change their nullability or turn them into lists.

ModifierGraphQLC#Page
Non-nullString!string (with NRT enabled)Non-Null
NullableStringstring?Non-Null
List[String!]!List<string>Lists

Organizing Your Schema

TopicPurposePage
Extending typesSplit a type definition across multiple classesExtending Types
DirectivesAdd metadata or alter runtime behaviorDirectives
Global object identificationStable IDs and the node field for Relay-compatible clientsRelay

Next Steps

  • "I want to define my first query." Start with Queries. It covers the [QueryType] attribute, naming conventions, and how to register multiple query classes.

  • "I want to fetch data from a database." See Resolvers for how fields load data, and DataLoader for batching.

  • "I want to understand how my C# types become GraphQL types." Read Object Types for a detailed walkthrough of the mapping rules.

Last updated on April 13, 2026 by Michael Staib