# Object Types - Hot Chocolate

> Define GraphQL object types in Hot Chocolate with the [ObjectType<T>] attribute, adding fields, resolvers, and type extensions to build your schema graph.

Canonical source: https://chillicream.com/docs/hotchocolate/defining-a-schema/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.

GraphQL

```
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.

C#

```
public 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.

C#

```
public 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:

GraphQL

```
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.

You can define resolver methods directly on your C# models, as shown below:

C#

```
public class Author
{
    public string Name { get; set; }

    public async Task<List<Book>> GetBooksAsync(
        BookService bookService,
        CancellationToken ct)
        => await bookService.GetBooksByAuthorAsync(Name, ct);
}
```

Here, the `BookService` parameter is injected from the dependency injection container, and `CancellationToken` is provided by the execution engine. Neither appears as a GraphQL argument.

However, a cleaner approach is to separate GraphQL-specific resolvers from your domain models by using a dedicated resolver class. This keeps your domain types free of API concerns and makes them more reusable:

C#

```
public class Author
{
    public string Name { get; set; }
}

[ObjectType<Author>]
public static partial class AuthorNode
{
    public static async Task<List<Book>> GetBooksAsync(
        [Parent] Author author,
        BookService bookService,
        CancellationToken ct)
        => await bookService.GetBooksByAuthorAsync(author.Name, ct);
}
```

The `[ObjectType<Author>]` attribute tells the source generator that this class contributes fields to the `Author` type. The `[Parent]` parameter receives the resolved `Author` instance. The class must be `static partial` so the source generator can wire it up.

Both approaches produce this schema:

GraphQL

```
type Author {
  name: String!
  books: [Book!]!
}
```

The naming rules for methods are the same as for [query fields](https://chillicream.com/docs/hotchocolate/defining-a-schema/queries#naming-conventions): `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

Use `[GraphQLName]` on a property to change its name in the schema:

C#

```
public class Author
{
    [GraphQLName("fullName")]
    public string Name { get; set; }
}
```

When using a separate resolver class, the method name determines the field name. The `Get` prefix is stripped and the result is camelCased, so `GetFullName` becomes `fullName`:

C#

```
public class Author
{
    public string Name { get; set; }
}

[ObjectType<Author>]
public static partial class AuthorNode
{
    public static string GetFullName([Parent] Author author)
        => author.Name;
}
```

If the naming convention does not produce the name you want, apply `[GraphQLName]` to the resolver method:

C#

```
public class Author
{
    public string Name { get; set; }
}

[ObjectType<Author>]
public static partial class AuthorNode
{
    [GraphQLName("fullName")]
    public static string GetFullName([Parent] Author author)
        => author.Name;
}
```

You can also rename the type itself.

Use `[GraphQLName]` on the domain class:

C#

```
[GraphQLName("BookAuthor")]
public class Author
{
    public string Name { get; set; }
}
```

When using a separate resolver class, apply `[GraphQLName]` to the resolver class instead:

C#

```
public class Author
{
    public string Name { get; set; }
}

[ObjectType<Author>]
[GraphQLName("BookAuthor")]
public static partial class AuthorNode
{
}
```

If only one client needs different names, prefer using [aliases](https://graphql.org/learn/queries/#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:

C#

```
public class Product
{
    public string Name { get; set; }

    [GraphQLIgnore]
    public string InternalSku { get; set; }
}
```

For resolver types, you can use the internal fluent API to ignore a field:

C#

```
public class Product
{
    public string Name { get; set; }
    public string InternalSku { get; set; }
}

[ObjectType<Product>]
public static partial class ProductNode
{
    static partial void Configure(IObjectTypeDescriptor<Product> descriptor)
        => descriptor.Ignore(t => t.InternalSku);
}
```

Often, you’ll ignore a field because you want to introduce a resolver that replaces a model property. This can be done with the `[BindMember]` attribute:

C#

```
public class Product
{
    public string Name { get; set; }
    public int BrandId { get; set; }
}

[ObjectType<Product>]
public static partial class ProductNode
{
    [BindMember(nameof(Product.BrandId))]
    public static async Task<Brand> GetBrandAsync(
        [Parent] Product product,
        BrandService brandService,
        CancellationToken cancellationToken)
        => await brandService.GetBrandByIdAsync(product.BrandId, cancellationToken);
}
```

You can also bind multiple members from your model to a single resolver.

### Descriptions

Descriptions appear in GraphQL introspection and tooling like Nitro. They help consumers of your API understand the purpose of each type and field.

Use `[GraphQLDescription]` on a class or property:

C#

```
[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).

C#

```
/// <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; }
}
```

When using a separate resolver class, apply `[GraphQLDescription]` to the resolver class or its methods. XML documentation comments work the same way:

C#

```
public class Product
{
    public string Name { get; set; }

    public decimal Price { get; set; }
}

[ObjectType<Product>]
[GraphQLDescription("A product in the catalog.")]
public static partial class ProductNode
{
    [GraphQLDescription("The display name shown to customers.")]
    public static string GetName([Parent] Product product)
        => product.Name;
}
```

### 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.

C#

```
public 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.

C#

```
builder
    .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](https://learn.microsoft.com/dotnet/csharp/nullable-references) 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.

C#

```
public class Product
{
    [GraphQLNonNullType]
    public string? Name { get; set; }
}
```

For full details on nullability, see [Non-Null](https://chillicream.com/docs/hotchocolate/defining-a-schema/non-null).

## Dictionary Support

Hot Chocolate 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.

C#

```
public class Product
{
    public string Name { get; set; }

    public Dictionary<string, string> Attributes { get; set; }
}
```

This produces the following schema:

GraphQL

```
type Product {
  name: String!
  attributes: [KeyValuePairOfStringAndString!]!
}

type KeyValuePairOfStringAndString {
  key: String!
  value: String!
}
```

Clients query dictionary fields like any other list.

GraphQL

```
{
  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](https://chillicream.com/docs/hotchocolate/defining-a-schema/queries).
- **Need to understand resolver patterns?** See [Resolvers](https://chillicream.com/docs/hotchocolate/resolvers).
- **Need to compose types from multiple classes?** See [Extending Types](https://chillicream.com/docs/hotchocolate/defining-a-schema/object-types).
- **Need to define input for mutations?** See [Input Object Types](https://chillicream.com/docs/hotchocolate/defining-a-schema/input-object-types).
- **Need to fetch data efficiently?** See [DataLoader](https://chillicream.com/docs/hotchocolate/fetching-data/batching/dataloader).
[Edit this page on GitHub](https://github.com/ChilliCream/graphql-platform/edit/main/website/content/docs/hotchocolate/defining-a-schema/object-types.md)

Last updated on **June 30, 2026** by **Tobias Tengler**
