Contracts
Deliver subsets of your enterprise supergraph
Contracts are available only for Enterprise plans.
Apollo Studio 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 Studio variant:
A contract filters definitions from your subgraph schemas based on @tag
directives that you add:
type Product {id: ID!name: String!codename: String! @tag(name: "internal")}
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:
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 @tag
s 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:
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.
- For Federation 2 contract variants, update your gateway's
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:
In the Build Configuration section, click Edit Configuration and enable support for @tag
.
3. Add @tag
s
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 @tag
s, 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:
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 individuallytype 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 ofProduct
s) - Two fields of
Product
(internalId
andpercentageMatch
)
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 @tag
s and contracts.
Whenever Studio composes your source variant's supergraph schema, that schema retains all of the @tag
s 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 Apollo Studio.
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:
Click Create Contract to open the following 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:
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.
- A type is considered unreachable in a schema if there exists no path to that type from either a root operation type or an executable-directive.
💡 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:
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:
If everything looks right, click Create. This kicks off the generation of your contract variant and its initial contract schema as a launch.
Studio 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 a contract in Apollo Studio. 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 @tag
s 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:
- Identify the source variant you want to move to Federation 2.
- Save the details for each of that source variant's existing contract variants (most importantly each variant's associated filters).
- Delete all of the source variant's existing contract variants.
- Now that the source variant has no associated contracts, you can configure it to use Federation 2 composition (learn how).
- 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:
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, Studio 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 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:
- Use the source variant's proposed supergraph schema (generated by composition checks) to generate and validate an updated contract schema
- Run operation checks against the updated contract schema
On your graph's Checks page in Studio, contract checks are shown as Downstream:
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:
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:
Open your source variant's Checks page and select the This Variant tab.
Click Configuration and scroll down to the Blocking Downstream Variants section:
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 Apollo Studio 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:
Error | Description |
---|---|
ADD_DIRECTIVE_DEFINITIONS_IF_NOT_PRESENT | An error occurred adding directive definitions for @tag , @inaccessible , and core directive usages. |
DIRECTIVE_DEFINITION_LOCATION_AUGMENTING | An error occured augmenting the directive definition for @tag to support OBJECT , FIELD_DEFINITION , INTERFACE , and UNION . |
EMPTY_OBJECT_AND_INTERFACE_MASKING | All of an object or interface type's fields were excluded, and an error occurred while excluding the entire type. |
EMPTY_UNION_MASKING | All of a union type's included types were excluded, and an error occurred while excluding the entire union. |
INPUT_VALIDATION | The contract is attempting to include and exclude the same tag. |
PARSING | After including and excluding fields, the resulting contract schema failed to parse. |
PARSING_TAG_DIRECTIVES | Studio encountered an error while trying to obtain all uses of @tag from the source variant schema. |
PARTIAL_INTERFACE_MASKING | An interface field's return type was excluded, and an error occurred while excluding that interface field. |
SCHEMA_RETRIEVAL | Studio encountered an error while retrieving the source variant's schema. It might not yet have a valid composed schema. |
TAG_INHERITING | Studio encountered an error while attempting to add parent tags to fields. |
TAG_MATCHING | Studio encountered an error determining which types and fields should be inaccessible based on their tags. |
TO_API_SCHEMA | Studio encountered an error while attempting to generate an API schema from the contract variant's supergraph schema. |
TO_FILTER_SCHEMA | Studio failed to generate and return a contract supergraph schema for an unknown reason. |
UNKNOWN | An unknown error occurred. |
UNREACHABLE_TYPE_MASKING | Studio encountered an error while attempting to exclude unreachable types in the resulting contract schema. |
VERSION_CHECK | The Federation version used is not supported by contracts. |
Rules for @tag
s 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 @tag
s
Whenever you tag the definition of an object or interface type, Studio 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 | Movietype 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)
- Built-in scalars (
A contract can exclude object fields that are used in a computed field's
@requires
directive without causing runtime errors.