babel/babel · error · Error

${msg(loc)} must be a string, an array of strings or an obje

Error message

${msg(loc)} must be a string, an array of strings or an object

What it means

Thrown by assertTargets when the 'targets' option is neither a browserslist query string (e.g. '> 0.25%'), an array of such strings, nor a plain object keyed by target names. Numbers, booleans, nested arrays, or non-browserslist values are rejected.

Source

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

function assertPluginTarget(loc: GeneralPath, value: unknown): PluginTarget {
  if (
    (typeof value !== "object" || !value) &&
    typeof value !== "string" &&
    typeof value !== "function"
  ) {
    throw new Error(`${msg(loc)} must be a string, object, function`);
  }
  return value as PluginTarget;
}

export function assertTargets(
  loc: GeneralPath,
  value: any,
): TargetsListOrObject {
  if (isBrowsersQueryValid(value)) return value;

  if (typeof value !== "object" || !value || Array.isArray(value)) {
    throw new Error(
      `${msg(loc)} must be a string, an array of strings or an object`,
    );
  }

  const browsersLoc = access(loc, "browsers");
  const esmodulesLoc = access(loc, "esmodules");

  assertBrowsersList(browsersLoc, value.browsers);
  assertBoolean(esmodulesLoc, value.esmodules);

  for (const key of Object.keys(value)) {
    const val = value[key];
    const subLoc = access(loc, key);

    if (key === "esmodules") assertBoolean(subLoc, val);
    else if (key === "browsers") assertBrowsersList(subLoc, val);
    else if (!Object.hasOwn(TargetNames, key)) {
      const validTargets = Object.keys(TargetNames).join(", ");

View on GitHub (pinned to 06b6eae39d)

Solutions

  1. Use a browserslist query string: targets: '> 0.25%, not dead'.
  2. Use an array of queries: targets: ['> 1%', 'last 2 versions'].
  3. Use an object keyed by target: targets: { chrome: 80, firefox: 78 }.

Example fix

// before
{ targets: 90 }
// after
{ targets: { chrome: 90 } }
Defensive patterns

Strategy: type-guard

Validate before calling

function isTargetsShape(v) {
  if (typeof v === 'string') return true;
  if (Array.isArray(v)) return v.every((x) => typeof x === 'string');
  if (typeof v === 'object' && v !== null) return true;
  return false;
}
if (opts.targets !== undefined && !isTargetsShape(opts.targets)) {
  throw new Error('targets must be string, array of strings, or object');
}

Type guard

function isTargetsShape(
  v: unknown,
): v is string | string[] | Record<string, unknown> {
  if (typeof v === 'string') return true;
  if (Array.isArray(v)) return v.every((x) => typeof x === 'string');
  return typeof v === 'object' && v !== null;
}

Try / catch

try {
  babel.loadOptions(opts);
} catch (e) {
  if (e instanceof Error && /must be a string, an array of strings or an object/.test(e.message)) {
    opts.targets = undefined; // fall back to .browserslistrc
    return babel.loadOptions(opts);
  }
  throw e;
}

Prevention

When it happens

Trigger: targets: 90; targets: true; targets: [['chrome', 80]] (nested array); targets: 'last 2 versions' is fine, but targets: { chrome: 'latest' } shape errors later (see 78). Here the top-level type itself is wrong.

Common situations: Passing a numeric browser version as the whole targets value; migrating from browserslist config expecting a different shape; JSON configs that wrap targets in an extra array.

Related errors


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