siyuan-note/siyuan · error

must be a string

Error message

%s must be a string

What it means

The string-valued settings of UpdateAttributeViewKeyConfig ("name", "type", "icon", "desc", "numberFormat", "dateFormat", "template", "renderTemplate") require the config value to be a JSON string. A non-string value (number, bool, object, null) is rejected with this error naming the offending setting.

Solutions

  1. Pass the value as a string, e.g. {"numberFormat": "2"} not {"numberFormat": 2}
  2. Coerce numeric/enum values with String(value) or the equivalent before sending
  3. Use the documented format-name strings for numberFormat/dateFormat rather than numeric codes

Example fix

// before
{"numberFormat": 2}
// after
{"numberFormat": "2"}
Defensive patterns

Strategy: type-guard

Validate before calling

const STRING_SETTINGS = ['name','type','icon','desc','numberFormat','dateFormat','template','renderTemplate'];
function assertStringValues(config) { for (const [k, v] of Object.entries(config)) if (STRING_SETTINGS.includes(k) && typeof v !== 'string') throw new Error(`${k} must be a string`); }

Type guard

const isString = (v) => typeof v === 'string';

Try / catch

try { await updateKeyConfig(avID, keyID, cfg); } catch (e) { if (/.+ must be a string$/.test(e.message)) { const [k] = Object.keys(cfg); return updateKeyConfig(avID, keyID, { [k]: String(cfg[k]) }); } throw e; }

Prevention

When it happens

Trigger: Sending {"dateFormat": 3} or {"icon": null} or {"numberFormat": {"fmt": "2"}} to the key-config update endpoint.

Common situations: Numeric format codes passed instead of their string names; null used to mean 'clear'; config values parsed from YAML/JSON without string coercion.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/a371038e028d45a5. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_key_config.go:46

func UpdateAttributeViewKeyConfig(avID, keyID string, config map[string]any) (err error) {
	if 1 != len(config) {
		return errors.New("config must contain exactly one setting; use separate calls for multiple settings")
	}
	attrView, err := av.ParseAttributeView(avID)
	if nil != err {
		return err
	}
	key, err := attrView.GetKey(keyID)
	if nil != err {
		return err
	}
	op := &Operation{AvID: avID, ID: keyID, Typ: string(key.Type), Name: key.Name}
	for setting, value := range config {
		switch setting {
		case "name", "type", "icon", "desc", "numberFormat", "dateFormat", "template", "renderTemplate":
			text, ok := value.(string)
			if !ok {
				return fmt.Errorf("%s must be a string", setting)
			}
			op.Data, op.Format = text, text
			switch setting {
			case "name":
				if "" == strings.TrimSpace(text) {
					return errors.New("field name must not be empty")
				}
				op.Name = text
				return updateAttributeViewColumn(op)
			case "type":
				if _, err = newAttributeViewKey(keyID, key.Name, text, key.Icon, key.DateFormat); nil != err {
					return err
				}
				op.Typ = text
				return updateAttributeViewColumn(op)
			case "icon":
				return setAttributeViewColIcon(op)
			case "desc":

View on GitHub (pinned to 9f775e8a12)