Most updates start with a client: a user clicks, the action runs, the framework diffs and patches. Server push is for the other case — when server-owned work finishes and needs to reach a session's live connections without any client request: a background job completes, a subscription delivers an event, a timer ticks.
session.TriggerAction(action, data) dispatches a named action into all of a
session's live connections, exactly as if the client had invoked it — the same
diff-and-patch pipeline, just enqueued from the server side.
Slow work started by a click does not belong here. If a user action kicks off the work and its result goes back to the same connection, use
livetemplate.Asyncinstead: it runs the work off the event loop, re-enters the loop to apply the result, and cancels itself if the connection drops — one method, no goroutine and no second action to name. Server push is for work the client did not ask for.Everything on this page remains the right tool when the work starts outside an action handler (
Mount,OnConnect, a webhook, a timer), reports progress repeatedly rather than completing once, or must reach connections other than the one that started it.Asynccovers none of those — it is action-handler-only, one-shot, and scoped to the originating connection.
Grab the session with ctx.Session() while you're still on a connection, then
hand it to whatever runs later — a goroutine, a timer callback, a job-result
handler:
func (c *Controller) OnConnect(state State, ctx *livetemplate.Context) (State, error) {
session := ctx.Session()
go func() {
result := fetchSlowData() // background work, no client involved
_ = session.TriggerAction("DataLoaded", map[string]any{"value": result})
}()
return state, nil
}
func (c *Controller) DataLoaded(state State, ctx *livetemplate.Context) (State, error) {
state.Value = ctx.GetString("value") // ordinary action — re-render from new state
return state, nil
}
DataLoaded is a normal action method; the only difference is who enqueued it.
Typical sources of a server push: a completed background job, an incoming
subscription or webhook event, a periodic timer.
TriggerAction reaches a session's connections by group ID — it does not use
topics, and the receiver does not need to have called Subscribe. That makes
it independent of Pubsub: pubsub is "an action fans out to
peers who opted in via a topic"; server push is "server-owned work pushes to
one session's connections."
TriggerAction targets one session group. When a single background event
must refresh many sessions — every viewer of a shared dashboard, every tab of
every user — don't keep a registry of Session handles and loop over it. Two
calls give you a fan-out with no registry at all:
Mount with ctx.Subscribe(topic)
(reconnect-durable, because Mount re-runs on reconnect).handler.Publish(topic, action, data) — no Context, safe from anywhere.
Every subscriber, across every session group, re-runs action and re-renders.The demo below runs one time.Ticker. On each tick it updates shared state
and calls handler.Publish("dashboard", "Refresh", nil). Open it in two tabs —
both advance in lockstep, driven by that single goroutine, with no per-tab timer.
Because a shared topic is cross-user, subscribing to a developer topic is
deny-all by default — the handler authorizes it with WithTopicACL, the one
security boundary such a topic has. (Joining your own ctx.SelfTopic() to reach
just your own tabs needs no ACL.) See the
server actions reference
for the full pattern and the per-user vs shared-group distinction.
| Need | Use |
|---|---|
| A background goroutine / timer / job should push to one session's connections | session.TriggerAction("...", data) (this page) |
| A background goroutine should refresh many sessions at once | ctx.Subscribe(topic) in Mount + out-of-band handler.Publish(topic, ...) (above) |
| A user action should also update peer tabs after it succeeds | Pubsub — Subscribe / Publish |
| The current connection should update from its own action | Return the new state from the action |
TriggerAction once per second.handler.Publish,
every viewer refreshed.Subscribe + Publish.