Skip to content

Conversation

@benjie
Copy link
Member

@benjie benjie commented Jan 15, 2026

(Extracted and reworked from the error behaviors PR (#1163) to stand alone, since it has broader utility and can progress faster alone.)

This PR introduces "service capabilities" - a mechanism for GraphQL services to advertise features and configuration that exist outside the type system - for example support for syntax features (executable descriptions, fragment arguments, document directives, etc), details of transport support (websocket endpoint, SSE endpoint, how to be notified of new schema versions), and other details that can enable automated client configuration.

Motivation

In addition to being a prerequisite of the error behaviors proposal, this is a desired feature for composite schemas and a key component of The GraphQL Golden Path Initiative (specifically enabling client auto-configuration).

The goal is that a user can point tooling at a GraphQL endpoint and much of the configuration can be implied based on the service's advertised capabilities - this massively reduces the amount of "out of band" communication required.

Example use cases:

  • Does the service support fragment arguments, or must the client transpile them?
  • Does it support customizing error propagation behavior?
  • Are subscriptions available over WebSockets? If so, what endpoint and protocol?
  • Does it support incremental delivery (@defer/@stream)? Which version?
  • Does it support automated document persistence? Which protocol?

Why "capabilities" not "metadata"?

Capabilities inform automated tooling/client decisions and actions. Metadata is ancillary and often intended for human consumption. The name "capabilities" is intentional - it signals the purpose and discourages using this system as a dumping ground for arbitrary metadata. Every capability should be actionable by clients or tooling.

Design

Introspection via __service: __Service meta-field (similar to __schema: __Schema):

type __Service {
  capabilities: [__Capability!]!
}

type __Capability {
  identifier: String!
  description: String
  value: String
}

Support can be probed via: { __type(name: "__Service") { name } } - if no result, then the service does not support service capabilities.

SDL syntax

Slightly changed from previous proposals, here's the syntax I now propose:

service {
  "Descriptions on operations and fragments are supported"
  capability graphql.operationDescriptions

  "WebSocket transport is supported via the given endpoint"
  capability example.transport.ws("wss://api.example.com/graphql")
}

extend service {
  capability com.example.customFeature
}

Notes on this choice:

  • Capabilities without "values" should look neat
  • Capabilities with values followed by capabilities without descriptions should not introduce easy syntax clashes (service { capability foo.bar "Value" capability foo.baz } would apply "Value" as the description for foo.baz rather than the value for foo.bar)
  • The extend syntax feels very natural
  • Avoids double-nesting of curly braces
  • Leaves space for additional service properties in future
  • Inserting other keywords between capabilities is acceptable, in the same way that inserting different directives between repeatable directives is acceptable
  • A directive-based syntax could be used (service @capability(identifier: "...", value: "...")), but the service keyword already introduces new syntax so it doesn't seem necessary to be constrained to using ugly directives when a neater and more palatable syntax is available; this also allows for descriptions for each capability.

Capability identifiers

A QualifiedName syntax (Name(.Name)+) inspired by reverse-domain notation encourages global uniqueness. The graphql. prefix is reserved for official GraphQL Foundation specifications (not just the main spec; e.g. graphql.http.* could be used by the GraphQL-over-HTTP spec).

@netlify
Copy link

netlify bot commented Jan 15, 2026

Deploy Preview for graphql-spec-draft ready!

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


ServiceCapability : Description? capability QualifiedName ServiceCapabilityValue?

ServiceCapabilityValue : ( StringValue )
Copy link
Contributor

Choose a reason for hiding this comment

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

why not

ServiceCapabilityArgument : ArgumentName: ConstantValue
ServiceCapabilityValue : ( ServiceCapabilityArgument+ )

Basically: why shouldn't we be able to use enum values or integers or have a service take in an input or etc? Seems like it'd be common to need 3-4 flags for one capability to work.

Copy link
Member Author

@benjie benjie Jan 15, 2026

Choose a reason for hiding this comment

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

Because we don't yet have the struct type which would be ideal for this.

But also, if you need multiple arguments (why?) either use custom serialization or use multiple capabilities.

service {
  capability graphql.ws
  capability graphql.ws.endpoint("ws://...")

  "To avoid thundering herd, multiply this by `(0.5 + rand())`"
  capability graphql.ws.reconnectDelay("3000")

  "The modern protocol"
  capability graphql.ws.protocol.graphqlTransportWs

  "The updated modern protocol"
  capability graphql.ws.protocol.graphqlTransportWs.v2

  "Configuration for V2 protocol"
  capability graphql.ws.protocol.graphqlTransportWs.v2.encoding("utf-8")

  "The legacy protocol"
  capability graphql.ws.protocol.graphqlWs
}

Arguments should be the rarer case, IMO - if in doubt, use a sub-name (like the protocols above) rather than an argument.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you have examples that don't fit this well?

identifies a capability. This structure is inspired by reverse domain notation
to encourage global uniqueness and collision-resistance; it is recommended that
identifiers defined by specific projects, vendors, or implementations begin with
a prefix derived from a DNS name they control (e.g., {"com.example."}).
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it would make sense to use a URI instead, the advantage being that it could point to documentation about the capability. Disadvantage: URIs are more verbose than a QualifiedName.

Comment on lines +2363 to +2365
**Capability Identifier**

:: A _capability identifier_ is a {QualifiedName} (a case-sensitive string value
Copy link
Contributor

Choose a reason for hiding this comment

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

As a Java person who likes qualified names, I'm happy if we don't have "Capability identifier" but instead always use "Capability qualified name".

Copy link
Contributor

Choose a reason for hiding this comment

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

Or maybe just "capability name", just like we have "type name", "field name", etc.... I don't think we need specific language here?

Comment on lines +547 to +548
- `identifier` must return the string _capability identifier_ uniquely
identifying this service capability.
Copy link
Contributor

@martinbonnin martinbonnin Jan 27, 2026

Choose a reason for hiding this comment

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

Should we be more explicit somewhere that capabilities MUST be unique?

service {
  capability com.example
  capability com.example # invalid (or maybe we allow it and it's a no-op?)
}


### Service Capabilities

ServiceCapability : Description? capability QualifiedName
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: maybe just Capability? This stays consistent with introspection?

Suggested change
ServiceCapability : Description? capability QualifiedName
Capability : Description? capability QualifiedName

INPUT_FIELD_DEFINITION
}

type __Service {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
type __Service {
type __Service {
description: String

@martinbonnin
Copy link
Contributor

Can we add a RFC label to this PR? Probably RFC1?


ServiceExtension :

- extend service Directives? { ServiceCapability\* }
Copy link
Contributor

@martinbonnin martinbonnin Jan 28, 2026

Choose a reason for hiding this comment

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

Should we allow empty extensions?

# Is that desirable?
extend service { }

(ping @BoD)

@martinbonnin
Copy link
Contributor

Apollo Kotlin pull request: apollographql/apollo-kotlin#6858

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