remix-run/remix · error · TypeError

${optionPath} must be a string

Error message

${optionPath} must be a string

What it means

Thrown by normalizeScriptTargetVersion in @remix-run/assets when a script target version value is not a string. The assets package normalizes the `script` target option and requires each version value (e.g. `target.script.es` or browser versions) to be a string like "2020". Non-string values such as numbers or booleans are rejected because they cannot be reliably parsed into an ES year.

Source

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

      continue
    }

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

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

  return Object.keys(normalizedTarget).length === 0 ? undefined : normalizedTarget
}

function normalizeScriptTargetVersion(value: unknown, optionPath: string): AssetTarget['es'] {
  if (typeof value !== 'string') {
    throw new TypeError(`${optionPath} must be a string`)
  }

  let normalizedValue = value.trim()
  if (normalizedValue.length === 0) {
    throw new TypeError(`${optionPath} must be a non-empty string`)
  }

  if (!/^\d+$/.test(normalizedValue)) {
    throw new TypeError(`${optionPath} must use a single numeric year like "2020"`)
  }

  if (!/^\d{4}$/.test(normalizedValue) || Number(normalizedValue) < 2015) {
    throw new TypeError(`${optionPath} must use a four-digit year of 2015 or higher`)
  }

  return `es${normalizedValue}`
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Quote the version value: use `{ script: { es: '2020' } }` instead of `{ script: { es: 2020 } }`
  2. Check any config files (JSON/YAML/env-derived) that feed the target option to ensure versions arrive as strings
  3. If generating config programmatically, coerce with `String(value)` before passing it in

Example fix

// before
let target = { script: { es: 2020 } }
// after
let target = { script: { es: '2020' } }
Defensive patterns

Strategy: validation

Validate before calling

for (let [key, value] of Object.entries(targetConfig.script ?? {})) {
  if (typeof value !== 'string') {
    throw new Error(`target.script.${key} must be a string, got ${typeof value}`)
  }
}

Type guard

let isStringVersion = (value: unknown): value is string => typeof value === 'string'

Prevention

When it happens

Trigger: Passing a non-string value in a target config, e.g. `normalizeTarget({ script: { es: 2020 } })` or `{ script: { chrome: 90 } }` (number instead of "90").

Common situations: Developers write version numbers as numbers instead of strings in build/asset config; config loaded from JSON or YAML where quotes were dropped; copying config from tools that accept numeric versions (browserslist, esbuild).

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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