Skip to content

Allow interfaces with no fields, unions with no members - #1229

Open
benjie wants to merge 2 commits into
empty-object-typesfrom
empty-types
Open

benjie wants to merge 2 commits into
empty-object-typesfrom
empty-types

Conversation

@benjie

@benjie benjie commented May 23, 2026

Copy link
Copy Markdown
Member

Follow up to:

This is again to help subschemas remain valid. Empty unions is fine (we already allow interfaces with no implementations, which is kind of the mirror), but empty interfaces makes it so people may want to use interfaces where unions should currently be used... Relevant:

Incidentally I'm okay with deprecating unions... And adding @oneOf output types instead.

@benjie benjie added the 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md) label May 23, 2026
@benjie

benjie commented May 25, 2026

Copy link
Copy Markdown
Member Author

Pulled from this great comment (emphasis mine):

A Union is a way to say "I may return one of a few things at this location" - it should be defined locally to the field. For example, a Video type doesn't need to describe that it might appear alongside Photo for some specific fields, though a specific fields needs to describe that it might return Video | Photo.

An Interface is a way to say "I conform to this contract" - it should be defined locally to the thing which conforms, the Object type. For example, a User type should be able to say that it fulfills the contract of Profile such that any type current known or unknown can be used knowing that it fulfills the contract.

This also drives the requirement that Interfaces must define at least one field - otherwise the Interface would not actually describe a contract to be fulfilled. This concept of contract-less interfaces is often called "marker interfaces" and in my opinion (hopefully not an uncommon opinion) are a bad practice borrowed from limited object-oriented languages.
-- @leebyron


Whilst I agree with the above, I also think that the GraphQL spec generally avoids dictating best practices and instead focuses on compatibility. I think "don't use marker interfaces" is a best practice that people should generally adhere to, and I think allowing empty interfaces does cause confusion of interface vs union as raised by Caleb, but I think this problem lives in the space of "best practices" rather than "hard requirements" and there are benefits to allowing for empty interfaces - both in terms of future expansion and prototyping (these two things are both "Searchable", but I don't know what that means yet... but when I do it will be an interface contract rather than a union) and in terms of subschema extractions (both of these implement the Searchable interface... [not included]).

I think it warrants debate.

@benjie benjie added the 🚀 Next Stage? This RFC believes it is ready for the next stage label May 25, 2026
@leebyron leebyron added 💡 Proposal (RFC 1) RFC Stage 1 (See CONTRIBUTING.md) and removed 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md) 🚀 Next Stage? This RFC believes it is ready for the next stage labels Jul 2, 2026

@martinbonnin martinbonnin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1000
For the record, I'm also very OK with marker interfaces. Java/Kotlin have had them pretty much forever

- UnionMemberTypes | NamedType
- = `|`? NamedType

GraphQL Unions represent an object that could be one of a list of GraphQL Object

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Suggested change
GraphQL Unions represent an object that could be one of a set of GraphQL Object


Union types have the potential to be invalid if incorrectly defined.

1. A Union type must include one or more unique member types.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Let's not lose the fact they are unique - ensure that it's declared as a "set" somewhere maybe?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I wonder how the direction suggested in #1249 (comment) applies here. Unions have no braces, so there is no way to tell an empty union from an incomplete one: union Empty would be the only form, and it becomes valid once this rule is removed. Is that fine for unions, or did you have something like union Empty = in mind?

@DaleSeo

DaleSeo commented Oct 26, 2026

Copy link
Copy Markdown

Another concrete example of why we need empty unions comes from the Schema Search RFC(graphql/ai-wg#187). When a search matches a field that returns a union but none of its members, the slice has to drop both the union and the field that returns it, since unions need at least one member today. With this change, the slice can keep union SearchResult as a reference, so the model can still see that the field exists.

I think the practical impact on interfaces is smaller. A slice already drops an implements clause if the interface isn’t included, and the result is still a valid subset. Empty interfaces would let the slice keep that clause, which could help a model that wants to use a fragment on the interface, but they aren’t required for validity.


1. An Interface type must define one or more fields.
2. For each field of an Interface type:
1. For each field of an Interface type:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as my note on #1228: replace the rule rather than delete it, so interface Empty {} is valid and a braceless interface Empty stays invalid.

1. An Interface type must define a set of fields. The set may be empty.


Union types have the potential to be invalid if incorrectly defined.

1. A Union type must include one or more unique member types.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I wonder how the direction suggested in #1249 (comment) applies here. Unions have no braces, so there is no way to tell an empty union from an incomplete one: union Empty would be the only form, and it becomes valid once this rule is removed. Is that fine for unions, or did you have something like union Empty = in mind?

This branch has not been deployed

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

Labels

💡 Proposal (RFC 1) RFC Stage 1 (See CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants