{"record":{"id":"fbc966a55bcae04b","repo":"halo-dev/halo","slug":"form-field-file-is-required","errorCode":null,"errorMessage":"Form field file is required","messagePattern":"Form field file is required","errorType":"validation","errorClass":"ServerWebInputException","httpStatus":400,"severity":"warning","filePath":"application/src/main/java/run/halo/app/core/endpoint/console/PluginEndpoint.java","lineNumber":656,"sourceCode":"                .flatMap(listResult -> ServerResponse.ok().bodyValue(listResult));\n    }\n\n    /** Multipart payload for installing a plugin. */\n    @Schema(name = \"PluginInstallRequest\", types = \"object\")\n    public static class InstallRequest {\n\n        private final MultiValueMap<String, Part> multipartData;\n\n        public InstallRequest(MultiValueMap<String, Part> multipartData) {\n            this.multipartData = multipartData;\n        }\n\n        /** Plugin JAR file. */\n        @Schema(requiredMode = NOT_REQUIRED)\n        public FilePart getFile() {\n            var part = multipartData.getFirst(\"file\");\n            if (part == null) {\n                throw new ServerWebInputException(\"Form field file is required\");\n            }\n            if (!(part instanceof FilePart file)) {\n                throw new ServerWebInputException(\"Invalid parameter of file\");\n            }\n            if (!Paths.get(file.filename()).toString().endsWith(\".jar\")) {\n                throw new ServerWebInputException(\"Invalid file type, only jar is supported\");\n            }\n            return file;\n        }\n\n        /** Plugin preset name. Halo finds the plugin from plugin presets. */\n        @Schema(requiredMode = NOT_REQUIRED)\n        public Mono<String> getPresetName() {\n            var part = multipartData.getFirst(\"presetName\");\n            if (part == null) {\n                return Mono.error(new ServerWebInputException(\"Form field presetName is required.\"));\n            }\n            if (!(part instanceof FormFieldPart presetName)) {","sourceCodeStart":638,"sourceCodeEnd":674,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/core/endpoint/console/PluginEndpoint.java#L638-L674","documentation":"InstallRequest.getFile() throws ServerWebInputException (HTTP 400) 'Form field file is required' when the multipart install request has no part named \"file\" at all. Plugin install accepts either an uploaded JAR (file) or a preset name; when neither is usable and the file part is absent, this guard fires.","triggerScenarios":"POST to the plugin install endpoint with no \"file\" part and no \"presetName\" — the user attempted to install nothing; or a client that intended to install from preset but the server-side fallback expected a file part first.","commonSituations":"Frontend install dialog submitted with no JAR selected and no preset; integration code that posts an empty multipart body; confusion about whether file or presetName is required.","solutions":["Provide a plugin JAR under the \"file\" multipart field, or a \"presetName\" field referencing a known preset.","On the client, require either a selected file or a chosen preset before enabling install.","If installing from preset, ensure presetName is sent and valid so the file path is not required."],"exampleFix":"// before\nconst fd = new FormData(); // nothing appended\n\n// after\nconst fd = new FormData();\nfd.append('file', pluginJarFile); // or fd.append('presetName', 'preset-name')","handlingStrategy":"validation","validationCode":"Part filePart = multipartData.getFirst(\"file\");\nif (filePart == null && multipartData.getFirst(\"presetName\") == null) {\n    return Mono.error(new ServerWebInputException(\"Form field file is required\"));\n}","typeGuard":"static boolean hasInstallSource(MultiValueMap<String, Part> fd) {\n    return fd.getFirst(\"file\") != null || fd.getFirst(\"presetName\") != null;\n}","tryCatchPattern":null,"preventionTips":["Require either a selected JAR (file) or a preset name before enabling install.","If installing from preset, send presetName so the file part is not required.","Validate the body is non-empty before posting."],"tags":["validation","plugin","install","multipart","http-400","required-field"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}