DocumentationDocumentation
API reference/Python API

State, queries, and refresh

Find state declarations, fields, query shapes, and invalidation notifications.

state: declare queryable data

State answers what is true now. Pages can read once or subscribe. After binding, register through plugin.bridge().state with a callback that reads current data. See Keep the interface in sync with data.

WebBridgeState
Public definitions
class WebBridgeState (Protocol):
  __call__(self, key: 'str', handler: 'WebStateHandler | None' = None, *, shape: 'WebStateShape' = 'item', fields: 'Mapping[str, WebStateFieldSchema] | None' = None, default_fields: 'tuple[str, ...]' = (), filterable: 'tuple[str, ...]' = (), sortable: 'tuple[str, ...]' = (), default_limit: 'int | None' = None, max_limit: 'int | None' = None, permissions: 'tuple[str, ...]' = ('nexus_web.state.plugin.read',), sensitivity: 'WebStateSensitivity' = 'safe_metadata') -> 'WebStateHandler | Callable[[WebStateHandler], WebStateHandler]'
  invalidate(self, key: 'str') -> 'None'
Parameter Meaning and limits
key, handler A plugin.* state name and synchronous query function. Omitting handler returns a decorator; supplying it registers and returns that function. A client cannot register the same key twice.
shape Defaults to item for one item; list declares a list. Determines whether pages read snapshot.item or snapshot.items.
fields Although the signature defaults to None, a nonempty field schema is required. Do not omit it. Give each field an explicit type.
default_fields Defaults to an empty tuple, using all declared fields when a query omits fields. Supplied defaults may reference only declared fields.
filterable, sortable Defaults to empty tuples, rejecting corresponding query fields. Declarations allow inputs; handlers still perform filtering and sorting.
default_limit, max_limit Both default to None. Supplied values must be positive integers, with default_limit no greater than max_limit. Requests exceeding max_limit fail rather than silently clamp.
permissions, sensitivity Default permission is nexus_web.state.plugin.read and sensitivity is safe_metadata. Declare user_content for user data; do not mislabel data to bypass permissions.

Field schemas and related types

WebStateFieldType
Public definitions
WebStateFieldType = typing.Literal['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']

fields maps names to schema descriptions, not data. Supported types are string, number, integer, boolean, object, array, and null. nullable permits null; enum is a tuple of allowed scalar values. Fields may declare permissions and sensitivity separately. Types do not replace business validation in callbacks.

Field schema example
python
fields = {
    "id": {"type": "string"},
    "name": {"type": "string"},
    "selected": {"type": "boolean"},
    "note": {"type": "string", "nullable": True, "sensitivity": "user_content"},
}
View field, shape, and sensitivity definitions
WebStateFieldSchema
Public definitions
class WebStateFieldSchema:
  __init__(<signature-unavailable: ValueError>)
  enum: ForwardRef('NotRequired[tuple[WebJSONScalar, ...]]', module='nexus_web.public')
  nullable: ForwardRef('NotRequired[bool]', module='nexus_web.public')
  permissions: ForwardRef('NotRequired[tuple[str, ...]]', module='nexus_web.public')
  sensitivity: ForwardRef('NotRequired[WebStateSensitivity]', module='nexus_web.public')
  type: ForwardRef('NotRequired[WebStateFieldType]', module='nexus_web.public')
WebStateShape
Public definitions
WebStateShape = typing.Literal['item', 'list']
WebStateSensitivity
Public definitions
WebStateSensitivity = typing.Literal['safe_metadata', 'user_content']

WebStateFieldSchema describes a dictionary, not a constructor you must call. ForwardRef/NotRequired indicate deferred types and optional fields. Use ordinary dictionaries as above.

Callback query and context

WebStateHandler
Public definitions
WebStateHandler = collections.abc.Callable[[nexus_web.public.WebStateQuery, nexus_web.public.WebStateContext], str | int | float | bool | None | list['WebJSONValue'] | dict[str, 'WebJSONValue']]
WebStateQuery
Public definitions
class WebStateQuery (dataclass frozen slots):
  __init__(fields: 'tuple[str, ...]', filter: 'Mapping[str, WebJSONValue]', sort: 'tuple[WebJSONValue, ...]', limit: 'int | None', raw: 'Mapping[str, WebJSONValue]') -> None
  fields: tuple[str, ...]
  filter: Mapping[str, WebJSONValue]
  sort: tuple[WebJSONValue, ...]
  limit: int | None
  raw: Mapping[str, WebJSONValue]
WebStateContext
Public definitions
class WebStateContext (dataclass frozen slots):
  __init__(origin: 'str', window_key: 'str | None' = None) -> None
  origin: str
  window_key: str | None = None

The callback signature is (query, context). query.fields is a normalized tuple, filter maps allowed filter fields to values, sort is an ordering tuple, limit is the result cap, and raw is the normalized query, not arbitrary extra request data. context provides origin and optional window_key; it is not bpy.context.

Custom state queries publicly support only fields, filter, sort, and limit, not offset, page, or cursor. Sort entries are declared field names or objects with field and direction (asc/desc). Unknown fields, disallowed filters/sorts, and excessive limits fail.

The callback performs actual filtering and sorting. The framework validates queries, projects fields, and limits lists; it does not apply filter or sort for the handler. Process ordering before returning the list. Premature truncation can produce incorrect results.

Return shapes: item and list

item may return an object or an object with an item field. list may return a list of objects or an object with items. Use [] for an empty list and None for no item. Return JSON-compatible data, not Blender objects or generators.

The framework builds snapshots, projects requested fields, and keeps the first limit entries, potentially marking truncated. String id fields form member IDs for state-source tracking. Declare and return stable IDs when actions operate on listed objects.

Standalone read-only example; call setup_state once after binding
python
from .nexus_web import WebPlugin, WebStateQuery, WebStateContext, WebJSONValue

_ROWS = [
    {"id": "draft", "name": "草稿", "selected": False},
    {"id": "final", "name": "定稿", "selected": True},
]

def list_presets(query: WebStateQuery, context: WebStateContext) -> WebJSONValue:
    rows = [dict(row) for row in _ROWS]
    if "selected" in query.filter:
        selected = query.filter["selected"]
        if not isinstance(selected, bool):
            raise ValueError("selected must be a boolean")
        rows = [row for row in rows if row["selected"] == selected]
    for order in reversed(query.sort):
        if isinstance(order, str):
            field, descending = order, False
        elif isinstance(order, dict):
            field = order["field"]
            descending = order["direction"] == "desc"
        else:
            raise ValueError("Invalid sort entry")
        if field != "name":
            raise ValueError("Only name sorting is supported")
        rows.sort(key=lambda row: row["name"], reverse=descending)
    return rows

def setup_state(plugin: WebPlugin) -> None:
    plugin.bridge().state(
        "plugin.my_addon.presets", list_presets, shape="list",
        fields={"id": {"type": "string"}, "name": {"type": "string"},
                "selected": {"type": "boolean"}},
        filterable=("selected",), sortable=("name",),
        default_limit=20, max_limit=100,
    )

This example reads two add-on-owned demo records without changing Blender. Keep names, fields, and page calls consistent. Do not wrap callback results in snapshot; pages read snapshot from the framework result.

External page script; use within an existing asynchronous reader
javascript
const webui = window.acquireNexusWebUI();
const { snapshot } = await webui.state.get("plugin.my_addon.presets", {
  fields: ["id", "name", "selected"],
  filter: { selected: true },
  sort: [{ field: "name", direction: "asc" }],
  limit: 20,
});
// Render snapshot.items in your existing view.

invalidate: request a reread after changes

bridge.state.invalidate(key) Returns None. Change application data first, then invalidate its key. Actions can declare invalidates for automatic refresh after completion. Blender changes do not automatically invalidate custom add-on state; connect to your existing change notifications.

invalidate does not return a new snapshot or guarantee one notification per call. Unchanged projected content may not be pushed, and hidden pages may converge later. The framework owns snapshotRevision; do not increment it or substitute local timestamps.

Keep queries read-only. Do not create or modify scenes on reread. Mutually triggering callbacks and invalidations cause pointless refreshes; identify the real data-change entry point.

Errors and subscription cleanup

Missing fields and duplicate keys are registration errors. Invalid queries, permissions, and unknown state cause query failures. Callback exceptions or unsupported results fail page reads. Do not convert every error to an empty list, hiding the difference between no data and a failed read.

Call state.unsubscribe when the page no longer needs state. If leaving before asynchronous subscription finishes, release it after creation. See Subscription lifecycle. Public WebBridgeState has no per-key remove method; do not use internal removal APIs. Registrations are cleared when disposing the bridge or client.

Definitions come from the public API baseline; projection, limits, and refresh semantics are checked against registration, query processing, and communication documentation.

Back to API reference · All topics