halo-dev/halo · error · ThemeInstallationException

problemDetail.theme.install.missingManifest

problemDetail.theme.install.missingManifest

Error message

Missing theme manifest

What it means

ThemeInstallationException 'Missing theme manifest' (code problemDetail.theme.install.missingManifest) is thrown from two spots: (1) locateThemeManifest() found no theme.yaml/theme.yml anywhere in the extracted archive (the switchIfEmpty in unzipThemeTo); (2) loadThemeManifest() found a manifest file but YamlUnstructuredLoader loaded zero documents from it (empty or unparseable YAML). Either way Halo cannot identify the theme. THEME_MANIFESTS is {theme.yaml, theme.yml}.

Source

Thrown at application/src/main/java/run/halo/app/theme/service/ThemeUtils.java:151

                                    copyRecursively(themeManifestPath.getParent(), themeTargetPath);
                                    sink.next(theme);
                                } catch (IOException e) {
                                    deleteRecursivelyAndSilently(themeTargetPath);
                                    sink.error(e);
                                }
                            });
                },
                tempDir -> FileUtils.deleteRecursivelyAndSilently(tempDir, null));
        if (scheduler != null) {
            return unzipThem.subscribeOn(scheduler);
        }
        return unzipThem;
    }

    static Unstructured loadThemeManifest(Path themeManifestPath) {
        var unstructureds = new YamlUnstructuredLoader(new FileSystemResource(themeManifestPath)).load();
        if (CollectionUtils.isEmpty(unstructureds)) {
            throw new ThemeInstallationException(
                    "Missing theme manifest", "problemDetail.theme.install.missingManifest", null);
        }
        return unstructureds.get(0);
    }

    @Nullable
    static Path resolveThemeManifest(Path tempDirectory) {
        for (String themeManifest : THEME_MANIFESTS) {
            Path path = tempDirectory.resolve(themeManifest);
            if (Files.exists(path)) {
                return path;
            }
        }
        return null;
    }

    static Optional<Path> locateThemeManifest(Path path) {
        if (!Files.isDirectory(path)) {

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Ensure the ZIP contains theme.yaml (or theme.yml) at the theme root; Halo searches recursively so put it inside the top-level theme folder.
  2. If the file exists, make sure it is valid, non-empty YAML with kind: Theme and metadata.name set.
  3. Re-zip so the manifest is included: zip -r theme.zip <theme-dir>/ where <theme-dir>/theme.yaml exists.
  4. Open theme.yaml locally and confirm it parses (e.g. yq or a YAML linter).

Example fix

# before: zipped the parent, theme.yaml missing from archive
#   zip -r out.zip .          # theme.yaml not at expected place
# after: zip the theme folder that holds theme.yaml
#   zip -r theme.zip my-theme/   # my-theme/theme.yaml exists
Defensive patterns

Strategy: validation

Validate before calling

// Verify a manifest exists and parses before installing:
Path manifest = resolveThemeManifest(themeRoot); // checks theme.yaml/theme.yml
if (manifest == null) {
    throw new IllegalStateException("Missing theme.yaml/theme.yml in archive");
}
if (loadThemeManifest(manifest) == null) { // throws if empty/unparseable
    throw new IllegalStateException("theme.yaml is empty or unparseable");
}

Try / catch

try {
    themeService.install(content).block();
} catch (ThemeInstallationException e) {
    if ("problemDetail.theme.install.missingManifest".equals(e.getCode())) {
        log.error("Theme archive has no valid theme.yaml/theme.yml");
    }
    throw e;
}

Prevention

When it happens

Trigger: Installing a ZIP that contains no theme.yaml/theme.yml at any depth; the manifest is present but empty; the manifest is malformed YAML that yields no Unstructured document; the file is named differently (e.g. theme.json or Theme.yaml with wrong case).

Common situations: Zipping the wrong folder (e.g. the parent instead of the theme root that has theme.yaml); case-sensitive filesystem mismatch; hand-authored theme missing the manifest; theme.yaml left empty by a broken generator.

Related errors


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