{"record":{"id":"d414ad595fa074f4","repo":"remix-run/remix","slug":"files-extensions-must-be-an-array","errorCode":null,"errorMessage":"files.extensions must be an array","messagePattern":"files\\.extensions must be an array","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/assets/src/lib/files/config.ts","lineNumber":184,"sourceCode":"\nconst reservedFileExtensions = new Set<string>([...supportedScriptExtensions, '.css', '.map'])\nconst defaultMaxRequestTransforms = 5\n\nexport function normalizeFilesOptions<transforms extends AssetRequestTransformMap>(\n  files: AssetServerFilesOptions<transforms> | undefined,\n): ResolvedAssetServerFilesOptions {\n  if (files == null) {\n    return {\n      extensions: [],\n      globalTransforms: [],\n      hasTransforms: false,\n      maxRequestTransforms: defaultMaxRequestTransforms,\n      transforms: new Map(),\n    }\n  }\n\n  if (!Array.isArray(files.extensions)) {\n    throw new TypeError('files.extensions must be an array')\n  }\n\n  let normalizedExtensions: string[] = []\n  let seen = new Set<string>()\n\n  for (let extension of files.extensions) {\n    if (typeof extension !== 'string') {\n      throw new TypeError('files.extensions values must be strings')\n    }\n\n    let normalizedExtension = extension.trim().toLowerCase()\n    if (!/^\\.[A-Za-z0-9_-]+$/.test(normalizedExtension)) {\n      throw new TypeError(\n        `files.extensions values must use \".ext\" format. Received \"${extension}\".`,\n      )\n    }\n\n    if (reservedFileExtensions.has(normalizedExtension)) {","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/assets/src/lib/files/config.ts#L166-L202","documentation":"When a `files` config object is provided to the asset server, its `extensions` field must be an array of file-extension strings. This error fires when `files.extensions` exists but is not an array (string, object, number, etc.).","triggerScenarios":"Passing `files: { extensions: '.css' }` (a single string) or a non-array value like an object map of extensions.","commonSituations":"Copy-pasting a glob string or comma-separated list ('.css,.md') into `extensions`; config schemas from other tools (esbuild `loader` maps) pasted wholesale; YAML parsing a single item into a scalar instead of a list.","solutions":["Wrap the value in an array: `extensions: ['.css', '.md']`","If you don't want to override the defaults, omit `files.extensions` entirely","Split comma-separated strings before passing: `extensions: raw.split(',')`"],"exampleFix":"// before\nfiles: { extensions: '.css' }\n// after\nfiles: { extensions: ['.css'] }","handlingStrategy":"type-guard","validationCode":"if (filesConfig.extensions !== undefined && !Array.isArray(filesConfig.extensions)) {\n  throw new Error('files.extensions must be an array of strings')\n}","typeGuard":"function isExtensionArray(v: unknown): v is string[] {\n  return Array.isArray(v) && v.every((e) => typeof e === 'string')\n}","tryCatchPattern":null,"preventionTips":["Always wrap single extensions in arrays","Validate the whole files config once at app startup with a small schema check"],"tags":["config","files","validation","extensions"],"backgroundTag":"config-type-validation","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}