Docs
Launch GraphOS Studio

Contracts

Deliver different subsets of your enterprise supergraph


This feature is only available with a GraphOS Enterprise plan. If your organization doesn't currently have an Enterprise plan, you can test this functionality by signing up for a free Enterprise trial.

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

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 s based on @tag s 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 s 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 that uses a contract . Clients that use a contract router's endpoint can only execute GraphQL s that the contract schema supports:

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

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

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

NOTE

Any @tag present in a source 's is also present in a contract 's .

Contract documentation

In Studio, each contract has its own README, reference, and . 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 s).

Setup

The 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 instead of the Apollo Router:

    • For Federation 2 contract s, update your gateway's @apollo/gateway library to version 2.0.2 or later.
    • For Federation 1 contract s, 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 s, update your Apollo Server s to use version 2.0.0 or later of the @apollo/subgraph library. For Federation 1 contract s, update @apollo/subgraph to version 0.1.1 or later.

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

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

2. (Fed1) Enable variant support for @tag

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

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

Open the Settings page for the 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 to types and s in your s to indicate whether to include or exclude them from your contract schema.

Before you can add @tags, you need to define the in your . 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 :

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 applies the @tag to the following locations:

  • The Product
  • The Query.topProducts (which returns a list of Products)
  • Two s of Product (internalId and percentageMatch)

Each @tag has a string name. You tag types and s 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 composes your source 's supergraph schema, that retains all of the @tags from your s. This is different from most other s, which are removed by default.

4. Publish updated subgraph schemas

After you're done adding tags, update your source by publishing your updated s to .

💡 TIP

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

Now you're ready to create your first contract!

5. Create a contract

Open your source '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
  • The source to use

NOTE

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 's , 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 's . If you later add tags with that name, the contract honors them.

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

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

💡 TIP

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

When you're finished adding tag names, click Generate Preview. Studio attempts to generate a contract 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 and its initial contract as a launch.

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

6. Use your new contract variant

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

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

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 :

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

Contracts and Federation 2

To create a contract that uses Federation 2, the contract's source variant must also use Federation 2. Learn how to move an existing variant to Federation 2.

Moving an existing contract to Federation 2

If a Federation 1 source 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 s with the following steps:

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

Automatic updates

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

  • successfully composes an updated for the contract's source .
  • You edit your contract.

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

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

Editing a contract

NOTE

You can't change an existing contract's name or source . Instead, you can create a new contract and delete the existing contract 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, automatically runs contract checks to determine whether those changes will affect any clients that user your contract .

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

Contract edit dialog with checks result

Contract checks

NOTE

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 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 Studio (which we recommend for all graphs), those checks include contract checks for any 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 on a source , contract checks execute alongside the other types of checks for that variant (build checks and checks).

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

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

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

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 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 .

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

To set blocking downstream s:

  1. Open your source '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 that should be a blocking downstream variant.

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

Error types

Whenever attempts to create or update your contract , 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 :

  • Definitions of object, interface, and union types
  • s of s (Federation 1 doesn't support tagging fields of interface types)
    • In Federation 1, you can still make an interface 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:

  • s of interface types
  • Enum types and their values
  • Input types and their s
  • Definitions of custom types
  • s of s, 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, 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 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 s that return that type. This produces an invalid contract .
  • If a contract excludes an object that implements an interface or is included in a union:

    • The contract is not required to exclude s that return that interface or union, as long as at least one other associated 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 resolves one of these s by returning an object of an excluded type, a runtime error occurs in the and the 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 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 :

    # 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 if any s 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 :

    # 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 if any s that return the excluded union type are included.
  • A contract cannot exclude any of the following, even if tagged:

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

Previous
Launches
Edit on GitHubEditForumsDiscord