graphql-query-complexity puts a complexity ceiling on a GraphQL schema
GraphQL query complexity analysis and validation for graphql-js
At a glance
- What is it?
- graphql-query-complexity is a graphql-js validation rule that scores a query before it runs and rejects it above a threshold, with a chain of pluggable estimators deciding what each field costs. The trade is that the score is only as good as the estimators you configure, and a default of one per field is not a cost model.
- Who is it for?
- graphql-query-complexity suits a GraphQL service that is publicly reachable and wants a cheap guard against a client sending a query with thousands of nested selections, and it suits teams that can describe the real cost of their fields with directives or field extensions. It does not suit a schema whose fields all cost about the same, where the estimator chain adds configuration without adding signal, and it does not replace resolver-level timeouts.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 94 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A query is cheap to write and expensive to resolve
The problem is a specific shape of abuse that only GraphQL invites. A REST endpoint has a fixed number of routes, so the work a client can request is bounded by what the API designer wrote. A GraphQL endpoint accepts an arbitrary document, and a single field can pull in a list of objects, each of which exposes more fields that pull in more lists. A client can ask for a modest looking tree that expands into millions of resolver calls, and the server does the work. This library provides GraphQL query analysis to reject complex queries to your server, and the README states the purpose directly: it protects GraphQL servers against resource exhaustion and denial of service attacks. The audience is a team running a public or semi-public GraphQL API, and the alternative to using it is either an unqueried field limit or a timeout that fires after the server has already done the expensive part. Installation is a single npm command:
npm install -S graphql-query-complexityIt is a graphql-js validation rule, so it runs before resolvers
The mechanism is a validation rule, and that placement is the important design decision. The library works with the graphql-js reference implementation, and what you create is a rule that the server's validation phase runs against the incoming document. Because validation happens before execution, a query that is too complex is refused without a single resolver being called, and the rejection is a normal GraphQL error rather than a timeout or a crashed process. The rule is created with createComplexityRule and a maximum complexity value, and the README notes that queries above that threshold are rejected. The score itself comes from a chain of estimators, which is where the real work of the configuration sits.
The estimator chain decides what a field costs
The complexity calculation is customised with what the README calls complexity estimators, and a complexity estimator is simply a function that calculates the complexity for a field. You can add any number of them to the rule, and they are executed one after another; the first estimator that returns a numeric value determines the complexity for that field. At least one estimator has to return a value, otherwise an exception is raised. Three are documented. simpleEstimator returns a fixed complexity for each field and is meant to sit last in the chain as a default. directiveEstimator sets the complexity via a directive in the schema definition, for example through GraphQL SDL. fieldExtensionsEstimator lets you set a numeric value or a custom estimator function in the field config extensions of your schema. The README also invites pull requests for more, which tells you the set is expected to grow.
Creating the rule takes one object with a dozen knobs
The configuration is a single object, and the shape of it is worth reading closely because several of the options are about observability rather than rejection. The rule takes maximumComplexity, the variables object, which is needed because the variables are not available in the visitor of the graphql-js library, an optional context object for the request, an optional operationName for evaluating multi-operation documents, and maxQueryNodes, the maximum number of query nodes to evaluate, counting fields, fragments and composite types. There is also an onComplete callback that receives the determined complexity and is invoked whether the query is rejected or not, so it can be used for logging or to implement rate limiting, and a createError callback that builds the error object with the maximum and the actual value. The estimator array closes the object. Here is the rule as the README documents it:
import {
createComplexityRule,
simpleEstimator
} from 'graphql-query-complexity';
const rule = createComplexityRule({
// The maximum allowed query complexity, queries above this threshold will be rejected
maximumComplexity: 1_000,
// The query variables. This is needed because the variables are not available
// in the visitor of the graphql-js library
variables: {},
// The context object for the request (optional)
context: {}maxQueryNodes is the guard that stops the estimator itself
One option deserves attention because it protects the analysis, not the resolver. maxQueryNodes is the maximum number of query nodes to evaluate, fields, fragments and composite types, and if a query contains more than that number of nodes the complexity rule throws an error regardless of the complexity of the query. The default is 10,000. This is the piece that makes the library safe rather than merely useful. Without it, a client could send a document with a hundred thousand aliased fields, each of which the estimator has to walk, and the cost of computing the cost would itself become the denial of service. The distinction between the two mechanisms is worth holding on to. maximumComplexity stops a query that is legitimately shaped but too expensive, and maxQueryNodes stops a query that is shaped to attack the analyser.
onComplete fires whether the query is accepted or rejected
Most rate limiting in a GraphQL server is done on requests, and this library points at a different unit. The onComplete callback receives the complexity number and is invoked whether the query is rejected or not, and the README says explicitly that it can be used for logging or to implement rate limiting. That makes the complexity score usable as a currency rather than only as a threshold. A resolver that returns a page of ten records and a resolver that fans out to a hundred downstream services do not cost the same, and a score that reflects that can be summed across a session or charged against a budget. The library does not implement that accounting for you, but it does give you the number, and it does so for rejected queries too, which is exactly when you want to see it. The createError callback is the companion hook, letting you shape the message your clients receive.
getComplexity scores a query outside the validation phase
There is a second entry point for cases where you want the number but not the rejection. The README describes using getComplexity to calculate the complexity of a query outside of the validation phase, for example to return the complexity value in a resolver. The call takes the estimators, the schema, the parsed query and the variables, and the example parses a document with the parse function, using a query with a count variable so the variables are concrete at scoring time. The example prints a complexity of 3 for that document, and the call is wrapped in a try and catch because the complexity cannot be calculated for an invalid query or a misconfiguration. This is the hook for a service that wants to expose the cost of a query in a response, or that wants to score a candidate query before committing to it.
A custom estimator receives the type, the field and the child complexity
Writing your own estimator means implementing a function with a fixed signature, and the arguments are more informative than the one-line description of an estimator suggests. The options object carries the composite type, interface, object or union, that the evaluated field belongs to, the GraphQLField being evaluated, the GraphQL node being evaluated as a FieldNode, the input arguments of the field, the complexity of all child selections for that field, and the context object for the request if one was provided. The child complexity figure is the part that makes estimation tractable, because a parent field's cost can be expressed as a function of what its children cost. The signature is:
type ComplexityEstimatorArgs = {
// The composite type (interface, object, union) that the evaluated field belongs to
type: GraphQLCompositeType;
// The GraphQLField that is being evaluated
field: GraphQLField<any, any>;
// The GraphQL node that is being evaluated
node: FieldNode;
// The input arguments of the field
args: { [key: string]: any };
Ships as dual CommonJS and ESM builds for graphql 16 and 17
The distribution details matter for anyone integrating this into an existing service. The package is graphql-query-complexity at version 2.0.0, published to npm, and the package.json declares a CommonJS entry point at dist/cjs/index.js with types at dist/cjs/index.d.ts and an ESM entry point at dist/esm/index.js, with an exports map that routes import and require to the two builds. The peer dependency is graphql at ^16.6.0 or ^17.0.0, and the only runtime dependency is lodash.get. The build is plain TypeScript with a separate tsconfig for each target, and the scripts run lint, build the ESM and CJS outputs, then run the test suite twice, once per module system, with Mocha and the check-leaks flag. The project was originally developed as part of the Slicknode GraphQL Framework and Headless CMS, and the README credits query complexity analysis in the Sangria GraphQL project as prior art.
Editorial conclusion
graphql-query-complexity suits a GraphQL service that is publicly reachable and wants a cheap guard against a client sending a query with thousands of nested selections, and it suits teams that can describe the real cost of their fields with directives or field extensions. It does not suit a schema whose fields all cost about the same, where the estimator chain adds configuration without adding signal, and it does not replace resolver-level timeouts. Before adopting, set maximumComplexity to a value derived from your own schema rather than a round number, decide whether the default simpleEstimator is enough, and note that the last push to the repository was on 2026-07-23 with v2.0.0 released the same day.
Frequently asked questions
How do I install graphql-query-complexity?
Install it from npm with `npm install -S graphql-query-complexity`. The peer dependency is graphql at ^16.6.0 or ^17.0.0, so graphql-js has to be present in your project already.
How do I set the maximum complexity for a GraphQL query?
Create the rule with createComplexityRule and pass maximumComplexity, the value above which queries are rejected. You also need at least one estimator in the estimators array, usually simpleEstimator({ defaultComplexity: 1 }) as the last entry in the chain.
What is maxQueryNodes and why does it exist?
It is the maximum number of query nodes to evaluate, counting fields, fragments and composite types, and it defaults to 10,000. If a query contains more nodes than that, the complexity rule throws an error regardless of the complexity of the query.
Can I calculate query complexity without rejecting the query?
Yes. getComplexity calculates the complexity of a query outside the validation phase, taking the estimators, schema, parsed query and variables, which is useful for returning the complexity value in a resolver or logging it.
Which complexity estimators are available?
simpleEstimator returns a fixed complexity per field and works as the last estimator in the chain, directiveEstimator sets the complexity through a directive in the schema definition, and fieldExtensionsEstimator reads a numeric value or custom function from the field config extensions of your schema.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/slicknode-graphql-query-complexity)Was this useful?