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
- Pass the value as a string, e.g. {"numberFormat": "2"} not {"numberFormat": 2}
- Coerce numeric/enum values with String(value) or the equivalent before sending
- 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
- Always stringify numeric format codes before sending
- Avoid null to mean 'clear'; send an explicit string or use the dedicated clear operation
- Type-check config payloads in your client wrapper
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
- block [ ] type is locked: expected , got
- config must contain exactly one setting; use separate calls…
- date display format is only available for date fields
- Field [keyIDs] is required
- Field [level] should be of type [Number]
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)