Skip to content

Allow empty object types - #1228

Open
benjie wants to merge 5 commits into
mainfrom
empty-object-types
Open

benjie wants to merge 5 commits into
mainfrom
empty-object-types

Conversation

@benjie

@benjie benjie commented May 23, 2026 •

Copy link
Copy Markdown
Member

Merge first:


Since 2015 we've always required an object type to define at least 1 field. As we continue to explore GraphQL's position in the new AI-world, we realise that we need schema subsets that represents part of a larger interface, in these cases it's sometimes useful to leave in type references without including full definitions of those types:

type Query {
  topAuthors: [Author!]
}
type Author {
  name: String!
  salesVolume: Int
  averageRating: Float
  topBooks: [Book!]
  mediaCoverage: [Article!]
  recentPublishers: [Publisher!]
}
type Book {}
type Article {}
type Publisher {}

Here the AI can see a basic schema it can write queries against, but it can also ask to expand the parts of the schema that are relevant to its interests - e.g. books or articles or publishers - without wasting context on things it doesn't care about.

We can make a schema like this valid by including at least one field on each of these types, but it's hard for a tool to know which field(s) to include, and not including fields makes it much more obvious to the LLM that the schema is partial. Making this partial schema itself valid would be helpful for tooling.

AI aside:

  • This is also very useful for mutation payloads where no obvious value exists to return yet but we may wish to expand in future. Currently type MyMutationPayload { success: Boolean! } is used as a workaround, but it's messy.
  • This is also very useful for schema evolution - it allows you to create a type before you know what it will contain, and allows you to deprecate all fields of a type without the schema being invalid.
  • This also enables mutation-only schemas whilst still supporting introspection Proposal: Do not require query as a root operation type #490

Related:

@netlify

netlify Bot commented May 23, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for graphql-spec-draft ready!

Name Link
🔨 Latest commit fc988a2
🔍 Latest deploy log https://app.netlify.com/projects/graphql-spec-draft/deploys/6ae8690459925334864260ee
😎 Deploy Preview https://deploy-preview-1228--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.

@benjie benjie added 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md) 🚀 Next Stage? This RFC believes it is ready for the next stage labels May 23, 2026
@benjie
benjie changed the base branch from main to empty-selection-sets May 23, 2026 22:51

@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

@benjie
benjie force-pushed the empty-object-types branch from 8c40697 to 21a2a8c Compare May 25, 2026 11:37
@benjie
benjie force-pushed the empty-selection-sets branch from 8c13477 to 220e8d4 Compare May 25, 2026 11:37

@jerelmiller jerelmiller left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💯

@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
@leebyron

leebyron commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Notes from meeting:

We should support syntax grammar for {} to clearly define no fields

key motivation is that "a schema minus all deprecated elements is always valid" which today could be not true! For a bunch of reasons but in this case:

type Example {
  onlyField @deprecated
}

this cannot create a valid non-deprecated schema! that's our aim.

Another example: a mutation payload (relay style) that wants to be defined up front as

Specifically important because the LLM-focused subschema generation still has some issues we're working out, and it's unclear that providing type Book {} to the LLM is what we want to do or not.

@martinbonnin

Copy link
Copy Markdown
Contributor

Looks like we'll need the same for input objects?

@andimarek

Copy link
Copy Markdown
Contributor

I am not sure this is the right place to comment, but together with the issue about interfaces without fields and interfaces without objects implementing them I am wondering if we should distinguish between valid schemas vs valid and executable schemas.

For example an interface without an implementation is not something I find sensible for a real schema that should be executed but for schema during design it is ok.

@martinbonnin

martinbonnin commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

an interface without an implementation is not something I find sensible for a real schema that should be executed

@andimarek My preference here would be a lint warning. The spec is already a bit vague on SDL validation (see #1233 (comment)) so we're not really equipped to have executable vs non-executable validation yet.

Base automatically changed from empty-selection-sets to main September 17, 2026 17:43
@DaleSeo

DaleSeo commented Oct 26, 2026 •

Copy link
Copy Markdown

A data point from the AI WG. I’m writing the Schema Search RFC (graphql/ai-wg#187). It describes a service returning a valid subset of its schema as SDL in response to a natural language query. That subset is exactly the kind of partial schema this PR covers, so I wanted to share how the current rule affects it.

Since object types need at least one field today, our slicer has to drop any type that has no matching fields. That also removes every field that returns that type, so the model never learns that Author.topBooks exists. With this change, the slice can keep type Book {} and preserve the reference. If needed, the model can ask for Book in a later request.

This also enables mutation-only schemas while still supporting introspection.

We also hit this case when searches match only mutations, like "create an issue." The slice needs Mutation.createIssue and its input and payload types, but nothing from Query. However, a schema must have a query root type, and today that type must have at least one field. So the slicer has to pick an unrelated Query field, such as viewer: User!, and include User too. The model then sees types that have nothing to do with the question, and the choice of field differs between implementations. With this change, the slice can say type Query {} and stop there.

@DaleSeo

DaleSeo commented Oct 26, 2026

Copy link
Copy Markdown

Looks like we'll need the same for input objects?

+1 from the schema-search side, with one caveat that I think is worth stating, because input objects behave differently from output types here.

A slice guarantees that any operation that validates against the slice will also validate against the full schema. An empty input Filter {} only preserves that guarantee if the full Filter definition has no required fields. Otherwise, the slice still needs to include those required fields.

Comment thread spec/Section 3 -- Type System.md Resolved

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

🚀 Next Stage? This RFC believes it is ready for the next stage 💡 Proposal (RFC 1) RFC Stage 1 (See CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The ability to represent empty objects

6 participants