Request Limits
Unlike REST, where each endpoint has a predictable cost, a single GraphQL request can trigger unbounded work through deep nesting, alias amplification, or fragment expansion. Hot Chocolate enforces limits at every stage of request processing (parsing, validation, and execution) to keep resource consumption bounded even under adversarial workloads.
Parser Limits#
Parser limits stop malicious payloads before the AST is fully constructed. Because parsing happens before validation, these limits are your first line of defense.
Configure parser limits with ModifyParserOptions:
builder
.AddGraphQL()
.ModifyParserOptions(o =>
{
o.MaxAllowedFields = 1024;
o.MaxAllowedDirectives = 4;
o.MaxAllowedRecursionDepth = 100;
});| Option | Default | Description |
|---|---|---|
MaxAllowedFields | 2048 | Maximum number of fields allowed in a query document. When you raise it, also raise MaxAllowedSelections. |
MaxAllowedDirectives | 4 per location | Maximum number of directives allowed on a single location (field, operation, or fragment definition). |
MaxAllowedRecursionDepth | 200 | Maximum nesting depth the parser allows for selection sets, list values, object values, and type references. When you raise it, also raise MaxAllowedSelectionSetDepth. |
MaxAllowedNodes | Unlimited | Maximum number of AST nodes the parser produces from a document. Defaults to unlimited. |
MaxAllowedTokens | Unlimited | Maximum number of tokens the lexer processes. Defaults to unlimited. |
Validation Limits#
After parsing, the validation layer checks the document against your schema. Some validation rules can become expensive on adversarial inputs.
Execution Depth#
Limits how deeply nested a query can be:
builder
.AddGraphQL()
.AddMaxExecutionDepthRule(10);This prevents queries that traverse deep relationship chains (e.g., user.friends.friends.friends...). Unlike the parser recursion depth (which prevents stack overflows), execution depth measures the logical nesting of field selections against your schema.
You can skip introspection fields from the depth count and allow per-request overrides:
builder
.AddGraphQL()
.AddMaxExecutionDepthRule(
maxAllowedExecutionDepth: 10,
skipIntrospectionFields: true,
allowRequestOverrides: true);Selection Set Depth#
Hot Chocolate limits how deeply the selection sets of an operation can be nested with its fragment spreads followed. Fields with a selection set, inline fragments, and fragment spreads each add one level. Operations that nest deeper than 200 levels are rejected with the error code HC0138:
builder
.AddGraphQL()
.ModifyValidationOptions(o => o.MaxAllowedSelectionSetDepth = 100);If you raise the parser option MaxAllowedRecursionDepth, raise this limit as well.
Selection Count#
Hot Chocolate limits how many selections an operation has once its fragment spreads are followed. Fields, inline fragments, and fragment spreads each count as one selection, and the selections of a fragment count every time it is spread. This bounds a small document that expands into a very large operation through repeated fragment spreads. Operations with more than 100,000 selections are rejected with the error code HC0139:
builder
.AddGraphQL()
.ModifyValidationOptions(o => o.MaxAllowedSelections = 50_000);Fragment Visits#
Each time a visitor enters a fragment spread counts as one visit. Queries with deeply nested or repeated fragment spreads can cause exponential visitor work. An operation that exceeds the limit is rejected with the error code HC0137. Hot Chocolate caps the total number of fragment visits per operation at 1,000 by default:
builder
.AddGraphQL()
.ModifyValidationOptions(o =>
{
o.MaxAllowedFragmentVisits = 1_000;
});Field Merge Comparisons#
The "overlapping fields can be merged" validation rule checks that fields with the same response name have compatible types and arguments. On adversarial inputs with deeply nested fragments, this check can become expensive.
Hot Chocolate caps the comparison budget at 100,000 by default. Queries exceeding this budget are rejected:
builder
.AddGraphQL()
.SetMaxAllowedFieldMergeComparisons(50_000);Field Coordinate Cycles#
Some schemas contain self-referential relationships. For example, a User type with a friends field that returns [User]. Without a limit, a client can nest this relationship arbitrarily deep, causing resolver fan-out that grows exponentially with each level.
The field cycle depth rule tracks how many times each schema coordinate (e.g., User.friends) appears on the query path:
builder
.AddGraphQL()
.AddMaxAllowedFieldCycleDepthRule(defaultCycleLimit: 3);With a limit of 3, the following query is valid:
{
user {
friends {
# User.friends — cycle 1
friends {
# User.friends — cycle 2
friends {
# User.friends — cycle 3
name
}
}
}
}
}Adding a fourth level of friends would be rejected.
You can override the limit for specific coordinates if some relationships are safe to traverse more deeply than others:
builder
.AddGraphQL()
.AddMaxAllowedFieldCycleDepthRule(
defaultCycleLimit: 3,
coordinateCycleLimits:
[
(new SchemaCoordinate("Category", "parent"), 10),
]);This rule is enabled by default in non-development environments as part of the default security policy. You can remove it with RemoveMaxAllowedFieldCycleDepthRule() if your schema does not contain self-referential relationships.
Validation Errors#
Documents designed to generate excessive validation errors can consume memory accumulating error objects. The default limit is 5. When the limit is reached, validation stops early instead of continuing to accumulate errors.
builder
.AddGraphQL()
.SetMaxAllowedValidationErrors(5);Introspection Depth#
Introspection queries with recursive fields like __Type.ofType or __Type.fields can be used to construct expensive queries that consume significant server resources. The concern is not schema discovery (the schema is available at /graphql/schema.graphql by default when using MapGraphQL, computed once with no performance impact), but resource consumption from deeply recursive introspection operations.
Recursive introspection queries are limited by default:
ofTypechain: 16 levels- List fields (
fields,inputFields,interfaces,possibleTypes): 1 level of recursion
builder
.AddGraphQL()
.SetIntrospectionAllowedDepth(
maxAllowedOfTypeDepth: 8,
maxAllowedListRecursiveDepth: 1);Execution Limits#
Timeout#
Requests are aborted after 30 seconds by default. The timeout is not enforced when a debugger is attached.
builder
.AddGraphQL()
.ModifyRequestOptions(o =>
{
o.ExecutionTimeout = TimeSpan.FromSeconds(10);
});Nodes Batch Size#
The nodes(ids: [ID!]!) field allows fetching multiple entities at once. The default batch limit is 50:
builder
.AddGraphQL()
.AddGlobalObjectIdentification(o => o.MaxAllowedNodeBatchSize = 25);Entity Representations#
The Apollo Federation _entities(representations: [_Any!]!) field resolves one entity per representation. A request resolves at most 10,000 representations, counted across all of its _entities selections and variable sets. A selection that would exceed the limit is rejected with the error code HC0140:
builder
.AddGraphQL()
.AddApolloFederation(o => o.MaxAllowedRepresentations = 20_000);Next Steps#
- Need cost analysis? See Cost Analysis.
- Need trusted documents? See Trusted Documents.
- Need to control introspection? See Introspection.
- Back to overview? See Securing Your API.