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

  1. Give every layer in manifest.json a unique, pattern-valid ID (slug form, <=64 chars), e.g. 'background', 'logo'
  2. Remove null layer entries from the layers array
  3. Rename duplicated IDs so each is unique within the array
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/e43fd62720525bb0. Report an issue: GitHub.