graphql-federation

install
source · Clone the upstream repo
git clone https://github.com/wundergraph/graphql-federation-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/wundergraph/graphql-federation-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/graphql-federation" ~/.claude/skills/wundergraph-graphql-federation-skill-graphql-federation && rm -rf "$T"
manifest: skills/graphql-federation/SKILL.md
source content

GraphQL Federation Skill

Create correct GraphQL federation subgraphs following WunderGraph Cosmo composition rules.

Core Concepts

Subgraph: A GraphQL microservice contributing to the federated graph. Federated Graph/Supergraph: The composed GraphQL interface for clients. Router: Plans operations, routes requests to subgraphs, joins data. Entity: A type with

@key
directive resolvable across subgraphs. Router Schema vs Client Schema: Router sees all fields; clients don't see
@inaccessible
items.

Essential Directives

@key - Define Entities

# Basic entity
type User @key(fields: "id") {
  id: ID!
  name: String!
}

# Multiple keys
type Product @key(fields: "id") @key(fields: "sku") {
  id: ID!
  sku: String!
  name: String!
}

# Composite key
type Review @key(fields: "author { id } product { id }") {
  author: User!
  product: Product!
  rating: Int!
}

# Non-resolvable key (reference only, can't resolve this entity)
type User @key(fields: "id", resolvable: false) {
  id: ID!
}

Key Rules:

  • Key fields cannot have arguments
  • Key fields cannot be abstract types (interfaces/unions)
  • Key fields must select leaf scalars or nested objects with selections
  • Multiple keys provide alternative resolution paths

@shareable - Allow Multiple Definitions

# When same field exists in multiple subgraphs
type User @key(fields: "id") {
  id: ID!
  name: String! @shareable  # Can be defined in other subgraphs
}

# Object-level shareable applies to all fields
type User @key(fields: "id") @shareable {
  id: ID!
  name: String!
  email: String!
}

Shareable Rules:

  • Fields defined in multiple subgraphs MUST be @shareable
  • Key fields used in other subgraphs' keys must be @shareable
  • Root type fields (Query, Mutation) are implicitly shareable

@external - Reference External Fields

# Mark fields resolved by another subgraph
extend type User @key(fields: "id") {
  id: ID! @external
  name: String! @external
  reviews: [Review!]!  # This subgraph resolves reviews
}

External Rules:

  • Use on key fields when extending entities
  • Use on fields referenced in @requires
  • Use on fields referenced in @provides
  • External fields are NOT resolved by this subgraph

@requires - Depend on External Data

type Product @key(fields: "id") {
  id: ID!
  price: Float! @external
  currency: String! @external
  formattedPrice: String! @requires(fields: "price currency")
}

Requires Rules:

  • Referenced fields must be @external
  • Can reference nested fields:
    @requires(fields: "details { weight }")
  • Router fetches required fields before calling this resolver

@provides - Declare Additional Fields

type Query {
  topProducts: [Product!]! @provides(fields: "name description")
}

type Product @key(fields: "id") {
  id: ID!
  name: String! @external
  description: String! @external
}

Provides Rules:

  • Declares that a resolver provides additional fields
  • Referenced fields must be @external in this subgraph
  • Optimizes router query planning

@override - Transfer Field Ownership

# In new subgraph, taking over from "old-service"
type User @key(fields: "id") {
  id: ID!
  name: String! @override(from: "old-service")
}

Override Rules:

  • Cannot override from same subgraph
  • Overridden field in source doesn't need @shareable
  • Used for gradual migration between subgraphs

@inaccessible - Hide from Client Schema

type User @key(fields: "id") {
  id: ID!
  name: String!
  internalId: String! @inaccessible  # Hidden from clients
}

# Entire type can be inaccessible
type InternalMetrics @inaccessible {
  requestCount: Int!
}

Inaccessible Rules:

  • Field exists in router schema but not client schema
  • Still subject to @shareable rules
  • All fields on a type cannot be @inaccessible (type becomes useless)
  • Required arguments cannot be @inaccessible alone

@interfaceObject - Treat Interface as Object

# Subgraph A defines the interface
interface Media @key(fields: "id") {
  id: ID!
  title: String!
}

type Book implements Media @key(fields: "id") {
  id: ID!
  title: String!
  author: String!
}

# Subgraph B adds fields to ALL implementations
type Media @key(fields: "id") @interfaceObject {
  id: ID!
  reviews: [Review!]!  # Added to Book and all Media types
}

InterfaceObject Rules:

  • Cannot define implementations alongside @interfaceObject
  • Fields propagate to all concrete implementations
  • Must have @key matching the interface's key

Authorization Directives

type Query {
  publicData: String!
  privateData: String! @authenticated
  adminData: String! @requiresScopes(scopes: [["admin"], ["superuser"]])
}

# Type-level auth
type SecretDocument @authenticated @requiresScopes(scopes: [["read:secrets"]]) {
  content: String!
}

Common Patterns

Entity Extension

# Subgraph A - owns User
type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
}

# Subgraph B - extends User with posts
extend type User @key(fields: "id") {
  id: ID! @external
  posts: [Post!]!
}

type Post @key(fields: "id") {
  id: ID!
  title: String!
  author: User!
}

Federated Mutations

type Mutation {
  createUser(input: CreateUserInput!): User!
}

# Entity returned from mutation allows field resolution from other subgraphs
type User @key(fields: "id") {
  id: ID!
  name: String!
}

Interface Entities

interface Node @key(fields: "id") {
  id: ID!
}

type User implements Node @key(fields: "id") {
  id: ID!
  name: String!
}

type Product implements Node @key(fields: "id") {
  id: ID!
  title: String!
}

Error Prevention

Shareability Errors

# ERROR: Field defined in multiple subgraphs without @shareable
# Subgraph A
type User @key(fields: "id") {
  id: ID!
  name: String!  # Missing @shareable
}

# Subgraph B
type User @key(fields: "id") {
  id: ID!
  name: String!  # Conflict!
}

# FIX: Add @shareable to both
type User @key(fields: "id") {
  id: ID!
  name: String! @shareable
}

Resolvability Errors

# ERROR: Field cannot be resolved from Query entry point
# Subgraph A
type Query {
  user: User!  # Only in A
}

type User {
  id: ID!
  name: String!  # Only in A
}

# Subgraph B
type User {
  email: String!  # Only in B, but User has no @key!
}

# FIX: Add @key to make User an entity
type User @key(fields: "id") {
  id: ID!
  name: String!
}

External Field Errors

# ERROR: @requires on non-external field
type Product @key(fields: "id") {
  id: ID!
  price: Float!  # Missing @external
  formattedPrice: String! @requires(fields: "price")
}

# FIX: Mark as external
type Product @key(fields: "id") {
  id: ID!
  price: Float! @external
  formattedPrice: String! @requires(fields: "price")
}

Reference Files

For detailed directive specifications, see:

  • references/directives.md
    - Complete directive documentation
  • references/composition-rules.md
    - Composition validation rules
  • references/patterns.md
    - Advanced federation patterns