siyuan-note/siyuan · error

view state storage is too large

Error message

view state storage is too large

What it means

marshalViewStateStorage enforces a size budget when serializing view-state storage. It evicts oldest views and trims data entries; if even the last remaining view's single data entry cannot be dropped (its Order is empty, i.e. nothing left to shed) and the payload still exceeds the limit, it gives up with this error rather than persisting an over-large blob.

Source

Thrown at kernel/model/view_state.go:394

	}
	return
}

func marshalViewStateStorage(storage *viewStateStorage) (data []byte, err error) {
	for {
		data, err = gulu.JSON.MarshalIndentJSON(storage, "", "  ")
		if err != nil {
			return nil, err
		}
		if len(data) <= maxViewStateStorageSize {
			return
		}
		if len(storage.Views) <= 1 {
			key := oldestViewStateKey(storage.Views)
			state := storage.Views[key]
			normalizeViewStateFieldOrder(state)
			if 0 == len(state.Order) {
				return nil, errors.New("view state storage is too large")
			}
			delete(state.Data, state.Order[0])
			state.Order = state.Order[1:]
			if 0 == len(state.Data) {
				delete(storage.Views, key)
			}
			continue
		}
		delete(storage.Views, oldestViewStateKey(storage.Views))
	}
}

func oldestViewStateKey(views map[string]*ViewState) (ret string) {
	for key, state := range views {
		if "" == ret || state.Updated < views[ret].Updated ||
			(state.Updated == views[ret].Updated && key < ret) {
			ret = key
		}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Reduce the size of individual view-state data values: store large payloads externally and keep only a reference/ID in the view state.
  2. Remove stale keys from the view's Data/Order so the trimmer has entries to evict.
  3. Split large state across multiple smaller data keys instead of one oversized entry.

Example fix

// before
await setViewState(viewId, "snapshot", hugeDocumentJSON);

// after
const id = saveExternalBlob(hugeDocumentJSON);
await setViewState(viewId, "snapshotRef", { blobId: id });
Defensive patterns

Strategy: validation

Validate before calling

function fitsViewStateBudget(obj, limit = 512 * 1024) {
  return JSON.stringify(obj).length <= limit; // guard before writing
}
if (!fitsViewStateBudget({ [key]: value })) {
  throw new Error("view state payload too large; store externally");
}

Try / catch

try {
  await setViewState(viewId, key, data);
} catch (e) {
  if (String(e).includes("view state storage is too large")) {
    await setViewState(viewId, key, await storeExternally(data)); // fallback to reference
  } else { throw e; }
}

Prevention

When it happens

Trigger: setViewStateStorage / getViewStateStorage marshals storage whose JSON exceeds the size limit even after pruning: a single view holds one huge data value and no further entries can be removed.

Common situations: A plugin stored a very large object (e.g. a serialized document, big JSON blob, base64 image) as one view-state data entry, so trimming order entries cannot shrink the payload below the cap.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/40fd4f92a4e7a7d9. Report an issue: GitHub.