remix-run/remix · error · TypeError

${optionPath} must use a four-digit year of 2015 or higher

Error message

${optionPath} must use a four-digit year of 2015 or higher

What it means

Thrown when a script target version is numeric but not a valid four-digit year of at least 2015. The assets normalizer maps the year to an esbuild-style target like es2020, and esbuild's lowest supported ES target is es2015, so shorter numbers, future/non-4-digit numbers, or years below 2015 fail.

Source

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

  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`)
  }

  if (!/^\d+(\.\d+){0,2}$/.test(value)) {
    throw new TypeError(`${optionPath} must use "X", "X.Y", or "X.Y.Z" version format`)
  }

View on GitHub (pinned to 9696913134)

Solutions

  1. Use a four-digit year >= 2015, e.g. '2020' or '2022'
  2. For legacy ES5 output, pick '2015' as the oldest supported value and verify your toolchain
  3. Validate year ranges in config UIs or scripts before submission

Example fix

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

Strategy: validation

Validate before calling

let isValidEsYear = (value: string) => /^\d{4}$/.test(value) && Number(value) >= 2015
if (!isValidEsYear(esTarget)) throw new Error('script target must be a 4-digit year >= 2015')

Type guard

let isSupportedEsYear = (value: unknown): value is string =>
  typeof value === 'string' && /^\d{4}$/.test(value.trim()) && Number(value.trim()) >= 2015

Prevention

When it happens

Trigger: `{ script: { es: '5' } }`, `{ script: { es: '20' } }`, `{ script: { es: '2014' } }`, or `{ script: { es: '302020' } }`.

Common situations: Using an ES level like '5' or '6' (as in tsconfig's ES5/ES6 naming) instead of a year; typos in the year; assuming any number is accepted because the earlier check only validates digits.

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/80eacac9e5f0c5c7. Report an issue: GitHub.