Skip to main content
Version: 7.x.x

Federated Directives

graphql-kotlin supports a number of directives that can be used to annotate a schema and direct certain behaviors.

For more details, see the Apollo Federation Specification.

@authenticated directive

directive @authenticated on
ENUM
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| SCALAR

Directive that is used to indicate that the target element is accessible only to the authenticated supergraph users. For more granular access control, see the [@requiresScopes[#requirescope-directive] directive usage. Refer to the Apollo Router documentation for additional details.

@composeDirective directive

directive @composeDirective(name: String!) repeatable on SCHEMA

By default, Supergraph schema excludes all custom directives. The @composeDirective is used to specify custom directives that should be exposed in the Supergraph schema.

In order to use composed directive, you subgraph needs to

  1. contain your custom directive definition
  2. import your custom directive from a corresponding link spec
  3. apply @composeDirective with custom directive name on your schema

Example: Given @custom directive we can preserve it in the Supergraph schema

// 1. directive definition
@GraphQLDirective(name = "custom", locations = [Introspection.DirectiveLocation.FIELD_DEFINITION])
annotation class CustomDirective

@LinkDirective(url = "https://myspecs.dev/myCustomDirective/v1.0", import = ["@custom"]) // 2. import custom directive from a spec
@ComposeDirective(name = "custom") // 3. apply @composeDirective to preserve it in the schema
class CustomSchema

class SimpleQuery {
@CustomDirective
fun helloWorld(): String = "Hello World"
}

it will generate following schema

schema
@composeDirective(name: "@custom")
@link(import : ["@custom"], url: "https://myspecs.dev/myCustomDirective/v1.0")
@link(url : "https://specs.apollo.dev/federation/v2.5")
{
query: Query
}

directive @custom on FIELD_DEFINITION

type Query {
helloWorld: String! @custom
}

See @composeDirective definition for more information.

@contact directive

directive @contact(
"Contact title of the subgraph owner"
name: String!
"URL where the subgraph's owner can be reached"
url: String
"Other relevant notes can be included here; supports markdown links"
description: String
) on SCHEMA

Contact schema directive can be used to provide team contact information to your subgraph schema. This information is automatically parsed and displayed by Apollo Studio. See Subgraph Contact Information for additional details.

Example usage on the schema class:

@ContactDirective(
name = "My Team Name",
url = "https://myteam.slack.com/archives/teams-chat-room-url",
description = "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall)."
)
class MySchema

will generate

schema @contact(description : "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall).", name : "My Team Name", url : "https://myteam.slack.com/archives/teams-chat-room-url"){
query: Query
}

@extends directive

caution

@extends directive is deprecated. Federation v2 no longer requires @extends directive due to the smart entity type merging. All usage of @extends directive should be removed from your Federation v2 schemas.

directive @extends on OBJECT | INTERFACE

@extends directive is used to represent type extensions in the schema. Native type extensions are currently unsupported by the graphql-kotlin libraries. Federated extended types should have corresponding @key directive defined that specifies primary key required to fetch the underlying object.

Example

@KeyDirective(FieldSet("id"))
@ExtendsDirective
class Product(@ExternalDirective val id: String) {
fun newFunctionality(): String = "whatever"
}

will generate

type Product @key(fields : "id") @extends {
id: String! @external
newFunctionality: String!
}

@external directive

# federation v1 definition
directive @external on FIELD_DEFINITION

# federation v2 definition
directive @external on OBJECT | FIELD_DEFINITION

The @external directive is used to mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. All the external fields should either be referenced from the @key, @requires or @provides directives field sets.

Due to the smart merging of entity types, Federation v2 no longer requires @external directive on @key fields and can be safely omitted from the schema. @external directive is only required on fields referenced by the @requires and @provides directive.

Example

@KeyDirective(FieldSet("id"))
class Product(val id: String) {
@ExternalDirective
var externalField: String by Delegates.notNull()

@RequiresDirective(FieldSet("externalField"))
fun newFunctionality(): String { ... }
}

will generate

type Product @key(fields : "id") {
externalField: String! @external
id: String!
newFunctionality: String!
}

@inaccessible directive

note

Only available in Federation v2.

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

Inaccessible directive marks location within schema as inaccessible from the GraphQL Gateway. While @inaccessible fields are not exposed by the gateway to the clients, they are still available for query plans and can be referenced from @key and @requires directives. This allows you to not expose sensitive fields to your clients but still make them available for computations. Inaccessible can also be used to incrementally add schema elements (e.g. fields) to multiple subgraphs without breaking composition.

See @inaccessible specification for additional details.

caution

Location within schema will be inaccessible from the GraphQL Gateway as long as ANY of the subgraphs marks that location as @inacessible.

Example

class Product(
val id: String,
@InaccessibleDirective
val secret: String
)

will be generated by the subgraph as

type Product {
id: String!
secret: String! @inaccessible
}

but will be exposed on the GraphQL Gateway as

type Product {
id: String!
}

@interfaceObject directive

note

Only available in Federation v2.

directive @interfaceObject on OBJECT

This directive provides meta information to the router that this entity type defined within this subgraph is an interface in the supergraph. This allows you to extend functionality of an interface across the supergraph without having to implement (or even be aware of) all its implementing types.

Example: Given an interface that is defined somewhere in our supergraph

interface Product @key(fields: "id") {
id: ID!
description: String
}

type Book implements Product @key(fields: "id") {
id: ID!
description: String
pages: Int!
}

type Movie implements Product @key(fields: "id") {
id: ID!
description: String
duration: Int!
}

We can extend Product entity in our subgraph and a new field directly to it. This will result in making this new field available to ALL implementing types.

@InterfaceObjectDirective
@KeyDirective(fields = FieldSet("id"))
data class Product(val id: ID) {
fun reviews(): List<Review> = TODO()
}

Which generates the following subgraph schema

type Product @key(fields: "id") @interfaceObject {
id: ID!
reviews: [Review!]!
}

@key directive

# federation v1 definition
directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE

# federation v2 definition
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE

The @key directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface. The specified field set can represent single field (e.g. "id"), multiple fields (e.g. "id name") or nested selection sets (e.g. "id user { name }"). Multiple keys can be specified on a target type.

Key directives should be specified on all entities (objects that can resolve its fields across multiple subgraphs). Key fields specified in the directive field set should correspond to a valid field on the underlying GraphQL interface/object.

Basic Example

@KeyDirective(FieldSet("id"))
@KeyDirective(FieldSet("upc"))
class Product(val id: String, val upc: String, val name: String)

will generate

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

Referencing External Entities

Entity types can be referenced from other subgraphs without contributing any additional fields, i.e. we can update type within our schema with a reference to a federated type. In order to generate a valid schema, we need to define stub for federated entity that contains only key fields and also mark it as not resolvable within our subgraph. For example, if we have Review entity defined in our supergraph, we can reference it in our product schema using following code

@KeyDirective(fields = FieldSet("id"))
class Product(val id: String, val name: String, val reviews: List<Review>)

// review stub referencing just the key fields
@KeyDirective(fields = FieldSet("id"), resolvable = false)
class Review(val id: String)

which will generate

type Product @key(fields: "id") {
id: String!
name: String!
reviews: [Review!]!
}

type Review @key(fields: "id", resolvable: false) {
id: String!
}

This allows end users to query GraphQL Gateway for any product review fields and they will be resolved by calling the appropriate subgraph.

note

Only available in Federation v2.

caution

While both custom namespace (as) and import arguments are optional in the schema definition, due to #1830 we currently always require those values to be explicitly provided.

directive @link(url: String!, as: String, import: [Import]) repeatable on SCHEMA
scalar Import

The @link directive links definitions within the document to external schemas. See @link specification for details.

External schemas are identified by their url, which ends with a name and version with the following format: {NAME}/v{MAJOR}.{MINOR}, e.g. url = "https://specs.apollo.dev/federation/v2.5".

External types are associated with the target specification by annotating it with @LinkedSpec meta annotation. External types defined in the specification will be automatically namespaced (prefixed with {NAME}__) unless they are explicitly imported. Namespace should default to the specification name from the imported spec url. Custom namespace can be provided by specifying as argument value.

External types can be imported using the same name or can be aliased to some custom name.

@LinkDirective(`as` = "custom", imports = [LinkImport(name = "@foo"), LinkImport(name = "@bar", `as` = "@myBar")], url = "https://myspecs.dev/custom/v1.0")
class MySchema

This will generate following schema:

schema @link(as: "custom", import : ["@foo", { name: "@bar", as: "@myBar" }], url : "https://myspecs.dev/custom/v1.0") {
query: Query
}

@LinkedSpec annotation

When importing custom specifications, we need to be able to identify whether given element is part of the referenced specification. @LinkedSpec is a meta annotation that is used to indicate that given directive/type is associated with imported @link specification.

In order to ensure consistent behavior, @LinkedSpec value have to match default specification name as it appears in the imported url and not the aliased value.

Example usage:

@LinkedSpec("custom")
@GraphQLDirective(
name = "foo",
locations = [DirectiveLocation.FIELD_DEFINITION]
)
annotation class Foo

In the example above, we specify that @foo directive is part of the custom specification. We can then reference @foo in the @link specification imports

schema @link(as: "custom", import : ["@foo"], url : "https://myspecs.dev/custom/v1.0") {
query: Query
}

directive @foo on FIELD_DEFINITION

If we don't import the directive, then it will automatically namespaced to the spec

schema @link(as: "custom", url : "https://myspecs.dev/custom/v1.0") {
query: Query
}

directive @custom__foo on FIELD_DEFINITION

@override directive

note

Only available in Federation v2.

directive @override(from: String!) on FIELD_DEFINITION

The @override directive is used to indicate that the current subgraph is taking responsibility for resolving the marked field away from the subgraph specified in the from argument, i.e. it is used for migrating a field from one subgraph to another. Name of the subgraph to be overriden has to match the name of the subgraph that was used to publish their schema. See Publishing schema to Apollo Studio for additional details.

caution

Only one subgraph can @override any given field. If multiple subgraphs attempt to @override the same field, a composition error occurs.

Example

Given SubgraphA

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

We can override gateway description field resolution to resolve it in the SubgraphB

type Product @key(fields: "id") {
id: String!
name: String!
description: String! @override(from: "SubgraphA")
}

@policy directive

directive @policy(policies: [[Policy!]!]!) on
ENUM
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| SCALAR

Directive that is used to indicate that access to the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor. Refer to the Apollo Router documentation for additional details.

@provides directive

# federation v1 definition
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION

# federation v2 definition
directive @provides(fields: FieldSet!) on FIELD_DEFINITION

The @provides directive is a router optimization hint specifying field set that can be resolved locally at the given subgraph through this particular query path. This allows you to expose only a subset of fields from the underlying entity type to be selectable from the federated schema without the need to call other subgraphs. Provided fields specified in the directive field set should correspond to a valid field on the underlying GraphQL interface/object type. @provides directive can only be used on fields returning entities.

info

Federation v2 does not require @provides directive if field can always be resolved locally. @provides should be omitted in this situation.

Example 1:

We might want to expose only name of the user that submitted a review.

@KeyDirective(FieldSet("id"))
class Review(val id: String) {
@ProvidesDirective(FieldSet("name"))
fun user(): User = getUserByReviewId(id)
}

@KeyDirective(FieldSet("userId"))
class User(
val userId: String,
@ExternalDirective val name: String
)

will generate

type Review @key(fields : "id") {
id: String!
user: User! @provides(fields : "name")
}

type User @key(fields : "userId") {
userId: String!
name: String! @external
}

Example 2:

Within our service, one of the queries could resolve all fields locally while other requires resolution from other subgraph

type Query {
remoteResolution: Foo
localOnly: Foo @provides("baz")
}

type Foo @key("id") {
id: ID!
bar: Bar
baz: Baz @external
}

In the example above, if user selects baz field, it will be resolved locally from localOnly query but will require another subgraph invocation from remoteResolution query.

@requires directive

# federation v1 definition
directive @requires(fields: _FieldSet!) on FIELD_DEFINITON

# federation v2 definition
directive @requires(fields: FieldSet!) on FIELD_DEFINITON

The @requires directive is used to specify external (provided by other subgraphs) entity fields that are needed to resolve target field. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other subgraphs. Required fields specified in the directive field set should correspond to a valid field on the underlying GraphQL interface/object and should be instrumented with @external directive.

All the leaf fields from the specified in the @requires selection set have to be marked as @external OR any of the parent fields on the path to the leaf is marked as @external.

Fields specified in the @requires directive will only be specified in the queries that reference those fields. This is problematic for Kotlin as the non-nullable primitive properties have to be initialized when they are declared. Simplest workaround for this problem is to initialize the underlying property to some default value (e.g. null) that will be used if it is not specified. This approach might become problematic though as it might be impossible to determine whether fields was initialized with the default value or the invalid/default value was provided by the federated query. Another potential workaround is to rely on delegation to initialize the property after the object gets created. This will ensure that exception will be thrown if queries attempt to resolve fields that reference the uninitialized property.

Example

@KeyDirective(FieldSet("id"))
class Product(val id: String) {
@ExternalDirective
var weight: Double by Delegates.notNull()

@RequiresDirective(FieldSet("weight"))
fun shippingCost(): String { ... }
}

will generate

type Product @key(fields : "id") {
id: String!
shippingCost: String! @requires(fields : "weight")
weight: Float! @external
}

@requiresScopes directive

directive @requiresScopes(scopes: [[Scope!]!]!) on
ENUM
| FIELD_DEFINITION
| INTERFACE
| OBJECT
| SCALAR

Directive that is used to indicate that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes. Refer to the Apollo Router documentation for additional details.

@shareable directive

note

Only available in Federation v2.

directive @shareable repeatable on FIELD_DEFINITION | OBJECT

Shareable directive indicates that given object and/or field can be resolved by multiple subgraphs. If an object is marked as @shareable then all its fields are automatically shareable without the need for explicitly marking them with @shareable directive. All fields referenced from @key directive are automatically shareable as well.

caution

Objects/fields have to specify same shareability (i.e. @shareable or not) mode across ALL subgraphs.

Example

type Product @key(fields: "id") {
id: ID! # shareable because id is a key field
name: String # non-shareable
description: String @shareable # shareable
}

type User @key(fields: "email") @shareable {
email: String # shareable because User is marked shareable
name: String # shareable because User is marked shareable
}

@tag directive

directive @tag(name: String!) repeatable on FIELD_DEFINITION
| OBJECT
| INTERFACE
| UNION
| ARGUMENT_DEFINITION
| SCALAR
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION

Tag directive allows users to annotate fields and types with additional metadata information. Used by Apollo Contracts to expose different graph variants to different customers. See @tag specification for details.

Example

type Product @tag(name: "MyCustomTag") {
id: String!
name: String!
}
caution

Apollo Contracts behave slightly differently depending on which version of Apollo Federation your graph uses (1 or 2). See documentation for details.