Overview
Cost analysis prevents a single GraphQL request from using too many system resources and slowing everything else down. A query requesting deeply nested lists can generate thousands of resolver calls and overwhelm subgraphs, while a simple field lookup costs almost nothing. Cost analysis allows assigning weights to fields and estimating query complexity before execution begins. When cost analysis is enabled, the router calculates an estimated cost for each incoming operation based on the fields requested, their configured weights and expected list sizes. Operations exceeding the configured limit are rejected immediately. No subgraph requests are made, protecting the infrastructure from resource exhaustion. The Cosmo Router implements the IBM GraphQL Cost Directive Specification, adapted for GraphQL Federation.Key differences from the IBM specification
Weights use plain integers instead of stringified floats as defined in the IBM spec. The IBM specification does not account for federation. Static cost is calculated based on the query plan, which uses a federation of subgraphs to create a chain of fetches. This chain is invisible at the supergraph level and may include entity calls,@requires fetches, etc. These are accounted for because they make
certain queries more expensive.
When a field returns a list of objects with a type weight, the IBM spec does not multiply the type’s own weight
by the list size — only the children’s costs are multiplied. This implementation multiplies both the type weight
and children’s costs by the list size, resulting in higher cost estimates for list fields with weighted types.
This is done because federation may trigger entity fetches between subgraphs.
Currently, only static (estimated) costs are supported.
Dynamic (runtime) cost, calculated from actual data returned by subgraphs, is in active development.
How Cost is Calculated
The router walks through the query and sums the cost of each field. By default, object types (including interfaces and unions) cost1, scalar and enum fields cost 0.
For example, this query has a cost of 4:
List Fields Multiply Cost
When a field returns a list, the cost of that field and all its children is multiplied by the expected list size. Since the router cannot determine actual list sizes during planning, it uses estimates.10 × (1 Employee + 1 Department) = 20
Configuration
Cost analysis is enabled in the router configuration:| Option | Environment Variable | Default | Description |
|---|---|---|---|
enabled | SECURITY_COST_ANALYSIS_ENABLED | false | When true, the router calculates costs for every operation. |
mode | SECURITY_COST_ANALYSIS_MODE | measure | measure calculates costs only; enforce rejects operations exceeding the estimated limit. |
estimated_limit | SECURITY_COST_ANALYSIS_ESTIMATED_LIMIT | 0 | Maximum allowed estimated cost. Only enforced when mode is enforce. 0 disables enforcement. |
estimated_list_size | SECURITY_COST_ANALYSIS_ESTIMATED_LIST_SIZE | 10 | Default assumed size for list fields when no @listSize directive is specified. |
Customizing Cost with Directives
Default weights work for many APIs, but the@cost and @listSize directives allow fine-tuning.
The @cost Directive
@cost assigns custom weights to types, fields or arguments that are more expensive than average.
When specified on a type — all fields returning this type inherit the weight:
The @listSize Directive
@listSize provides better list size estimates than the global default.
Static size with assumedSize — when a field consistently returns a predictable number of items:
slicingArguments — when the list size is controlled by a pagination argument:
Accessing Cost in Custom Modules
In custom modules, the calculated cost is accessible through the operation context:- Apply rate limiting based on query cost
- Log expensive operations for analysis
- Support billing based on query complexity
- Throttle requests during high-load periods
Error Responses
When a query exceeds the estimated cost limit (in enforce mode), the router returns a 400 status:Best Practices
Start with Measure Mode
Start withmode: measure to calculate costs without rejecting queries.
This reveals traffic patterns before enabling enforcement.
Pick Parameters for @list_size Carefully
The default list size significantly impacts cost calculations. If lists typically return 5–20 items, a default of 10 is reasonable. For fields that return large lists (100+ items), consider:- Using
@listSize(assumedSize: N)on those specific fields - Requiring pagination with
slicingArguments
Annotate Expensive Resolvers
Apply@cost to fields that trigger expensive operations:
- External API calls
- Complex computations
- Large database queries
- Fields that fan out to multiple subgraphs
Design for Pagination
Pagination improves cost accuracy.slicingArguments provides precise multipliers based on client requests:
Nested Lists
Nested list fields require special attention, as costs multiply:@listSize to provide realistic estimates for deeply nested structures.
Features Not Yet Implemented
- Runtime (dynamic) cost measurement based on actual list sizes returned by subgraphs
- Cost telemetry — OTEL metrics and span attributes for cost values (
cost.estimated,cost.actual,cost.delta) sizedFieldsin @listSize for applying list size estimates to specific child fields rather than all childrenrequireOneSlicingArgumentfor validation of queries- Input object fields when used as parameters of fields
- Arguments of directives are not accounted for weights