remix-run/remix · error · TypeError

${optionPath} values must be strings

Error message

${optionPath} values must be strings

What it means

Every entry in an extension list option (e.g. `files.transformExtensions`) must be a string. The validator iterates the array and throws on the first non-string entry.

Source

Thrown at packages/assets/src/lib/files/config.ts:333

function normalizeTransformExtensions(
  extensions: readonly string[] | undefined,
  optionPath: string,
): readonly string[] | undefined {
  if (extensions === undefined) return undefined
  if (!Array.isArray(extensions)) {
    throw new TypeError(`${optionPath} must be an array`)
  }
  if (extensions.length === 0) {
    throw new TypeError(`${optionPath} must include at least one extension`)
  }

  let normalizedExtensions: string[] = []
  let seen = new Set<string>()

  for (let extension of extensions) {
    if (typeof extension !== 'string') {
      throw new TypeError(`${optionPath} values must be strings`)
    }

    let normalizedExtension = extension.trim().toLowerCase()
    if (!/^\.[A-Za-z0-9_-]+$/.test(normalizedExtension)) {
      throw new TypeError(`${optionPath} values must use ".ext" format. Received "${extension}".`)
    }

    if (seen.has(normalizedExtension)) continue
    seen.add(normalizedExtension)
    normalizedExtensions.push(normalizedExtension)
  }

  return normalizedExtensions
}

export function serializeAssetTransformInvocations<transforms extends AssetRequestTransformMap>(
  transforms: readonly AssetTransformInvocation<transforms>[],
  transformsByName: ResolvedAssetRequestTransformMap,

View on GitHub (pinned to 9696913134)

Solutions

  1. Convert entries to strings before passing: extensions.map(String)
  2. Fix the source producing non-string entries (validate JSON schema of your config)
  3. Type the variable as readonly string[] so TS catches it earlier

Example fix

// before
files: { transformExtensions: ['.css', 42] }
// after
files: { transformExtensions: ['.css', '.js'] }
Defensive patterns

Strategy: validation

Validate before calling

if (!ext.every((e) => typeof e === 'string')) ext = ext.map(String)

Type guard

function isStringArray(v: unknown): v is readonly string[] {
  return Array.isArray(v) && v.every((e) => typeof e === 'string')
}

Prevention

When it happens

Trigger: Passing an array containing numbers, null, or objects, e.g. `['.css', 42]` or `[null]`, to resolveAssetServerOptions.

Common situations: Mixed arrays from JSON config with loose typing, arrays built with `.concat(someNumber)`, or TS types bypassed with `as any`.

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/3613fcd9116703f2. Report an issue: GitHub.