# Directive Reference - Fusion

> Reference for Fusion composition directives like @key, @lookup, @require, and @shareable, with SDL definitions, arguments, and composed output examples.

Canonical source: https://chillicream.com/docs/fusion/directives-reference

Fusion implements the [GraphQL Composite Schemas Specification](https://graphql.github.io/composite-schemas-spec/draft/). The directives defined in this specification are applied to source schemas (subgraph schemas) to control how they compose into a unified composite schema. Each directive entry below shows its SDL definition, what it does, and a practical example with the resulting composed output.

In Hot Chocolate, many of these directives are expressed using C# attributes. See the individual guide pages for supported C# usage and tutorials.

## Quick Reference

| Directive                            | Locations                               | Repeatable | Purpose                                           |
| ------------------------------------ | --------------------------------------- | ---------- | ------------------------------------------------- |
| [@key](#key)                         | OBJECT, INTERFACE                       | Yes        | Define entity identity                            |
| [@lookup](#lookup)                   | FIELD\_DEFINITION                       | No         | Mark entity lookup resolvers                      |
| [@is](#is)                           | ARGUMENT\_DEFINITION                    | No         | Map lookup arguments to entity fields             |
| [@interfaceObject](#interfaceobject) | OBJECT                                  | No         | Contribute fields to an interface                 |
| [@implement](#implement)             | FIELD\_DEFINITION                       | No         | Replace a projected interface-object default      |
| [@require](#require)                 | ARGUMENT\_DEFINITION                    | No         | Declare cross-subgraph data dependencies          |
| [@shareable](#shareable)             | OBJECT, FIELD\_DEFINITION               | Yes        | Allow multiple subgraphs to define the same field |
| [@provides](#provides)               | FIELD\_DEFINITION                       | No         | Declare locally-resolvable subfields              |
| [@external](#external)               | FIELD\_DEFINITION                       | No         | Mark field as owned by another subgraph           |
| [@override](#override)               | FIELD\_DEFINITION                       | No         | Migrate field ownership between subgraphs         |
| [@internal](#internal)               | OBJECT, FIELD\_DEFINITION               | No         | Hide from composite schema and merge process      |
| [@inaccessible](#inaccessible)       | 10 locations                            | No         | Hide from client-facing composite schema          |
| [@eventStream](#eventstream)         | FIELD\_DEFINITION                       | No         | Back a subscription field with a message broker   |
| [@eventCursor](#eventcursor)         | ARGUMENT\_DEFINITION, FIELD\_DEFINITION | No         | Mark the resume cursor for resumable streams      |

---

## Entity Identity and Resolution

### `@key`

Designates an entity's unique key, which identifies how to uniquely reference an instance of an entity across different source schemas.

GraphQL

```
directive @key(fields: FieldSelectionSet!) repeatable on OBJECT | INTERFACE
```

| Argument | Type               | Description                                                                  |
| -------- | ------------------ | ---------------------------------------------------------------------------- |
| fields   | FieldSelectionSet! | A field selection set that forms the unique key (e.g. "id" or "tenantId id") |

Each `@key` directive on a type specifies one distinct unique key for that entity. Apply multiple `@key` directives to define alternative keys that the gateway can use to resolve the entity. Fields referenced in a key are implicitly shareable across subgraphs -- you do not need to add `@shareable` to key fields.

Key fields may supply constant arguments to select a specific variant of a field (for example, `@key(fields: "id(scope: LOCAL)")`). Argument values must be constant literals (no variables), must match the field's declared argument definitions, and all required arguments must be supplied. Composition reports unknown, incompatible, or missing-required arguments as `KEY_INVALID_ARGUMENTS`.

**Example -- single key:**

GraphQL

```
# Source schema
type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Float!
}
```

GraphQL

```
# Composed schema
type Product {
  id: ID!
  name: String!
  price: Float!
}
```

**Example -- multiple keys:**

GraphQL

```
# Source schema
type Product @key(fields: "id") @key(fields: "sku") {
  id: ID!
  sku: String!
  name: String!
}
```

**Example -- composite key:**

GraphQL

```
# Source schema (both fields required together)
type Product @key(fields: "id sku") {
  id: ID!
  sku: String!
  name: String!
}
```

> **In C#:** `[EntityKey("id")]` attribute. See [Entities and Lookups](https://chillicream.com/docs/fusion/entities-and-lookups).

---

### `@lookup`

Marks a field as an entity lookup resolver that the gateway uses to resolve an entity by a stable key.

GraphQL

```
directive @lookup on FIELD_DEFINITION
```

Lookup fields provide the gateway with entry points into a subgraph for entity resolution. A source schema can define multiple lookup fields for the same entity to support resolution by different keys. Lookup fields must return a nullable type and must not return a list.

**Example:**

GraphQL

```
# Source schema
type Query {
  productById(id: ID!): Product @lookup
  productByName(name: String!): Product @lookup
}

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

GraphQL

```
# Composed schema
type Query {
  productById(id: ID!): Product
  productByName(name: String!): Product
}

type Product {
  id: ID!
  name: String!
}
```

> **In C#:** `[Lookup]` attribute. See [Entities and Lookups](https://chillicream.com/docs/fusion/entities-and-lookups).

---

### `@is`

Maps a lookup argument to a field on the entity type when the argument name does not match the field name.

GraphQL

```
directive @is(field: FieldSelectionMap!) on ARGUMENT_DEFINITION
```

| Argument | Type               | Description                                                                   |
| -------- | ------------------ | ----------------------------------------------------------------------------- |
| field    | FieldSelectionMap! | A selection map that describes the mapping from entity fields to the argument |

When a lookup argument name matches the corresponding field on the return type, you can omit `@is`. Use `@is` when the names differ or when the mapping involves nested fields. Fields in the selection map may carry constant arguments (for example, `@is(field: "id(scope: LOCAL)")`); argument values must be constant literals (no variables), must match the field's argument definitions, and all required arguments must be supplied. Argument errors surface as `IS_INVALID_FIELDS`.

**Example -- argument name differs from field name:**

GraphQL

```
# Source schema
type Query {
  personById(personId: ID! @is(field: "id")): Person @lookup
}

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

GraphQL

```
# Composed schema
type Query {
  personById(personId: ID!): Person
}

type Person {
  id: ID!
  name: String!
}
```

**Example -- nested field reference:**

GraphQL

```
# Source schema
type Query {
  personByAddressId(id: ID! @is(field: "address.id")): Person @lookup
}
```

> **In C#:** The `@is` mapping is inferred automatically from the argument name. When it does not match, use the field parameter convention described in [Entities and Lookups](https://chillicream.com/docs/fusion/entities-and-lookups).

---

## Interface Objects

### `@interfaceObject`

Declares an object type as a stand-in for an interface with the same name in another source schema. The stand-in can contribute fields to the interface without declaring its concrete implementations.

GraphQL

```
directive @interfaceObject on OBJECT
```

The stand-in must declare at least one `@key`, and each key must match a key on the interface entity. A source that contributes non-key fields through the stand-in must also provide a lookup returning the stand-in.

**Example:**

GraphQL

```
# Catalog source schema
interface Media @key(fields: "id") {
  id: ID!
  title: String!
}

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

GraphQL

```
# Analytics source schema
type Query {
  mediaByKey(id: ID!): Media @lookup @internal
}

type Media @interfaceObject @key(fields: "id") {
  id: ID!
  views: Int!
}
```

GraphQL

```
# Composed schema
interface Media {
  id: ID!
  title: String!
  views: Int!
}

type Book implements Media {
  id: ID!
  title: String!
  views: Int!
}
```

See [Interface Objects](https://chillicream.com/docs/fusion/interface-objects) for lookup requirements, projected fields, and concrete type recovery.

---

### `@implement`

Marks a field as an explicit implementation that replaces a default projected from an interface object.

GraphQL

```
directive @implement on FIELD_DEFINITION
```

Use `@implement` only when a concrete object or a more-specific interface stand-in declares a field that would otherwise adopt a projected default. Composition rejects an unmarked replacement and rejects `@implement` when there is no applicable default.

**Example:**

GraphQL

```
# Catalog source schema
type Book implements Media @key(fields: "id") {
  id: ID!
  title: String!
  views: Int! @implement
}
```

GraphQL

```
# Analytics source schema
type Media @interfaceObject @key(fields: "id") {
  id: ID!
  views: Int!
}
```

In the composed schema, Catalog owns `Book.views`. Other `Media` implementations continue to use the Analytics default.

See [Replace a Projected Default](https://chillicream.com/docs/fusion/interface-objects#replace-a-projected-default) for the complete pattern.

---

## Data Requirements

### `@require`

Declares that a resolver argument needs data from fields owned by other subgraphs. The gateway resolves the required data first, then passes it to the resolver. Arguments annotated with `@require` are removed from the composed client-facing schema.

GraphQL

```
directive @require(field: FieldSelectionMap!) on ARGUMENT_DEFINITION
```

| Argument | Type               | Description                                                             |
| -------- | ------------------ | ----------------------------------------------------------------------- |
| field    | FieldSelectionMap! | A selection map describing which fields from the entity type are needed |

Use `@require` when a resolver in one subgraph needs data that another subgraph owns. The gateway handles the data fetching automatically. This shifts cross-service data dependencies from hidden runtime failures to validated build-time contracts. Fields in the selection map may carry constant arguments to select a specific variant (for example, `@require(field: "dimension(unit: METRIC)")`); argument values must be constant literals (no variables), must match the field's argument definitions, and all required arguments must be supplied. Argument errors surface as `REQUIRE_INVALID_FIELDS`.

**Example -- scalar requirement:**

GraphQL

```
# Source schema (Shipping subgraph)
type Product {
  shippingEstimate(zip: String!, weight: Float! @require(field: "weight")): Int!
}
```

GraphQL

```
# Composed schema (weight argument removed)
type Product {
  shippingEstimate(zip: String!): Int!
}
```

**Example -- structured requirement with input type:**

GraphQL

```
# Source schema
type Product {
  delivery(
    zip: String!
    dimension: ProductDimensionInput!
      @require(field: "{ size: dimension.size, weight: dimension.weight }")
  ): DeliveryEstimates
}
```

> **In C#:** `[Require]` attribute on method parameters. See [Data Requirements](https://chillicream.com/docs/fusion/data-requirements-and-mapping).

---

## Field Ownership and Sharing

### `@shareable`

Allows multiple subgraphs to define the same field. Without `@shareable`, defining the same non-key field in two subgraphs causes a composition error.

GraphQL

```
directive @shareable repeatable on OBJECT | FIELD_DEFINITION
```

When multiple subgraphs mark the same field as `@shareable`, they declare that the field is semantically equivalent across all definitions. The gateway is free to resolve the field from any subgraph that defines it. Apply `@shareable` to an object type to make all its fields shareable.

**Example:**

GraphQL

```
# Source schema A (Products subgraph)
type Product @key(fields: "id") {
  id: ID!
  name: String! @shareable
  description: String!
}
```

GraphQL

```
# Source schema B (Inventory subgraph)
type Product @key(fields: "id") {
  id: ID!
  name: String! @shareable
  inStock: Boolean!
}
```

GraphQL

```
# Composed schema
type Product {
  id: ID!
  name: String!
  description: String!
  inStock: Boolean!
}
```

> **In C#:** `[Shareable]` attribute. See [Field Ownership](https://chillicream.com/docs/fusion/field-ownership-and-sharing).

---

### `@provides`

Declares that a field returning an entity can resolve specific subfields of that entity locally, without requiring an additional call to another subgraph.

GraphQL

```
directive @provides(fields: FieldSelectionSet!) on FIELD_DEFINITION
```

| Argument | Type               | Description                                                                                        |
| -------- | ------------------ | -------------------------------------------------------------------------------------------------- |
| fields   | FieldSelectionSet! | A field selection set describing the subfields of the returned type that this subgraph can resolve |

This is a query-planning optimization. When a client requests provided subfields through this particular field path, the gateway resolves them from the current subgraph instead of making a separate call. Fields referenced in `@provides` must be marked `@external` on the return type. Fields in a `@provides` selection must not have arguments; composition reports any argument usage as `PROVIDES_FIELDS_HAS_ARGUMENTS`.

**Example:**

GraphQL

```
# Source schema (Reviews subgraph)
type Review {
  id: ID!
  body: String!
  author: User @provides(fields: "email")
}

type User @key(fields: "id") {
  id: ID!
  email: String! @external
}
```

GraphQL

```
# Composed schema
type Review {
  id: ID!
  body: String!
  author: User
}

type User {
  id: ID!
  email: String!
}
```

> **In C#:** `[Provides("email")]` attribute. See [Field Ownership](https://chillicream.com/docs/fusion/field-ownership-and-sharing).

---

### `@external`

Marks a field as owned by another subgraph. The current subgraph references the field for entity identification (via `@key`) or to provide it locally through `@provides`.

GraphQL

```
directive @external on FIELD_DEFINITION
```

Every `@external` field must be referenced by at least one `@provides` directive or used in a `@key`. An unused `@external` field causes a composition error. External fields cannot be combined with `@override` or `@provides` on the same field.

**Example -- external field used with `@provides`:**

GraphQL

```
# Source schema (Reviews subgraph)
type Review {
  id: ID!
  author: User @provides(fields: "email")
}

type User @key(fields: "id") {
  id: ID!
  email: String! @external
}
```

**Example -- external field used as entity key:**

GraphQL

```
# Source schema (Payments subgraph)
type Product @key(fields: "sku") {
  sku: String! @external
  price: Float!
}

type Query {
  productBySku(sku: String!): Product @lookup
}
```

> **In C#:** `[External]` attribute (from `HotChocolate.ApolloFederation.Types`). See [Field Ownership](https://chillicream.com/docs/fusion/field-ownership-and-sharing).

---

### `@override`

Migrates field ownership from one subgraph to another. The current subgraph takes responsibility for resolving the field, and the original subgraph stops serving it. The original subgraph does not need to be modified.

GraphQL

```
directive @override(from: String!) on FIELD_DEFINITION
```

| Argument | Type    | Description                                                       |
| -------- | ------- | ----------------------------------------------------------------- |
| from     | String! | The name of the source schema that originally provided this field |

Use `@override` to move a field to a new subgraph during schema evolution. The overriding subgraph typically marks the entity's key fields as `@external`. Cyclic overrides cause a composition error.

**Example:**

GraphQL

```
# Source schema: original Catalog subgraph (unchanged)
type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Float!
}
```

GraphQL

```
# Source schema: new Payments subgraph (takes over price)
type Product @key(fields: "id") {
  id: ID! @external
  price: Float! @override(from: "Catalog")
  tax: Float!
}
```

GraphQL

```
# Composed schema
type Product {
  id: ID!
  name: String!
  price: Float!
  tax: Float!
}
```

> **In C#:** `[Override("Catalog")]` attribute (from `HotChocolate.ApolloFederation.Types`). See [Schema Exposure and Evolution](https://chillicream.com/docs/fusion/schema-exposure-and-evolution).

---

## Visibility

### `@internal`

Hides a type or field from the composite schema and excludes it from the standard schema-merging process. The gateway can still use internal fields as lookup entry points for entity resolution.

GraphQL

```
directive @internal on OBJECT | FIELD_DEFINITION
```

Internal types and fields do not collide with similarly named fields or types on other source schemas, because they bypass merge rules entirely. Use `@internal` to create resolution-only entry points that clients cannot query directly.

**Example:**

GraphQL

```
# Source schema A
type Query {
  productById(id: ID!): Product @lookup
  productBySku(sku: ID!): Product @lookup @internal
}

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

GraphQL

```
# Composed schema (internal lookup removed)
type Query {
  productById(id: ID!): Product
}

type Product {
  id: ID!
  sku: ID!
  name: String!
}
```

> **In C#:** `[Internal]` attribute. See [Schema Exposure and Evolution](https://chillicream.com/docs/fusion/schema-exposure-and-evolution).

---

### `@inaccessible`

Prevents a type system member from appearing in the client-facing composite schema, even if it is accessible in the underlying source schemas.

GraphQL

```
directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
```

Unlike `@internal`, inaccessible elements still participate in composition merging and can be referenced by `@require` dependencies in other subgraphs. If any source schema marks a type system member as `@inaccessible`, it is hidden from the composite schema -- even if other schemas expose the same member without `@inaccessible`.

**Example:**

GraphQL

```
# Source schema A
type Product @key(fields: "id") @key(fields: "sku") {
  id: ID!
  sku: String! @inaccessible
  name: String!
}
```

GraphQL

```
# Source schema B
type Product @key(fields: "sku") {
  sku: String!
  price: Float!
}
```

GraphQL

```
# Composed schema (sku hidden by schema A's @inaccessible)
type Product {
  id: ID!
  name: String!
  price: Float!
}
```

> **In C#:** `[Inaccessible]` attribute. See [Schema Exposure and Evolution](https://chillicream.com/docs/fusion/schema-exposure-and-evolution).

---

## `@internal` vs `@inaccessible`

These two directives both hide elements from the composite schema, but they behave differently during composition:

| Aspect         | @internal                        | @inaccessible                         |
| -------------- | -------------------------------- | ------------------------------------- |
| Scope          | Local to its source schema       | Global across all subgraphs           |
| Merge behavior | Bypasses merge rules entirely    | Participates in merge, then hidden    |
| Collision      | No collisions with other schemas | Hides even if other schemas expose it |
| Use case       | Internal lookup entry points     | Restrict client access to fields      |

Use `@internal` when a field or type exists solely for the gateway's entity resolution and should not interact with other schemas at all. Use `@inaccessible` when a field carries data that other subgraphs may depend on through `@require`, but clients should not query it directly.

---

## Federated Event Streams

### `@eventStream`

Backs a subscription root field with a message broker (NATS, Kafka, Azure Event Hubs, Amazon SQS, or Redis). The gateway subscribes to the configured topics and, for each event, resolves the field's selection set across the subgraphs from the event payload.

GraphQL

```
directive @eventStream(
  message: FieldSelectionSet!
  topics: [String!]
  broker: String
) on FIELD_DEFINITION
```

| Argument | Type               | Description                                                                                                                           |
| -------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| message  | FieldSelectionSet! | The shape of the event payload, as a selection set over the field's return type (e.g. "{ id }").                                      |
| topics   | \[String!\]        | The broker topic(s) to subscribe to. Supports {$args.<name>} templates. When omitted, inferred from the field name and its arguments. |
| broker   | String             | The name of the registered broker. Defaults to the unnamed broker.                                                                    |

**Example -- broker-backed subscription:**

GraphQL

```
# Source schema (Products subgraph)
type Subscription {
  onProductPriceChanged(productId: ID!): Product @eventStream(message: "{ id }")
}
```

GraphQL

```
# Composed schema
type Subscription {
  onProductPriceChanged(productId: ID!): Product
}
```

The directive is removed from the client-facing schema; the gateway records the broker binding on its internal subscribe metadata.

> **In C#:** `[EventStream("...")]` attribute or `.EventStream(...)`. See [Subscriptions](https://chillicream.com/docs/fusion/subscriptions).

---

### `@eventCursor`

Enables resumable subscriptions. On a subscription field argument it marks the input that accepts a resume cursor; on an output field it marks the value that carries each event's cursor for the client to store and replay after a reconnect.

GraphQL

```
directive @eventCursor on ARGUMENT_DEFINITION | FIELD_DEFINITION
```

The marked argument and field must both be of type `String`. A subscription field may declare at most one cursor argument and at most one cursor field.

**Example -- resumable subscription:**

GraphQL

```
# Source schema (Products subgraph)
type Subscription {
  onProductPriceChanged(
    productId: ID!
    after: String @eventCursor
  ): ProductPriceChange @eventStream(message: "{ product { id } }")
}

type ProductPriceChange {
  product: Product!
  cursor: String @eventCursor
}
```

GraphQL

```
# Composed schema
type Subscription {
  onProductPriceChanged(productId: ID!, after: String): ProductPriceChange
}

type ProductPriceChange {
  product: Product!
  cursor: String
}
```

During composition the two `@eventCursor` markers are recorded on the gateway's internal subscribe metadata (as `cursorField` and `cursorArgument`), so the gateway knows which field surfaces the cursor and which argument resumes the stream.

> **In C#:** `[EventCursor]` attribute or `.EventCursor()`. See [Client-resumable subscriptions](https://chillicream.com/docs/fusion/subscriptions#client-resumable-subscriptions).

---

## See Also

- [GraphQL Composite Schemas Specification](https://graphql.github.io/composite-schemas-spec/draft/) \-- The specification that defines these directives
- [Getting Started](https://chillicream.com/docs/fusion/getting-started) \-- Introduction to Fusion in practice
- [Entities and Lookups](https://chillicream.com/docs/fusion/entities-and-lookups) \-- Entity resolution patterns with `@key`, `@lookup`, and `@is`
- [Field Ownership](https://chillicream.com/docs/fusion/field-ownership-and-sharing) \-- Ownership model with `@shareable`, `@external`, and `@provides`
- [Data Requirements](https://chillicream.com/docs/fusion/data-requirements-and-mapping) \-- Cross-subgraph data dependencies with `@require`
- [Schema Exposure and Evolution](https://chillicream.com/docs/fusion/schema-exposure-and-evolution) \-- Visibility control with `@internal`, `@inaccessible`, and `@override`
- [Subscriptions](https://chillicream.com/docs/fusion/subscriptions) \-- Federated event streams with `@eventStream` and `@eventCursor`
- [Cache Control](https://chillicream.com/docs/fusion/cache-control) \-- CDN and HTTP caching behavior
- [Composition](https://chillicream.com/docs/fusion/composition) \-- How directives affect schema merging
[Edit this page on GitHub](https://github.com/ChilliCream/graphql-platform/edit/main/website/content/docs/fusion/directives-reference.md)

Last updated on **July 10, 2026** by **Michael Staib**
