{"record":{"id":"df7f6547b6a32e6c","repo":"Budibase/budibase","slug":"file-filename-has-no-extension-an-extension","errorCode":null,"errorMessage":"File \"${fileName}\" has no extension, an extension is required to upload a file","messagePattern":"File \"(.+?)\" has no extension, an extension is required to upload a file","errorType":"http","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"packages/server/src/api/controllers/static/index.ts","lineNumber":274,"sourceCode":"  let files = file && Array.isArray(file) ? Array.from(file) : [file]\n\n  ctx.body = await Promise.all(\n    files.map(async file => {\n      const fileName = getUploadFilename(file)\n      const filePath = getUploadPath(file)\n      const rawMimeType = getUploadMimeType(file)\n      if (!fileName) {\n        throw new BadRequestError(\n          \"Attempted to upload a file without a filename\"\n        )\n      }\n      if (!filePath) {\n        throw new BadRequestError(\"Attempted to upload a file without a path\")\n      }\n\n      const extension = [...fileName.split(\".\")].pop()\n      if (!extension) {\n        throw new BadRequestError(\n          `File \"${fileName}\" has no extension, an extension is required to upload a file`\n        )\n      }\n\n      const extensionLower = extension.toLowerCase()\n      const isPublicUser =\n        ctx.roleId === roles.BUILTIN_ROLE_IDS.PUBLIC ||\n        ctx.user?.roleId === roles.BUILTIN_ROLE_IDS.PUBLIC\n      const enforceInvalidExtension = isPublicUser || !env.SELF_HOSTED\n      if (\n        enforceInvalidExtension &&\n        InvalidFileExtensions.includes(extensionLower)\n      ) {\n        throw new BadRequestError(\n          `File \"${fileName}\" has an invalid extension: \"${extension}\"`\n        )\n      }\n","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/static/index.ts#L256-L292","documentation":"Thrown by uploadFile when the filename has no extension (the last dot-separated segment is empty). Extensions drive storage keys, content type resolution, and the security extension checks, so extensionless uploads are rejected.","triggerScenarios":"Uploading files named like 'README' or '.gitignore' (split('.') yields empty last segment) or blobs given extensionless names.","commonSituations":"macOS/Linux config or dotfiles, scripts uploading temp files without names, or API clients hardcoding names without extensions.","solutions":["Rename the file to include an extension before upload (e.g. notes.txt)","Append a filename with extension when creating the FormData entry","Pick an extension matching the actual content type"],"exampleFix":"// before\nfd.append('file', blob, 'config')\n// after\nfd.append('file', blob, 'config.json')","handlingStrategy":"validation","validationCode":"function hasExtension(name) {\n  const ext = name.split('.').pop()\n  return Boolean(ext) && name.includes('.')\n}","typeGuard":"const hasFileExtension = (f: { name: string }): f is { name: string } & { ext: string } =>\n  f.name.includes('.') && f.name.split('.').pop()!.length > 0","tryCatchPattern":"try {\n  await api.uploadFile(fd)\n} catch (err) {\n  if (err instanceof BadRequestError && err.message.includes('has no extension')) {\n    // rename to include an extension and resend\n  } else throw err\n}","preventionTips":["Always name uploaded files with a sensible extension","Handle dotfiles (.gitignore) by renaming before upload","Derive the extension from the MIME type if the original name lacks one"],"tags":["upload","validation","file-extension"],"backgroundTag":"upload-extension-required","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}