Payloads
Payloads define what data Heimdall sends when an interaction runs. A trigger answers when. A payload answers what.
In Heimdall, payload gathering usually stays close to the DOM boundary that already owns the current UI state, but the runtime also supports direct objects, references, self-sourced data, and explicit selectors.
1. What a Payload Means in Heimdall
A payload is the request data gathered when a Heimdall interaction fires. It does not define the trigger, the target, or the swap. It only defines what request data is sent to the action.
Trigger = when the interaction begins
Payload = what data is sent
Action = what server code runs
Swap = how returned HTML is applied2. The Core Pattern
Heimdall payload binding is usually boundary-based. Instead of inventing a separate client-side state store, the runtime gathers data from a nearby form or state boundary already present in the DOM.
button.Heimdall()
.Click("Counter.Increment")
.PayloadFromClosestState()
.SwapOuter();3. Why Payload Boundaries Matter
Heimdall works best when request data is gathered from the same DOM structure that already represents the current UI truth. That keeps the browser simple and keeps data ownership visible.
4. Closest Form
Closest form gathers request data from the nearest containing form. This is the natural fit for submit flows, interactive forms, and any interaction where form fields already define the request shape.
Strongly Typed Setup
form.Heimdall()
.OnSubmit("Contact.Submit")
.PayloadFromClosestForm()
.SwapNone();Rendered HTML Mental Model
<form
heimdall-content-submit="Contact.Submit"
heimdall-payload-from="closest-form"
heimdall-content-swap="none">
...
</form>This is the cleanest payload boundary when the interaction is already form-shaped.
5. Closest State
Closest state gathers request data from the nearest Heimdall state boundary. This is a natural fit for counters, cards, rows, lazy-loading sentinels, and other UI regions where the state already lives on the DOM boundary rather than in form inputs.
button.Heimdall()
.Click("Counter.Increment")
.PayloadFromClosestState()
.SwapOuter();This works especially well when the element invoking the server lives inside a stateful UI fragment that should remain self-contained.
<div data-heimdall-state='{"count":1}'>
<button
heimdall-content-click="Counter.Increment"
heimdall-payload-from="closest-state"
heimdall-content-swap="outer">
Increment
</button>
</div>6. Keyed Closest State
Keyed closest state narrows payload binding to a specific named state boundary. This is especially useful when multiple state boundaries exist in the same region or when a component should carry only one particular slice of state forward.
Strongly Typed Setup
sentinel.Heimdall()
.Visible("Weather.LoadMore")
.PayloadFromClosestState("weather")
.Target("#weather-sentinel")
.SwapOuter();Rendered HTML Mental Model
<div
id="weather-sentinel"
data-heimdall-state-weather='{"page":2}'
heimdall-content-visible="Weather.LoadMore"
heimdall-payload-from="closest-state:weather"
heimdall-content-target="#weather-sentinel"
heimdall-content-swap="outer">
Loading more...
</div>Keyed state works well when the DOM boundary is carrying continuation data, filters, cursors, or other component-local request context.
7. State and Form Payloads Are Complementary
Form payloads and state payloads solve different problems. Forms are a natural request boundary for user-entered fields. State boundaries are a natural request boundary for server-owned UI fragments that carry their own current context.
Form payloads:
- user-entered fields
- validation surfaces
- submit-oriented interactions
State payloads:
- server-rendered UI fragments
- counters, rows, cards, sentinels
- continuation or cursor state8. Self
Self tells the runtime to build the payload from the element’s own dataset. This is useful when a button, row action, or compact UI command already carries the small amount of data needed for the request.
<button
data-id="42"
data-status="active"
heimdall-content-click="Users.Select"
heimdall-payload-from="self"
heimdall-content-swap="outer">
Select
</button>This is a good fit for lightweight command-style interactions where a full form or state boundary would be unnecessary.
9. Explicit Form Selector
The runtime can also gather payload from an explicit selector. This is useful when the trigger lives outside the form boundary but should still submit that form’s current data.
<button
heimdall-content-click="Search.Run"
heimdall-payload-from="#search-form"
heimdall-content-target="#search-results"
heimdall-content-swap="inner">
Search
</button>
<form id="search-form">
...
</form>This keeps the request source explicit without forcing the trigger to be nested inside the form itself.
10. Inline Object Payload
For small fixed request objects, Heimdall can take the payload directly from inline JSON on the element. This is useful when the request shape is known at render time and does not need a separate boundary.
<button
heimdall-content-click="Reports.Load"
heimdall-payload='{"page":2,"sort":"desc"}'
heimdall-content-swap="inner">
Load
</button>This is best reserved for small, stable payloads. Once the object becomes large or dynamic, form, state, or reference-based sources are usually clearer.
11. Referenced Object Payload
Heimdall can also resolve a payload from a global object path. This is useful when some surrounding page script already owns a request object and the interaction should reuse it directly.
<button
heimdall-content-click="Reports.Load"
heimdall-payload-ref="App.Reports.Filters"
heimdall-content-swap="inner">
Refresh
</button>This keeps the interaction HTML-first while still allowing reuse of an existing browser-side object when one already exists.
12. Ref-Based Payload Source
The runtime also supports a ref-based payload source through heimdall-payload-from. Conceptually this is similar to payload-ref: the payload comes from an object path rather than from form or DOM state boundaries.
<button
heimdall-content-click="Reports.Load"
heimdall-payload-from="ref:App.Reports.Filters"
heimdall-content-swap="inner">
Refresh
</button>Both approaches are supported. The main distinction is whether the payload source is expressed as its own attribute or through payload-from.
13. Payload Source Precedence
The runtime checks payload sources in a specific order. Inline payload is considered first, then payload-ref, then payload-from. In practice that means a direct payload object wins over the other payload-source mechanisms if multiple are present.
1. heimdall-payload
2. heimdall-payload-ref
3. heimdall-payload-from14. What the Action Receives
Once Heimdall gathers the payload, the action receives it as a normal DTO. Payload binding stays a browser concern. Action parameters stay a server concern.
public sealed class WeatherState
{
public int Page { get; set; }
}The binding model stays straightforward: a payload object, optional framework types, and optional DI services.
[ContentInvocation]
public static IHtmlContent LoadMore(WeatherState state)
{
return RenderNextPage(state.Page);
}15. Payload Binding Is Not a Client State Store
It is important not to over-read payload binding. Heimdall does not introduce a client-side application state layer here. It simply gathers data from boundaries or objects that already exist at the moment the interaction runs.
DOM boundary or object already exists
-> trigger fires
-> runtime gathers payload
-> server decides next HTML16. Choosing the Right Payload Source
The right payload source is usually the boundary or object that already owns the interaction’s current data.
17. Payloads Work the Same Across Triggers
Payload binding is independent of whether the interaction starts from click, submit, input, visible, load, or polling. The trigger decides when the request starts. The payload decides what data that request carries.
click + closest-state
submit + closest-form
visible + closest-state:key
load + closest-state
poll + closest-state
click + self
click + ref:object18. Common Mental Model
A good way to think about payloads in Heimdall is that the browser gathers the smallest meaningful request context from the boundary or object already representing the current interaction state.
Trigger starts interaction
-> payload source is resolved
-> request data is gathered
-> action runs
-> returned HTML becomes the next UI truth19. Why This Fits the Heimdall Model
Payload sources reinforce Heimdall’s larger architecture. The DOM remains the interaction boundary, the server remains the source of truth, and the browser stays a thin runtime rather than becoming a second application.
DOM = current interaction boundary
Server = next UI decision
Payload = bridge between the twoNext Steps
Once payloads are clear, the next step is understanding how the browser runtime interprets triggers, modifiers, polling, targets, and swaps together, and then how those pieces compose into larger patterns.