GraphQL Complexity
Welcome to GraphQL-Complexity! This Python library provides functionality to compute the complexity of a GraphQL operation, contributing to better understanding and optimization of your GraphQL APIs. This library is designed to be stable, robust, and highly useful for developers working with GraphQL.
✨ Why GraphQL Complexity?
Protect your GraphQL API from expensive queries and potential DoS attacks by calculating query complexity before execution.
Watch the library live in our demo playground here .
Features
- Compute complexity of GraphQL queries
- Multiple built-in estimators for complexity computation
- Customizable estimators for specific use cases
- Support for Strawberry GraphQL library
Table of Contents
- Installation
- Getting Started
- Estimators
- Advanced Examples
- Framework Integration
- Real-World Use Cases
- Credits
Installation (Quick Start)
You can install the library via pip:
pip install graphql-complexity
For Strawberry GraphQL integration, use the following command:
pip install graphql-complexity[strawberry-graphql]
Getting Started
Create a file named complexity.py with the following content:
from graphql_complexity import get_complexity, SimpleEstimator
from graphql import build_schema
schema = build_schema("""
type User {
id: ID!
name: String!
}
type Query {
user: User
}
""")
query = """
query SomeQuery {
user {
id
name
}
}
"""
complexity = get_complexity(
query=query,
schema=schema,
estimator=SimpleEstimator(complexity=10)
)
if complexity > 10:
raise Exception("Query is too complex")
The library exposes the method get_complexity with the algorithm to compute the complexity of a GraphQL operation.
The algorithm visits each node of the query and computes the complexity of each field using an estimator.
Understanding Complexity Calculations with explain_complexity
The library provides an explain_complexity function that offers detailed insights into how complexity is calculated. This debugging tool returns the inner details that led to the final complexity value, making it invaluable for:
- Debugging expensive queries - Identify which fields contribute most to complexity
- Comparing different queries - Understand why one query is more expensive than another
- Learning the library - See exactly how the estimator evaluates each field
- Tuning estimators - Fine-tune your complexity configuration
from graphql_complexity import explain_complexity, SimpleEstimator
from graphql import build_schema
schema = build_schema("""
type User {
id: ID!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
}
type Query {
user: User
}
""")
query = """
query {
user {
id
name
posts {
id
title
}
}
}
"""
explanation = explain_complexity(
query=query,
schema=schema,
estimator=SimpleEstimator(complexity=5)
)
print(explanation)
Example Output:
================================================================================
GraphQL Complexity Explanation
================================================================================
Total Complexity: 35
Estimator Used:
Name: SimpleEstimator
Details:
complexity_constant: 5
Complexity Tree:
--------------------------------------------------------------------------------
root (RootNode) = 35
user (Field) = 35
id (Field) = 5
name (Field) = 5
posts (ListField) = 25
id (Field) = 5
title (Field) = 5
Field-by-Field Breakdown:
--------------------------------------------------------------------------------
user (Field)
Field complexity: 5
Children complexity: 30
Total: 35
user.id (Field)
Field complexity: 5
Children complexity: 0
Total: 5
user.name (Field)
Field complexity: 5
Children complexity: 0
Total: 5
user.posts (ListField) [multiplier: 1]
Field complexity: 5
Children complexity: 10
Total: 25
user.posts.id (Field)
Field complexity: 5
Children complexity: 0
Total: 5
user.posts.title (Field)
Field complexity: 5
Children complexity: 0
Total: 5
================================================================================
The explanation shows:
- Total Complexity: The final calculated value
- Estimator Details: Which estimator was used and its configuration
- Complexity Tree: A hierarchical view of how complexity accumulates
- Field Breakdown: Per-field analysis showing how each field contributes to the total
Estimators
GraphQL-Complexity provides various built-in estimators for computing query complexity:
SimpleEstimator
Estimate fields complexity based on constants for complexity and multiplier. This assigns a constant complexity value to each field and multiplies it by another constant, which is propagated along the depth of the query.
from graphql_complexity import SimpleEstimator
estimator = SimpleEstimator(complexity=2)
DirectivesEstimator
Define fields complexity using schema directives. This assigns a complexity value to each field and multiplies it by the depth of the query. It also supports the @complexity directive to assign a custom complexity value to a field.
from graphql_complexity import DirectivesEstimator
schema = """
directive @complexity(
value: Int!
) on FIELD_DEFINITION
type Query {
oneField: String @complexity(value: 5)
otherField: String @complexity(value: 1)
withoutDirective: String
}
"""
estimator = DirectivesEstimator(schema)
ArgumentsEstimator
Estimate complexity by multiplying a base value by a numeric argument (e.g. limit, first, ids).
This is useful when your API exposes pagination or batch arguments that directly drive the cost of a field.
- Int argument — the integer value is used as the multiplier (
limit: 10→ ×10). - List argument — the length of the list is used as the multiplier (
ids: ["a","b","c"]→ ×3). - No matching argument — the field gets
default_complexity(multiplier of 1).
from graphql_complexity import ArgumentsEstimator, get_complexity
from graphql import build_schema
schema = build_schema("""
type Query {
users(limit: Int): [User!]!
usersByIds(ids: [ID!]): [User!]!
}
type User {
id: ID!
name: String!
}
""")
estimator = ArgumentsEstimator(
multipliers=["limit", "ids"],
default_complexity=1,
)
# users(limit: 50) → complexity 50
complexity = get_complexity(
query="query { users(limit: 50) { id name } }",
schema=schema,
estimator=estimator,
)
print(complexity) # 52 (users=50, id=1, name=1)
# usersByIds(ids: ["a","b","c"]) → complexity 3
complexity = get_complexity(
query='query { usersByIds(ids: ["a", "b", "c"]) { id } }',
schema=schema,
estimator=estimator,
)
print(complexity) # 4 (usersByIds=3, id=1)
Custom estimator
Custom estimators can be defined to compute the complexity of a field using the ComplexityEstimator interface.
from graphql_complexity import ComplexityEstimator
class CustomEstimator(ComplexityEstimator):
def get_field_complexity(self, node, type_info, path) -> int:
if node.name.value == "specificField":
return 100
return 1
Advanced Examples
Handling Fragments
GraphQL fragments allow you to reuse query parts. Here's how complexity is calculated for queries with fragments:
from graphql_complexity import get_complexity, SimpleEstimator
from graphql import build_schema
schema = build_schema("""
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
users: [User!]!
}
""")
# Query with named fragment
query = """
query GetUsers {
users {
...UserFields
posts {
...PostFields
}
}
}
fragment UserFields on User {
id
name
email
}
fragment PostFields on Post {
id
title
content
}
"""
complexity = get_complexity(
query=query,
schema=schema,
estimator=SimpleEstimator(complexity=1)
)
print(f"Query complexity: {complexity}")
Complex Nested Queries
When dealing with nested relationships, complexity can grow exponentially. Here's how to handle it:
from graphql_complexity import get_complexity, DirectivesEstimator
from graphql import build_schema
schema = build_schema("""
directive @complexity(
value: Int!
) on FIELD_DEFINITION
type Organization {
id: ID!
name: String!
teams: [Team!]! @complexity(value: 5)
}
type Team {
id: ID!
name: String!
members: [User!]! @complexity(value: 3)
}
type User {
id: ID!
name: String!
email: String!
tasks: [Task!]! @complexity(value: 2)
}
type Task {
id: ID!
title: String!
description: String!
}
type Query {
organization(id: ID!): Organization
}
""")
# Deeply nested query
query = """
query GetOrgStructure {
organization(id: "1") {
id
name
teams {
id
name
members {
id
name
email
tasks {
id
title
description
}
}
}
}
}
"""
estimator = DirectivesEstimator(schema)
complexity = get_complexity(query=query, schema=schema, estimator=estimator)
# Set a reasonable limit
MAX_COMPLEXITY = 100
if complexity > MAX_COMPLEXITY:
raise Exception(f"Query too complex: {complexity} > {MAX_COMPLEXITY}")
Working with Arguments
Field arguments can significantly impact query complexity, especially with pagination. The built-in ArgumentsEstimator handles the common case where a numeric argument (like first or limit) or a list argument (like ids) directly drives the cost of a field:
from graphql_complexity import ArgumentsEstimator, get_complexity
from graphql import build_schema
schema = build_schema("""
type Product {
id: ID!
name: String!
price: Float!
}
type Query {
products(first: Int, offset: Int): [Product!]!
product(id: ID!): Product
}
""")
estimator = ArgumentsEstimator(multipliers=["first"], default_complexity=1)
query = """
query GetProducts {
products(first: 50) {
id
name
price
}
}
"""
complexity = get_complexity(query=query, schema=schema, estimator=estimator)
print(f"Query complexity with pagination: {complexity}")
# products(first: 50) → 50, id/name/price → 1 each → total: 53
For more fine-grained control (e.g. capping limits or applying field-specific multipliers), use a Custom Estimator instead.
Combining Multiple Estimators
You can create sophisticated complexity analysis by combining estimators:
from graphql_complexity import (
ComplexityEstimator,
SimpleEstimator,
DirectivesEstimator,
get_complexity
)
from graphql import build_schema
schema = build_schema("""
directive @complexity(value: Int!) on FIELD_DEFINITION
type User {
id: ID!
name: String!
expensiveComputation: String! @complexity(value: 50)
posts: [Post!]!
}
type Post {
id: ID!
title: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
""")
class CompositeEstimator(ComplexityEstimator):
"""Combines multiple estimation strategies"""
def __init__(self):
self.directive_estimator = DirectivesEstimator(schema)
self.simple_estimator = SimpleEstimator(complexity=1)
def get_field_complexity(self, node, type_info, path) -> int:
# First, try directive-based estimation
directive_complexity = self.directive_estimator.get_field_complexity(
node, type_info, path
)
# If directive provides a value, use it
if directive_complexity > 1:
return directive_complexity
# Otherwise, fall back to simple estimation
return self.simple_estimator.get_field_complexity(node, type_info, path)
query = """
query GetUserData {
user(id: "1") {
id
name
expensiveComputation
posts {
id
title
}
}
}
"""
complexity = get_complexity(
query=query,
schema=schema,
estimator=CompositeEstimator()
)
print(f"Combined estimator complexity: {complexity}")
Framework Integration
Strawberry GraphQL
The library is compatible with strawberry-graphql. Use the following command to install the library with Strawberry support:
poetry install --extras strawberry-graphql
To use the library with Strawberry GraphQL, use the build_complexity_extension method to build the complexity
extension and add it to the schema. This method receives an estimator and returns a complexity extension that can be added to the schema.
import strawberry
from graphql_complexity import SimpleEstimator
from graphql_complexity.extensions.strawberry_graphql import build_complexity_extension
@strawberry.type
class Query:
@strawberry.field()
def hello_world(self) -> str:
return "Hello world!"
extension = build_complexity_extension(estimator=SimpleEstimator())
schema = strawberry.Schema(query=Query, extensions=[extension])
schema.execute_sync("query { helloWorld }")
The build_complexity_extension method accepts an estimator as optional argument giving the possibility to use one
of the built-in estimators or a custom estimator.
Django Integration
Integrate complexity analysis with Django and Graphene:
# myapp/complexity.py
from graphql_complexity import get_complexity, SimpleEstimator
from graphql import GraphQLError
class ComplexityMiddleware:
def __init__(self, max_complexity=1000):
self.max_complexity = max_complexity
self.estimator = SimpleEstimator(complexity=1)
def resolve(self, next, root, info, **args):
# Calculate complexity on first field resolution
if not hasattr(info.context, '_complexity_checked'):
try:
complexity = get_complexity(
query=info.operation.loc.source.body,
schema=info.schema,
estimator=self.estimator
)
if complexity > self.max_complexity:
raise GraphQLError(
f"Query is too complex: {complexity}. "
f"Maximum allowed complexity: {self.max_complexity}"
)
info.context._complexity_checked = True
info.context._query_complexity = complexity
except Exception as e:
raise GraphQLError(f"Complexity analysis failed: {str(e)}")
return next(root, info, **args)
# settings.py
GRAPHENE = {
'MIDDLEWARE': [
'myapp.complexity.ComplexityMiddleware',
],
}
FastAPI Integration
Use complexity analysis with FastAPI and Strawberry:
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
import strawberry
from graphql_complexity import SimpleEstimator
from graphql_complexity.extensions.strawberry_graphql import build_complexity_extension
@strawberry.type
class User:
id: strawberry.ID
name: str
email: str
@strawberry.type
class Query:
@strawberry.field
def user(self, id: strawberry.ID) -> User:
return User(id=id, name="John Doe", email="john@example.com")
@strawberry.field
def users(self) -> list[User]:
return [
User(id="1", name="John Doe", email="john@example.com"),
User(id="2", name="Jane Smith", email="jane@example.com"),
]
# Configure complexity limit
MAX_COMPLEXITY = 1000
extension = build_complexity_extension(
estimator=SimpleEstimator(complexity=1),
max_complexity=MAX_COMPLEXITY
)
schema = strawberry.Schema(query=Query, extensions=[extension])
graphql_app = GraphQLRouter(schema)
app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")
Flask Integration
Integrate with Flask-GraphQL:
from flask import Flask, request, jsonify
from flask_graphql import GraphQLView
from graphql import build_schema, GraphQLError
from graphql_complexity import get_complexity, SimpleEstimator
app = Flask(__name__)
schema = build_schema("""
type User {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
""")
MAX_COMPLEXITY = 1000
estimator = SimpleEstimator(complexity=1)
def validate_complexity(query_string):
"""Validate query complexity before execution"""
try:
complexity = get_complexity(
query=query_string,
schema=schema,
estimator=estimator
)
if complexity > MAX_COMPLEXITY:
raise GraphQLError(
f"Query exceeds complexity limit. "
f"Query complexity: {complexity}, Max allowed: {MAX_COMPLEXITY}"
)
return complexity
except Exception as e:
raise GraphQLError(f"Complexity validation error: {str(e)}")
class ComplexityGraphQLView(GraphQLView):
def dispatch_request(self):
# Get the query from request
data = request.get_json()
query = data.get('query', '')
# Validate complexity
try:
complexity = validate_complexity(query)
# Store complexity in request context for logging
request.query_complexity = complexity
except GraphQLError as e:
return jsonify({'errors': [{'message': str(e)}]}), 400
# Proceed with normal GraphQL execution
return super().dispatch_request()
app.add_url_rule(
'/graphql',
view_func=ComplexityGraphQLView.as_view(
'graphql',
schema=schema,
graphiql=True
)
)
if __name__ == '__main__':
app.run(debug=True)
🍳 Recipes
1. Reject a query before execution
The most common pattern — block expensive queries before they hit your resolvers.
from graphql_complexity import get_complexity, SimpleEstimator
MAX_COMPLEXITY = 10
def execute_query(schema, query):
complexity = get_complexity(
query=query,
schema=schema,
estimator=SimpleEstimator(complexity=1)
)
if complexity > MAX_COMPLEXITY:
raise Exception(f"Query too complex ({complexity}). Max allowed: {MAX_COMPLEXITY}")
return schema.execute(query)
2. Set different limits per user role
Give trusted users more headroom without opening the door to abuse.
COMPLEXITY_LIMITS = {
"anonymous": 5,
"authenticated": 15,
"admin": 50,
}
def execute_query(schema, query, user_role="anonymous"):
complexity = get_complexity(
query=query,
schema=schema,
estimator=SimpleEstimator(complexity=1)
)
limit = COMPLEXITY_LIMITS.get(user_role, 5)
if complexity > limit:
raise Exception(f"Query too complex ({complexity}). Max allowed for {user_role}: {limit}")
return schema.execute(query)
3. Log complexity without blocking
Useful when rolling out complexity limits gradually — monitor first, enforce later.
import logging
from graphql_complexity import get_complexity, SimpleEstimator
logger = logging.getLogger(__name__)
WARN_THRESHOLD = 10
def execute_query(schema, query):
complexity = get_complexity(
query=query,
schema=schema,
estimator=SimpleEstimator(complexity=1)
)
if complexity > WARN_THRESHOLD:
logger.warning(f"High complexity query detected: {complexity}")
# Always execute — no blocking yet
return schema.execute(query)
Tip: Use this pattern in production for a week to understand your traffic’s complexity distribution before choosing your enforcement threshold.
Real-World Use Cases
E-commerce Platform
Protect your e-commerce API from expensive queries:
from graphql_complexity import ComplexityEstimator, get_complexity
from graphql import build_schema
schema = build_schema("""
directive @complexity(value: Int!) on FIELD_DEFINITION
type Product {
id: ID!
name: String!
description: String!
price: Float!
reviews: [Review!]! @complexity(value: 10)
relatedProducts: [Product!]! @complexity(value: 15)
}
type Review {
id: ID!
rating: Int!
comment: String!
author: User!
}
type User {
id: ID!
name: String!
orders: [Order!]! @complexity(value: 20)
}
type Order {
id: ID!
items: [Product!]!
total: Float!
}
type Query {
product(id: ID!): Product
products(limit: Int, offset: Int): [Product!]!
}
""")
# This query would be too expensive
expensive_query = """
query ExpensiveQuery {
products(limit: 100) {
id
name
reviews {
author {
orders {
items {
relatedProducts {
reviews {
comment
}
}
}
}
}
}
}
}
"""
# Reasonable query
reasonable_query = """
query ReasonableQuery {
product(id: "123") {
id
name
price
description
}
}
"""
Social Media API
Rate limit complex social graph queries:
from graphql_complexity import ComplexityEstimator, get_complexity
from graphql import build_schema
schema = build_schema("""
directive @complexity(value: Int!) on FIELD_DEFINITION
type User {
id: ID!
username: String!
followers: [User!]! @complexity(value: 10)
following: [User!]! @complexity(value: 10)
posts: [Post!]! @complexity(value: 5)
}
type Post {
id: ID!
content: String!
author: User!
likes: [User!]! @complexity(value: 5)
comments: [Comment!]! @complexity(value: 3)
}
type Comment {
id: ID!
text: String!
author: User!
}
type Query {
user(id: ID!): User
feed(limit: Int): [Post!]!
}
""")
class SocialMediaEstimator(ComplexityEstimator):
"""Custom estimator for social media queries"""
def get_field_complexity(self, node, type_info, path) -> int:
field_name = node.name.value
# Heavy penalty for deeply nested social graphs
depth = len(path)
depth_penalty = 2 ** depth if depth > 3 else 1
# Base costs
costs = {
'followers': 10,
'following': 10,
'posts': 5,
'likes': 5,
'comments': 3,
'feed': 8,
}
base_cost = costs.get(field_name, 1)
return base_cost * depth_penalty
# Example: Preventing graph explosion
problematic_query = """
query DeepSocialGraph {
user(id: "1") {
followers {
following {
posts {
likes {
followers {
# This gets exponentially expensive!
username
}
}
}
}
}
}
}
"""
Analytics Dashboard
Manage complexity for data-heavy analytics queries:
from graphql_complexity import ComplexityEstimator, get_complexity
from graphql import build_schema
from datetime import datetime
schema = build_schema("""
directive @complexity(value: Int!) on FIELD_DEFINITION
type Analytics {
pageViews: Int! @complexity(value: 5)
uniqueVisitors: Int! @complexity(value: 5)
averageSessionDuration: Float! @complexity(value: 10)
conversionRate: Float! @complexity(value: 15)
revenueByDay: [DailyRevenue!]! @complexity(value: 20)
}
type DailyRevenue {
date: String!
amount: Float!
transactions: Int!
}
type Query {
analytics(
startDate: String!
endDate: String!
granularity: String
): Analytics @complexity(value: 10)
}
""")
def calculate_analytics_complexity(query: str, date_range_days: int) -> int:
"""Calculate complexity based on date range"""
base_complexity = get_complexity(
query=query,
schema=schema,
estimator=SimpleEstimator(complexity=1)
)
# Multiply by date range (more days = more expensive)
range_multiplier = max(1, date_range_days / 7) # Weekly baseline
return int(base_complexity * range_multiplier)
Credits
Estimators idea was heavily inspired by graphql-query-complexity.
About
Python library to compute the complexity of a GraphQL operation
Topics
python graphql strawberry graphql-core
License
Metadata
Release files for graphql-complexity 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| graphql_complexity-1.0.0.tar.gz | 21.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| graphql_complexity-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.7 kB
Release files / graphql_complexity-1.0.0.tar.gz
| Download URL | graphql_complexity-1.0.0.tar.gz |
|---|---|
| Size | 21.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5dfe13ab7c105f24fea16070900b557145e87717f64b92598dcd8035f4212aec
|
|
BLAKE2b-256 checksum How to use checksums |
45f49d02bc41c62055606de7368863690aab1ded661c9512e14bd40d0e42132c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 19, 2026.
Transparency logRelease files / graphql_complexity-1.0.0-py3-none-any.whl
| Download URL | graphql_complexity-1.0.0-py3-none-any.whl |
|---|---|
| Size | 20.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cd0c7176cb319b0750bda57147248114d83f2570dfdb7c6db956a6084e70a273
|
|
BLAKE2b-256 checksum How to use checksums |
ba1b58e6666b85a026a4d9ab524250861055ee1f550181b11d0ee37d0b28a1bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 19, 2026.
Transparency logSecurity for graphql-complexity 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for graphql_complexity-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.