From 5e2ca12538ceb52665e5fb02d6b711645870fe00 Mon Sep 17 00:00:00 2001 From: Brendan Bondurant Date: Mon, 2 Jun 2025 12:13:32 -0700 Subject: [PATCH 1/5] Add external directive page --- docs/docs.json | 5 +- docs/federation/directives.mdx | 3 +- docs/federation/directives/external.mdx | 154 ++++++++++++++++++ .../federation-directives-index.mdx | 2 +- 4 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 docs/federation/directives/external.mdx diff --git a/docs/docs.json b/docs/docs.json index 05d35367..cdca6234 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -75,17 +75,18 @@ "group": "Federation", "pages": [ "federation/federation-compatibility-matrix", + "federation/federation-directives-index", { "group": "directives", "icon": "sign-post", "pages": [ "federation/directives", - "federation/federation-directives-index", "federation/directives/shareable", "federation/directives/authenticated", "federation/directives/requiresscopes", "federation/directives/openfed__subscriptionfilter", - "federation/directives/openfed__configuredescription" + "federation/directives/openfed__configuredescription", + "federation/directives/external" ] }, { diff --git a/docs/federation/directives.mdx b/docs/federation/directives.mdx index 02e487cb..43b4f56c 100644 --- a/docs/federation/directives.mdx +++ b/docs/federation/directives.mdx @@ -23,7 +23,8 @@ Open Federation is compatible with Apollo Federation v1 & v2. - + + \ No newline at end of file diff --git a/docs/federation/directives/external.mdx b/docs/federation/directives/external.mdx new file mode 100644 index 00000000..7ffdcbc5 --- /dev/null +++ b/docs/federation/directives/external.mdx @@ -0,0 +1,154 @@ +--- +title: '@external' +description: Marks a field or type as declared in this subgraph but resolved by another. Enables use in @requires, @provides, or interface implementations. +keywords: [external, directive, federation, graphql, provides, requires, key, composition] +icon: "link" +--- + +Supported in both Federation v1 and v2, though usage and validation rules differ between versions. + +## Overview + +The `@external` directive declares that a field or type is defined in a subgraph schema but resolved by another subgraph. +It allows your subgraph to reference fields it cannot resolve on its own, typically in support of other directives like `@requires` and `@provides`. + +```graphql +directive @external on FIELD_DEFINITION | OBJECT +``` + +## When to Use + +Only use `@external` when: +- The field is **unresolvable** from the current subgraph **and** +- The field is **referenced by**: + - `@key(fields: "...")` (in Federation v1 only) + - `@provides(fields: "...")` + - `@requires(fields: "...")` +- OR the field is required to satisfy an **interface** implemented by the type + - For example, a type may need to declare `@external` fields to fulfill an interface it implements from another subgraph. + +A valid `@external` field must have a matching, resolvable definition in another subgraph. +This is known as a **shared field instance** and ensures that the field can be correctly composed and resolved across your federated graph. + +## Behavior in Federation Versions + +|Feature|Federation v1|Federation v2| +|---|---|---| +|Fields in `@key` must be marked `@external`|Yes|No| +|Fields in `@requires` or `@provides` must be `@external`|Yes|Yes — but only leaf fields or their ancestors| +|Validation behavior|Lenient|Strict and explicit| + +In Federation v2, only **leaf fields** (or their ancestors) used in `@requires` or `@provides` selections must be marked as `@external`. +Fields in `@key` do **not** require `@external`. + +WunderGraph Cosmo helps validate `@external` usage, especially in Federation v1 environments where misconfigurations can be harder to detect. + +## How It Works + +The `@external` directive: +- Marks a field as **referenced but unresolvable** in the current subgraph +- Exempts the field from **shareability checks** +- Allows safe use of the field in directive arguments without composition errors + +You can apply it to: + +- **Individual fields** on an object +- An **entire object type**, making all fields on that type external + +### Example: Field-Level Usage + +```graphql +type User @key(fields: "id") { + id: ID! + email: String! @external + profilePicture: String +} + +type Query { + recentSignups: [User!]! @provides(fields: "email") +} +``` + +In this example, the `User.email` field is defined in the schema but only resolved by another subgraph. This subgraph references it via `@provides`. + +### Example: Type-Level Usage + +```graphql +type Location @external { + city: String! + country: String! +} +``` + +Applying `@external` to a type marks **all its fields** as externally defined. + +## Tips and Best Practices + +- Use @external only when needed for `@requires`, `@provides`, or `@key` +- In Federation v2, leave it out unless it’s strictly necessary +- Always ensure an external field is defined and resolvable in another subgraph + +## Edge Cases and Normalization Behavior + +### External fields without a matching definition + +If you mark a field as `@external` but no other subgraph defines and resolves that field, the composition process will fail. +Every `@external` field must match a resolvable field in another subgraph. + +```graphql +# Subgraph A +type Product @key(fields: "sku") { + sku: String! + name: String! @external +} +``` + +```graphql +# Subgraph B (missing definition) +type Product @key(fields: "sku") { + sku: String! +} +``` + +This will fail composition: Product.name is marked @external in Subgraph A, but no other subgraph defines it. +This triggers a composition error (`EXTERNAL_MISSING_ON_BASE`). + +--- + +### Normalization of @external on extended types + +Applying `@external` to a type does **not** automatically apply it to fields added via later `extend` blocks. +This is especially important when normalizing schemas across subgraphs. + +```graphql +# Subgraph A +type Location @external { + city: String! +} + +extend type Location { + country: String! +} +``` + +In this example, only `city` is treated as `@external`. +The `country` field is **not** external unless explicitly marked. +For clarity and correctness, you should annotate it directly: + +```graphql +extend type Location { + country: String! @external +} +``` + +--- + +### Federation v1 inconsistencies + +In Federation v1, marking a field as `@external` is often **required**, even when it’s not referenced by `@requires` or `@provides`, due to weaker validation and looser assumptions. + +If you're migrating from v1 to v2: + +* Review `@external` usage carefully +* Remove unnecessary annotations when they are no longer required (e.g., in `@key`) +* Prefer field-level precision over broad `@external` usage diff --git a/docs/federation/federation-directives-index.mdx b/docs/federation/federation-directives-index.mdx index b2910bd6..f3b8dc0e 100644 --- a/docs/federation/federation-directives-index.mdx +++ b/docs/federation/federation-directives-index.mdx @@ -15,7 +15,7 @@ This page lists the GraphQL Federation directives currently supported by WunderG | [`@edfs__natsPublish`](/federation/event-driven-federation/nats) | Marks a mutation field that publishes an event to a NATS subject. Requires a subject and provider ID. | | [`@edfs__natsRequest`](/federation/event-driven-federation/nats) | Declares a query field that requests an event over NATS and expects a response. Used in EDG Query fields. | | [`@edfs__natsSubscribe`](/federation/event-driven-federation/nats) | Marks a subscription field that listens to one or more NATS subjects. Can include stream configuration. | -| `@external` | Marks a field as coming from another subgraph in a federated GraphQL schema. The field exists on the base type in a different subgraph and is not resolved by the current one, but it can be referenced if needed by directives or to satisfy an interface. In addition to indicating data ownership, `@external` is used in several advanced scenarios: to satisfy an interface implementation, to reference a field that may be conditionally provided via `@provides`, or to declare a dependency used by `@requires`. | +| [`@external`](/federation/directives/external) | Marks a field as coming from another subgraph in a federated GraphQL schema. The field exists on the base type in a different subgraph and is not resolved by the current one, but it can be referenced if needed by directives or to satisfy an interface. In addition to indicating data ownership, `@external` is used in several advanced scenarios: to satisfy an interface implementation, to reference a field that may be conditionally provided via `@provides`, or to declare a dependency used by `@requires`. | | `@extends` | Marks an object or interface as an extension of a type that is defined in another subgraph, typically when the subgraph's GraphQL implementation does not support the `extend` keyword. This directive is mainly used by subgraph servers that lack native support for type extensions. In Federation v2, this is no longer required to mark entity types as extensions. Any subgraph can contribute fields to an entity without declaring it as an extension. In Federation v1, each entity had an originating subgraph, and any other subgraph referencing that entity was required to use the `extend` keyword. The `@extends` directive remains in use primarily for backward compatibility and edge cases in tooling support. | | `@inaccessible` | Hides a field or type from the client schema. It cannot be queried by the user, but the planner may still access it if used in a `@key`, `@requires`, or `@provides` directive. Types marked as inaccessible are excluded from unions and interfaces. If the server returns one, the router will either return an error or render the object as null depending on the field’s nullability. | | `@interfaceObject` | Used to mark a type that corresponds to an interface in another subgraph, where that interface has a `@key` directive. This kind of interface is referred to as an entity interface. The directive allows the subgraph to contribute fields to every type that implements the entity interface in the other subgraph. When using `@interfaceObject`, the same subgraph must not define any of the implementing types, or it will result in a composition error. | From 3c8558e233bfdccb12cfa76893200159099e8d57 Mon Sep 17 00:00:00 2001 From: Brendan Bondurant Date: Mon, 2 Jun 2025 15:24:27 -0700 Subject: [PATCH 2/5] Clarification added after provided feedback --- docs/federation/directives/external.mdx | 185 ++++++++++++++++++------ 1 file changed, 143 insertions(+), 42 deletions(-) diff --git a/docs/federation/directives/external.mdx b/docs/federation/directives/external.mdx index 7ffdcbc5..45bc3ad2 100644 --- a/docs/federation/directives/external.mdx +++ b/docs/federation/directives/external.mdx @@ -1,6 +1,6 @@ --- title: '@external' -description: Marks a field or type as declared in this subgraph but resolved by another. Enables use in @requires, @provides, or interface implementations. +description: Marks a field or type as declared in this subgraph but resolved by another. Enables use in `@requires`, `@provides`, or interface implementations. keywords: [external, directive, federation, graphql, provides, requires, key, composition] icon: "link" --- @@ -9,51 +9,90 @@ Supported in both Federation v1 and v2, though usage and validation rules differ ## Overview -The `@external` directive declares that a field or type is defined in a subgraph schema but resolved by another subgraph. -It allows your subgraph to reference fields it cannot resolve on its own, typically in support of other directives like `@requires` and `@provides`. +The `@external` directive marks a field or type that is declared in this subgraph but resolved by another subgraph. +It allows a subgraph to reference fields it doesn’t resolve itself, typically for use in `@requires` and `@provides`, or to satisfy interfaces. ```graphql directive @external on FIELD_DEFINITION | OBJECT ``` +## Behavior in Federation Versions + +### Federation v1 +Federation v1 has looser validation rules, and `@external` may be required even when a field is technically resolvable. + +**`@key` usage**: +Fields in `@key(fields: ...)` must be marked `@external` if not owned by the subgraph. This is especially true for entity extensions. + +**`@requires` and `@provides`**: +All fields in these FieldSets must be marked `@external`, regardless of whether they are leaf or non-leaf fields. + +**Validation**: +In Federation v1, misconfigured `@external` fields are often silently removed during composition, with no visible error. + +--- + +### Federation v2 +Federation v2 introduces stricter, more precise rules for `@external`. + +**`@key` usage**: +Fields in `@key(fields: ...)` do not need `@external` unless they cannot be resolved in the subgraph. + +**`@requires` and `@provides`**: +Only leaf fields (or their ancestors within the same FieldSet) that are not owned must be marked `@external`. + +**Validation**: +In Federation v2, `@external` is only valid if the field is: +- Referenced in a `@key`, `@requires`, or `@provides` FieldSet. +- **OR** required to satisfy an interface. + +It must also have a matching non-`@external` definition in another subgraph. Otherwise, composition fails. + +WunderGraph Cosmo helps validate `@external` usage, especially in Federation v1 environments where misconfigurations can be harder to detect. + +## How It Works + +The `@external` directive: + +- Declares that the field may be: + - **Unresolvable** — the subgraph cannot return a value for this field on its own. + - **Conditionally resolvable** — the field may be resolved in specific query paths (e.g., via `@provides`). + - **Syntactically required** — included to support directive compatibility, such as in `@key(fields: ...)` in Federation v1. +- Exempts the field from **shareability checks**. +- Enables safe use in **directive arguments** like `@requires` and `@provides` without triggering composition errors. + +You can apply `@external` to: +- **Individual fields** on an object. +- An **entire object type**, which treats all of its fields as external. + +The key idea: `@external` marks a field that is defined but not owned by this subgraph. +Whether and how the field resolves depends on the query path and the directives involved. + ## When to Use Only use `@external` when: -- The field is **unresolvable** from the current subgraph **and** -- The field is **referenced by**: +- The field is **unresolvable** from the current subgraph **and** the field is **referenced by**: - `@key(fields: "...")` (in Federation v1 only) - `@provides(fields: "...")` - `@requires(fields: "...")` -- OR the field is required to satisfy an **interface** implemented by the type +- **OR** the field is required to satisfy an **interface** implemented by the type - For example, a type may need to declare `@external` fields to fulfill an interface it implements from another subgraph. A valid `@external` field must have a matching, resolvable definition in another subgraph. This is known as a **shared field instance** and ensures that the field can be correctly composed and resolved across your federated graph. -## Behavior in Federation Versions - -|Feature|Federation v1|Federation v2| -|---|---|---| -|Fields in `@key` must be marked `@external`|Yes|No| -|Fields in `@requires` or `@provides` must be `@external`|Yes|Yes — but only leaf fields or their ancestors| -|Validation behavior|Lenient|Strict and explicit| - -In Federation v2, only **leaf fields** (or their ancestors) used in `@requires` or `@provides` selections must be marked as `@external`. -Fields in `@key` do **not** require `@external`. +## Interpreting `@external` +The meaning of `@external` depends on context. It can indicate: -WunderGraph Cosmo helps validate `@external` usage, especially in Federation v1 environments where misconfigurations can be harder to detect. - -## How It Works +| Meaning | Description | +| ---------------------------- | -------------------------------------------------------------------------------- | +| **Unresolvable** | The field cannot be resolved by the current subgraph | +| **Conditionally resolvable** | The field is resolved only in certain paths (e.g., via `@provides`) | +| **Legacy `@key` usage (v1)** | The field is resolvable, but marked `@external` to satisfy a key on an extension | +| **Interface satisfaction** | The field is required to fulfill an interface but not owned by this subgraph | -The `@external` directive: -- Marks a field as **referenced but unresolvable** in the current subgraph -- Exempts the field from **shareability checks** -- Allows safe use of the field in directive arguments without composition errors +These distinctions help explain why `@external` may appear in places that seem redundant or unnecessary, especially in Federation v1. -You can apply it to: - -- **Individual fields** on an object -- An **entire object type**, making all fields on that type external ### Example: Field-Level Usage @@ -69,7 +108,8 @@ type Query { } ``` -In this example, the `User.email` field is defined in the schema but only resolved by another subgraph. This subgraph references it via `@provides`. +In this example, the `User.email` field is defined in the schema but only resolved by another subgraph. +This subgraph references it via `@provides`. ### Example: Type-Level Usage @@ -84,16 +124,53 @@ Applying `@external` to a type marks **all its fields** as externally defined. ## Tips and Best Practices -- Use @external only when needed for `@requires`, `@provides`, or `@key` +- Use `@external` only when needed for `@requires`, `@provides`, or `@key` - In Federation v2, leave it out unless it’s strictly necessary -- Always ensure an external field is defined and resolvable in another subgraph -## Edge Cases and Normalization Behavior +## Edge Cases + +### Legacy `@external` usage with @key on extensions (Federation v1) +In Federation v1, if a type is extended and includes a `@key(fields: ...)` directive, any field used in that key must be marked `@external`, even if it's resolvable by the subgraph. +```graphql +# Subgraph A (Federation v1) +extend type Product @key(fields: "id") { + id: ID! @external +} +``` +While this legacy syntax is no longer required in Federation v2, it's still valid and necessary in v1 to ensure composition doesn't fail. + +### Misusing `@external` on key fields in Federation v2 + +In Federation v2, you can still annotate key fields with `@external`, but doing so implies that the field is not resolvable from the current subgraph. +If the field is actually resolvable, marking it `@external` will cause satisfiability errors at composition time. +This is a common pitfall when migrating from v1: key fields marked `@external` must remain resolvable in v2. + +```graphql +# Subgraph A (incorrect in Federation v2) +type Product @key(fields: "id") @key(fields: "upc") { + id: ID! @external + upc: String! @external + name: String! +} +``` +```graphql +# Subgraph B +type Product @key(fields: "id") @key(fields: "upc") { + id: ID! + upc: String! + stock: Int! +} +``` +This fails composition because Subgraph A marks `id` and `upc` as `@external`, but doesn’t actually provide a way to resolve them. +The router cannot satisfy queries that require navigating from A to B using these keys. + ### External fields without a matching definition If you mark a field as `@external` but no other subgraph defines and resolves that field, the composition process will fail. -Every `@external` field must match a resolvable field in another subgraph. +Additionally, composition typically fails if a type has no locally defined fields — that is, if all of its fields are marked `@external`. +Every type must own at least one field in the subgraph to be valid in the composed supergraph. +Otherwise, the router has no anchor point for resolution. ```graphql # Subgraph A @@ -110,15 +187,14 @@ type Product @key(fields: "sku") { } ``` -This will fail composition: Product.name is marked @external in Subgraph A, but no other subgraph defines it. This triggers a composition error (`EXTERNAL_MISSING_ON_BASE`). --- -### Normalization of @external on extended types +### Normalization of `@external` on extended types -Applying `@external` to a type does **not** automatically apply it to fields added via later `extend` blocks. -This is especially important when normalizing schemas across subgraphs. +Applying `@external` to a type does **not** automatically apply it to fields added later via `extend` blocks. +This distinction is especially important when normalizing schemas across subgraphs. ```graphql # Subgraph A @@ -131,9 +207,8 @@ extend type Location { } ``` -In this example, only `city` is treated as `@external`. -The `country` field is **not** external unless explicitly marked. -For clarity and correctness, you should annotate it directly: +Only `city` is treated as `@external`. +To ensure clarity and correctness, `country` should be annotated directly: ```graphql extend type Location { @@ -142,6 +217,7 @@ extend type Location { ``` --- +## Migration & Validation Notes ### Federation v1 inconsistencies @@ -149,6 +225,31 @@ In Federation v1, marking a field as `@external` is often **required**, even whe If you're migrating from v1 to v2: -* Review `@external` usage carefully -* Remove unnecessary annotations when they are no longer required (e.g., in `@key`) -* Prefer field-level precision over broad `@external` usage +* Review `@external` usage carefully. +* Remove unnecessary annotations when they are no longer required (e.g., in `@key`). +* Prefer field-level precision over broad `@external` usage. + +### Silent removal of unresolved `@external` fields +In Federation v1, a field marked `@external` that does not have a matching, resolvable definition in another subgraph is silently removed during composition. +```graphql +# Subgraph A +type Product { + legacyTag: String! @external +} +``` +If no other subgraph defines `legacyTag`, it will not appear in the composed supergraph at all. + +WunderGraph Cosmo will emit a warning if it detects `@external` fields that do not match a known definition elsewhere. + +### Validation prevents fully-external types +While Federation v1 permits liberal use of `@external`, composition will fail if all fields of a type are marked `@external`. + +| Use Case | Valid? | Notes | +| ---------------------------------------- | ------ | ------------------------------------------------------ | +| Field used in `@requires` or `@provides` | ✅ | Field must be unresolvable or conditionally resolvable | +| Field satisfies an interface | ✅ | Always valid if required by the interface | +| Field used in `@key` (v1, on extension) | ✅ | Legacy pattern — required even if resolvable | +| Field used in `@key` (v2, on object) | ⚠️ | Only valid if truly unresolvable | +| Type-level `@external` | ✅ | Applies to all fields on that type | +| No non-external counterpart exists | ❌ | Invalid — triggers error (v2) or silent removal (v1) | +| All fields on a type marked `@external` | ❌ | Invalid — must own at least one field | From a28756839eb203f7621b184df8bdea064eb243b8 Mon Sep 17 00:00:00 2001 From: Brendan Bondurant Date: Tue, 3 Jun 2025 09:31:18 -0700 Subject: [PATCH 3/5] Final read through, and edit for precise verbiage --- docs/federation/directives/external.mdx | 41 ++++++++++++++++--------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/docs/federation/directives/external.mdx b/docs/federation/directives/external.mdx index 45bc3ad2..4b7298e3 100644 --- a/docs/federation/directives/external.mdx +++ b/docs/federation/directives/external.mdx @@ -10,7 +10,8 @@ Supported in both Federation v1 and v2, though usage and validation rules differ ## Overview The `@external` directive marks a field or type that is declared in this subgraph but resolved by another subgraph. -It allows a subgraph to reference fields it doesn’t resolve itself, typically for use in `@requires` and `@provides`, or to satisfy interfaces. +It indicates that the field’s value is resolved by another subgraph, sometimes unconditionally and sometimes only in specific query paths. +Subgraphs use `@external` to reference fields they don’t resolve directly—typically when using `@requires`, `@provides`, or to satisfy interface contracts. ```graphql directive @external on FIELD_DEFINITION | OBJECT @@ -22,7 +23,7 @@ directive @external on FIELD_DEFINITION | OBJECT Federation v1 has looser validation rules, and `@external` may be required even when a field is technically resolvable. **`@key` usage**: -Fields in `@key(fields: ...)` must be marked `@external` if not owned by the subgraph. This is especially true for entity extensions. +Fields in `@key(fields: ...)` must be marked `@external` if they are declared in the subgraph but resolved in another. This is especially true for entity extensions. **`@requires` and `@provides`**: All fields in these FieldSets must be marked `@external`, regardless of whether they are leaf or non-leaf fields. @@ -39,7 +40,7 @@ Federation v2 introduces stricter, more precise rules for `@external`. Fields in `@key(fields: ...)` do not need `@external` unless they cannot be resolved in the subgraph. **`@requires` and `@provides`**: -Only leaf fields (or their ancestors within the same FieldSet) that are not owned must be marked `@external`. +Only leaf fields (or their ancestors within the same FieldSet) that are declared in the subgraph but resolved in another must be marked `@external`. **Validation**: In Federation v2, `@external` is only valid if the field is: @@ -65,7 +66,7 @@ You can apply `@external` to: - **Individual fields** on an object. - An **entire object type**, which treats all of its fields as external. -The key idea: `@external` marks a field that is defined but not owned by this subgraph. +The key idea: `@external` marks a field that is defined in this subgraph but resolved in another. Whether and how the field resolves depends on the query path and the directives involved. ## When to Use @@ -89,7 +90,7 @@ The meaning of `@external` depends on context. It can indicate: | **Unresolvable** | The field cannot be resolved by the current subgraph | | **Conditionally resolvable** | The field is resolved only in certain paths (e.g., via `@provides`) | | **Legacy `@key` usage (v1)** | The field is resolvable, but marked `@external` to satisfy a key on an extension | -| **Interface satisfaction** | The field is required to fulfill an interface but not owned by this subgraph | +| **Interface satisfaction** | The field is required to fulfill an interface but is resolved in a different subgraph | These distinctions help explain why `@external` may appear in places that seem redundant or unnecessary, especially in Federation v1. @@ -129,15 +130,18 @@ Applying `@external` to a type marks **all its fields** as externally defined. ## Edge Cases -### Legacy `@external` usage with @key on extensions (Federation v1) -In Federation v1, if a type is extended and includes a `@key(fields: ...)` directive, any field used in that key must be marked `@external`, even if it's resolvable by the subgraph. +### Legacy `@external` usage with `@key` on extensions (Federation v1) +In Federation v1, an `@external` field that is referenced by a `@key(fields: ...)` FieldSet on an extension definition must be explicitly marked. +This is considered legacy syntax, and the field is always resolvable by that subgraph in V1 and V2. ```graphql # Subgraph A (Federation v1) extend type Product @key(fields: "id") { id: ID! @external } ``` -While this legacy syntax is no longer required in Federation v2, it's still valid and necessary in v1 to ensure composition doesn't fail. +This syntax was required in Federation v1 for entity extensions. +In v1, primary key fields on extensions had to be marked with `@external`, even if the subgraph could resolve them. +The field was always resolvable — `@external` was simply part of the legacy composition model. ### Misusing `@external` on key fields in Federation v2 @@ -163,7 +167,16 @@ type Product @key(fields: "id") @key(fields: "upc") { ``` This fails composition because Subgraph A marks `id` and `upc` as `@external`, but doesn’t actually provide a way to resolve them. The router cannot satisfy queries that require navigating from A to B using these keys. - +```graphql +{ + products { + id + } +} +``` +This fails for the following reasons: +- `Product.id` is not resolvable from Subgraph A. +- The router cannot move to Subgraph B using either key, since their fields are not resolvable from Subgraph A. ### External fields without a matching definition @@ -194,7 +207,7 @@ This triggers a composition error (`EXTERNAL_MISSING_ON_BASE`). ### Normalization of `@external` on extended types Applying `@external` to a type does **not** automatically apply it to fields added later via `extend` blocks. -This distinction is especially important when normalizing schemas across subgraphs. +This distinction is important when normalizing schemas across subgraphs. ```graphql # Subgraph A @@ -221,7 +234,7 @@ extend type Location { ### Federation v1 inconsistencies -In Federation v1, marking a field as `@external` is often **required**, even when it’s not referenced by `@requires` or `@provides`, due to weaker validation and looser assumptions. +In Federation v1, marking a field as `@external` is often required, even when it’s not referenced by `@requires` or `@provides`, due to weaker validation and looser assumptions. If you're migrating from v1 to v2: @@ -230,7 +243,7 @@ If you're migrating from v1 to v2: * Prefer field-level precision over broad `@external` usage. ### Silent removal of unresolved `@external` fields -In Federation v1, a field marked `@external` that does not have a matching, resolvable definition in another subgraph is silently removed during composition. +In Federation v1, a field marked `@external` without a matching, resolvable definition in another subgraph is removed during composition. ```graphql # Subgraph A type Product { @@ -239,7 +252,7 @@ type Product { ``` If no other subgraph defines `legacyTag`, it will not appear in the composed supergraph at all. -WunderGraph Cosmo will emit a warning if it detects `@external` fields that do not match a known definition elsewhere. +Cosmo emits a warning if it detects `@external` fields that do not match a known definition elsewhere. ### Validation prevents fully-external types While Federation v1 permits liberal use of `@external`, composition will fail if all fields of a type are marked `@external`. @@ -252,4 +265,4 @@ While Federation v1 permits liberal use of `@external`, composition will fail if | Field used in `@key` (v2, on object) | ⚠️ | Only valid if truly unresolvable | | Type-level `@external` | ✅ | Applies to all fields on that type | | No non-external counterpart exists | ❌ | Invalid — triggers error (v2) or silent removal (v1) | -| All fields on a type marked `@external` | ❌ | Invalid — must own at least one field | +| All fields on a type marked `@external` | ❌ | Invalid — the subgraph must define at least one field without @external | From 0f557c7626820fa5a7e711ca5e66decba43ca760 Mon Sep 17 00:00:00 2001 From: Brendan Bondurant Date: Fri, 6 Jun 2025 09:32:00 -0700 Subject: [PATCH 4/5] Small rewrites for clarity --- docs/federation/directives/external.mdx | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/federation/directives/external.mdx b/docs/federation/directives/external.mdx index 4b7298e3..42e9bac2 100644 --- a/docs/federation/directives/external.mdx +++ b/docs/federation/directives/external.mdx @@ -10,7 +10,7 @@ Supported in both Federation v1 and v2, though usage and validation rules differ ## Overview The `@external` directive marks a field or type that is declared in this subgraph but resolved by another subgraph. -It indicates that the field’s value is resolved by another subgraph, sometimes unconditionally and sometimes only in specific query paths. +The field may be resolved unconditionally or only in specific query paths, such as when used with `@provides`. Subgraphs use `@external` to reference fields they don’t resolve directly—typically when using `@requires`, `@provides`, or to satisfy interface contracts. ```graphql @@ -26,7 +26,7 @@ Federation v1 has looser validation rules, and `@external` may be required even Fields in `@key(fields: ...)` must be marked `@external` if they are declared in the subgraph but resolved in another. This is especially true for entity extensions. **`@requires` and `@provides`**: -All fields in these FieldSets must be marked `@external`, regardless of whether they are leaf or non-leaf fields. +All fields in these field sets must be marked `@external`, regardless of whether they are leaf or non-leaf fields. **Validation**: In Federation v1, misconfigured `@external` fields are often silently removed during composition, with no visible error. @@ -40,14 +40,15 @@ Federation v2 introduces stricter, more precise rules for `@external`. Fields in `@key(fields: ...)` do not need `@external` unless they cannot be resolved in the subgraph. **`@requires` and `@provides`**: -Only leaf fields (or their ancestors within the same FieldSet) that are declared in the subgraph but resolved in another must be marked `@external`. +Only leaf fields, or parent fields explicitly listed in the same `@requires` or `@provides` field set string, must be marked `@external` if they are declared in the subgraph but resolved in another. +A field set is the string argument passed to these directives, such as "user { email }". **Validation**: In Federation v2, `@external` is only valid if the field is: -- Referenced in a `@key`, `@requires`, or `@provides` FieldSet. +- Referenced in a `@key`, `@requires`, or `@provides` field set. - **OR** required to satisfy an interface. -It must also have a matching non-`@external` definition in another subgraph. Otherwise, composition fails. +It must also have a matching field in another subgraph that is not marked `@external`, known as a shared field instance. Otherwise, composition fails. WunderGraph Cosmo helps validate `@external` usage, especially in Federation v1 environments where misconfigurations can be harder to detect. @@ -63,26 +64,25 @@ The `@external` directive: - Enables safe use in **directive arguments** like `@requires` and `@provides` without triggering composition errors. You can apply `@external` to: -- **Individual fields** on an object. -- An **entire object type**, which treats all of its fields as external. +- **Individual field definitions** within an object. +- An **entire object type**, which marks all of its fields as external. The key idea: `@external` marks a field that is defined in this subgraph but resolved in another. Whether and how the field resolves depends on the query path and the directives involved. ## When to Use -Only use `@external` when: -- The field is **unresolvable** from the current subgraph **and** the field is **referenced by**: - - `@key(fields: "...")` (in Federation v1 only) - - `@provides(fields: "...")` - - `@requires(fields: "...")` -- **OR** the field is required to satisfy an **interface** implemented by the type +Only use `@external` when the field is **unresolvable** from the current subgraph **and** the field is **referenced by**: + - `@key(fields: "...")` (in Federation v1 only) + - `@provides(fields: "...")` + - `@requires(fields: "...")` +**OR** the field is required to satisfy an **interface** implemented by the type - For example, a type may need to declare `@external` fields to fulfill an interface it implements from another subgraph. -A valid `@external` field must have a matching, resolvable definition in another subgraph. -This is known as a **shared field instance** and ensures that the field can be correctly composed and resolved across your federated graph. +A valid `@external` field must have a matching, resolvable definition in another subgraph that is not marked `@external`. +This creates a shared field instance—a field defined in one subgraph and referenced externally in another—that enables composition and cross-subgraph resolution. -## Interpreting `@external` +## What `@external` Means in Different Contexts The meaning of `@external` depends on context. It can indicate: | Meaning | Description | @@ -131,7 +131,7 @@ Applying `@external` to a type marks **all its fields** as externally defined. ## Edge Cases ### Legacy `@external` usage with `@key` on extensions (Federation v1) -In Federation v1, an `@external` field that is referenced by a `@key(fields: ...)` FieldSet on an extension definition must be explicitly marked. +In Federation v1, an `@external` field that is referenced by a `@key(fields: ...)` field set on an extension definition must be explicitly marked. This is considered legacy syntax, and the field is always resolvable by that subgraph in V1 and V2. ```graphql # Subgraph A (Federation v1) From a5529191669b76d91286b92f7acf8a949a9871cf Mon Sep 17 00:00:00 2001 From: Brendan Bondurant Date: Fri, 6 Jun 2025 09:40:54 -0700 Subject: [PATCH 5/5] added some inline code --- docs/federation/directives/external.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/federation/directives/external.mdx b/docs/federation/directives/external.mdx index 42e9bac2..406c3a4b 100644 --- a/docs/federation/directives/external.mdx +++ b/docs/federation/directives/external.mdx @@ -11,7 +11,7 @@ Supported in both Federation v1 and v2, though usage and validation rules differ The `@external` directive marks a field or type that is declared in this subgraph but resolved by another subgraph. The field may be resolved unconditionally or only in specific query paths, such as when used with `@provides`. -Subgraphs use `@external` to reference fields they don’t resolve directly—typically when using `@requires`, `@provides`, or to satisfy interface contracts. +Subgraphs use `@external` to reference fields they don’t resolve directly—typically when using `@requires`, `@provides`, or to satisfy `interface` contracts. ```graphql directive @external on FIELD_DEFINITION | OBJECT @@ -41,12 +41,12 @@ Fields in `@key(fields: ...)` do not need `@external` unless they cannot be reso **`@requires` and `@provides`**: Only leaf fields, or parent fields explicitly listed in the same `@requires` or `@provides` field set string, must be marked `@external` if they are declared in the subgraph but resolved in another. -A field set is the string argument passed to these directives, such as "user { email }". +A field set is the string argument passed to these directives, such as `"user { email }"`. **Validation**: In Federation v2, `@external` is only valid if the field is: - Referenced in a `@key`, `@requires`, or `@provides` field set. -- **OR** required to satisfy an interface. +- **OR** required to satisfy an `interface`. It must also have a matching field in another subgraph that is not marked `@external`, known as a shared field instance. Otherwise, composition fails. @@ -76,7 +76,7 @@ Only use `@external` when the field is **unresolvable** from the current subgrap - `@key(fields: "...")` (in Federation v1 only) - `@provides(fields: "...")` - `@requires(fields: "...")` -**OR** the field is required to satisfy an **interface** implemented by the type +**OR** the field is required to satisfy an `interface` implemented by the type - For example, a type may need to declare `@external` fields to fulfill an interface it implements from another subgraph. A valid `@external` field must have a matching, resolvable definition in another subgraph that is not marked `@external`. @@ -89,7 +89,7 @@ The meaning of `@external` depends on context. It can indicate: | ---------------------------- | -------------------------------------------------------------------------------- | | **Unresolvable** | The field cannot be resolved by the current subgraph | | **Conditionally resolvable** | The field is resolved only in certain paths (e.g., via `@provides`) | -| **Legacy `@key` usage (v1)** | The field is resolvable, but marked `@external` to satisfy a key on an extension | +| **Legacy `@key` usage (v1)** | The field is resolvable, but marked `@external` to satisfy a `@key` on an extension | | **Interface satisfaction** | The field is required to fulfill an interface but is resolved in a different subgraph | These distinctions help explain why `@external` may appear in places that seem redundant or unnecessary, especially in Federation v1.