Skip to content

GAP-4 Add Field Extensions Spec#4

Open
egoodwinx wants to merge 5 commits into
graphql:mainfrom
egoodwinx:field-extensions
Open

GAP-4 Add Field Extensions Spec#4
egoodwinx wants to merge 5 commits into
graphql:mainfrom
egoodwinx:field-extensions

Conversation

@egoodwinx

Copy link
Copy Markdown

@magicmark

magicmark commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

# This is not valid GraphQL

So is the idea to compile this out of the SDL before passing to a GraphQL server?

@egoodwinx

egoodwinx commented Feb 12, 2026

Copy link
Copy Markdown
Author

So is the idea to compile this out of the SDL before passing to a GraphQL server?

Yes

@magicmark

Copy link
Copy Markdown
Contributor

I think this would benefit then from being layered on top of the soon-to-be-proposed language in the spec to clarify the difference between "human written"(?) schemas and "runtime" schemas. Not required for this to merge, but worth following along!

@benjie wdyt about a dedicated 2xxx range for "language and syntax extensions"?

side note, not directly relevant to this proposal but:

  • since this is valid syntax folks don't need to worry about existing tooling (syntax highlighters, ast parsing functions and such) breaking on schema that uses this - I think it's only invalid at runtime when validation rules get applied.
  • I wonder what should happen if a GAP proposes new syntax - and at that point you're on your own, there's no guarantee that any other tooling would work. Obviously this isn't a problem to worry about for now, and could be solvable in future I imagine with plugins (similar to babel) and whatnot.

@magicmark magicmark mentioned this pull request Mar 5, 2026

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

Ugh as a GAP proposal figuring out the difference between a field extension and an original field definition inside an extension is tough, because I think we must have some kind of syntax, or at least a special directive.

I ran into a bunch of bugs before getting an internal representation where fields can be pure extensions, and I still have unresolvable bugs in going from SDL => SchemaSet (then do work on that set) => SDL. It's very frustrating.

Comment thread gaps/GAP-2/DRAFT/INDEX.md Outdated
Comment on lines +15 to +17
extend type Query {
id: ID @deprecated(reason: "Use globalId instead")
}

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.

I actually think it would be cleaner to do:

Suggested change
extend type Query {
id: ID @deprecated(reason: "Use globalId instead")
}
extend type Query {
extend id: ID @deprecated(reason: "Use globalId instead")
}

Basically you're being explicit that this field DOES already exist but you're adding more to it.

I have this exact situation and have an internal representation that we actually see that I can't do a round trip into the SDL and back into SchemaSet in https://github.com/facebook/relay/blob/main/compiler/crates/schema-set/src/set_exclude.rs#L413

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm good with this syntax.

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.

For field extensions:

  • Replacing the response type is not an extension, so the type should be excluded
  • Replacing an argument is not an extension, so the existing arguments should be excluded
  • Maybe you want to add an argument
  • Maybe you want to add a directive
  • Maybe you want to add a description (?)

So I'd propose:

extend type Query {
  # Add a directive to `Query.id`
  extend id @deprecated(reason: "Use globalId instead")

  # Add an argument to `Query.findUsers`
  extend findUsers(includeArchived: Boolean! = false)
  
  # Add a description to `Query.currentUser`
  """The person who is currently logged into the API, if any"""
  extend currentUser
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I actually talked to @martinbonnin and since the only implementation is currently in Kotlin we would prefer to keep it as the single extends for now. I am going to work on the graphql-js implementation next.

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.

Yup, we have implemented the very simple version of it in Apollo Kotlin:

      type Query {
        random: Int
      }      
      extend type Query {
        random: Int @deprecated
      }

I agree we can probably do better but that's not something we're going to pursue in the short term. The main use case we wanted to unlock was adding directive to existing field definitions. If we could point users at this GAP, it'd be useful. If not, it's not big deal either.

magicmark added a commit that referenced this pull request May 19, 2026
#33)

I have been working on a lot of operations that benefit from treating
the GraphQL Schema as a Set. For context, see changes in the Relay
schema-set crate:
https://github.com/facebook/relay/tree/main/compiler/crates/schema-set

One of the core issues is there's no syntax I can use to get partial
sets. For example I can have a type-definition and field definition
removed, but there is still an argument on the field present.

This proposes to solve that through syntax that looks like:
```
extend type Person {
  extend field(argument: String) @deprecated
}
```

We could get into this state if we did something like:
```
type Person {
  field(argument: String): String @deprecated
}
```
**exclude**
```
type Person {
  field: String
}
```

This proposal should give a *syntax* that we could use in @egoodwinx's
proposal in #4
@martinbonnin

Copy link
Copy Markdown
Contributor

@magicmark @egoodwinx can we get that one merged in? I recognize this is probably not the perfect endgame solution but it's serving us well and we have this implemented in Apollo Kotlin. Would be cooil to be able to have a GAP number attached to it.

@magicmark

magicmark commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

@martinbonnin good to merge pending some fiddly process things (apologies):

  • metadata.yml needs fixing up
  • this is GAP-4, not GAP-2 (this pr was opened before the great renumbering)

Comment thread gaps/GAP-2/metadata.yml Outdated
Comment thread gaps/GAP-2/metadata.yml Outdated
@benjie benjie changed the title GAP-0002 Add Field Extensions Spec GAP-4 Add Field Extensions Spec Jul 2, 2026
Comment thread gaps/GAP-2/DRAFT/INDEX.md Outdated
@benjie benjie force-pushed the field-extensions branch from 730968b to d77da13 Compare July 2, 2026 14:23
@benjie

benjie commented Jul 2, 2026

Copy link
Copy Markdown
Member

@egoodwinx I took the liberty of rebasing your last two commits so 32e22ef now includes deletion of GAP-2 (7f7e887 only added the new files, didn't remove the old) so it shows up in history as a rename now.

@egoodwinx

Copy link
Copy Markdown
Author

@egoodwinx I took the liberty of rebasing your last two commits so 32e22ef now includes deletion of GAP-2 (7f7e887 only added the new files, didn't remove the old) so it shows up in history as a rename now.

Whoops - thanks! I ran the prettier command and it reformatted all the files for some reason so I must have missed it.

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.

5 participants