siyuan-note/siyuan · error

The attribute name can only contain lowercase English letter

Error message

The attribute name can only contain lowercase English letters, digits, and hyphens, and must start with a lowercase English letter

What it means

In setNodeAttrs0, for each requested attribute the name is lower-cased and passed to isValidAttrName; if it fails the function returns errors.New(Conf.Language(25) + ' [' + node.ID + ']') — 'The attribute name can only contain lowercase English letters, digits, and hyphens, and must start with a lowercase English letter'. isValidAttrName (blockial.go:418) requires: non-empty, first char a-z, subsequent chars a-z/0-9/-, with the special 'custom-' prefix case also requiring a lowercase letter after the hyphen.

Source

Thrown at kernel/model/blockial.go:360

			}
		}
	}
	oldAttrs = parse.IAL2Map(node.KramdownIAL)
	newAttrsUnEsc := parse.IAL2MapUnEsc(node.KramdownIAL)
	for name := range nameValues {
		if "fold" == strings.ToLower(name) {
			delete(newAttrsUnEsc, "heading-fold")
			break
		}
	}

	for name, value := range nameValues {
		value = util.RemoveInvalidRetainCtrl(value)
		value = strings.TrimSpace(value)
		lowerName := strings.ToLower(name)
		// 转换为小写再验证属性名
		if !isValidAttrName(lowerName) {
			err = errors.New(Conf.Language(25) + " [" + node.ID + "]")
			return
		}
		if lowerName == "data-task" {
			err = errors.New(`setting or removing [data-task] attribute is not allowed via this interface. Please use "/api/block/updateTaskListItemMarker" or "/api/block/batchUpdateTaskListItemMarker" to update the task list item marker`)
			return
		}

		// 处理文档标签 https://github.com/siyuan-note/siyuan/issues/13311
		if lowerName == "tags" {
			var tags []string
			tmp := strings.SplitSeq(value, ",")
			for t := range tmp {
				t = strings.TrimSpace(t)
				if "" != t {
					tags = append(tags, t)
				}
			}
			tags = gulu.Str.RemoveDuplicatedElem(tags)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Lower-case and sanitize the key client-side before sending: match ^[a-z][a-z0-9-]*(custom-[a-z][a-z0-9-]*)?$ semantics.
  2. Replace disallowed characters (spaces/underscores) with hyphens and strip the rest.
  3. For user-defined keys, prefer the 'custom-' prefix convention.

Example fix

// before
attrs := map[string]string{"Created_At": "2024-01-01"}

// after: normalize the name to the allowed alphabet
name := strings.ToLower("Created_At")
name = strings.ReplaceAll(name, "_", "-")
attrs := map[string]string{name: "2024-01-01"}
Defensive patterns

Strategy: validation

Validate before calling

// Replicate isValidAttrName before sending.
func validAttrName(name string) bool {
    name = strings.ToLower(name)
    if name == "" || name[0] < 'a' || name[0] > 'z' { return false }
    for i := 0; i < len(name); i++ {
        c := name[i]
        if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-') { return false }
    }
    return true
}

Try / catch

// HTTP caller: normalize then retry on this specific error.
if (r.code === -1 && /attribute name/i.test(r.msg)) {
    const norm = key.toLowerCase().replace(/[^a-z0-9-]/g, '-')
    attrs[norm] = attrs[key]; delete attrs[key]
    r = await fetchSyncPost('/api/attr/setBlockAttrs', {id, attrs})
}

Prevention

When it happens

Trigger: POST /api/attr/setBlockAttrs (or batchSetBlockAttrs) with an attr key like 'Foo', '1abc', 'name space', 'custom-A', 'custom-' (prefix only), or any key containing underscore/uppercase/non-ASCII. Also via MCP attr tool and the CLI attr command.

Common situations: Plugins externalizing an internal field name verbatim (CamelCase or with underscores); user-facing 'key' input that wasn't normalized; migration tooling that copied arbitrary metadata keys.

Related errors


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