Skip to content

Prohibit root mutation and subscription types from implementing interfaces#1225

Open
robrichard wants to merge 1 commit intomainfrom
robrichard/root-interfaces
Open

Prohibit root mutation and subscription types from implementing interfaces#1225
robrichard wants to merge 1 commit intomainfrom
robrichard/root-interfaces

Conversation

@robrichard
Copy link
Copy Markdown
Contributor

@robrichard robrichard commented Apr 17, 2026

I ran into this issue working on validation rules for @defer and @stream: We would like to prevent @defer and @stream from being used on root level mutation or subscription fields, since these fields have special meaning.

One approach for a validation rule for this would be to do an algorithm like CollectFields to gather all the root level fields by traversing through fragment spreads. This is what's currently done in the (Subscription Operation Definitions - Single Root Field)[https://spec.graphql.org/September2025/#sec-Single-Root-Field] rule.

An easier approach is to just check the parent type and see if that type is the root subscription or mutation field, which is what I opted for.

That validation rule looks something like this:

- Let {mutationType} be the root Mutation type in {schema}.
- Let {subscriptionType} be the root Subscription type in {schema}.
- If {directiveName} is "defer" or "stream":
  - The parent type of {directive} must not be {mutationType} or
    {subscriptionType}.

But this could be bypassed if these root fields implement an interface. Example:

schema {
  query: Query
  mutation: MutationRoot
}

type Query {
  field: String
}

interface SomeInterface {
  test: String
}

type MutationRoot implements SomeInterface {
  test: String
}
mutation {
  ...mutationFragment
}
fragment mutationFragment on SomeInterface {
  ... @defer(label: "bad") {
    test
  }
}

This is a breaking change, but i'd like to see if there are any use cases for this or if we think it's used by the community. If not this could simplify defer/stream and possibly future validation rules, otherwise I can switch to the collectFields approach.

Further question - should we also expand this validation to union types? It's not required for this validation use case but might also be nice to have.

GraphQL-JS PR: graphql/graphql-js#4669

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 17, 2026

Deploy Preview for graphql-spec-draft ready!

Name Link
🔨 Latest commit c7df6d3
🔍 Latest deploy log https://app.netlify.com/projects/graphql-spec-draft/deploys/69e285803fd6e80008f130e6
😎 Deploy Preview https://deploy-preview-1225--graphql-spec-draft.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@robrichard robrichard requested a review from benjie April 17, 2026 18:59
Copy link
Copy Markdown
Member

@benjie benjie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change so warrants discussion. In particular, I'd love to know anyone whose schema this would actually break... who has interfaces on Mutation/Subscription? Why?

I'd also like to see text that says that the Mutation and Subscription root operation types may not be the named type of any field in the schema.

Comment on lines +147 to +148
service does not support mutations. If it is provided, it must be an Object type
that does not implement interfaces.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
service does not support mutations. If it is provided, it must be an Object type
that does not implement interfaces.
service does not support mutations. If it is provided, it must be an Object type
that does not implement any interface and is not a member of any union.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were to also restrict the mutation and subscription root type from being a member of a union, I think that validation should go here instead?

https://github.com/graphql/graphql-spec/blob/September2025/spec/Section%203%20--%20Type%20System.md?plain=1#L1452-L1457

Since it would make the union type invalid, not the root type?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you’re saying, but a union won’t know whether something is a root operation type or not. Nothing knows that until the schema itself is constructed, I think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the whole schema has to be constructed in either case? At least in GraphQL-JS we know what the root types are when validating union members.

is not a member of any union

To translate this to code we'd have to iterate through each type, find all unions and then check each member to see if it's the same as the root type.

Adding it to the union validation we just need to get the root subscription and mutation types from the schema and check against each member.

Similarly, the {`subscription`} _root operation type_ is also optional; if it is
not provided, the service does not support subscriptions. If it is provided, it
must be an Object type.
must be an Object type that does not implement interfaces.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
must be an Object type that does not implement interfaces.
must be an Object type that does not implement any interface and is not a
member of any union.

@robrichard
Copy link
Copy Markdown
Contributor Author

Discussed at the Primary May 2026 WG, consensus was that this could potentially block a future implementation of nested serially executed fields or mutation fields nested under namespaces. I will keep this PR open for a bit to gather any other feedback but will be proceeding with updating the defer/stream validation rules to avoid this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants