Execution Engine

Learn about the Hot Chocolate v16 request execution pipeline, keyed middleware, and how to add custom middleware with precise ordering.

Overview

The Hot Chocolate execution engine processes GraphQL requests through a pipeline of request middleware. Each middleware handles one step of execution, such as parsing the document, validating semantics, or executing the operation. You can extend, replace, or reorder middleware in this pipeline.

Request Pipeline

When a GraphQL request arrives, the execution engine passes a RequestContext through a chain of middleware. Each middleware performs its work and then calls the next middleware in the chain. The default pipeline includes the following middleware, in order:

  1. InstrumentationMiddleware -- Records diagnostic events and telemetry
  2. ExceptionMiddleware -- Catches unhandled exceptions and converts them to GraphQL errors
  3. TimeoutMiddleware -- Enforces execution timeout
  4. DocumentCacheMiddleware -- Looks up previously parsed documents in the cache
  5. DocumentParserMiddleware -- Parses the GraphQL document from source text
  6. DocumentValidationMiddleware -- Validates the document against the schema
  7. OperationCacheMiddleware -- Looks up previously compiled operations in the cache
  8. OperationResolverMiddleware -- Resolves and compiles the operation from the document
  9. SkipWarmupExecutionMiddleware -- Short-circuits execution for warmup requests
  10. OperationVariableCoercionMiddleware -- Coerces variable values to their expected types
  11. OperationExecutionMiddleware -- Executes the compiled operation and produces a result

RequestContext

In v16, the IRequestContext interface has been replaced by the concrete RequestContext class. This class carries all request state through the pipeline.

Key properties on RequestContext:

PropertyTypeDescription
SchemaISchemaDefinitionThe schema the request executes against
RequestIOperationRequestThe incoming GraphQL request
RequestServicesIServiceProviderThe request-scoped service provider
OperationDocumentInfoOperationDocumentInfoParsed document metadata
RequestAbortedCancellationTokenCancellation token for the request
VariableValuesImmutableArray<IVariableValueCollection>Coerced variable value sets
ResultIExecutionResult?The execution result
ContextDataIDictionary<string, object?>Arbitrary request state
FeaturesIFeatureCollectionFeature collection for extensibility

OperationDocumentInfo

Document-related information that was previously scattered across IRequestContext properties is now consolidated into the OperationDocumentInfo class, accessible via RequestContext.OperationDocumentInfo.

PropertyTypeDescription
DocumentDocumentNode?The parsed GraphQL document
IdOperationDocumentIdUnique document identifier
HashOperationDocumentHashHash of the document
OperationCountintNumber of operation definitions in the document
IsCachedboolWhether the document was retrieved from the cache
IsPersistedboolWhether the document came from a persisted operation store
IsValidatedboolWhether the document has been validated

Keyed Middleware Pipeline

In v16, the request pipeline uses a keyed middleware system. Every built-in middleware has a unique key defined in WellKnownRequestMiddleware. This allows you to insert custom middleware at a precise position relative to any built-in middleware.

Adding Custom Middleware

Use the UseRequest() method on IRequestExecutorBuilder to add middleware. The method accepts optional key, before, and after parameters for positioning.

Append to the end of the pipeline

When you call UseRequest() without positioning parameters, the middleware is appended to the end of the pipeline:

C#
builder
.AddGraphQL()
.UseRequest(next => async context =>
{
// Custom logic before the next middleware
await next(context);
// Custom logic after the next middleware
});

Insert before a specific middleware

Use the before parameter with a WellKnownRequestMiddleware constant to insert your middleware before a built-in one:

C#
builder
.AddGraphQL()
.UseRequest(
middleware: next => async context =>
{
// Runs before document validation
await next(context);
},
key: "MyPreValidationMiddleware",
before: WellKnownRequestMiddleware.DocumentValidationMiddleware);

Insert after a specific middleware

Use the after parameter to insert your middleware after a built-in one:

C#
builder
.AddGraphQL()
.UseRequest(
middleware: next => async context =>
{
// Runs after document parsing completes
await next(context);
},
key: "MyPostParsingMiddleware",
after: WellKnownRequestMiddleware.DocumentParserMiddleware);

Prevent duplicate registration

Set allowMultiple to false (the default) so that if a middleware with the same key already exists, the registration is skipped:

C#
builder
.AddGraphQL()
.UseRequest(
middleware: next => async context =>
{
await next(context);
},
key: "MyMiddleware",
after: WellKnownRequestMiddleware.ExceptionMiddleware,
allowMultiple: false);

You can specify either before or after, but not both at the same time. If neither is specified, the middleware is appended to the end.

Using a Class-Based Middleware

You can define middleware as a class instead of a delegate. The class receives the next RequestDelegate in its constructor:

C#
public class MyRequestMiddleware
{
private readonly RequestDelegate _next;
public MyRequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async ValueTask InvokeAsync(RequestContext context)
{
// Pre-processing logic
await _next(context);
// Post-processing logic
}
}

Register it with precise positioning using the generic UseRequest<T>() overload:

C#
builder
.AddGraphQL()
.UseRequest<MyRequestMiddleware>(
key: "MyRequestMiddleware",
after: WellKnownRequestMiddleware.DocumentValidationMiddleware);

WellKnownRequestMiddleware Constants

The WellKnownRequestMiddleware static class provides constants for all built-in middleware keys:

ConstantDescription
InstrumentationMiddlewareDiagnostic events and telemetry
ExceptionMiddlewareUnhandled exception handling
TimeoutMiddlewareExecution timeout enforcement
DocumentCacheMiddlewareDocument cache lookup
DocumentParserMiddlewareGraphQL document parsing
DocumentValidationMiddlewareDocument validation
OperationCacheMiddlewareCompiled operation cache lookup
OperationResolverMiddlewareOperation resolution and compilation
SkipWarmupExecutionMiddlewareWarmup request short-circuit
OperationVariableCoercionMiddlewareVariable coercion
OperationExecutionMiddlewareOperation execution
ReadPersistedOperationMiddlewarePersisted operation lookup
WritePersistedOperationMiddlewarePersisted operation storage
PersistedOperationNotFoundMiddlewarePersisted operation not-found handling
AutomaticPersistedOperationNotFoundMiddlewareAPQ not-found handling
OnlyPersistedOperationsAllowedEnforce persisted-operations-only mode
AuthorizeRequestMiddlewareRequest authorization
PrepareAuthorizationMiddlewareAuthorization preparation
CostAnalyzerMiddlewareCost analysis

Field Middleware

Field middleware runs during field resolution, allowing you to add logic before or after a resolver executes. Field middleware is separate from request middleware and operates at the field level.

Learn more about field middleware

Resolver Compiler

The resolver compiler builds an optimized resolver pipeline for each field. You can customize it by providing parameter expression builders.

Next Steps

Last updated on April 13, 2026 by Michael Staib