remix-run/remix · error · TypeError

${optionPath} must use a single numeric year like "2020"

Error message

${optionPath} must use a single numeric year like "2020"

What it means

Thrown when a script target version string is not composed solely of digits. The ES script target only accepts a plain numeric year, so values like 'es2020', '2020.1', 'v2020', or '20x20' are rejected before the four-digit/2015+ check runs.

Source

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

      `${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}`
}

function normalizeBrowserTargetVersion(value: unknown, optionPath: string): AssetTargetVersion {
  if (typeof value !== 'string') {
    throw new TypeError(`${optionPath} must be a string`)
  }

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

View on GitHub (pinned to 9696913134)

Solutions

  1. Use a bare four-digit year without prefix: '2020' instead of 'es2020'
  2. If migrating from tsconfig, strip the 'es' prefix programmatically
  3. Double-check for stray characters, spaces, or quotes introduced by env vars or config templating

Example fix

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

Strategy: validation

Validate before calling

let isValidEsYear = (value: string) => /^\d+$/.test(value.trim())
if (!isValidEsYear(esTarget)) throw new Error('script target must be a plain numeric year')

Type guard

let isPlainNumericYear = (value: unknown): value is string =>
  typeof value === 'string' && /^\d+$/.test(value.trim())

Prevention

When it happens

Trigger: `{ script: { es: 'es2020' } }` (including the es prefix), `'latest'`, `'ES2020'`, `'2020-01'`, or any value with non-digit characters after trimming.

Common situations: Copying values from tsconfig `target` (which accepts lowercase years like 'es2020') into the assets target option; passing browserslist-style names; assuming the API accepts the same values as TypeScript or 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/d217729ef4c55324. Report an issue: GitHub.