{"version":"v1.0","client_state":{"DocsNavigation.default":{"fields":{"query":""},"persist":"memory","send_to_server":true},"ColorScheme.default":{"fields":{"mode":"system"},"persist":"local","send_to_server":true}},"endpoints":{"action":"/_bretzel/action","sse":"/_bretzel/sse","refetch":"/_bretzel/refetch"},"csrf":"399035c337fd5aae4c763baa062ee271","page_id":"3860447749464a90b8e0cc84d44a0b9d","address":""}

Server actions

The server side of actions: when an interaction changes the truth, `on_<event>=` receives a Python function — a handler. One round trip, and the truth mutates server side.

Wiring a handler

Every interactive component exposes events (`click`, `change`, `input`, `focus`, `blur`, `submit`, …). One wires a function with `on_<event>=`.
def save() -> None:
    ...

ui.button("Save", on_click=save)

What can be passed to on_<event>=

The handler is found again by its import path (`module::function`), with no per-page table stored. So it must be addressable.
FormAccepted?
Module-level function (on_click=save)Yes
@staticmethod / @classmethodYes
functools.partial(handler, arg)Yes — to pass an argument
Lambda (on_click=lambda: …)No — error at render
A closure (a function defined inside a function)No — error at render
Instance methodNo — not addressable
A lambda or a closure has no stable import path, hence the refusal at render time.
This is the server route (a function). `on_<event>=` also accepts a string, evaluated client side with no handler — cf.Client actions.

Passing an argument — functools.partial

To give the handler an argument (typically a row's id), one uses `partial`. The arguments travel with the request.
from functools import partial

def delete_item(item_id: str) -> None:
    ...

# one row per item, each with its own id:
ui.icon_button("trash-2",
               on_click=partial(delete_item, item_id))
The arguments must be JSON-serialisable: str, int, float, bool, None, list, dict.

Reading the form's data

Three ways, depending on the need:
from bretzel.state import get

# 1. a parameter named like a field → forwarded
def submit(title: str) -> None:
    ...

# 2. a State-typed parameter → hydrated from the form
def save(form: Draft) -> None:
    # form.title, form.price … already filled + validated
    ...

# 3. get() → one raw field, occasionally
def other() -> None:
    note = get("note")
No `name=` to write by hand: `ui.input(value=draft.title)` derives the `title` field automatically (cf. the Components chapter).

Avoiding double submits — @idempotent

On a sensitive action (payment, creation), `@idempotent` makes a double send of the same render run only once; the second receives a 204.
from bretzel import idempotent

@idempotent
def charge_card() -> None:
    ...

What next

What re-renders after the handler:Server reactivity →
Acting with no round trip:Client actions →