{"record":{"id":"db98051460284612","repo":"halo-dev/halo","slug":"invalid-multipart-type-of-file","errorCode":null,"errorMessage":"Invalid multipart type of file","messagePattern":"Invalid multipart type of file","errorType":"http","errorClass":"ServerWebInputException","httpStatus":400,"severity":"error","filePath":"application/src/main/java/run/halo/app/theme/endpoint/ThemeEndpoint.java","lineNumber":450,"sourceCode":"     *\n     * @param uri remote URI of the theme ZIP file\n     */\n    public record UpgradeFromUriRequest(\n            @Schema(requiredMode = REQUIRED) URI uri) {}\n\n    public static class UpgradeRequest implements IUpgradeRequest {\n\n        private final MultiValueMap<String, Part> multipartData;\n\n        public UpgradeRequest(MultiValueMap<String, Part> multipartData) {\n            this.multipartData = multipartData;\n        }\n\n        @Override\n        public FilePart getFile() {\n            var part = multipartData.getFirst(\"file\");\n            if (!(part instanceof FilePart filePart)) {\n                throw new ServerWebInputException(\"Invalid multipart type of file\");\n            }\n            if (!filePart.filename().endsWith(\".zip\")) {\n                throw new ServerWebInputException(\"Only zip extension supported\");\n            }\n            return filePart;\n        }\n    }\n\n    private Mono<ServerResponse> upgrade(ServerRequest request) {\n        // validate the theme first\n        var name = request.pathVariable(\"name\");\n        return request.multipartData()\n                .map(UpgradeRequest::new)\n                .map(UpgradeRequest::getFile)\n                .flatMap(filePart -> themeService.upgrade(name, filePart.content()))\n                .flatMap((updatedTheme) -> templateEngineManager\n                        .clearCache(updatedTheme.getMetadata().getName())\n                        .thenReturn(updatedTheme))","sourceCodeStart":432,"sourceCodeEnd":468,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/theme/endpoint/ThemeEndpoint.java#L432-L468","documentation":"UpgradeRequest.getFile() reads the multipart part named 'file' from the upgrade request. If that part exists but is not a FilePart (e.g. it is a plain form FieldPart with no filename/binary content), a ServerWebInputException (HTTP 400) 'Invalid multipart type of file' is thrown. This guards the upgrade-by-upload endpoint before the .zip extension is even checked.","triggerScenarios":"POST to the theme upgrade endpoint with a multipart body where the 'file' part was sent as a text form field (Content-Disposition: form-data; name=\"file\" without a filename), or the part is otherwise not a FilePart. Common with hand-crafted curl/Postman requests using -F 'file=value' instead of -F 'file=@path'.","commonSituations":"Wrong curl flag (-F file=value vs -F file=@file.zip); a frontend sending a string instead of a File object in FormData; a proxy normalizing the multipart boundary/disposition.","solutions":["Send the file as a binary upload: curl -F 'file=@/path/theme.zip'.","On the frontend, append a real File/Blob to FormData under the 'file' key.","Verify the Content-Disposition of the 'file' part includes a filename parameter.","Ensure the request Content-Type is multipart/form-data (set automatically by FormData; do not set it manually)."],"exampleFix":"// before (wrong): text field, not a file\n//   curl -F 'file=something' /api/.../themes/name/upgrade\n// after: binary file part\n//   curl -F 'file=@theme.zip' /api/.../themes/name/upgrade","handlingStrategy":"validation","validationCode":"// Client-side: ensure the file part is a real File before sending\nconst f = fileInput.files && fileInput.files[0];\nif (!(f instanceof File)) {\n    showError(\"Please choose a file to upload.\");\n    return;\n}\nconst fd = new FormData();\nfd.append(\"file\", f, f.name);\n// curl: curl -F 'file=@theme.zip' ...","typeGuard":"// JS: narrow to a real File (binary part)\nfunction isBinaryFile(v) {\n  return v instanceof File;\n}","tryCatchPattern":"// Server-side, the endpoint already returns 400; map it for the client:\ntry { await upgradeTheme(fd); }\ncatch (e) { if (/Invalid multipart type/.test(e.message)) alert(\"Upload a file, not text.\"); throw e; }","preventionTips":["Always use -F 'file=@path' (curl) or FormData.append('file', File).","Never set Content-Type manually for FormData; the browser sets the boundary.","Validate fileInput.files[0] is a File before enabling the submit button.","In Postman, attach the file under the 'file' key as type File, not Text."],"tags":["theme","multipart","upload","api","validation"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}