Contextual Arguments
Contextual arguments let a field receive data from one of its ancestors in the response, without
the client ever sending it. A type declares a named context with @context, and a field argument
further down the graph declares where its value comes from with @fromContext. Hive Router fills in
the argument value from the nearest ancestor that sets the matching context.
This solves a common problem in federated schemas: a deeply nested field sometimes depends on data
that lives higher up in the response. Without contexts, you’d have to either expose that data as a
public argument the client must repeat, or push it into entity @keys so that every type along the
way carries data it doesn’t otherwise need.
Contextual arguments are part of Federation v2.8 and are supported by both Hive Router and Hive’s native composition.
Defining a context
Import @context and @fromContext in your subgraph schema:
@context(name: "userContext")onUsermakes everyUserobject in a response a provider of theuserContextcontext.@fromContext(field: "$userContext { currency }")on thecodeargument says: “take thecurrencyfield of the nearestuserContextprovider above me and use it ascode”.
The code argument is removed from the public API schema, together with the context directives.
Clients see only this:
Querying contextual fields
Clients query contextual fields like any other field:
Given two users, Alice with currency: "USD" and Bob with currency: "EUR", the subgraph resolves
Alice’s transactions with code: "USD" and Bob’s transactions with code: "EUR". Every entity
gets the value of its own ancestor.
How the router resolves contextual arguments
The router plans a contextual argument similar to a @requires, except that the required data
lives on an ancestor instead of the parent:
- Fetch the context value (
currency) together with the ancestor (User). - Fetch the contextual field (
amount) in a separate entity request after that. - For each entity, read the context value from the response and send it as a variable.
This is the query plan for the operation above:
Even though both fields live in the same subgraph, amount is fetched through a second _entities
request. The context value must exist in the response before the router can send it.
Context rewrites in the query plan
The second fetch tells the executor where to find the context value relative to each entity. In the
JSON representation of the query plan, the fetch has contextVariables and contextRewrites:
Read the rewrite as: go up one object (Parent), check that it’s a User, read its currency, and
send the value as $__context_0.
The nearest ancestor wins
When the same context is set several times along a path, the value always comes from the closest ancestor. Consider users that have friends, who are users too:
Each transaction gets the currency of the friend it belongs to, not of the outer user.
If the nearest ancestor can’t provide the value, the field fails to plan. The router doesn’t fall back to an ancestor further up. This follows the Federation specification and matches Apollo Router’s behavior.
Contexts on interfaces and unions
A context can also be set on an interface or a union. Use type conditions in the selection to read fields from the concrete type the ancestor turns out to be:
Only the branch matching the actual type of the ancestor is used. Transactions of a User get the
user’s currency, and transactions of an Organization get the organization’s currency.
Contexts across subgraphs
The ancestor providing the context and the contextual field can live in different subgraphs. The
subgraph defining the contextual field declares the context fields it reads as @external:
For this query:
The router fetches the currency from accounts, then resolves lastTransaction and the contextual
field in a single request to transactions:
The query planner merges contextual fetches into other fetches whenever it’s safe to do so. Here,
the contextual fetch for Transaction merges into the entity fetch for its ancestor User, which
saves a round trip. A contextual fetch is never merged with the fetch producing its context value,
since the value has to exist before the request is sent.
One request for many context values
Contextual arguments make entity fetching harder. Entities in a list usually share one _entities
field, but entities whose ancestors hold different context values can’t, because each needs a
different argument value. The straightforward approach is to send one subgraph request per distinct
value. A list of users in 20 currencies then turns one entity fetch into 20 HTTP requests to the
same subgraph, and the number grows with your data.
Hive Router avoids this. It groups the entities by their context values and sends all groups in a
single subgraph request, each group as its own aliased _entities field:
| Distinct context values | One request per value | Hive Router |
|---|---|---|
| 1 | 1 request | 1 request |
| 2 | 2 requests | 1 request |
| 20 | 20 requests | 1 request |
Fewer requests mean fewer HTTP round trips, less connection pool pressure, and less per-request overhead on both the router and the subgraph, which can resolve all groups in one execution.
For the users example above, with Alice in USD and Bob in EUR, the subgraph receives one
request:
The first alias receives only the transactions of USD users, the second only the transactions of
EUR users. The router then maps the responses and errors back to the original positions in the
client response, so aliasing is completely invisible to clients. When all entities share the same
context values, the router sends the original, non-aliased operation.
On top of that:
- It works together with entity batching. Contextual fetches to the same subgraph from different parts of a query are combined into one batched request, and every context group repeats each batched fetch inside that same request. Instead of multiplying requests, contexts and batching share a single round trip.
- Entities are deduplicated per group. A transaction that appears several times under users
with the same currency is sent once. The same transaction under a
USDuser and anEURuser is sent once in each group, because the results can differ. - Building the aliased query is cheap. The query planner compiles each contextual fetch into a template once. At execution time, the router writes the query for any number of groups by copying strings, without parsing, re-planning, or caching a separate query per group count.
- Grouping is fast. Entities under the same ancestor are usually next to each other in the response, so the router reuses the previous entity’s group without reading and hashing the context values again. Context values are borrowed from the response instead of copied.
Limiting aliases per request
The number of groups depends on the response data, not on the query. A list with 50 users in 50
different currencies produces 50 aliased _entities fields. Subgraphs that limit aliases in an
operation (for example, with graphql-armor’s max-aliases rule) can start rejecting these requests
when the data changes.
Use traffic_shaping.max_aliases_per_request
to cap how many aliased _entities fields the router puts in a single subgraph request:
With a cap of 5, a fetch with 12 distinct context value groups is sent as three requests with 5,
5 and 2 groups. These requests run in parallel, so splitting doesn’t add round trips one after
another. The option is not set by default, so all groups of a fetch go in one request.
Behavior details
Missing values are sent as null
If the ancestor’s context field is missing or null, or a null sits anywhere along the selection
path, the router sends null for the argument. Whether that’s valid depends on the argument type in
your subgraph schema. Apollo Router behaves the same.
Non-null contextual arguments are not combined
Fetches with a non-null contextual argument (for example code: String!) are never combined into
one request with other groups or fetches. Each context value group is sent as its own request, and
all of them run in parallel.
Combining requests sometimes requires the router to send null for a context variable that a
particular part of the request doesn’t use. A non-null variable would make the whole request fail
variable coercion. Keeping these fetches separate also prevents one group with a missing value from
failing unrelated groups.
Prefer nullable contextual arguments when you expect many distinct context values, so the router can combine them into fewer requests.
Mutations
A mutation root field whose result contains contextual fields finishes resolving them before the next mutation root field runs, preserving the sequential execution order of mutations.
Contextual fields inside @requires
A @requires selection can include a contextual field, as long as the context is set within the
required selection itself. For example, if Plan.discount requires offer { value }, then
Offer.value can read a context set by Plan. The router fetches the context, then value, then
discount.
If a contextual field selected by @requires (or by another @fromContext selection) needs a
context set above the starting point of that selection, planning fails with: