dgraph-io/dgraph · error

remote schema doesn't have any mutations.

Error message

remote schema doesn't have any mutations.

What it means

During validation of a @custom directive pointing at a remote GraphQL endpoint, the library introspects the remote schema. When the operation being validated is a mutation, the remote introspection result must contain a MutationType root; if the remote schema has no mutation root type, this error is returned. It means the remote endpoint simply cannot serve the mutation your custom directive references.

Solutions

  1. Verify the remote endpoint URL in the @custom directive is the correct GraphQL endpoint and that its schema actually exposes a mutation root.
  2. Re-run the remote introspection to confirm MutationType is present; if the remote removed mutations, delete or convert the local mutation fields accordingly.
  3. Change the operation type of the custom directive to `query` if you only intended to read data.
  4. Check environment-specific config (staging vs prod) — some environments may disable mutations.

Example fix

// before
@custom(directive: {name: "custom", arguments: [{name: "http", value: {url: "https://api.example.com/graphql", method: POST}}], mode: SINGLE, operation: mutation})
// after
// point at an endpoint whose schema defines `type Mutation { ... }`, or switch to:
@custom(directive: {name: "custom", arguments: [{name: "http", value: {url: "https://api.example.com/graphql", method: GET}}], mode: SINGLE, operation: query})
Defensive patterns

Strategy: validation

Validate before calling

// before applying config, introspect the remote and check the root exists
const hasMutationRoot = introspection.__schema.mutationType != null
if (opType === 'mutation' && !hasMutationRoot) {
  throw new Error(`remote ${endpoint} has no mutation root; fix @custom directive config`)
}

Type guard

function hasMutationRoot(s) { return s?.__schema?.mutationType?.name != null }

Prevention

When it happens

Trigger: A field marked with @custom graphql(...) whose operation is `mutation` is validated, and remoteIntrospection.Data.Schema.MutationType is nil in the fetched remote introspection (graphql/schema/remote.go:243).

Common situations: Pointing a custom mutation at an introspection URL of a read-only schema (e.g. a gateway exposing only queries, a CDN-published SDL without mutation root, or the wrong endpoint/env URL); remote service updated to remove mutations while local config still declares them.

Related errors


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

Appendix: source

Thrown at graphql/schema/remote.go:243

// validates the graphql given in @custom->http->graphql by introspecting remote schema.
// It assumes that the graphql syntax is correct, only remote validation is needed.
func validateRemoteGraphql(metadata *remoteGraphqlMetadata) error {
	remoteIntrospection, err := introspectRemoteSchema(metadata.url, metadata.headers)
	if err != nil {
		return err
	}

	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)
	}

View on GitHub (pinned to 759e242be6)