siyuan-note/siyuan · error

Field [level] should be of type [Number]

Error message

Field [level] should be of type [Number]

What it means

The /api/block/getHeadingLevelTransaction request requires a numeric level field. The kernel returns this error when level is missing, an explicit null, or not a JSON number (string, boolean, object). The level selects the target heading level for the heading-conversion transaction.

Solutions

  1. Send level as a JSON number: "level": 2 (1-6 for heading levels)
  2. Convert string input with Number(level) before building the payload
  3. Always include level in the payload alongside ids or id

Example fix

// before
{ "ids": ["20240101120000-abcdefg"], "level": "3" }
// after
{ "ids": ["20240101120000-abcdefg"], "level": 3 }
Defensive patterns

Strategy: validation

Validate before calling

const levelNum = Number(level);
if (!Number.isFinite(levelNum)) throw new TypeError('level must be a number');
payload.level = levelNum;

Type guard

const isHeadingLevel = (v) => typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 6;

Try / catch

try { await fetchPost('/api/block/getHeadingLevelTransaction', payload); } catch (e) { if (/Field \[level\] should be of type \[Number\]/.test(e?.message)) { payload.level = Number(payload.level); /* retry */ } }

Prevention

When it happens

Trigger: POST /api/block/getHeadingLevelTransaction with {"ids":[...]} but no level; "level":"2" (string instead of number); "level":null.

Common situations: Dropdown UI binding the level as a string option value; omitting level because older examples only showed ids; JSON forms serializing numeric inputs as strings.

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/eeb6c41084a3d2df. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/block_remaining.go:159

		return request, errors.New("Field [notebook] is required")
	}
	if request.Scope == "notebook" {
		request.Notebook = strings.TrimSpace(request.Notebook)
		if request.Notebook == "" {
			return request, errors.New("Field [notebook] must not be empty")
		}
	}
	_ = json.Unmarshal(fields["id"], &request.ID)
	return
}

func decodeHeadingLevel(reader io.Reader) (request HeadingLevelRequest, err error) {
	fields, err := blockRequestFields(reader, "/api/block/getHeadingLevelTransaction")
	if err != nil {
		return request, err
	}
	if len(fields["level"]) == 0 || bytes.Equal(fields["level"], []byte("null")) || json.Unmarshal(fields["level"], &request.Level) != nil {
		return request, errors.New("Field [level] should be of type [Number]")
	}
	var entries []json.RawMessage
	if raw := fields["ids"]; len(raw) > 0 && !bytes.Equal(raw, []byte("null")) && json.Unmarshal(raw, &entries) == nil {
		request.IDs = []string{}
		seen := map[string]bool{}
		for _, entry := range entries {
			var id string
			if bytes.Equal(entry, []byte("null")) || json.Unmarshal(entry, &id) != nil {
				return request, errors.New("Field [ids] should contain strings")
			}
			if !seen[id] {
				request.IDs = append(request.IDs, id)
				seen[id] = true
			}
		}
	} else if len(fields["id"]) == 0 || bytes.Equal(fields["id"], []byte("null")) || json.Unmarshal(fields["id"], &request.ID) != nil {
		return request, errors.New("Field [id] should be of type [String]")
	}

View on GitHub (pinned to 9f775e8a12)