Agent Skill · WunderGraph

graphql-federation

GraphQL Federation skill for creating correct federation subgraphs. Use when: (1) Creating new federation subgraphs, (2) Adding entities with @key directives, (3) Extending types across subgraphs, (4) Using federation directives (@shareable, @external, @override, @provides, @requires, @inaccessible, @interfaceObject, @authenticated, @requiresScopes), (5) Debugging composition errors, (6) Understanding federation rules and patterns. MANDATORY TRIGGERS: federation, subgraph, @key, entity, federated graph, supergraph, GraphQL composition, router schema

Provider: WunderGraph Path in repo: skills/graphql-federation/SKILL.md

Skill body

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:

@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:

@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:

@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:

@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:

@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:

@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:

@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:

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: