{"record":{"id":"79f2d1563cd54e98","repo":"gotify/server","slug":"file-with-key-file-must-be-present","errorCode":null,"errorMessage":"file with key 'file' must be present","messagePattern":"file with key 'file' must be present","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"api/application.go","lineNumber":417,"sourceCode":"//\t        $ref: \"#/definitions/Error\"\n//\t  404:\n//\t    description: Not Found\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\n//\t  500:\n//\t    description: Server Error\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\nfunc (a *ApplicationAPI) UploadApplicationImage(ctx *gin.Context) {\n\twithID(ctx, \"id\", func(id uint) {\n\t\tapp, err := a.DB.GetApplicationByID(id)\n\t\tif success := successOrAbort(ctx, 500, err); !success {\n\t\t\treturn\n\t\t}\n\t\tif app != nil && app.UserID == auth.GetUserID(ctx) {\n\t\t\tfile, err := ctx.FormFile(\"file\")\n\t\t\tif err == http.ErrMissingFile {\n\t\t\t\tctx.AbortWithError(400, errors.New(\"file with key 'file' must be present\"))\n\t\t\t\treturn\n\t\t\t} else if err != nil {\n\t\t\t\tctx.AbortWithError(500, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\thead := make([]byte, 261)\n\t\t\topen, _ := file.Open()\n\t\t\topen.Read(head)\n\t\t\tif !filetype.IsImage(head) {\n\t\t\t\tctx.AbortWithError(400, errors.New(\"file must be an image\"))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\text := filepath.Ext(file.Filename)\n\t\t\tif !ValidApplicationImageExt(ext) {\n\t\t\t\tctx.AbortWithError(400, errors.New(\"invalid file extension\"))\n\t\t\t\treturn\n\t\t\t}","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/application.go#L399-L435","documentation":"The image-upload endpoint requires a multipart form field named exactly 'file'. When the multipart request contains no file under that key, gin's ctx.FormFile returns http.ErrMissingFile and the handler converts it into this 400 error. It is the API's way of saying the required upload part is absent.","triggerScenarios":"POSTing to the upload route with multipart/form-data that lacks a part named 'file'; sending JSON instead of multipart; sending the file under a different field name such as 'image' or 'upload'; sending an empty file part.","commonSituations":"Frontend FormData.append('image', f) instead of append('file', f); using curl -F without the right key; clients sending application/json bodies; axios default content-type overriding multipart; testing tools omitting the file.","solutions":["Send the file in multipart/form-data under the exact key 'file' (FormData.append('file', file))","With curl use: curl -F \"file=@/path/to/img.png\" ...","Verify the request Content-Type is multipart/form-data and the body is not JSON","Check client code/libraries that the field name was not renamed"],"exampleFix":"// before\nconst fd = new FormData();\nfd.append('image', file);\nfetch('/applications/1/image', {method:'POST', body: fd});\n// after\nconst fd = new FormData();\nfd.append('file', file);\nfetch('/applications/1/image', {method:'POST', body: fd});","handlingStrategy":"validation","validationCode":"const fd = new FormData();\nif (!file) throw new Error('a file is required');\nfd.append('file', file, file.name);\nconsole.log(fd.get('file')); // must be non-null under key 'file'","typeGuard":null,"tryCatchPattern":"try {\n  await api.post(`/applications/${id}/image`, fd);\n} catch (e) {\n  if (e.response?.status === 400 && e.response?.data?.includes(\"must be present\")) {\n    // ensure multipart part is named 'file' and retry\n  } else throw e;\n}","preventionTips":["Always append the upload as FormData key 'file', matching the server's ctx.FormFile(\"file\")","Verify Content-Type is multipart/form-data; never override it with application/json for uploads","Log the FormData entries before sending in tests","Check the server handler's expected field name whenever integrating a new upload endpoint"],"tags":["http-400","multipart","file-upload","gin","go"],"backgroundTag":"missing-required-form-file","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}