Type System
A GraphQL schema defines the contract that clients interact with. It specifies the available operations, selectable fields, accepted arguments, and the structure of responses.
Hot Chocolate enables you to define this contract in C# and review the resulting GraphQL SDL. This page serves as your guide: it introduces the main type system concepts, demonstrates how a C# model translates to SDL, and directs you to detailed pages for each modeling task.
Understanding the Schema as the API Contract
When working with GraphQL, everything starts with the schema. The schema defines the contract between your API and its clients, describing the available operations and the data that can be requested.
Clients interact with your API by sending GraphQL operations. They do not call your C# methods or classes directly. Instead, they rely on the schema to understand what is possible.
To introduce the main concepts of the GraphQL type system, consider the following example. This schema highlights the essential building blocks you will encounter:
type Query { bookById(id: ID!): Book}
type Mutation { createBook(input: CreateBookInput!): Book!}
type Book { id: ID! title: String! authors: [Author!]!}
type Author { id: ID! name: String!}
input CreateBookInput { title: String! authorIds: [ID!]!}
This example presents queries, mutations, object types, input types, and scalar values. Each part of the schema plays a specific role in shaping how clients interact with your API. The table below breaks down these elements and explains their purpose within the type system.
| SDL part | Type system member | What it means | Learn more |
|---|---|---|---|
Query | Operation root type | Entry point for read operations. | Queries |
Mutation | Operation root type | Entry point for write operations. | Mutations |
bookById and createBook | Fields | Selectable members on a type. | Object Types |
id and input | Arguments | Values supplied to a field. | Arguments |
Book and Author | Object types | Returned data shapes. | Object Types |
CreateBookInput | Input object type | Structured data sent by a client. | Input Object Types |
ID and String | Scalars | Leaf values with no subfields. | Scalars |
! and [] | Type modifiers | Non-null and list wrappers. | Lists and Non-Null |
If you are unsure how to identify the parts of your schema, start by looking at the SDL. Elements that a client can select in a query are fields. Values that a client supplies to those fields are arguments or input fields. When you see ! or [] wrapping another type, these are type modifiers that indicate non-nullability or lists.
For example, a client might send the following query and mutation:
query { bookById(id: "1") { title authors { name } }}
mutation { createBook(input: { title: "New Book", authorIds: ["2"] }) { id title }}
In these examples, bookById and createBook are fields. The id and input values are arguments. The selections inside the curly braces, such as title and authors, are fields on the returned types.
Schema Authoring Styles
Hot Chocolate offers two C# authoring styles, both of which produce GraphQL SDL.
Implementation-First
The implementation-first approach allows you to define your GraphQL schema using standard C# types and attributes. This keeps your contract close to your domain code with minimal ceremony, provides compile-time feedback, and ensures a clear mapping between code and schema. This workflow is streamlined for most application schemas.
Common building blocks include:
[QueryType],[MutationType], and[SubscriptionType]for operation root fieldspartialsource-generator classes for annotated root type classes- Attributes such as
[GraphQLName],[GraphQLDescription],[GraphQLIgnore],[ID],[Node],[InterfaceType],[UnionType], and[OneOf]when the inferred schema needs guidance - Generated dependency injection modules for GraphQL types
Code-First
The code-first approach allows you to define GraphQL types and their structure directly in C# using the Hot Chocolate type descriptor API. This is useful when your GraphQL schema needs to differ from your C# model or when building reusable schema components.
using HotChocolate.Types;
public sealed class BookType : ObjectType<Book>{ protected override void Configure(IObjectTypeDescriptor<Book> descriptor) { descriptor .Field(t => t.Title) .Type<NonNullType<StringType>>() .Description("The title displayed to readers."); }}
Navigating the Type System Map
The following map helps you select the next detailed page without needing to learn every rule here.
Root Types
GraphQL defines a single root type for each operation kind. In C#, you can organize root fields across multiple semantic classes, and Hot Chocolate will merge them into the final root type.
| Element | Purpose | Authoring cue | Learn more |
|---|---|---|---|
| Query | Read entry point. Query fields should be side-effect-free and may execute in parallel. | [QueryType] or AddQueryType. | Queries |
| Mutation | Write entry point. Top-level mutation fields execute serially. | [MutationType] or AddMutationType. | Mutations |
| Subscription | Event stream entry point. | [SubscriptionType] or AddSubscriptionType. | Subscriptions |
Output Types
Output types describe the shapes of data that clients can select after a field resolves.
| Element | Use it for | Learn more |
|---|---|---|
| Object type | Normal returned data shapes, such as Book or Author. | Object Types |
| Scalar | Leaf values such as String, Int, Boolean, ID, UUID, URI, DateTime, Any, and custom scalars. | Scalars |
| Enum | A closed set of symbolic values in input or output positions. | Enums |
| Interface | Polymorphic output types that share fields. | Interfaces |
| Union | Polymorphic output types that do not need shared fields. | Unions |
Fields and Arguments
Fields and arguments define how clients interact with your schema. Fields are selectable members on types, while arguments allow clients to supply values to those fields.
| Element | Purpose | Learn more |
|---|---|---|
| Field | A selectable member on a root type or object type. Root fields start operations. Nested fields shape returned data. | Queries, Mutations, Object Types |
| Argument | A value supplied to a field. Common uses include lookup IDs, filters, paging arguments, and mutation payloads. | Arguments |
Not every C# method parameter becomes an argument. Service parameters, parent values, cancellation tokens, and resolver context parameters are resolver concerns.
Input Types
Input types define the shapes of data that clients can send to your API, such as arguments and input objects for mutations and queries.
| Element | Use it for | Learn more |
|---|---|---|
| Argument | A scalar, enum, ID, list, or input object supplied to a field. | Arguments |
| Input object type | Structured payloads for mutations, filters, and other complex field inputs. | Input Object Types |
GraphQL separates input and output type systems. Input objects can use defaults, Optional<T>, and @oneOf, but those rules are detailed on the input pages.
Type Modifiers
Type modifiers such as non-null and list indicate whether a field or argument is required or can accept multiple values.
| Modifier | Example | What it says | Learn more |
|---|---|---|---|
| Non-null | String! | The field or argument must not be null in the contract. | Lists and Non-Null |
| List | [Book] | The value is a collection. | Lists and Non-Null |
| List plus item non-null | [Book!]! | The list is required, and every item is required. | Lists and Non-Null |
Input optionality, default values, and output nullability are related but not identical. Refer to the type modifier and input object pages when the distinction matters.
Schema Organization and Advanced Modeling
Organize your schema for maintainability and support advanced modeling scenarios using type extensions, Relay helpers, and dynamic schemas.
| Topic | Use it for | Learn more |
|---|---|---|
| Type extensions | Split large object or root type definitions across classes. Extensions are merged into the final schema. | Object Types |
| Relay helpers | Use stable IDs, global object identification, node, nodes, [ID], [Node], and [NodeResolver]. | Relay |
| Dynamic schemas | Generate types from CMS, multi-tenant, or configuration-driven metadata with ITypeModule. | Dynamic Schemas |
Contract Metadata and Lifecycle
Metadata and lifecycle features help you document, annotate, and evolve your schema safely over time.
| Topic | Use it for | Learn more |
|---|---|---|
| Descriptions | Add schema documentation from XML comments, [GraphQLDescription], or descriptors. | Documentation Comments |
| Directives | Add schema metadata or executable behavior, depending on the directive kind. | Directives |
| Deprecation and evolution | Communicate lifecycle changes and plan compatible schema updates. | Versioning |
Use type extensions for static modularity. Choose dynamic schemas only when the schema must change based on external metadata or runtime configuration.
Next steps
- Build a read entry point with Queries.
- Learn how returned shapes are inferred and configured in Object Types.
- Add field inputs with Arguments and Input Object Types.
- Strengthen the contract with Lists and Non-Null.
- Move to runtime behavior with Resolvers and DataLoader after the schema shape is clear.