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
- Keep caller property values to primitives (boolean, string, number) or null/undefined.
- Flatten nested config into named primitive properties (e.g. `targetESVersion: 2020` instead of `target: { esVersion: 2020 }`).
- 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
- Keep caller properties to primitives only.
- Flatten nested objects into named primitive fields.
- Encode complex data as a JSON string if a plugin must read it.
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
- ${msg(loc)} set but does not contain "name" property string
- ${msg(loc)} must be a "root", "upward", "upward-optional" or
- ${msg(loc)} must be a boolean, "inline", "both", or undefine
- ${msg(loc)} must be a boolean, "auto", or undefined
- ${msg(loc)} must be "module", "commonjs", "script", "unambig
AI-assisted analysis of babel/babel@06b6eae39d (2026-08-03).
Data as JSON: /data/errors/6b66a346fcbf7e84.json.
Report an issue: GitHub.