|
| 1 | +--- |
| 2 | +title: "response_extensions" |
| 3 | +--- |
| 4 | + |
| 5 | +import { Callout } from "@hive/design-system/hive-components/callout"; |
| 6 | + |
| 7 | +A GraphQL response can carry a top-level `extensions` object, a free-form JSON object used to attach |
| 8 | +metadata (cache hints, tracing, warnings, timing, and other custom data) alongside `data` and `errors` keys. |
| 9 | + |
| 10 | +By default, the router does **not** forward `extensions` returned by your subgraphs - they are |
| 11 | +dropped. The `response_extensions` configuration lets you opt in and propagate them to the final |
| 12 | +client response, with full control over which keys are forwarded and how values from multiple |
| 13 | +subgraph responses are merged together. |
| 14 | + |
| 15 | +## Configuration Structure |
| 16 | + |
| 17 | +```yaml title="router.config.yaml" |
| 18 | +response_extensions: |
| 19 | + propagate: |
| 20 | + algorithm: last # first | last | append. default: last |
| 21 | + allow: # optional key whitelist. omit to forward all keys |
| 22 | + - foo |
| 23 | + - bar |
| 24 | +``` |
| 25 | +
|
| 26 | +Propagation is only active when `response_extensions.propagate` is present. Without it, behavior is |
| 27 | +unchanged and nothing is forwarded. |
| 28 | + |
| 29 | +| Key | Type | Description | |
| 30 | +| :---------- | :--------- | :----------------------------------------------------------------------------------------------- | |
| 31 | +| `algorithm` | `string` | How to merge an extension key seen across multiple subgraph responses. Default: `last`. | |
| 32 | +| `allow` | `string[]` | Optional whitelist of top-level extension keys to forward. When omitted, all keys are forwarded. | |
| 33 | + |
| 34 | +## Merge Algorithms |
| 35 | + |
| 36 | +Because a single GraphQL operation can fan out to multiple subgraphs, the same extension key can |
| 37 | +appear in several responses. The `algorithm` setting decides what the client sees and how merging is performed: |
| 38 | + |
| 39 | +- `last` **(default)** - the last subgraph to respond wins. Good for scalar metadata where any value |
| 40 | + is equally valid. |
| 41 | +- `first` - the first subgraph to respond wins. Useful when you want a stable value and don't want |
| 42 | + later subgraphs to overwrite it. |
| 43 | +- `append` - every value is collected into an array, **always an array even when only one subgraph |
| 44 | + contributed**. Use this when you want to keep all values (e.g. cache hints, tracing spans, or |
| 45 | + warnings from multiple services). |
| 46 | + |
| 47 | +### Example |
| 48 | + |
| 49 | +Two subgraphs both return an `extensions.foo` key, with subgraph `a` responding before `b`: |
| 50 | + |
| 51 | +```json |
| 52 | +// subgraph a |
| 53 | +{ "extensions": { "foo": { "some": ["array"] } } } |
| 54 | +
|
| 55 | +// subgraph b |
| 56 | +{ "extensions": { "foo": { "some": "object" } } } |
| 57 | +``` |
| 58 | + |
| 59 | +| `algorithm` | client sees | |
| 60 | +| :---------- | :--------------------------------------------------------------------------- | |
| 61 | +| `first` | `{ "extensions": { "foo": { "some": ["array"] } } }` | |
| 62 | +| `last` | `{ "extensions": { "foo": { "some": "object" } } }` | |
| 63 | +| `append` | `{ "extensions": { "foo": [{ "some": ["array"] }, { "some": "object" }] } }` | |
| 64 | + |
| 65 | +With `append`, even a single contributing subgraph produces an array, so clients can consume it |
| 66 | +without special-casing: |
| 67 | + |
| 68 | +```json |
| 69 | +{ "extensions": { "foo": [{ "some": ["array"] }] } } |
| 70 | +``` |
| 71 | + |
| 72 | +## Key Whitelist |
| 73 | + |
| 74 | +The optional `allow` list restricts propagation to specific top-level extension keys. When omitted, |
| 75 | +all keys from all subgraphs are forwarded. Keys not in the list are silently dropped. |
| 76 | + |
| 77 | +```yaml title="router.config.yaml" |
| 78 | +response_extensions: |
| 79 | + propagate: |
| 80 | + algorithm: last |
| 81 | + allow: |
| 82 | + - cacheControl |
| 83 | + - warnings |
| 84 | +``` |
| 85 | + |
| 86 | +With the config above, only `cacheControl` and `warnings` reach the client; any other key a subgraph |
| 87 | +sends is ignored. |
| 88 | + |
| 89 | +## Precedence |
| 90 | + |
| 91 | +Extension keys set by the router itself or by [plugins](/docs/router/customizations/plugins) always |
| 92 | +take precedence over subgraph-propagated values. If a plugin sets `extensions.foo` and a subgraph |
| 93 | +also returns `extensions.foo`, the plugin's value wins. |
| 94 | + |
| 95 | +<Callout type="warning"> |
| 96 | + The `queryPlan` key is permanently reserved by the router and is never |
| 97 | + propagated from subgraphs, regardless of your config - even if you add it to |
| 98 | + the `allow` list. |
| 99 | +</Callout> |
| 100 | + |
| 101 | +## Ordering and Determinism |
| 102 | + |
| 103 | +`first` and `last` are relative to subgraph **response order**, not plan order. For sequential plan |
| 104 | +nodes the order is deterministic. For parallel fetches it depends on which subgraph responds first - |
| 105 | +the same non-determinism that already applies to |
| 106 | +[response header propagation](/docs/router/configuration/headers). If you need stable output under |
| 107 | +parallel fetches, use `append` and sort on the client. |
0 commit comments