Object Types
Object types are the building blocks of a GraphQL schema. Each object type has a name and a set of fields. Fields can return scalars like String and Int, or other object types, forming a graph that clients traverse through their queries.
type Product { name: String! price: Decimal! inStock: Boolean!}
type Author { name: String! bio: String books: [Book!]!}
type Book { title: String! author: Author!}
Every field in a query resolves to a concrete value. Object types define the shape of that value. Understanding how to define and configure them is the foundation of building a Hot Chocolate schema.
Defining Object Types
In the implementation-first approach, a C# class becomes a GraphQL object type automatically. The source generator picks up public properties and methods and maps them to fields. In the code-first approach, you create a class that inherits from ObjectType<T> and configure it explicitly.
// Types/Author.cspublic class Author{ public string Name { get; set; }
public string? Bio { get; set; }}
Public properties become fields on the Author object type. No additional registration or configuration is required beyond the standard AddTypes call generated by the source generator.
Properties as Fields
Public properties with a getter are automatically mapped to GraphQL fields. Hot Chocolate converts the property name to camelCase for the schema.
// Types/Product.cspublic class Product{ public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool InStock { get; set; }}
This produces the following schema:
type Product { id: Int! name: String! price: Decimal! inStock: Boolean!}
Methods as Resolvers
Public methods on a class become resolver fields. This is how you add computed fields or fields that fetch data from external sources. Method parameters that are registered services are injected automatically.
// Types/Author.cspublic class Author{ public string Name { get; set; }
public async Task<List<Book>> GetBooksAsync( BookService bookService, CancellationToken ct) => await bookService.GetBooksByAuthorAsync(Name, ct);}
The BookService parameter is resolved from the dependency injection container. CancellationToken is provided by the execution engine. Neither appears as a GraphQL argument.
Both approaches produce this schema:
type Author { name: String! books: [Book!]!}
The naming rules for methods are the same as for query fields: Get prefixes and Async suffixes are stripped, and the result is camelCased.
Field Configuration
You can rename fields, ignore them, and add descriptions without changing the shape of your C# classes.
Renaming Fields
// Types/Author.cspublic class Author{ [GraphQLName("fullName")] public string Name { get; set; }}
You can also rename the type itself.
// Types/Author.cs[GraphQLName("BookAuthor")]public class Author{ public string Name { get; set; }}
If only one client needs different names, prefer using aliases in that client's queries instead of changing the schema.
Ignoring Fields
Use the [GraphQLIgnore] attribute to prevent a property or method from appearing in the schema.
// Types/Product.cspublic class Product{ public string Name { get; set; }
[GraphQLIgnore] public string InternalSku { get; set; }}
Descriptions
Descriptions appear in GraphQL introspection and tooling like Nitro. They help consumers of your API understand the purpose of each type and field.
// Types/Product.cs[GraphQLDescription("A product in the catalog.")]public class Product{ [GraphQLDescription("The display name shown to customers.")] public string Name { get; set; }
public decimal Price { get; set; }}
You can also use XML documentation comments. Hot Chocolate reads <summary> tags when UseXmlDocumentation is enabled (it is enabled by default).
// Types/Product.cs/// <summary>/// A product in the catalog./// </summary>public class Product{ /// <summary> /// The display name shown to customers. /// </summary> public string Name { get; set; }
public decimal Price { get; set; }}
Explicit Binding
By default, all public properties and methods are included as fields. You can switch to explicit binding, where you opt in to each field individually.
// Types/ProductType.cspublic class ProductType : ObjectType<Product>{ protected override void Configure(IObjectTypeDescriptor<Product> descriptor) { descriptor.BindFieldsExplicitly();
descriptor.Field(f => f.Name); descriptor.Field(f => f.Price); }}
Only name and price appear in the schema. All other properties on Product are excluded.
You can also set this globally, which affects all types.
// Program.csbuilder .AddGraphQL() .ModifyOptions(options => { options.DefaultBindingBehavior = BindingBehavior.Explicit; });
Nullability
Hot Chocolate uses C# nullability to determine whether a GraphQL field is nullable or non-null. When nullable reference types are enabled in your project, the mapping is straightforward.
| C# Type | GraphQL Type |
|---|---|
string | String! |
string? | String |
int | Int! |
int? | Int |
List<string> | [String!]! |
List<string?> | [String]! |
List<string>? | [String!] |
Value types (int, bool, decimal) are non-null by default. Their nullable counterpart (int?, bool?) maps to a nullable GraphQL field.
Reference types follow your project's nullable reference type settings. With nullable reference types enabled (recommended), string maps to String! and string? maps to String. Without nullable reference types enabled, all reference type fields are nullable by default.
You can override the inferred nullability when needed.
// Types/Product.cspublic class Product{ [GraphQLNonNullType] public string? Name { get; set; }}
For full details on nullability, see Non-Null.
Dictionary Support
Hot Chocolate v16 automatically maps Dictionary<TKey, TValue> properties to a list of key-value pair objects. This eliminates the need for custom resolvers when exposing dictionary data.
// Types/Product.cspublic class Product{ public string Name { get; set; }
public Dictionary<string, string> Attributes { get; set; }}
This produces the following schema:
type Product { name: String! attributes: [KeyValuePairOfStringAndString!]!}
type KeyValuePairOfStringAndString { key: String! value: String!}
Clients query dictionary fields like any other list.
{ product { name attributes { key value } }}
This works with any key and value types. For example, Dictionary<string, int> produces KeyValuePairOfStringAndInt32 with the appropriate scalar types.
Next Steps
- Need to define query entry points? See Queries.
- Need to understand resolver patterns? See Resolvers.
- Need to compose types from multiple classes? See Extending Types.
- Need to define input for mutations? See Input Object Types.
- Need to fetch data efficiently? See DataLoader.