dgraph-io/dgraph · error
found `%s` operation, it can only have query/mutation.
Error message
found `%s` operation, it can only have query/mutation.
What it means
validateRemoteGraphql only accepts `query` or `mutation` as the operation type of a custom directive. If any other value reaches the switch's default branch, this error is returned with the offending operation name. The comment notes upstream @custom directive validation normally prevents this, so hitting it signals a validation gap or unexpected configuration.
Source
Thrown at graphql/schema/remote.go:249
}
var remoteQueryTypename string
operationType := string(metadata.graphqlOpDef.Operation)
switch operationType {
case "query":
if remoteIntrospection.Data.Schema.QueryType == nil {
return errors.Errorf("remote schema doesn't have any queries.")
}
remoteQueryTypename = remoteIntrospection.Data.Schema.QueryType.Name
case "mutation":
if remoteIntrospection.Data.Schema.MutationType == nil {
return errors.Errorf("remote schema doesn't have any mutations.")
}
remoteQueryTypename = remoteIntrospection.Data.Schema.MutationType.Name
default:
// this case is not possible as we are validating the operation to be query/mutation in
// @custom directive validation
return errors.Errorf("found `%s` operation, it can only have query/mutation.", operationType)
}
remoteTypes := make(map[string]*types)
for _, typ := range remoteIntrospection.Data.Schema.Types {
remoteTypes[typ.Name] = typ
}
remoteQryType, ok := remoteTypes[remoteQueryTypename]
if !ok {
return missingRemoteTypeError(remoteQueryTypename)
}
// check whether given query/mutation is present in remote schema
var introspectedRemoteQuery *gqlField
givenQuery := metadata.graphqlOpDef.SelectionSet[0].(*ast.Field)
for _, remoteQuery := range remoteQryType.Fields {
if remoteQuery.Name == givenQuery.Name {
introspectedRemoteQuery = remoteQueryView on GitHub (pinned to 759e242be6)
Solutions
- Set the custom directive's operation argument to exactly `query` or `mutation`.
- Ensure the upstream @custom directive validation runs (the operation is normally rejected earlier); fix config generation that skips it.
- If you need subscriptions, use a supported subscription mechanism instead of a remote HTTP custom directive.
Example fix
// before
@custom(directive: {name: "custom", arguments: [{name: "http", value: {url: "https://api.example.com/graphql"}}], mode: SINGLE, operation: subscription})
// after
@custom(directive: {name: "custom", arguments: [{name: "http", value: {url: "https://api.example.com/graphql"}}], mode: SINGLE, operation: query}) Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED = ['query', 'mutation']
if (!ALLOWED.includes(config.operation)) {
throw new Error(`@custom operation must be query or mutation, got: ${config.operation}`)
} Type guard
function isValidOperation(op) { return op === 'query' || op === 'mutation' } Prevention
- Validate custom directive configs against a JSON schema before applying
- Never hand-write operation strings; use config generation tooling
- Reject subscriptions for HTTP-based custom directives at config time
When it happens
Trigger: A @custom directive whose `operation` argument resolves to something other than "query" or "mutation" (e.g. `subscription`, misspelled value, or programmatic construction of the directive config bypassing normal validation) reaches validateRemoteGraphql.
Common situations: Typos like `operation: queries`, using a subscription over a remote HTTP custom directive, or tooling that generates the config schema incorrectly; older/newer config versions where operation validation rules changed.
Related errors
- remote schema doesn't have any queries.
- remote schema doesn't have any mutations.
- %s `%s` is not present in remote schema.
- Cycle detected: %s
- Missing fragment: %s
AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01).
Data as JSON: /api/errors/3b1ad08469233dd6.
Report an issue: GitHub.