siyuan-note/siyuan · error

Field [id] must not be empty

Error message

Field [id] must not be empty

What it means

The removeDoc contract decoder validates the 'id' field: it must be a non-empty string after trimming whitespace. An empty id cannot identify any document, so the request is rejected with 'Field [id] must not be empty' before reaching the filetree logic.

Solutions

  1. Ensure a valid document ID (e.g. 20240101120000-abcdefg) is loaded before calling the API
  2. Add a client-side check that the ID is non-empty before invoking the endpoint
  3. Re-query the document to refresh its ID if it may have changed
  4. Skip the call entirely when no document is selected

Example fix

// before
fetchPost("/api/filetree/removeDoc", { id: docId || "" });
// after
if (!docId?.trim()) return;
fetchPost("/api/filetree/removeDoc", { id: docId.trim() });
Defensive patterns

Strategy: validation

Validate before calling

const trimmed = String(id ?? "").trim();
if (!trimmed) throw new Error("cannot call removeDoc without a document id");

Type guard

const hasId = (b) => typeof b.id === "string" && b.id.trim() !== "";

Try / catch

try {
  await fetchPost("/api/filetree/removeDoc", { id });
} catch (e) {
  if (String(e.message).includes("[id] must not be empty")) {
    // re-resolve the current document id before retrying
  }
}

Prevention

When it happens

Trigger: POST /api/filetree/removeDoc with {"id":""} or {"id":" "}.

Common situations: A variable holding the block/document ID that was never assigned; UI code deleting before the doc finished loading (ID still empty); string trimming removing a whitespace-only ID that looked valid; stale frontend state after a doc was re-created.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at kernel/apicontract/filetree_input.go:169

	fileTreeJSONDecoder(&CreateDocWithMd)
	fileTreeJSONDecoder(&GetDocCreateSavePath)
	fileTreeJSONDecoder(&GetRefCreateSavePath)
	fileTreeJSONDecoder(&GetShorthandSavePath)
	fileTreeJSONDecoder(&ChangeSort)
	fileTreeJSONDecoder(&SearchDocs)
	fileTreeJSONDecoder(&GetPublishAccess)
	fileTreeJSONDecoder(&AuthFilePublishAccess)
	trimIDDecoder := func(path string) func(io.Reader) (FileTreeTrimIDRequest, error) {
		return func(reader io.Reader) (r FileTreeTrimIDRequest, err error) {
			fields, err := fileTreeRequestFields(reader, path)
			if err != nil {
				return r, err
			}
			r.ID, err = legacyField[string](fields, "id", "String", true)
			if err == nil {
				r.ID = strings.TrimSpace(r.ID)
				if r.ID == "" {
					err = fmt.Errorf("Field [id] must not be empty")
				}
			}
			return r, err
		}
	}
	RemoveDocByID.decodeRequest = trimIDDecoder(RemoveDocByID.definition.Path)
	GetPathByID.decodeRequest = trimIDDecoder(GetPathByID.definition.Path)
	GetIDsByHPath.decodeRequest = func(reader io.Reader) (r FileTreeOptionalPathRequest, err error) {
		fields, err := fileTreeRequestFields(reader, GetIDsByHPath.definition.Path)
		if err != nil {
			return r, err
		}
		for _, key := range []string{"path", "notebook"} {
			if len(fields[key]) == 0 || bytes.Equal(fields[key], []byte("null")) {
				return r, nil
			}
		}
		return fileTreeBind[FileTreeOptionalPathRequest](fields)

View on GitHub (pinned to 9f775e8a12)