halo-dev/halo · error · ServerWebInputException

Only zip extension supported

Error message

Only zip extension supported

What it means

After confirming the 'file' part is a FilePart, UpgradeRequest.getFile() checks that the filename ends with '.zip'. If not, a ServerWebInputException (HTTP 400) 'Only zip extension supported' is thrown. Theme upgrades only accept ZIP archives.

Source

Thrown at application/src/main/java/run/halo/app/theme/endpoint/ThemeEndpoint.java:453

    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))
                .flatMap(updatedTheme -> ServerResponse.ok().bodyValue(updatedTheme));
    }

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Repackage the theme as a .zip archive and re-upload.
  2. If you produced a .tar.gz, convert it: unzip equivalent or re-zip the theme directory.
  3. Verify the filename actually ends in '.zip' before submitting (case-sensitive suffix match).

Example fix

# before
#   zip -r theme.tar.gz theme/   # wrong format
# after
#   zip -r theme.zip theme/      # then upload theme.zip
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: block non-zip filenames before upload
const name = file.name.toLowerCase();
if (!name.endsWith(".zip")) {
    showError("Only .zip files are supported.");
    return;
}
// curl: ensure the uploaded file is a zip
//   [[ "$(file -b --mime-type theme.zip)" == application/zip* ]]

Type guard

function isZipFilename(name) {
  return String(name).toLowerCase().endsWith(".zip");
}

Try / catch

try { await upgradeTheme(fd); }
catch (e) { if (/Only zip extension/.test(e.message)) alert("Repackage as .zip first."); throw e; }

Prevention

When it happens

Trigger: Uploading a non-zip file (e.g. theme.tar.gz, theme.rar, or an uncompressed directory) to the theme upgrade endpoint via multipart. The check is a pure suffix match on filePart.filename().

Common situations: User picks the wrong archive format (tar.gz instead of zip); OS hiding extensions causing a misnamed file; a build pipeline producing tar archives.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/8c160cdea5edf645. Report an issue: GitHub.