jestjs/jest · error · TypeError

expect.extend: ` ` is not a valid matcher. Must be a…

Error message

expect.extend: `${key}` is not a valid matcher. Must be a function, is "${getType(matcher)}"

What it means

expect.extend(matchers) registers custom matchers; setMatchers iterates the keys and requires each value to be a function (the matcher implementation). If any value is not a function (e.g. an object, a boolean, or a config descriptor), it throws a TypeError naming the offending key and its actual type. This prevents registering non-callable 'matchers' that would fail later with a less clear error.

Solutions

  1. Ensure every value in the object passed to expect.extend is a function with the matcher signature.
  2. If your matchers are exported as named functions, import and pass them directly: `expect.extend({ toBeFoo })`.
  3. If a module exports matchers under a key (e.g. `matchers` property), spread only that sub-object, not the whole module namespace.

Example fix

// before
import * as matchers from './my-matchers';
expect.extend(matchers); // module namespace has non-function members

// after
import { toBeFoo, toBeBar } from './my-matchers';
expect.extend({ toBeFoo, toBeBar });
Defensive patterns

Strategy: type-guard

Validate before calling

function extendSafe(obj) {
  for (const [key, value] of Object.entries(obj)) {
    if (typeof value !== 'function') {
      throw new TypeError(`expect.extend: '${key}' is not a function (got ${typeof value})`);
    }
  }
  expect.extend(obj);
}

Type guard

function isMatchersObject(v: Record<string, unknown>): boolean {
  return Object.values(v).every(x => typeof x === 'function');
}

if (!isMatchersObject(matchers)) {
  throw new TypeError('All expect.extend values must be functions');
}
expect.extend(matchers);

Prevention

When it happens

Trigger: Calling `expect.extend({ name: value })` where value is not a function — e.g. passing a plain object, an array, a default-export module object, or a boolean flag. Also when spreading a module's exports that include non-function members.

Common situations: Importing a matcher module's default object but passing the namespace instead of the function; copying a matcher config that includes options as top-level values; a typo where a config object is passed in place of the matcher function.

Related errors


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

Appendix: source

Thrown at packages/expect/src/jestMatchersObject.ts:65

export const setState = <State extends MatcherState = MatcherState>(
  state: Partial<State>,
): void => {
  Object.assign((globalThis as any)[JEST_MATCHERS_OBJECT].state, state);
};

export const getMatchers = (): MatchersObject =>
  (globalThis as any)[JEST_MATCHERS_OBJECT].matchers;

export const setMatchers = (
  matchers: MatchersObject,
  isInternal: boolean,
  expect: Expect,
): void => {
  for (const key of Object.keys(matchers)) {
    const matcher = matchers[key];

    if (typeof matcher !== 'function') {
      throw new TypeError(
        `expect.extend: \`${key}\` is not a valid matcher. Must be a function, is "${getType(
          matcher,
        )}"`,
      );
    }

    Object.defineProperty(matcher, INTERNAL_MATCHER_FLAG, {
      value: isInternal,
    });

    if (!isInternal) {
      // expect is defined

      class CustomMatcher extends AsymmetricMatcher<
        [unknown, ...Array<unknown>]
      > {
        constructor(inverse = false, ...sample: [unknown, ...Array<unknown>]) {
          super(sample, inverse);

View on GitHub (pinned to 8e6d128e4a)