Since 2.8

Use contexts to share data

Share data along type hierarchies without overloading @keys


The @context and @fromContext directives are Enterprise features of the Apollo Router and require an organization with a GraphOS Enterprise plan. If your organization doesn't have an Enterprise plan, you can test it out by signing up for a free Enterprise trial.

In an entity, a nested object may have a dependency on an ancestor type and thus need access to that ancestor's field(s).

To enable a descendant to access an ancestor's field, you could add it as a @key field to every entity along the chain of nested types, but that can become problematic. Deeply nested types can change the @key fields of many entities. The added field may be irrelevant to the entities it's added to. Most importantly, overloading @key fields often breaks the separation of concerns between different subgraphs.

The @context and @fromContext directives enable a subgraph to share fields without overloading @key fields. You can use these directives to define one or more contexts in a subgraph. Contexts provide a way for a subgraph to share data between types of a nested-type hierarchy without overloading entity keys with extraneous fields. Contexts also preserve the separation of concerns between different subgraphs.

Using one context

As an example of a single context, the subgraph below tracks the last financial transaction made per user. The Transaction type is a child of the User type. Each transaction depends on the associated user's currency to calculate the transaction amount in the user's currency. That dependency—the currencyCode argument of a Transaction depending on the userCurrency { isoCode } of a User— is defined with a context. The @context directive on User sets the context, and @fromContext on currencyCode gets the contextual data.

GraphQL
Example: using @context and @fromContext
1scalar CurrencyCode;
2
3type Currency {
4   id: ID!
5   isoCode: CurrencyCode!
6}
7
8type User @key(fields: "id") @context(name: "userContext") {
9   id: ID!
10   lastTransaction: Transaction!
11   userCurrency: Currency!
12}
13
14type Transaction @key(fields: "id") {
15  id: ID!
16  currency: Currency!
17  amount: Int!
18  amountInUserCurrency(
19    currencyCode: CurrencyCode
20        @fromContext(field: "$userContext { userCurrency { isoCode } }")
21  ): Int!
22}
note
  • Context names cannot include underscores.
    • In the example above, userContext is a valid context name, but user_context wouldn't be.
  • An argument of @fromContext doesn't appear in the API schema. Instead, it's populated automatically by the router.
    • In the example above, the argument currencyCode: CurrencyCode! wouldn't appear in the API schema.

Using type conditions in @fromContext

In this example, note how the @fromContext directive uses a series of type condition to select the desired field when accessing Child.prop1. A type condition is not required if all possible contexts have a field present as is the case for Child.prop2.

GraphQL
Example: using multiple contexts
1type Query {
2  a: A!
3  b: B!
4  c: C!
5}
6
7type A @key(fields: "id") @context(name: "context1"){
8  id: ID!
9  field: String!
10  someField: String!
11  child: Child!
12} 
13
14type B @key(fields: "id") @context(name: "context1"){
15  id: ID!
16  field: String!
17  someField: String!
18  child: Child!
19} 
20
21type C @key(fields: "id") @context(name: "context1") {
22  id: ID!
23  field: String!
24  someOtherField: String!
25  child: Child!
26} 
27
28type Child @key(fields: "id") {
29  id: ID!
30  prop1(
31    arg: String! 
32        @fromContext(field: "$context1 ... on A { someField } ... on B { someField } ... on C { someOtherField }")
33  ): Int!
34  prop2(
35    arg: String! 
36        @fromContext(field: "$context1 { field }")
37  ): Int!
38}

When the same contextual value is set in multiple places—as in the example with the Child.prop1 and Child.prop2 args—the FieldValue must resolve all types from each place into a single value that matches the parameter type.

note
Federation doesn't guarantee which context will be used if a field is reachable via multiple contexts.

Disambiguating contexts

When multiple ancestor entities in the type hierarchy could fulfill a set context, the nearest ancestor is chosen. For example, if both the parent and grandparent of a type can provide the value of a context, the parent is chosen because it's the closer ancestor.

In the following example, given nested types A, B, and C, with C referencing a context that either A or B could provide, C uses the value from B because it's a closer ancestor to C than A:

GraphQL
1type Query { 
2  a: A!
3}
4
5type A @key(fields: "id") @context(name: "context1") {
6  id: ID!
7  field: String!
8  b: B!
9} 
10
11type B @key(fields: "id") @context(name: "context1") {
12  id: ID!
13  field: String!
14  c: C!
15} 
16
17type C @key(fields: "id") {
18  id: ID!
19  prop(
20    arg: String! @fromContext(field: "$context1 { field }")
21  ): Int!
22} 

In a more complex graph, a field could be reachable via multiple paths, and a different field could be used to resolve the prop depending on which path was used.

Referencing fields across subgraphs

The definition of context scopes can only exist in one subgraph schema. The @fromContext directive can't reference a @context defined in another subgraph. However, you can use contexts to share data across subgraphs using the @external reference.

Reusing the Transaction example, imagine a subgraph responsible for the User and Currency types:

GraphQL
1scalar CurrencyCode
2
3type Currency @shareable {
4   id: ID!
5   isoCode: CurrencyCode!
6}
7
8type User @key(fields: "id") {
9   id: ID!
10   userCurrency: Currency!
11}

If you want to reference those fields from another subgraph, you can use the @external directive to pass data across subgraph boundaries:

GraphQL
1scalar CurrencyCode
2
3type Currency @shareable {
4   id: ID!
5   isoCode: CurrencyCode!
6}
7
8type User @key(fields: "id") @context(name: "userContext") {
9   id: ID!
10   
11  # This is a reference to the field resolved elsewhere
12   userCurrency: Currency! @external
13   
14   # We add this field to our type here
15   lastTransaction: Transaction! 
16}
17
18type Transaction @key(fields: "id") {
19  id: ID!
20  currency: Currency!
21  amount: Int!
22  amountInUserCurrency(
23    currencyCode: CurrencyCode 
24        @fromContext(field: "$userContext { userCurrency { isoCode } }")
25  ): Int!
26}
Feedback

Edit on GitHub

Forums