halo-dev/halo · error · ServerWebInputException
Invalid multipart type of file
Error message
Invalid multipart type of file
What it means
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.
Source
Thrown at application/src/main/java/run/halo/app/theme/endpoint/ThemeEndpoint.java:450
*
* @param uri remote URI of the theme ZIP file
*/
public record UpgradeFromUriRequest(
@Schema(requiredMode = REQUIRED) URI uri) {}
public static class UpgradeRequest implements IUpgradeRequest {
private final MultiValueMap<String, Part> multipartData;
public UpgradeRequest(MultiValueMap<String, Part> multipartData) {
this.multipartData = multipartData;
}
@Override
public FilePart getFile() {
var part = multipartData.getFirst("file");
if (!(part instanceof FilePart filePart)) {
throw new ServerWebInputException("Invalid multipart type of file");
}
if (!filePart.filename().endsWith(".zip")) {
throw new ServerWebInputException("Only zip extension supported");
}
return filePart;
}
}
private Mono<ServerResponse> upgrade(ServerRequest request) {
// validate the theme first
var name = request.pathVariable("name");
return request.multipartData()
.map(UpgradeRequest::new)
.map(UpgradeRequest::getFile)
.flatMap(filePart -> themeService.upgrade(name, filePart.content()))
.flatMap((updatedTheme) -> templateEngineManager
.clearCache(updatedTheme.getMetadata().getName())
.thenReturn(updatedTheme))View on GitHub (pinned to d2f5165f9c)
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).
Example fix
// before (wrong): text field, not a file // curl -F 'file=something' /api/.../themes/name/upgrade // after: binary file part // curl -F 'file=@theme.zip' /api/.../themes/name/upgrade
Defensive patterns
Strategy: validation
Validate before calling
// Client-side: ensure the file part is a real File before sending
const f = fileInput.files && fileInput.files[0];
if (!(f instanceof File)) {
showError("Please choose a file to upload.");
return;
}
const fd = new FormData();
fd.append("file", f, f.name);
// curl: curl -F 'file=@theme.zip' ... Type guard
// JS: narrow to a real File (binary part)
function isBinaryFile(v) {
return v instanceof File;
} Try / catch
// Server-side, the endpoint already returns 400; map it for the client:
try { await upgradeTheme(fd); }
catch (e) { if (/Invalid multipart type/.test(e.message)) alert("Upload a file, not text."); throw e; } Prevention
- 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.
When it happens
Trigger: 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'.
Common situations: 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.
Related errors
- Invalid parameter of file, binary data is required
- Only zip extension supported
- Invalid file type, only zip format is supported
- Invalid part of file
- Invalid part of policyName
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/db98051460284612.
Report an issue: GitHub.