siyuan-note/siyuan · error
invalid or duplicate layer ID
Error message
invalid or duplicate layer ID
What it means
Each manifest layer must have a non-nil object, an ID that passes isValidBootAppearanceID (pattern + <=64 chars), and an ID unique within the layers array. loadBootAppearance aborts otherwise, since layer IDs key the layer list used by the frontend.
Source
Thrown at kernel/model/boot_appearance.go:456
if err = validateOptionalBootAppearanceColor(color); err != nil {
return nil, err
}
}
ret.OfficialUI.TextColor = manifest.OfficialUI.TextColor
ret.OfficialUI.ProgressColor = manifest.OfficialUI.ProgressColor
ret.OfficialUI.TrackColor = manifest.OfficialUI.TrackColor
if manifest.Style != "" {
if _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, manifest.Style, "style"); err != nil {
return nil, fmt.Errorf("invalid style: %w", err)
}
ret.Style = bootAppearanceAssetURL(pkg.Name, appearanceID, manifest.Style)
}
layerIDs := map[string]bool{}
for _, layer := range manifest.Layers {
if layer == nil || !isValidBootAppearanceID(layer.ID) || layerIDs[layer.ID] {
err = errors.New("invalid or duplicate layer ID")
return nil, err
}
layerIDs[layer.ID] = true
if layer.Type != "image" && layer.Type != "video" {
err = fmt.Errorf("unsupported layer type [%s]", layer.Type)
return nil, err
}
if _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, layer.Src, layer.Type); err != nil {
return nil, fmt.Errorf("invalid layer source: %w", err)
}
if layer.Type == "video" {
if layer.Poster == "" {
err = errors.New("video poster is required")
return nil, err
}
if _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, layer.Poster, "image"); err != nil {
return nil, fmt.Errorf("invalid video poster: %w", err)
}View on GitHub (pinned to 8641553a1f)
Solutions
- Give every layer in manifest.json a unique, pattern-valid ID (slug form, <=64 chars), e.g. 'background', 'logo'
- Remove null layer entries from the layers array
- Rename duplicated IDs so each is unique within the array
- Update the manifest's schemaVersion/id checks only after fixing IDs so the load can proceed
Example fix
// before
"layers": [
{"id": "bg", "type": "image", "src": "bg.png"},
{"id": "bg", "type": "image", "src": "bg2.png"}
]
// after
"layers": [
{"id": "bg", "type": "image", "src": "bg.png"},
{"id": "bg-alt", "type": "image", "src": "bg2.png"}
] Defensive patterns
Strategy: validation
Validate before calling
const seen = new Set();
manifest.layers?.forEach((l, i) => {
if (!l || typeof l.id !== "string" || l.id.length > 64 || !/^[a-z0-9][a-z0-9-]*$/.test(l.id)) throw new Error(`layer ${i}: invalid id`);
if (seen.has(l.id)) throw new Error(`duplicate layer id ${l.id}`);
seen.add(l.id);
}); Type guard
const hasValidLayerId = (l: unknown): l is { id: string } =>
!!l && typeof (l as any).id === "string" && (l as any).id.length <= 64 && /^[a-z0-9][a-z0-9-]*$/.test((l as any).id); Prevention
- Make layer IDs unique per appearance; suffix copies ('logo', 'logo-2')
- Run a manifest lint step in the plugin build pipeline
- Never emit null layer entries from generators
- Keep IDs slug-formatted and <= 64 chars
When it happens
Trigger: GetBootAppearances/getBootAppearanceByID iterating manifest.Layers when a layer entry is null, has an empty/invalid ID (spaces, slashes, non-ASCII, >64 chars), or duplicates an ID already seen in the same layers array.
Common situations: Copy-pasting a layer JSON object and forgetting to change its id; a template left 'id': '' or 'id': 'layer 1' with a space; a generator emitting null entries for optional layers; two layers both named 'logo'.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- too many layers: %d
- unsupported layer type [%s]
- video poster is required
- image layer cannot declare a poster
- Duplicated filename
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/e43fd62720525bb0.
Report an issue: GitHub.