Docs
Launch GraphOS Studio

Contracts

Deliver subsets of your enterprise supergraph


This feature is available only with an Enterprise plan.

If your organization doesn't currently have an Enterprise plan, you can test out this functionality by signing up for a free Enterprise trial.

GraphOS contracts enable you to deliver different subsets of your supergraph to different consumers. Each contract filters specific portions of your supergraph's schema into a different GraphOS variant:

Contract variant A
Contract variant B
Source variant
Filter schema
according to contract A
Filter schema
according to contract B
Contract schema A
Contract schema B
Supergraph
schema

A contract filters definitions from your subgraph schemas based on @tag directives that you add:

Source variant subgraph schema
type Product {
id: ID!
name: String!
codename: String! @tag(name: "internal")
}
Contract variant API schema
type Product {
id: ID!
name: String!
# codename field is filtered out
}

In the above example, a contract excludes types and fields marked with the internal @tag.

What are contracts for?

You usually create a contract to support a contract router or contract documentation (or both).

Contract routers

You can deploy a managed instance of your graph router that uses a contract schema. Clients that use a contract router's endpoint can only execute GraphQL operations that the contract schema supports:

Standard router
Contract router
(All subgraphs)
Admin app
User app

This enables you to hide experimental types and fields that are still in development, or to limit a particular audience's access to only the portions of your graph that they need.

Contract routers can safely connect to the same subgraph instances as any other router, because their clients can only interact with data that's represented in the contract schema. This does not affect internal routing (filtered fields can still be used in a @requires selection set).

Any @tags that are present in a source variant's supergraph schema are also present in a contract variant's supergraph schema.

Contract documentation

In Studio, each contract variant has its own README, schema reference, and Explorer. If you make a contract variant public, you can provide these resources to external client developers to help them interact with a specific portion of your graph (while omitting irrelevant types and fields).

Setup

⚠️ The Apollo Studio steps below require an organization member with the Org Admin or Graph Admin role. Learn about member roles.

1. Update your router and subgraphs

Before you create any contracts:

  1. If you're using the @apollo/gateway library for your router instead of the Apollo Router:

    • For Federation 2 contract variants, update your gateway's @apollo/gateway library to version 2.0.2 or later.
    • For Federation 1 contract variants, update your gateway's @apollo/gateway library to version 0.34.0 or later.

    Otherwise, you'll encounter runtime errors.

  2. For Federation 2 contract variants, update your Apollo Server subgraphs to use version 2.0.0 or later of the @apollo/subgraph library. For Federation 1 contract variants, update @apollo/subgraph to version 0.1.1 or later.

    • @apollo/subgraph recently replaced @apollo/federation for Apollo Server instances acting as subgraphs. Symbol names are unchanged.

Older versions of the above libraries and tools don't fully support the required @tag directive.

2. (Fed1) Enable variant support for @tag

This step is required for Federation 1 supergraphs only. If you have a Federation 2 supergraph, proceed to the next step.

A contract uses one of your graph's existing variants (called the source variant) to generate its contract schema. If your source variant uses Federation 1, you need to enable its support for the @tag directive in Apollo Studio.

Open the Settings page for the variant you want to use as your source variant, then select the This Variant tab:

Edit supported directives in Studio

In the Build Configuration section, click Edit Configuration and enable support for @tag.

3. Add @tags

With contracts, you apply the @tag directive to types and fields in your subgraph schemas to indicate whether to include or exclude them from your contract schema.

Before you can add @tags, you need to define the directive in your subgraph schema. The way you do this depends on which version of federation you're using:

For example, let's take a look at this Federation 2 subgraph schema:

products.graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key", "@tag"])
type Query {
topProducts: [Product!]! @tag(name: "partner")
}
# All fields of the Product object type automatically inherit
# the "partner" tag so we can avoid tagging them individually
type Product @key(fields: "upc") @tag(name: "partner") {
upc: ID!
name: String!
description: String!
internalId: ID! @tag(name: "internal")
percentageMatch: Float! @tag(name: "experimental")
}

This schema applies the @tag directive to the following locations:

  • The Product object type
  • The Query.topProducts field (which returns a list of Products)
  • Two fields of Product (internalId and percentageMatch)

Each @tag has a string name. You tag types and fields with the same name if they should be included or excluded as a group by a particular contract.

For in-depth details on valid @tag usage, see Rules for @tags and contracts.

Whenever GraphOS composes your source variant's supergraph schema, that schema retains all of the @tags from your subgraph schemas (this is different from most other directives, which are removed by default).

4. Publish updated subgraph schemas

After you're done adding tags, update your source variant by publishing your updated subgraph schemas to GraphOS.

After publishing, if Studio doesn't reflect the the tags that you've added in your subgraph schemas, make sure you've updated all required libraries and tools. If you obtain your subgraph schemas via introspection, older subgraph libraries might strip the @tag directive.

Now you're ready to create your first contract!

5. Create a contract

Open your source variant's Settings page and select the This Variant tab. This time, click Contracts:

Contract settings in Studio

Click Create Contract to open the following dialog:

Create contract dialog

Basic details

In the first step of the dialog, provide the following:

  • A name for your new contract variant
  • The source variant to use

You can't change these values after the contract is created.

Then click Continue.

Contract filters

Next, you specify tag-based filters for your contract:

Create contract dialog

The dialog detects all tag names that are used in your source variant's schema, and it populates its dropdown lists with those names. You can add any number of tag names to each list.

You can also add tag names that aren't yet present in your source variant's schema. If you later add tags with that name, the contract honors them.

Your contract will filter types and fields from its source variant according to the following rules:

  • If the Included Tags list is empty, the contract schema includes each type and object/interface field unless it's tagged with an excluded tag.
  • If the Included Tags list is non-empty, the contract schema excludes each union type and object/interface field unless it's tagged with an included tag.
    • Each object and interface type is included as long as at least one of its fields is included (unless the type is explicitly excluded)
    • The contract schema excludes a type or field if it's tagged with both an included tag and an excluded tag.
  • If you enable the option to hide unreachable types, the contract schema excludes each unreachable object, interface, union, input, enum, and scalar unless it's tagged with an included tag.

💡 In Apollo Federation 2, if you want to exclude a type or field from your source variant's API schema and all of its contract schemas, you can use the @inaccessible directive instead of @tag. For details, see Using @inaccessible.

When you're finished adding tag names, click Generate Preview. Studio attempts to generate a contract schema based on the filters you provided and displays the result:

Result of contract preview generation

After you generate the preview, you can click Review to continue to the next step.

Review and launch

You can now review all of the details of your contract:

Create contract dialog

If everything looks right, click Create. This kicks off the generation of your contract variant and its initial contract schema as a launch.

GraphOS might encounter an error while generating your contract schema. For descriptions of these errors, see Error types.

6. Use your new contract variant

Congratulations! You've created your first GraphOS contract. You can now use your contract variant to provide a contract router or contract documentation to your users.

For example, you can complete the managed federation setup for a new router instance that uses your contract variant.

Federation 1 limitations

Contracts behave slightly differently depending on which version of Apollo Federation your graph uses (1 or 2). Most importantly, graphs that use Federation 1 cannot use @tags to exclude the following from a contract schema:

  • Custom scalar types (default scalar types can never be excluded)
  • Enum types or their values
  • Input types or their fields
  • Arguments of object fields or interface fields

Contracts and Federation 2

To create a contract variant that uses Federation 2, the contract's source variant must also use Federation 2.

Moving an existing contract to Federation 2

If a Federation 1 source variant already has one or more associated contracts, it isn't possible to move that variant or its contract variants to Federation 2. Instead, you need to delete and recreate your contract variants with the following steps:

  1. Identify the source variant you want to move to Federation 2.
  2. Save the details for each of that source variant's existing contract variants (most importantly each variant's associated filters).
  3. Delete all of the source variant's existing contract variants.
  4. Now that the source variant has no associated contracts, you can configure it to use Federation 2 composition (learn how).
  5. Recreate your deleted contract variants, which will now use Federation 2 composition like the modified source variant.

Automatic updates

Apollo automatically updates your contract schema whenever any of the following occurs:

  • GraphOS successfully composes an updated supergraph schema for the contract's source variant.
  • You edit your contract.

This makes sure that your contract schema reflects the latest version of your source variant's schema, and that the correct types and fields are included and excluded.

Updates to your contract schema are automatically fetched by your managed contract routers.

Editing a contract

You can't change an existing contract's name or source variant. Instead, you can create a new contract (and delete the existing contract variant if you no longer need it).

After you create a contract, you can edit its lists of included and excluded tags. From the Contracts list in your graph's Settings page, click Edit Contract where shown:

Edit contract menu

This opens a dialog similar to the one you used to create the contract.

If you make any changes to the contract's included or excluded tags, GraphOS automatically runs contract checks to determine whether those changes will affect any clients that user your contract variant.

The result of these checks is shown in the Review Schema step of the contract edit dialog:

Contract edit dialog with checks result

Contract checks

⚠️ Important: To run contract checks via rover subgraph check, you must update the Rover CLI to v0.8.2 or later.

  • If you use Rover v0.8.0 or v0.8.1, contract checks do run, but Rover ignores any contract check failures in blocking downstream variants you've set.
  • If you use a version prior to v0.8.0, rover subgraph check does not run contract checks at all.

If you run schema checks in Apollo Studio (which we recommend for all graphs), those checks include contract checks for any variant that acts as a source variant for contracts. Contract checks help your team identify when proposed changes to a source variant will negatively affect one or more downstream contracts.

When you run schema checks on a source variant, contract checks execute alongside the other types of checks for that variant (composition checks and operation checks).

Contract checks rely on the supergraph schema that's generated by composition checks, so they don't execute if that composition fails.

Contract checks do the following for each of a source variant's associated contracts:

  1. Use the source variant's proposed supergraph schema (generated by composition checks) to generate and validate an updated contract schema
  2. Run operation checks against the updated contract schema

On your graph's Checks page in Studio, contract checks are shown as Downstream:

Downstream checks shown in Studio UI

You can click the Downstream item in the Tasks column to view a summary of all contract checks that ran, and you can click a particular check to view more details:

Downstream checks shown in Studio UI

Failing contract checks

Unlike with other types of checks, failing contract checks don't cause their associated schema checks workflow to fail by default. This means that the rover subgraph check command doesn't return an error state that would prevent a CI task from merging or deploying changes to the source variant.

If you do want failing contract checks to fail your entire checks workflow, you can set blocking downstream variants for your source variant. If contract checks fail for any of these specified contract variants, the source variant's checks workflow does fail.

To set blocking downstream variants:

  1. Open your source variant's Checks page and select the This Variant tab.

  2. Click Configuration and scroll down to the Blocking Downstream Variants section:

    Blocking downstream variants for checks
  3. Select each contract variant that should be a blocking downstream variant.

You're done! From now on, whenever schema checks run for your source variant, those checks will fail if contract checks fail for any blocking downstream variant.

Error types

Whenever GraphOS attempts to create or update your contract schema, it might encounter an error. Errors are identified by the step in the creation process where they occurred:

ErrorDescription
ADD_DIRECTIVE_DEFINITIONS_IF_NOT_PRESENTAn error occurred adding directive definitions for @tag, @inaccessible, and core directive usages.
DIRECTIVE_DEFINITION_LOCATION_AUGMENTINGAn error occured augmenting the directive definition for @tag to support OBJECT, FIELD_DEFINITION, INTERFACE, and UNION.
EMPTY_OBJECT_AND_INTERFACE_MASKINGAll of an object or interface type's fields were excluded, and an error occurred while excluding the entire type.
EMPTY_UNION_MASKINGAll of a union type's included types were excluded, and an error occurred while excluding the entire union.
INPUT_VALIDATIONThe contract is attempting to include and exclude the same tag.
PARSINGAfter including and excluding fields, the resulting contract schema failed to parse.
PARSING_TAG_DIRECTIVESGraphOS encountered an error while trying to obtain all uses of @tag from the source variant schema.
PARTIAL_INTERFACE_MASKINGAn interface field's return type was excluded, and an error occurred while excluding that interface field.
SCHEMA_RETRIEVALGraphOS encountered an error while retrieving the source variant's schema. It might not yet have a valid composed schema.
TAG_INHERITINGGraphOS encountered an error while attempting to add parent tags to fields.
TAG_MATCHINGGraphOS encountered an error determining which types and fields should be inaccessible based on their tags.
TO_API_SCHEMAGraphOS encountered an error while attempting to generate an API schema from the contract variant's supergraph schema.
TO_FILTER_SCHEMAGraphOS failed to generate and return a contract supergraph schema for an unknown reason.
UNKNOWNAn unknown error occurred.
UNREACHABLE_TYPE_MASKINGGraphOS encountered an error while attempting to exclude unreachable types in the resulting contract schema.
VERSION_CHECKThe Federation version used is not supported by contracts.

Rules for @tags and contracts

Valid @tag locations

In both Federation 1 and Federation 2, you can apply tags to the following to filter your contract schema:

  • Definitions of object, interface, and union types
  • Fields of object types (Federation 1 doesn't support tagging fields of interface types)
    • In Federation 1, you can still make an interface field inaccessible by tagging the interface definition, or by ensuring that object fields that implement the interface field are removed.

In Federation 2 only, you can also apply tags to the following:

  • Fields of interface types
  • Enum types and their values
  • Input types and their fields
  • Definitions of custom scalar types
  • Arguments of fields, but not directive arguments at this time

Valid @tag names

  • Tag names can include alphanumeric characters (a-z, A-Z, 0-9), along with hyphen (-) and forward slash (/).
  • Each tag name cannot exceed 128 characters.
type User {
id: ID!
name: String! @tag(name: "a/b/c/1-2-3")
}

Dependent @tags

  • Whenever you tag the definition of an object or interface type, GraphOS automatically considers that tag to be applied to all fields of that type:

    type InternalUser @tag(name: "internal") {
    id: ID! # Also considered to have @tag(name: "internal")
    }
  • Whenever you tag the definition of an object, interface, or union type, you should always apply that same tag to every field that returns that type:

    type BillingAccount @tag(name: "internal") {
    id: ID!
    acctNumber: String!
    }
    type Query {
    billingAccounts: [BillingAccount!]! @tag(name: "internal")
    }
    • If you don't do this, a contract might exclude a type while including fields that return that type. This produces an invalid contract schema.
  • If a contract excludes an object that implements an interface or is included in a union:

    • The contract is not required to exclude schema fields that return that interface or union, as long as at least one other associated object type remains:

      # Two object types implement this interface.
      interface Identity {
      id: ID!
      name: String!
      }
      # If this implementing type is excluded...
      type InternalUser implements Identity @tag(name: "internal") {
      id: ID!
      name: String!
      }
      # ...but this implementing type remains...
      type ExternalUser implements Identity {
      id: ID!
      name: String!
      }
      type Query {
      # ...then this field doesn't need to be excluded.
      currentIdentity: Identity
      }
    • However, if a subgraph resolves one of these fields by returning an object of an excluded type, a runtime error occurs in the router and the operation fails.

Special cases for filtering

  • If a contract defines a list of Included Tags, any object or interface type without an included tag is still included in the contract schema if at least one of its fields is included:

    # This type definition is included because one if its fields is included.
    type User {
    id: ID! @tag(name: "includeMe")
    }
  • If a contract excludes every field of an object or interface type, the entire type definition is excluded from the contract schema:

    # This object type is excluded because all of its fields are excluded.
    type User {
    id: ID! @tag(name: "excludeMe")
    }
    • This can produce an invalid contract schema if any fields that return the excluded type are included.
  • If a contract excludes every object type that's part of a union type, the entire union type definition is excluded from the contract schema:

    # This union type is excluded because all of its possible types are excluded.
    union Media = Book | Movie
    type Book @tag(name: "excludeMe") {
    title: String!
    }
    type Movie @tag(name: "excludeMe") {
    title: String!
    }
    • This can produce an invalid contract schema if any fields that return the excluded union type are included.
  • A contract cannot exclude any of the following, even if tagged:

    • Built-in scalars (Int, Float, etc.)
    • Built-in directives (@skip, @include, etc.)
    • Custom directives that are applied to type system locations (see the list)
  • A contract can exclude object fields that are used in a computed field's @requires directive without causing runtime errors.

Previous
Launches
Edit on GitHubEditForumsDiscord