remix-run/remix · error · TypeError

${optionPath} must be an object

Error message

${optionPath} must be an object

What it means

The `script.target` option (e.g. `script: { target: ... }` or build.target) must be a plain object mapping target names to versions, not a string, array, or class instance. normalizeScriptTargetObject duck-checks with isPlainObject and throws otherwise.

Source

Thrown at packages/assets/src/lib/target.ts:94

  let lightningCssTargets: ResolvedStyleTarget = {}
  for (let browserTargetName of browserTargetNames) {
    let version = resolvedTarget[browserTargetName]
    if (version == null) continue
    lightningCssTargets[lightningCssTargetNameByBrowserTargetName[browserTargetName]] =
      toLightningCssTargetVersion(version)
  }

  return lightningCssTargets
}

function normalizeScriptTargetObject(
  target: AssetTarget | undefined,
  optionPath: string,
): NormalizedAssetTarget | undefined {
  if (target == null) return undefined
  if (!isPlainObject(target)) {
    throw new TypeError(`${optionPath} must be an object`)
  }

  let normalizedTarget: NormalizedAssetTarget = {}

  for (let [key, value] of Object.entries(target)) {
    if (key === 'es') {
      normalizedTarget.es = normalizeScriptTargetVersion(value, `${optionPath}.es`)
      continue
    }

    if (!browserTargetNameSet.has(key)) {
      throw new TypeError(`${optionPath}.${key} is not a supported target`)
    }

    normalizedTarget[key as BrowserTargetName] = normalizeBrowserTargetVersion(
      value,
      `${optionPath}.${key}`,
    )

View on GitHub (pinned to 9696913134)

Solutions

  1. Use the object form: { es: 2022 } or { chrome: '100' }
  2. When accepting string config from users, convert 'es2022' to { es: 2022 } before passing
  3. Ensure the value isn't a class instance — use a plain object literal

Example fix

// before
script: { target: 'es2022' }
// after
script: { target: { es: 2022 } }
Defensive patterns

Strategy: type-guard

Validate before calling

if (target != null && (typeof target !== 'object' || Array.isArray(target))) {
  throw new Error('script.target must be an object like { es: 2022 }')
}

Type guard

function isPlainTargetObject(v: unknown): v is Record<string, string | number | boolean> {
  return v != null && typeof v === 'object' && !Array.isArray(v)
    && Object.getPrototypeOf(v) === Object.prototype
}

Prevention

When it happens

Trigger: Passing `script: { target: 'es2022' }` (string), `target: ['es2022']` (array), or a class instance with target fields to the assets options.

Common situations: Porting esbuild/browserslist config where target is a string or array; spreading a config class instance; JSON configs typed loosely.

Understand the failure class

Background: Invalid option value errors: "must be one of", "is not a valid", and "only allows" failures explained — this error's family across 23 libraries.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/d6e1f03622b64f6a. Report an issue: GitHub.