GraphQL Fusion: Distributed Schema Governance [2026 Guide]
What Is GraphQL Fusion?
GraphQL Fusion is an open composite-schema specification that lets large engineering organizations split a single public GraphQL API across many independently owned subgraphs — without forcing every team to adopt the same server framework or runtime. Unlike Apollo Federation, which ties you to Apollo Router and the @key / @external directive vocabulary, Fusion defines a transport-agnostic contract: any compliant gateway can stitch the graph together at request time.
The reference implementation that reached general availability in April 2026 is Hot Chocolate 14 by ChilliCream, but the spec is implementation-neutral. This tutorial walks through the complete workflow — from bootstrapping your first subgraph to enforcing team-level schema governance policies in CI — using Hot Chocolate 14 tooling throughout.
Bottom Line
GraphQL Fusion decouples schema ownership from gateway implementation. Each team publishes a self-describing SDL slice; the fusion-gateway composes them at startup. Governance happens in CI — not in meetings — through automated composition checks and policy directives that prevent unauthorized cross-team field access.
Prerequisites
Before you begin, ensure you have:
- .NET 9 SDK or later installed (
dotnet --versionshould show9.x) - Hot Chocolate 14 NuGet packages (
HotChocolate.AspNetCore≥ 14.0.0) - fusion CLI:
dotnet tool install -g ChilliCream.Fusion.Cli - At least two distinct ASP.NET Core projects representing different domain teams
- A shared Git repository or artifact store for publishing subgraph SDL files
- Docker (optional but recommended for running the gateway locally)
Step 1: Initialize the Subgraph
Each team starts by bootstrapping their subgraph. In this tutorial the Orders team and the Accounts team each own a separate ASP.NET Core project.
# Orders team — inside their project folder
dotnet new web -n OrdersSubgraph
cd OrdersSubgraph
dotnet add package HotChocolate.AspNetCore --version 14.0.0
dotnet add package HotChocolate.Fusion.AspNetCore --version 14.0.0
# Initialize the Fusion subgraph manifest
dotnet fusion subgraph init --name orders
The fusion subgraph init command writes a fusion-subgraph.json manifest to the project root. This file declares the subgraph name, the URL it will be reachable at, and the governance contact (team email or Slack channel). Commit this file — it becomes the contract the gateway trusts.
Register the subgraph in Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddGraphQLServer()
.AddFusionSubgraph()
.AddQueryType<OrderQuery>()
.AddMutationType<OrderMutation>();
var app = builder.Build();
app.MapGraphQL();
app.Run();
Repeat the same initialization for the Accounts team in their own project.
Step 2: Annotate Entity Boundaries
Fusion uses the @fusion__lookup directive (replacing Federation's @key) to declare which fields can serve as cross-subgraph entity entry points. Annotate your entity types in the SDL or via C# attributes:
// OrderQuery.cs — Orders subgraph
public class OrderQuery
{
[FusionLookup]
[GraphQLName("orderById")]
public Order? GetOrder(Guid id) => OrderRepository.Find(id);
}
[ObjectType]
public class Order
{
public Guid Id { get; set; }
public Guid AccountId { get; set; } // foreign key — resolved by Accounts subgraph
public decimal Total { get; set; }
public DateTime CreatedAt { get; set; }
}
In the Accounts subgraph, expose the complementary lookup:
public class AccountQuery
{
[FusionLookup]
[GraphQLName("accountById")]
public Account? GetAccount(Guid id) => AccountRepository.Find(id);
}
[ObjectType]
public class Account
{
public Guid Id { get; set; }
public string Email { get; set; } = "";
public string DisplayName { get; set; } = "";
}
Tip: use the TechBytes Code Formatter to normalize indentation and brace styles across team SDL contributions before committing — small formatting drift causes noisy diffs in SDL review.
Step 3: Compose the Supergraph
Publishing the supergraph is a CI step, not a manual hand-off. Each team exports their SDL, then the platform team runs composition:
# Each team publishes their SDL to a shared artifact location
dotnet fusion subgraph publish \
--subgraph orders \
--url https://orders.internal/graphql \
--output ./sdl/orders.graphql
dotnet fusion subgraph publish \
--subgraph accounts \
--url https://accounts.internal/graphql \
--output ./sdl/accounts.graphql
# Platform team composes
dotnet fusion compose \
--subgraph ./sdl/orders.graphql \
--subgraph ./sdl/accounts.graphql \
--output ./gateway/supergraph.fgp
The dotnet fusion compose command produces a .fgp (Fusion Gateway Package) file — a binary-encoded, versioned supergraph artifact. This file is what the gateway loads at startup. Store it in your artifact registry (S3, Azure Blob, Artifactory) alongside a semantic version tag.
Add composition to your GitHub Actions pipeline:
- name: Compose Supergraph
run: |
dotnet fusion compose \
--subgraph sdl/orders.graphql \
--subgraph sdl/accounts.graphql \
--output gateway/supergraph.fgp
# Fails the PR if composition breaks — no merging broken schemas
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: supergraph
path: gateway/supergraph.fgp
Step 4: Enforce Governance Policies
Governance in GraphQL Fusion is expressed through directives applied at compose time. Two directives carry most of the policy weight:
@fusion__require— Marks a field as requiring the caller to have a specific claim or scope. The gateway enforces this during query planning; unauthorized fields are removed from the plan before execution.@fusion__hide— Completely removes a field from the composed schema for external consumers while keeping it visible within the trusted internal graph.
Apply them in your SDL annotations:
type Order {
id: ID!
total: Float!
internalCostBasis: Float @fusion__hide # never exposed externally
refundEligible: Boolean @fusion__require(scopes: ["billing:read"])
}
For team-level ownership, add a governance block to fusion-subgraph.json:
{
"name": "orders",
"team": "commerce-platform",
"contact": "commerce-platform@example.com",
"governance": {
"breakingChangePolicy": "require-approval",
"deprecationNoticeDays": 30,
"allowedConsumers": ["gateway", "internal-bff"]
}
}
The breakingChangePolicy: require-approval flag causes the composition step to emit a non-zero exit code when a breaking change (removed field, changed argument type) is detected, blocking the PR until a designated reviewer approves the BREAKING_CHANGE_APPROVAL label in GitHub.
Step 5: Set Up the Fusion Gateway
The gateway is a separate ASP.NET Core application that loads the .fgp artifact and handles all incoming client queries:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddFusionGatewayServer()
.ConfigureFromFile("./supergraph.fgp", watchForUpdates: true);
var app = builder.Build();
app.MapGraphQL();
app.Run();
The watchForUpdates: true flag enables hot-reload of the supergraph artifact. When your CI pipeline publishes a new .fgp to a shared volume or object store and your deployment system copies it in place, the gateway reloads within seconds — zero downtime schema updates.
Configure health checks for liveness and readiness probes:
app.MapHealthChecks("/health/live");
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("fusion")
});
Verification & Expected Output
After starting all three services (orders subgraph, accounts subgraph, gateway), run a cross-subgraph query against the gateway:
curl -X POST https://localhost:5000/graphql \
-H 'Content-Type: application/json' \
-d '{
"query": "{ orderById(id: \"abc-123\") { id total account { email displayName } } }"
}'
Expected response:
{
"data": {
"orderById": {
"id": "abc-123",
"total": 149.99,
"account": {
"email": "user@example.com",
"displayName": "Jane Smith"
}
}
}
}
The gateway resolved orderById from the Orders subgraph, extracted accountId, then issued a second fetch to the Accounts subgraph for accountById — all transparent to the client. Verify the query plan by adding the X-Fusion-Query-Plan: true header; the gateway returns the plan as an HTTP trailer.
Run the composition check standalone to confirm governance policies are enforced:
dotnet fusion compose --validate-only \
--subgraph sdl/orders.graphql \
--subgraph sdl/accounts.graphql
# Exit 0 = valid. Exit 1 = composition error with structured JSON diagnostics.
Troubleshooting Top 3
1. "No lookup found for entity type 'Order'"
This error fires during composition when the gateway cannot find a @fusion__lookup-annotated resolver for an entity referenced across subgraphs. Fix: Ensure every entity type that appears in more than one subgraph has at least one resolver decorated with [FusionLookup] in the owning subgraph. Re-run dotnet fusion subgraph publish after adding the attribute, then recompose.
2. Gateway returns 200 with "Subgraph unreachable" partial error
GraphQL Fusion uses partial results by default — if one subgraph is down, the gateway returns data from healthy subgraphs and a structured error for the failed slice. This is intentional but can surprise teams expecting a full 503. Fix: Set "errorBehavior": "fail-fast" in the gateway config if you require all-or-nothing semantics, or handle partial errors in your client with data?.errors inspection.
3. Breaking change blocked in CI but no reviewer is assigned
When breakingChangePolicy: require-approval is set, composition exits 1 and the GitHub Action fails. If no one holds the BREAKING_CHANGE_APPROVAL label power, the PR is permanently blocked. Fix: Add a CODEOWNERS entry mapping *.fgp and fusion-subgraph.json to the platform team. Alternatively, set "breakingChangePolicy": "warn" in non-production environments so developers can iterate freely, with enforcement only on the main branch.
What's Next
You now have a working GraphQL Fusion supergraph with team-scoped ownership, CI-enforced composition, and field-level governance policies. From here, consider:
- Schema Registry Integration: Pipe
.fgpartifacts to a versioned registry (Apollo GraphOS, Confluent Schema Registry, or a custom S3 bucket with lifecycle policies) so you can roll back to any prior supergraph in under a minute. - Distributed Tracing: Hot Chocolate 14's gateway emits OpenTelemetry spans per subgraph fetch. Wire these into your observability stack to pinpoint which subgraph is the latency culprit on any given query.
- Demand Control: Enable
AddFusionGatewayServer().AddDemandControl()to reject queries whose estimated cost exceeds a per-client budget — critical for public-facing APIs where a single deeply nested query could fan out to dozens of subgraph calls. - Persisted Operations: Register allowlisted query hashes to prevent clients from sending arbitrary ad-hoc queries against your production gateway.
Get Engineering Deep-Dives in Your Inbox
Weekly breakdowns of architecture, security, and developer tooling — no fluff.
Related Deep-Dives
GraphQL Federation v2: Distributed Supergraphs at Scale
How Apollo Federation v2 composes, plans queries, and evolves schemas safely — with under 10ms gateway latency at scale.
AI EngineeringAgent-First API Design Pattern for Autonomous LLMs
Designing GraphQL and REST interfaces specifically for LLM agents that need predictable, self-describing, tool-friendly contracts.
System ArchitecturegRPC vs tRPC vs REST [2026] Protocol Decision Guide
A structured comparison of three dominant API protocols to help engineering teams pick the right transport for their use case.