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 = remoteQuery

View on GitHub (pinned to 759e242be6)

Solutions

  1. Set the custom directive's operation argument to exactly `query` or `mutation`.
  2. Ensure the upstream @custom directive validation runs (the operation is normally rejected earlier); fix config generation that skips it.
  3. 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

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


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/3b1ad08469233dd6. Report an issue: GitHub.