siyuan-note/siyuan · error
marketplace package manifest must be at the archive root or…
Error message
marketplace package manifest must be at the archive root or its only top-level directory
What it means
This error is thrown by localPackageRoot after ExtractLocalPackage finished extracting: no marketplace manifest (plugin.json/theme.json/icon.json/template.json/widget.json) was found at the archive root, and the extraction directory does not contain exactly one top-level directory either. The root-detection step requires the manifest to sit either at the archive root or inside a single top-level wrapper folder; anything else (multiple top-level entries, loose files alongside the folder, no directory at all) fails here.
Solutions
- Repackage so the zip root contains exactly one directory that directly holds the manifest: `cd my-plugin && zip -r ../plugin.zip .` (manifest at zip root) or `cd parent && zip -r ../plugin.zip my-plugin` (single top-level folder with the manifest directly inside)
- Verify the manifest filename is exactly one of plugin.json, theme.json, icon.json, template.json, widget.json and sits directly in the package root folder
- Remove extra top-level entries (README, dotfiles, screenshots) from the archive root so there is exactly one top-level directory
- Flatten nested wrappers: if the manifest is at a/b/plugin.json, restructure so it is at plugin.json or only my-plugin/plugin.json
Example fix
// before: manifest ends up at my-plugin/dist/plugin.json in the archive zip -r plugin.zip my-plugin // after: build to a staging folder whose root holds the manifest mkdir stage && cp -r build/* stage/ && (cd stage && zip -r ../plugin.zip .)
Defensive patterns
Strategy: validation
Validate before calling
// Before upload: check the zip has a manifest at root or in its only top-level dir
function hasValidManifestLayout(zipPath) {
const entries = listZipEntries(zipPath); // e.g. via unzip -Z1
const tops = new Set(entries.map(n => n.split("/")[0]).filter(Boolean));
if (tops.has("plugin.json") || tops.has("theme.json") || tops.has("icon.json") || tops.has("template.json") || tops.has("widget.json")) return true;
if (tops.size === 1) {
const dir = [...tops][0];
return entries.includes(`${dir}/plugin.json`) || entries.includes(`${dir}/theme.json`) || entries.includes(`${dir}/icon.json`) || entries.includes(`${dir}/template.json`) || entries.includes(`${dir}/widget.json`);
}
return false;
} Try / catch
try {
await installLocalPackage(zipPath);
} catch (err) {
if (String(err).includes("manifest must be at the archive root")) {
// restructure the zip: manifest at root or in its single top-level directory
}
} Prevention
- Package from inside the project folder (`zip -r ../pkg.zip .`) so the manifest is at the zip root
- Keep the manifest file name exactly one of plugin.json/theme.json/icon.json/template.json/widget.json
- Never leave extra files (README, screenshots) at the same level as the package folder in the zip
- Verify the layout locally with `unzip -l pkg.zip | head` before distributing
When it happens
Trigger: Calling ExtractLocalPackage(archivePath) with a zip whose root contains e.g. two folders (a nested project layout), or loose files (README, .git artifacts) plus the package folder, or only files but no manifest at the root (e.g. manifest named wrongly or in a second-level subfolder like my-plugin/plugin/plugin.json).
Common situations: Zipping the project directory instead of its contents, yielding one folder that itself contains another wrapper level; leaving extra files (README.md, screenshot.png) at the zip root next to the package folder; renaming the manifest file or placing it under a nested src/ directory; using a manifest filename not in the supported set.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- 199
- invalid package name
- marketplace package contains an unsupported file
- all asset mappings failed
- assetPath is not an image referenced by the document
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/b3a6f199646d18d8.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/bazaar/local.go:186
return errors.New("marketplace package contains a file that is too large")
}
if uint64(written) > maxLocalPackageExtractSize-*extractedTotal {
return errors.New("marketplace package is too large")
}
*extractedTotal += uint64(written)
return nil
}
func localPackageRoot(extractPath string) (string, error) {
if hasLocalPackageManifest(extractPath) {
return extractPath, nil
}
entries, err := os.ReadDir(extractPath)
if err != nil {
return "", err
}
if len(entries) != 1 || !entries[0].IsDir() {
return "", errors.New("marketplace package manifest must be at the archive root or its only top-level directory")
}
root := filepath.Join(extractPath, entries[0].Name())
if !hasLocalPackageManifest(root) {
return "", errors.New("marketplace package manifest not found")
}
return root, nil
}
func hasLocalPackageManifest(root string) bool {
for manifestName := range localPackageManifests {
if info, err := os.Stat(filepath.Join(root, manifestName)); err == nil && info.Mode().IsRegular() {
return true
}
}
return false
}
View on GitHub (pinned to 9f775e8a12)