website/src/docs/hotchocolate/v16/execution-engine/index.md
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.
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:
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:
| Property | Type | Description |
|---|---|---|
Schema | ISchemaDefinition | The schema the request executes against |
Request | IOperationRequest | The incoming GraphQL request |
RequestServices | IServiceProvider | The request-scoped service provider |
OperationDocumentInfo | OperationDocumentInfo | Parsed document metadata |
RequestAborted | CancellationToken | Cancellation token for the request |
VariableValues | ImmutableArray<IVariableValueCollection> | Coerced variable value sets |
Result | IExecutionResult? | The execution result |
ContextData | IDictionary<string, object?> | Arbitrary request state |
Features | IFeatureCollection | Feature collection for extensibility |
Document-related information that was previously scattered across IRequestContext properties is now consolidated into the OperationDocumentInfo class, accessible via RequestContext.OperationDocumentInfo.
| Property | Type | Description |
|---|---|---|
Document | DocumentNode? | The parsed GraphQL document |
Id | OperationDocumentId | Unique document identifier |
Hash | OperationDocumentHash | Hash of the document |
OperationCount | int | Number of operation definitions in the document |
IsCached | bool | Whether the document was retrieved from the cache |
IsPersisted | bool | Whether the document came from a persisted operation store |
IsValidated | bool | Whether the document has been validated |
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.
Use the UseRequest() method on IRequestExecutorBuilder to add middleware. The method accepts optional key, before, and after parameters for positioning.
When you call UseRequest() without positioning parameters, the middleware is appended to the end of the pipeline:
builder
.AddGraphQL()
.UseRequest(next => async context =>
{
// Custom logic before the next middleware
await next(context);
// Custom logic after the next middleware
});
Use the before parameter with a WellKnownRequestMiddleware constant to insert your middleware before a built-in one:
builder
.AddGraphQL()
.UseRequest(
middleware: next => async context =>
{
// Runs before document validation
await next(context);
},
key: "MyPreValidationMiddleware",
before: WellKnownRequestMiddleware.DocumentValidationMiddleware);
Use the after parameter to insert your middleware after a built-in one:
builder
.AddGraphQL()
.UseRequest(
middleware: next => async context =>
{
// Runs after document parsing completes
await next(context);
},
key: "MyPostParsingMiddleware",
after: WellKnownRequestMiddleware.DocumentParserMiddleware);
Set allowMultiple to false (the default) so that if a middleware with the same key already exists, the registration is skipped:
builder
.AddGraphQL()
.UseRequest(
middleware: next => async context =>
{
await next(context);
},
key: "MyMiddleware",
after: WellKnownRequestMiddleware.ExceptionMiddleware,
allowMultiple: false);
You can specify either
beforeorafter, but not both at the same time. If neither is specified, the middleware is appended to the end.
You can define middleware as a class instead of a delegate. The class receives the next RequestDelegate in its constructor:
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:
builder
.AddGraphQL()
.UseRequest<MyRequestMiddleware>(
key: "MyRequestMiddleware",
after: WellKnownRequestMiddleware.DocumentValidationMiddleware);
The WellKnownRequestMiddleware static class provides constants for all built-in middleware keys:
| Constant | Description |
|---|---|
InstrumentationMiddleware | Diagnostic events and telemetry |
ExceptionMiddleware | Unhandled exception handling |
TimeoutMiddleware | Execution timeout enforcement |
DocumentCacheMiddleware | Document cache lookup |
DocumentParserMiddleware | GraphQL document parsing |
DocumentValidationMiddleware | Document validation |
OperationCacheMiddleware | Compiled operation cache lookup |
OperationResolverMiddleware | Operation resolution and compilation |
SkipWarmupExecutionMiddleware | Warmup request short-circuit |
OperationVariableCoercionMiddleware | Variable coercion |
OperationExecutionMiddleware | Operation execution |
ReadPersistedOperationMiddleware | Persisted operation lookup |
WritePersistedOperationMiddleware | Persisted operation storage |
PersistedOperationNotFoundMiddleware | Persisted operation not-found handling |
AutomaticPersistedOperationNotFoundMiddleware | APQ not-found handling |
OnlyPersistedOperationsAllowed | Enforce persisted-operations-only mode |
AuthorizeRequestMiddleware | Request authorization |
PrepareAuthorizationMiddleware | Authorization preparation |
CostAnalyzerMiddleware | Cost analysis |
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
The resolver compiler builds an optimized resolver pipeline for each field. You can customize it by providing parameter expression builders.