babel/babel · error

${msg(propLoc)} must be null, undefined, a boolean, a string

Error message

${msg(propLoc)} must be null, undefined, a boolean, a string, or a number.

What it means

Thrown by assertCallerMetadata (option-assertions.ts:156) when iterating the caller object's properties. Each property value must be null, undefined, a boolean, a string, or a number — Babel serializes caller metadata (e.g. for caching/logging) and complex values like objects, arrays, or functions break that contract. The offending property path is included via msg(propLoc).

Source

Thrown at packages/babel-core/src/config/validation/option-assertions.ts:156

    if (typeof obj.name !== "string") {
      throw new Error(
        `${msg(loc)} set but does not contain "name" property string`,
      );
    }

    for (const prop of Object.keys(obj)) {
      const propLoc = access(loc, prop);
      const value = obj[prop];
      if (
        value != null &&
        typeof value !== "boolean" &&
        typeof value !== "string" &&
        typeof value !== "number"
      ) {
        // NOTE(logan): I'm limiting the type here so that we can guarantee that
        // the "caller" value will serialize to JSON nicely. We can always
        // allow more complex structures later though.
        throw new Error(
          `${msg(
            propLoc,
          )} must be null, undefined, a boolean, a string, or a number.`,
        );
      }
    }
  }
  // @ts-expect-error todo(flow->ts)
  return value;
}

export function assertInputSourceMap(
  loc: OptionPath,
  value: unknown,
): RootInputSourceMapOption {
  if (
    value !== undefined &&
    typeof value !== "boolean" &&

View on GitHub (pinned to 06b6eae39d)

Solutions

  1. Keep caller property values to primitives (boolean, string, number) or null/undefined.
  2. Flatten nested config into named primitive properties (e.g. `targetESVersion: 2020` instead of `target: { esVersion: 2020 }`).
  3. If a plugin needs complex caller data, encode it as a JSON string and parse inside the plugin.

Example fix

// before
babel.transformSync(code, { caller: { name: 'tool', opts: { loose: true } } });
// after
babel.transformSync(code, { caller: { name: 'tool', loose: true } });
Defensive patterns

Strategy: type-guard

Validate before calling

function sanitizeCaller(caller) {
  const out = {};
  for (const [k, v] of Object.entries(caller)) {
    if (v == null || typeof v === 'boolean' || typeof v === 'string' || typeof v === 'number') {
      out[k] = v;
    }
  }
  return out;
}

Type guard

function isPrimitiveCallerValue(v): boolean {
  return v == null || ['boolean','string','number'].includes(typeof v);
}

Try / catch

try {
  babel.transformSync(code, opts);
} catch (e) {
  if (e.message.includes('must be null, undefined, a boolean, a string, or a number')) {
    const c = {};
    for (const [k, v] of Object.entries(opts.caller)) {
      if (v == null || typeof v !== 'object') c[k] = v;
    }
    babel.transformSync(code, { ...opts, caller: c });
  } else throw e;
}

Prevention

When it happens

Trigger: Setting `caller: { name: 'x', config: { foo: 1 } }` or `caller: { name: 'x', items: [1,2] }` or `caller: { name: 'x', fn: () => {} }`. The loop at option-assertions.ts:144 detects a value that is not null/undefined/boolean/string/number.

Common situations: Passing a nested config object or array as a caller property expecting plugins to read it; storing a function reference in caller metadata; spreading a complex tool config into caller.

Related errors


AI-assisted analysis of babel/babel@06b6eae39d (2026-08-03). Data as JSON: /data/errors/6b66a346fcbf7e84.json. Report an issue: GitHub.