on_click, on_change, on_submit) that accept an action, a list of actions, or None. When the event fires, the action runs.
For the most common case, syncing an interactive component’s value to state, the name prop handles it automatically. Input(name="query") writes to the query key on every keystroke without any explicit action. You reach for actions when you need side effects beyond syncing: showing a toast, calling your server, updating multiple keys at once, or running logic conditionally.
Two families
Prefab draws a clear line between two kinds of actions. Client actions run entirely in the browser. They execute instantly, require no network, and always succeed.SetState, ToggleState, AppendState, ShowToast, OpenLink, SetInterval: these are your tools for managing UI state and giving immediate feedback. From the user’s perspective, they’re instantaneous.
Server actions cross a network boundary. CallTool invokes an MCP tool on your server. Fetch makes an HTTP request. They’re asynchronous: they take time and they can fail. The UI needs to account for both the happy path and errors.
Most real interactions use both. A “Save” button typically sets a loading flag (client, instant), calls the server (server, async), then shows success or failure feedback (client, instant based on the outcome). The split is a design constraint that keeps the UI responsive and makes the code predictable.
| Client actions | Purpose |
|---|---|
SetState | Set a state key to a value |
ToggleState | Flip a boolean state key |
AppendState | Add an item to a state array |
PopState | Remove an item from a state array by index |
ShowToast | Display a brief notification |
OpenLink | Open a URL |
SetInterval | Schedule an action to repeat on a timer |
CallHandler | Invoke a developer-registered JavaScript handler |
| Server actions | Purpose |
|---|---|
CallTool | Call an MCP tool; result available as $result in on_success |
Fetch | Make an HTTP request; result available as $result in on_success |
SendMessage | Send a message to the conversation (MCP hosts only) |
UpdateContext | Push structured context to the model (MCP hosts only) |
The $event variable
When an interaction fires, the component emits a value: the slider’s current position, the input’s current text, the checkbox’s checked state. That value is available inside action arguments as$event.
Most of the time you don’t need $event directly, because the name prop already syncs the component’s value to state automatically. Where $event becomes useful is in actions that need to reference the emitted value as part of a larger operation, like writing it to a different key, sending it to the server, or using it in a computation:
Chaining actions
A single interaction often needs to do more than one thing. An “Add” button might append an item to a list and then clear the input field. A “Save” button might set a loading flag and then call the server. To run multiple actions from one event, pass a list. The actions execute in order, and if any action fails, execution stops at that point so a failed server call won’t silently proceed to the cleanup steps. Here’s a crew list where the “Add” button chains two actions:AppendState pushes the input value onto the array, then SetState clears the input field. Both execute before the next render, so the user sees the item appear and the field reset simultaneously.
The list syntax [action1, action2, ...] works with any event handler: on_click, on_change, on_submit.
Callbacks: on_success and on_error
Action lists handle the setup: things to do before or alongside an interaction. Callbacks handle the outcomes: things to do after an async action completes. Every action supportson_success and on_error. They fire after the action resolves, with the outcome determining which branch runs. Both accept a single action or a list.
$result is available inside on_success callbacks; it holds the return value of the action that just completed. $error is available inside on_error callbacks; it holds the error message from the failed action. These are a matched pair: each exists only within its respective callback scope.
Callbacks can themselves have callbacks, making it possible to chain dependent server calls: the result of the first determines what to fetch next.
Custom handlers
CallHandler invokes a developer-registered JavaScript function client-side. Where CallTool crosses the network to your server, CallHandler runs instantly in the browser — useful for state transformations that are too complex for expressions but don’t need server involvement.
Register handlers via js_actions on PrefabApp, then reference them by name:
{state, event, arguments} and returns state updates to merge. See Custom Handlers for the full API, including custom pipes.
Common patterns
Loading state
The combination of lists and callbacks is what makes loading state clean. Set a flag before the server call in the list; clear it in both callback branches so it always resolves, whether the call succeeds or fails:Populating results
For search and filter patterns, useRESULT in an on_success callback to write the tool’s response directly into state, then have a ForEach or Slot display it:
RESULT holds the response data. SetState writes it to results, and the ForEach re-renders with whatever came back.
Optimistic updates
For interactions where you’re confident the server will succeed, update state immediately and let the server confirm in the background. Revert on failure:on_error branch flips it back and shows a toast.