API-96Required
Limit the depth and cost of every GraphQL query
Before a GraphQL query runs, measure its depth, its aliases and its estimated cost, and refuse it above fixed caps. Start with depth 10, 15 aliases and a cost of 1,000.
How to do it
Skip this rule when the server runs only known queries from your own apps. An open endpoint lets one short query nest lists inside lists, so 10 levels of 10 friends ask for 10 billion records. Give each field a weight, and multiply list fields by their first or limit argument. Add the weights and compare the total with the cap during validation, before any resolver runs. Also cap the operations in one batch and the total time of one request. Name the caps in the schema docs, and return the cost in the extensions of each answer, so clients can stay below it. One request can hold hundreds of operations through aliases or a batch. Count each login, code check or other costly field as its own attempt inside the resolver. In production, turn off introspection and field suggestions unless the API is public. Check ownership in the resolver of every object, as check ownership on every record says, including objects reached through node and nested fields.
Examples
JavaScript example
import { createComplexityRule, simpleEstimator, fieldExtensionsEstimator } from 'graphql-query-complexity';
import depthLimit from 'graphql-depth-limit';
const MAX_DEPTH = 10;
const MAX_COST = 1000;
export const validationRules = [
depthLimit(MAX_DEPTH),
createComplexityRule({
maximumComplexity: MAX_COST,
estimators: [fieldExtensionsEstimator(), simpleEstimator({ defaultComplexity: 1 })],
}),
];
Python example
from strawberry.extensions import MaxAliasesLimiter, QueryDepthLimiter
MAX_DEPTH = 10
MAX_ALIASES = 15
schema = strawberry.Schema(
query=Query,
extensions=[QueryDepthLimiter(max_depth=MAX_DEPTH), MaxAliasesLimiter(max_alias_count=MAX_ALIASES)],
)
How to check
Send a query nested 11 levels deep, one with 16 aliases and one whose first arguments multiply past the cost cap. Each is refused before any resolver runs, with an error code such as QUERY_TOO_COMPLEX, and the database log shows no query for it. In production, { __schema { types { name } } } and a misspelled field return no type names or suggestions. One request with 50 aliased logins for a test account is limited like 50 separate logins. As test user A, a request for user B's record through a nested field or node(id:) returns no data.
Sources
Related rules
- API-93 Cap the size of every request body in API design
- API-81 Accept GraphQL mutations only by POST with a JSON body in API design
- API-83 Page GraphQL lists as cursor connections in API design
- API-86 Allow only known queries from your own apps in API design
- ACC-77 Check ownership on every record in Accounts and identity
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/api-design/graphql-and-grpc/graphql-cost-limits">Agent Wranglers API design playbook</a>. In code you write, add a comment next to it with the same link.