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

localPackageRoot could not find a recognized manifest at the extraction root, and the root does not contain exactly one top-level directory entry. Thrown at kernel/bazaar/local.go:185-186 when len(entries) != 1 or the single entry is not a directory. Packages must lay out as either manifest-at-root or a single wrapping directory.

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 251596fc0d)

Solutions

  1. Restructure the archive so the manifest is either at the zip root or inside exactly one top-level directory
  2. Remove sibling top-level entries (README at root is fine only if there is a single wrapping dir)
  3. Re-zip from the correct level so the layout is root/manifest.json or root/<pkgdir>/manifest.json

Example fix

# before: two top-level entries, manifest inside one of them
myplugin/
README.md
# -> fails: len(entries) == 2

# after: single top-level dir holding manifest + README
myplugin/plugin.json
myplugin/README.md
Defensive patterns

Strategy: validation

Validate before calling

// After test-extraction, confirm layout is manifest-at-root or single wrapping dir
func assertLayout(extractPath string) error {
    if hasManifest(extractPath) { return nil }
    es, err := os.ReadDir(extractPath)
    if err != nil { return err }
    if len(es) != 1 || !es[0].IsDir() {
        return errors.New("need manifest at root or exactly one top-level dir")
    }
    return nil
}

Try / catch

if _, err := localPackageRoot(tempPath); err != nil {
    cleanup()
    return fmt.Errorf("package layout rejected: %w", err)
}

Prevention

When it happens

Trigger: After extraction, the extract root has zero, two, or more top-level entries, or a single non-directory entry, and none of the recognized manifests (plugin.json/theme.json/icon.json/template.json/widget.json) sits directly at the root.

Common situations: The author zipped the contents flat (manifest buried in a subdir) alongside sibling folders; the archive wraps multiple top-level projects; the manifest sits one level too deep or too shallow.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/b3a6f199646d18d8. Report an issue: GitHub.