# Versioning - Hot Chocolate

> Version a GraphQL schema in Hot Chocolate without URL versioning: deprecate fields with @deprecated and gate new features behind @requiresOptIn.

Canonical source: https://chillicream.com/docs/hotchocolate/defining-a-schema/versioning

Unlike REST APIs, GraphQL schemas do not use URL-based versioning (like `/graphql/v2`). Most schema changes are additive and non-breaking: adding new types and new fields does not affect existing queries. Removing a field or changing its nullability, however, is a breaking change.

GraphQL provides two directives to manage the lifecycle of schema elements:

- `@deprecated` signals that a field is being phased out and consumers should migrate away.
- `@requiresOptIn` signals that a field is not yet stable and requires explicit consumer consent.

GraphQL

```
type Query {
  users: [User] @deprecated(reason: "Use the `authors` field instead")
  authors: [User]
  recommendations: [Book] @requiresOptIn(feature: "experimentalRecommendations")
}
```

## Deprecation

You can deprecate output fields, input fields, arguments, and enum values without any extra configuration. Object types can be deprecated too, but only once you enable a separate opt-in option (see [Deprecating Object Types](#deprecating-object-types)). Deprecated elements remain functional but are flagged in introspection, warning consumers to migrate.

C#

```
[QueryType]
public static partial class BookQueries
{
    [GraphQLDeprecated("Use the `authors` field instead")]
    public static User[] GetUsers()
    {
        // ...
    }

    public static User[] GetAuthors()
    {
        // ...
    }
}
```

The .NET `[Obsolete("reason")]` attribute works the same way as `[GraphQLDeprecated("reason")]` for fields, arguments, input fields, and enum values. Deprecating an object type itself is different: see [Deprecating Object Types](#deprecating-object-types).

Warning

You cannot deprecate non-null arguments or input fields that have no default value. Deprecating a required field would silently break queries that depend on it.

### Deprecating Object Types

Deprecating an object type is not yet part of the released GraphQL specification. It tracks [graphql-spec RFC #997](https://github.com/graphql/graphql-spec/pull/997), which is still open, so its final shape could still change. Enable it deliberately, and treat it as subject to change until the RFC is merged.

Enable it in your schema options:

C#

```
builder
    .AddGraphQL()
    .ModifyOptions(o => o.EnableObjectDeprecation = true);
```

Once enabled, `@deprecated` becomes valid on object types:

C#

```
[GraphQLDeprecated("No longer known to exist.")]
public class Baiji
{
    public string? Name { get; set; }
}
```

Note

The .NET `[Obsolete]` attribute does not deprecate an object type; only `[GraphQLDeprecated]` on the class does. Every other deprecatable member honors both attributes identically, but honoring `[Obsolete]` on a class would silently deprecate types across existing codebases the moment this option was enabled, which is a schema-build failure for a field that returns such a type without itself being deprecated. Unifying the two attributes for object types is deferred to a future major version.

A field that is not itself deprecated cannot return a deprecated object type. Reaching a deprecated type indirectly is fine, so the following schema is valid even though `Baiji` is deprecated:

GraphQL

```
type Query {
  animals: [Animal]
}

interface Animal {
  name: String
}

type Dog implements Animal {
  name: String
}

type Baiji implements Animal @deprecated(reason: "No longer known to exist.") {
  name: String
}
```

`animals` returns the interface `Animal`, not `Baiji` directly, so no field needs deprecating. Contrast a field that returns `Baiji` directly:

GraphQL

```
type Query {
  baiji: Baiji
}

type Baiji @deprecated(reason: "No longer known to exist.") {
  name: String
}
```

Warning

`Query.baiji` is not deprecated but returns the deprecated `Baiji` type, so building the schema fails. Deprecate the field, or change its return type.

A deprecated object type remains a valid union member and interface implementation. Only the field returning it is checked.

Introspection exposes object type deprecation through `__Type.isDeprecated` and `__Type.deprecationReason`. Deprecated object types are hidden from `__schema.types` and `__Type.possibleTypes` by default:

GraphQL

```
{
  __schema {
    types(includeDeprecated: true) {
      name
      isDeprecated
      deprecationReason
    }
  }
}
```

## Opt-In Features

While `@deprecated` marks schema elements that are going away, `@requiresOptIn` marks schema elements that are not yet stable. This is useful for rolling out experimental features, expensive operations, or anything where consumers should make a deliberate choice to use it.

Schema elements marked with `@requiresOptIn` are hidden from introspection by default. Consumers opt in by specifying the feature name.

### Enabling Opt-In Features

Opt-in feature support is disabled by default. Enable it in your schema options:

C#

```
builder
    .AddGraphQL()
    .ModifyOptions(o => o.EnableOptInFeatures = true);
```

### Marking Schema Elements as Opt-In

Apply `@requiresOptIn` to output fields, input fields, arguments, enum values, and directive definitions. The directive is repeatable, so a single element can require multiple features.

C#

```
public class Session
{
    public string Id { get; set; }
    public string Title { get; set; }

    [RequiresOptIn("experimentalInstantApi")]
    public Instant? StartInstant { get; set; }

    [RequiresOptIn("experimentalInstantApi")]
    public Instant? EndInstant { get; set; }
}
```

Warning

Like `@deprecated`, you cannot apply `@requiresOptIn` to non-null arguments or input fields without a default value. Hiding a required field would break queries.

### Introspection

Consumers discover opt-in fields by passing the `includeOptIn` argument:

GraphQL

```
{
  __type(name: "Session") {
    fields(includeOptIn: ["experimentalInstantApi"]) {
      name
      requiresOptIn
    }
  }
}
```

The `includeOptIn` argument is available on `fields`, `args`, `inputFields`, `enumValues`, and `directives` in introspection queries. A directive definition exposes its own required features via `__Directive.requiresOptIn`, mirroring the `requiresOptIn` field on other introspection types.

To discover all opt-in features in the schema:

GraphQL

```
{
  __schema {
    optInFeatures
  }
}
```

### Feature Stability

You can declare the stability level of each opt-in feature. This helps consumers understand whether a feature is experimental, preview, or has some other status.

C#

```
builder
    .AddGraphQL()
    .ModifyOptions(o => o.EnableOptInFeatures = true)
    .OptInFeatureStability("experimentalInstantApi", "experimental");
```

Consumers query feature stability through introspection:

GraphQL

```
{
  __schema {
    optInFeatureStability {
      feature
      stability
    }
  }
}
```

## Next Steps

- **Need to add descriptions?** See [Documentation](https://chillicream.com/docs/hotchocolate/defining-a-schema/documentation).
- **Need to create custom directives?** See [Directives](https://chillicream.com/docs/hotchocolate/defining-a-schema/directives).
- **Need to understand schema evolution?** See [Extending Types](https://chillicream.com/docs/hotchocolate/defining-a-schema/object-types).
[Edit this page on GitHub](https://github.com/ChilliCream/graphql-platform/edit/main/website/content/docs/hotchocolate/defining-a-schema/versioning.md)

Maintained by ChilliCream. Last updated on **August 03, 2026** by **Glen**
