Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs-website/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ docs-website/

## Writing Guidelines

### Tone and Style
- Write as reference documentation, not as an answer to a question. State facts directly.
- Avoid em dashes. Use periods or restructure the sentence instead.
- Avoid filler and hedging ("simply", "just", "easily", "of course", "it should be noted that").
- Do not over-explain. One clear sentence beats three cautious ones.
- Prefer short, declarative sentences. If a sentence has more than one comma-separated clause, consider splitting it.
- Use structured lists when presenting multiple distinct items. Do not pack them into a single paragraph.
- Do not sound like an LLM. No "This powerful feature allows you to..." or "In other words...". Describe what things do, not how impressive they are.

### Format
1. Use MDX format for all documentation
2. Place images in the appropriate `images/` subdirectory
3. Follow the existing navigation structure in `docs.json`
Expand Down
7 changes: 6 additions & 1 deletion docs-website/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@
},
"router/gRPC/grpc-services",
"router/gRPC/graphql-support",
"router/gRPC/field-resolvers"
"router/gRPC/field-resolvers",
"router/gRPC/requires"
]
},
"router/authentication-and-authorization",
Expand Down Expand Up @@ -409,6 +410,10 @@
"icon": "sign-post",
"pages": [
"federation/federation-directives-index",
"federation/directives/key",
"federation/directives/external",
"federation/directives/provides",
"federation/directives/requires",
"federation/directives/shareable",
"federation/directives/authenticated",
"federation/directives/requiresscopes",
Expand Down
12 changes: 12 additions & 0 deletions docs-website/federation/directives.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ sidebarTitle: "Overview"
Open Federation is compatible with Apollo Federation v1 & v2.

<CardGroup cols={2}>
<Card title="@key" icon="fingerprint" horizontal href="/federation/directives/key">

</Card>
<Card title="@external" icon="arrow-right-from-bracket" horizontal href="/federation/directives/external">

</Card>
<Card title="@provides" icon="hand-holding" horizontal href="/federation/directives/provides">

</Card>
<Card title="@requires" icon="link" horizontal href="/federation/directives/requires">

</Card>
<Card title="@shareable" icon="share-nodes" horizontal href="/federation/directives/shareable">

</Card>
Expand Down
89 changes: 89 additions & 0 deletions docs-website/federation/directives/external.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
title: "@external"
icon: "arrow-right-from-bracket"
description: "The @external directive declares that a field is not independently resolvable by the current subgraph, making it available for use with @requires and @provides."
---

## Definition

```graphql
directive @external on FIELD_DEFINITION | OBJECT
```

## Overview

The `@external` directive declares that a field is not independently resolvable by the current subgraph. The exact behavior depends on where the field is referenced:

- **Unresolvable by this subgraph** — An `@external` field referenced only in a [`@requires`](/federation/directives/requires) `FieldSet`, or defined only to satisfy an interface. The router will never consider this subgraph to resolve it. The field is defined locally so that `@requires` can reference it in its `FieldSet` or to satisfy an interface contract.
- **Conditionally resolvable by this subgraph** — An `@external` field referenced in a [`@provides`](/federation/directives/provides) `FieldSet`. The subgraph can resolve this field, but only at specific query paths where `@provides` is applied. At all other paths, the router fetches it from another subgraph.

An `@external` field must be referenced in `@key`, `@provides`, and/or `@requires`, or defined to satisfy an interface. If it is not used in any of these ways, composition will reject the schema. Every `@external` field must also have a non-external counterpart: the same field defined without `@external` in at least one other subgraph.

In most schemas, `@external` only appears on entity types (types with a [`@key`](/federation/directives/key) directive), where fields come from multiple subgraphs.

## Field-Level Declaration

When applied to a single field definition, `@external` marks only that field as coming from another subgraph. In this example, two subgraphs contribute to the `Event` entity: the **"scheduling"** subgraph resolves `capacity` and `registrationCount`, while the **"waitlist"** subgraph needs those values to compute `spotsRemaining` and therefore declares them `@external`:

```graphql
# scheduling subgraph — resolves capacity and registrationCount
type Event @key(fields: "id") {
id: ID!
name: String!
capacity: Int!
registrationCount: Int!
}
```

```graphql
# waitlist subgraph — capacity and registrationCount belong to scheduling
type Event @key(fields: "id") {
id: ID!
capacity: Int @external
registrationCount: Int @external
spotsRemaining: Int! @requires(fields: "capacity registrationCount")
}
```

`capacity` and `registrationCount` are declared here only so that `@requires` can reference them. The router will still fetch their values from the scheduling subgraph.

## Object-Level Declaration

Applying `@external` to an object type marks all fields currently defined on that type as external:

```graphql
# notifications subgraph — ContactInfo is resolved by the users subgraph
type ContactInfo @external {
email: String!
phone: String
}
```

This is equivalent to marking each field `@external` individually. It is typically used when a subgraph needs to reference a complete object type resolved by another subgraph.

## Behavior at Composition

External fields are excluded from the subgraph's contribution to the composed supergraph schema. They exist in the subgraph's SDL only to support directives that reference them. The router knows which subgraph resolves the field and will fetch it from there.

If a field is declared `@external` in one subgraph but is never defined (without `@external`) in any other subgraph, composition will fail.

## Key Fields and `@external`

Key fields (those listed in `@key(fields: "...")`) are implicitly shareable across subgraphs and do not need `@external`. If a subgraph only needs to reference an entity without resolving any of its fields, use `resolvable: false` on the `@key` instead of adding `@external` to each field:

```graphql
# ticketing subgraph — references Event but cannot resolve its fields
type Event @key(fields: "id", resolvable: false) {
id: ID!
}
```

<Warning>
Explicitly marking a key field as `@external` makes it truly external: the subgraph will no longer be considered a source for that field, which changes how the router plans queries. Only do this intentionally.
</Warning>

For more details on how these semantics differ across federation versions, see [Understanding Federation Versions](https://wundergraph.com/blog/understanding-federation-versions).

## See Also

[`@key`](/federation/directives/key) designates an object type as a federation entity. [`@requires`](/federation/directives/requires) uses external fields to declare dependencies for a locally-resolved field. [`@provides`](/federation/directives/provides) declares that external fields can be resolved at a specific query path.
190 changes: 190 additions & 0 deletions docs-website/federation/directives/key.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
---
title: "@key"
icon: "fingerprint"
description: "The @key directive designates an object type as a federation entity and specifies the fields that uniquely identify it across subgraphs."
---

## Definition

```graphql
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
```

## Arguments

| Argument | Type | Default | Description |
|----------|------|---------|-------------|
| `fields` | `FieldSet!` | — | A selection set (as a string) of one or more fields that together uniquely identify an instance of the entity. |
| `resolvable` | `Boolean` | `true` | When `false`, indicates that this subgraph does not implement a reference resolver for the entity. The router will not route queries to this subgraph solely to resolve entity fields. |

## Overview

The `@key` directive is the foundation of GraphQL Federation entities. Applying `@key` to an object type declares it an _entity_: a type whose fields can be distributed across multiple subgraphs and resolved independently by each. The key fields uniquely identify an instance of the entity so the router can correlate data from different subgraphs.

Any subgraph that resolves or references an entity must declare at least one `@key`. Different subgraphs may use different key fields as long as the router can traverse between them — either directly (both subgraphs share a common key) or through an intermediary subgraph that declares both keys. See [When Multiple Keys Are Needed](#when-multiple-keys-are-needed) for an example. The router uses these keys to construct _representations_ when fetching entity data from subgraphs that contribute additional fields.

## Single Key

The most common case is identifying an entity by a single scalar field. Here both the scheduling and analytics subgraphs contribute fields to the `Event` entity:

```graphql
# scheduling subgraph
type Event @key(fields: "id") {
id: ID!
name: String!
startsAt: String!
}
```

```graphql
# analytics subgraph
type Event @key(fields: "id") {
id: ID!
totalRegistrations: Int!
pageViews: Int!
}
```

Both subgraphs declare `Event` as an entity with the `id` key. The router can now combine fields from both subgraphs into a single `Event` response.

## Multiple Keys

A type can have more than one `@key` directive. Each additional key gives the query planner another set of fields it can use to jump between subgraphs when resolving the entity.

```graphql
type Event @key(fields: "id") @key(fields: "slug") {
id: ID!
slug: String!
name: String!
}
```

The entity must be resolvable by any one of its declared keys.

<Warning>
Multiple keys increase the number of possible query plan paths, which adds complexity to query planning. In most cases a single key is sufficient. The primary reason to declare a second key is during a key migration, where the old key is kept temporarily while subgraphs transition to the new one. Avoid permanent multi-key setups unless you have a clear need.
</Warning>

### When Multiple Keys Are Needed

Multiple keys become necessary when different subgraphs can only resolve an entity by different identifiers. Consider a `User` entity with a public `id` and an `internalId`. Subgraph A can only resolve users by `id`, subgraph B can only resolve users by `internalId`, and subgraph C defines both keys:

```graphql
# subgraph A — resolves by public id only
type User @key(fields: "id") {
id: ID!
displayName: String!
}
```

```graphql
# subgraph B — resolves by internal id only
type User @key(fields: "internalId") {
internalId: ID!
role: String!
}
```

```graphql
# subgraph C — resolves by either key
type User @key(fields: "id") @key(fields: "internalId") {
id: ID!
internalId: ID!
email: String!
}
```

Subgraph C acts as a bridge: it can accept either key and resolve the other. The router uses this to jump between subgraphs that share no common key.

<Warning>
This bridge pattern introduces extra hops. If a query starts at subgraph B and needs a field from subgraph A, the router must go B → C → A because B and A share no common key. This "leap-frogging" adds latency and indicates a design problem. If possible, align subgraphs on a single shared key to avoid unnecessary indirection.
</Warning>

## Compound Keys

A compound key spans multiple fields, including nested object fields. All fields in the compound key's selection set must be present on the type:

```graphql
type Session @key(fields: "id event { id }") {
id: ID!
event: Event!
title: String!
speakerName: String!
}

type Event {
id: ID!
}
```

When the key includes nested fields, the router passes the full nested structure in representations so the subgraph can resolve the entity. For the schema above, a representation looks like this:

```json
{
"__typename": "Session",
"id": "session-1",
"event": {
"id": "event-42"
}
}
```

The subgraph's entity resolver receives both `id` and the nested `event.id` to uniquely identify the `Session`.

## Non-Resolvable Keys (`resolvable: false`)

A subgraph sometimes needs to return a reference to an entity it does not resolve. For example, a ticketing subgraph stores which event a ticket belongs to, but the event's details (name, date, venue) live in a separate scheduling subgraph. Setting `resolvable: false` tells the router to never send entity resolution requests to this subgraph for the `Event` type:

```graphql
# ticketing subgraph
type Event @key(fields: "id", resolvable: false) {
id: ID!
}

type Ticket @key(fields: "id") {
id: ID!
event: Event!
seatNumber: String!
attendeeName: String!
}
```

The subgraph can return `Event` references (e.g. `Ticket.event`), but the router will resolve `Event` fields through subgraphs that declare a resolvable key for it. If you need to use the `id` field in [`@requires`](/federation/directives/requires) or [`@provides`](/federation/directives/provides) within this subgraph, mark it as [`@external`](/federation/directives/external).

<Note>
In Federation v1, each entity had a single "origin" subgraph (the type definition). All other subgraphs used type extensions and had to mark key fields as `@external`. Federation v2 removed the origin concept entirely. Entities can now be defined as regular types across multiple subgraphs, and key fields are implicitly shareable. See [Understanding Federation Versions](https://wundergraph.com/blog/understanding-federation-versions) for more details.
</Note>

## Keys on Interfaces

Applying `@key` to an interface creates an _entity interface_, allowing a subgraph to contribute fields to every object type that implements the interface across subgraphs.

### Rules

- The subgraph that defines the entity interface must also define every entity type that implements it. Other subgraphs may also define these entities.
- Every implementing entity must include all `@key` directives from the interface. Additional keys are permitted but generally discouraged (see [Multiple Keys](#multiple-keys)).
- Key field values must be unique across all implementing types. No instance of `Concert` can have the same key value as any instance of `Workshop`.
- The interface must have at least one `@key` directive.

```graphql
# scheduling subgraph
interface ScheduledItem @key(fields: "id") {
id: ID!
startsAt: String!
}

# Both implementing types repeat @key(fields: "id") from the interface.
type Concert implements ScheduledItem @key(fields: "id") {
id: ID!
startsAt: String!
}

type Workshop implements ScheduledItem @key(fields: "id") {
id: ID!
startsAt: String!
}
```

## See Also

[`@external`](/federation/directives/external) declares that a field is not independently resolvable by the current subgraph, making it available for use in `@requires` or `@provides`. [`@requires`](/federation/directives/requires) declares that an entity field depends on external fields that must be fetched from another subgraph first. [`@provides`](/federation/directives/provides) declares that a subgraph can resolve certain external entity fields at a specific query path.
Loading
Loading