facebook/relay · error

commitMutation: Expected mutation operation

Error message

commitMutation: Expected mutation operation

What it means

commitMutation in relay-runtime checks that the request object built from config.mutation has operationKind 'mutation'. Supplying a compiled query or subscription throws this error before any network work begins.

Source

Thrown at packages/relay-runtime/mutations/commitMutation.js:96

 * Higher-level helper function to execute a mutation against a specific
 * environment.
 */
function commitMutation<
  TVariables extends Variables,
  TData,
  TRawResponse = {...},
>(
  environment: IEnvironment,
  config: CommitMutationConfig<TVariables, TData, TRawResponse>,
): Disposable {
  invariant(
    isRelayModernEnvironment(environment),
    'commitMutation: expected `environment` to be an instance of ' +
      '`RelayModernEnvironment`.',
  );
  const mutation = getRequest(config.mutation);
  if (mutation.params.operationKind !== 'mutation') {
    throw new Error('commitMutation: Expected mutation operation');
  }
  if (mutation.kind !== 'Request') {
    throw new Error('commitMutation: Expected mutation to be of type request');
  }
  let {optimisticResponse, optimisticUpdater, updater} = config;
  const {configs, cacheConfig, onError, onUnsubscribe, variables, uploadables} =
    config;
  const operation = createOperationDescriptor(
    mutation,
    variables,
    cacheConfig,
    generateUniqueClientID(),
  );
  // TODO: remove this check after we fix flow.
  if (typeof optimisticResponse === 'function') {
    /* $FlowFixMe[incompatible-use] error exposed when improving flow typing of
     * commitMutation */
    optimisticResponse = optimisticResponse();

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Verify the .graphql file passed to relay-compiler uses `mutation` as the operation keyword and recompile
  2. Confirm the import in config.mutation matches the generated artifact of the mutation (check params.operationKind in the artifact)
  3. Use requestSubscription or fetchQuery if the operation is genuinely not a mutation

Example fix

// before
import CandidateQuery from './__generated__/CandidateQuery.graphql';
commitMutation(env, {mutation: CandidateQuery, ...});
// after
import CandidateMutation from './__generated__/CandidateMutation.graphql';
commitMutation(env, {mutation: CandidateMutation, ...});
Defensive patterns

Strategy: validation

Validate before calling

import {getRequest} from 'relay-runtime';
const mutation = getRequest(config.mutation);
if (mutation.params.operationKind !== 'mutation') {
  throw new Error(`${mutation.params.name} is a ${mutation.params.operationKind}, not a mutation`);
}

Type guard

function isCommitableMutation(m: {params: {operationKind: string}}): m is {params: {operationKind: 'mutation'}} {
  return m.params.operationKind === 'mutation';
}

Try / catch

try {
  const id = commitMutation(environment, config);
} catch (e) {
  if (e.message.includes('Expected mutation operation')) {
    // wrong operation kind — redirect to requestSubscription/fetchQuery as appropriate
  }
}

Prevention

When it happens

Trigger: commitMutation(environment, {mutation: <generated query or subscription artifact>}) — i.e. the GraphQL document's operation keyword is `query` or `subscription` rather than `mutation`.

Common situations: Wrong generated module imported (auto-import picked the query file with a similar name); the .graphql operation keyword was changed or mistyped; renaming operations so the mutation import now points at a query artifact.

Related errors


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/3adb95f32f2eee9a. Report an issue: GitHub.