jestjs/jest · error · ValidationError

Watch plugin configuration error: ${error}

Error message

Watch plugin configuration error:

${error}

What it means

Thrown as a ValidationError when two watch plugins attempt to register the same key, or when a plugin tries to register a key that is reserved internally by Jest. The conflict is detected during key registration in `determineWatchPlugins`/`updateKeypressHandler` and names both conflicting plugins or the reserved key name.

Source

Thrown at packages/jest-core/src/watch.ts:514

  Watch plugin ${chalk.bold.red(
    getPluginIdentifier(plugin),
  )} attempted to register key ${chalk.bold.red(`<${key}>`)},
  that is reserved internally for ${chalk.bold.red(
    conflictor.forbiddenOverwriteMessage,
  )}.
  Please change the configuration key for this plugin.`.trim();
  } else {
    const plugins = [conflictor.plugin, plugin]
      .map(p => chalk.bold.red(getPluginIdentifier(p)))
      .join(' and ');
    error = `
  Watch plugins ${plugins} both attempted to register key ${chalk.bold.red(
    `<${key}>`,
  )}.
  Please change the key configuration for one of the conflicting plugins to avoid overlap.`.trim();
  }

  throw new ValidationError('Watch plugin configuration error', error);
};

const getPluginIdentifier = (plugin: WatchPlugin) =>
  // This breaks as `displayName` is not defined as a static, but since
  // WatchPlugin is an interface, and it is my understanding interface
  // static fields are not definable anymore, no idea how to circumvent
  // this :-(
  // @ts-expect-error: leave `displayName` be.
  plugin.constructor.displayName || plugin.constructor.name;

const getPluginKey = (
  plugin: WatchPlugin,
  globalConfig: Config.GlobalConfig,
) => {
  if (typeof plugin.getUsageInfo === 'function') {
    return (plugin.getUsageInfo(globalConfig) || {key: null}).key;
  }

View on GitHub (pinned to 8e6d128e4a)

Solutions

  1. Read the error message — it names both conflicting plugins and the disputed key.
  2. Change the key configuration for one of the plugins in your jest.config `watchPlugins` array (pass a different key as the plugin's config option).
  3. If a plugin attempts a reserved key, the message names the reserved purpose — pick a non-reserved key.
  4. Remove one of the conflicting watch plugins if both are not needed.

Example fix

// before: jest.config.js — both plugins use 'p'
module.exports = {
  watchPlugins: [
    ['jest-watch-typeahead/filename', { key: 'p' }],
    ['jest-watch-select-projects', { key: 'p' }],
  ],
};

// after: give each a unique key
module.exports = {
  watchPlugins: [
    ['jest-watch-typeahead/filename', { key: 'f' }],
    ['jest-watch-select-projects', { key: 'p' }],
  ],
};
Defensive patterns

Strategy: validation

Validate before calling

// Before Jest starts, check watch plugin keys for collisions
function validateWatchPluginKeys(plugins) {
  const keys = new Map();
  for (const [name, opts] of plugins) {
    const key = opts?.key;
    if (!key) continue;
    if (keys.has(key)) {
      throw new Error(`Key '${key}' is used by both ${keys.get(key)} and ${name}`);
    }
    keys.set(key, name);
  }
}

Prevention

When it happens

Trigger: Configuring two `watchPlugins` entries in jest.config that both claim the same key (e.g. both return `{key: 'f'}` from `getUsageInfo`), or a custom plugin returning a key like `q` or `u` that Jest reserves internally. Also triggered when a plugin sets `overwritable: false` (default) and another plugin tries to use the same key.

Common situations: Installing multiple community watch plugins that overlap on default keys. Writing a custom watch plugin and unknowingly using a key Jest already binds. Upgrading a plugin whose default key changed to collide with another.

Related errors


AI-assisted analysis of jestjs/jest@8e6d128e4a (2026-08-10). Data as JSON: /api/errors/5d469a7c0c9e41f4. Report an issue: GitHub.