StarView — Datastar SSE for Plug and Phoenix
StarView sends Datastar Server-Sent Events from ordinary Plug requests. Phoenix helpers let you update browser signals and patch HEEx function components while threading the current connection.
It requires Elixir 1.17+ and Erlang/OTP 27+; JSON uses OTP's built-in :json.
Phoenix and its component support are optional integrations. StarView owns the
server protocol; your application owns its Datastar client, authentication,
authorization, and durable business actions.
What it solves
| Repeated work | StarView's API |
|---|---|
| Keep a render assign and browser signal in sync | signal/3 assigns and patches together |
| Render one changed region after an event | patch_element/3 accepts a HEEx function component |
| Read raw signal JSON without losing the updated conn | Signals.fetch/2 returns the conn on success and error |
| Construct dispatch URLs with quotes, slashes, or Unicode | Actions encodes segments and JavaScript string values |
| End PubSub streams without leaking ownership across keep-alive requests | StarView.Stream.open/2 and StarView.Stream.run/2 own probes and exact claim release |
For one-module page callbacks or shared UI with event handlers, v0.5.0 adds the
optional StarView.Page, StarView.Component and StarView.Router surface.
Follow pages, streams and components. Existing
controller entrypoints remain supported.
A short Phoenix event looks like this:
def handle_event("increment", %{"count" => count}, %{method: "POST"} = conn)
when is_integer(count) and count >= 0 and count < 1_000_000 do
conn
|> Plug.Conn.assign(:label, "Count")
|> signal(:count, count + 1)
|> patch_element(&summary/1)
end
def handle_event(_event, _signals, conn), do: conn
Dispatch validates signal input and starts SSE before calling that handler.
signal/3 sends its update immediately; there is no final flush step. The
submitted count is browser UI state in this example. A saved counter should be
updated by an authorized application action using trusted actor/tenant context.
Follow the complete counter example for the page mount, layout, component, validation, and error state.
Installation
For a Phoenix project with Igniter:
mix igniter.install star_view
The installer adds the dependency, dedicated :star_view web entrypoint,
layout, POST dispatch route and CSRF renaming plug, sample search page/controller, optional stream registry,
and HTTPS development setup. To skip optional parts:
mix igniter.install star_view --no-stream-dedup --no-https --no-example
--no-example skips the sample search page/controller. It retains the default
POST dispatch route and CSRF plug. For an existing manual integration,
mix star_view.setup.dispatch adds that routing/plug setup independently.
When using the generated HTTPS development configuration, install mkcert
and run mix star_view.trust before mix dev. The trust step writes local
certificate files and may require permission to update the host entry. See
installation and
development server for the full setup.
For manual installation:
def deps do
[{:star_view, "~> 0.5.0"}]
end
For Phoenix, add the dedicated web entrypoint and layout, then route the dispatcher through your browser pipeline:
pipeline :browser do
# Existing session/authentication plugs run before dispatch.
plug StarView.Plug.RenameCsrfParam
plug :protect_from_forgery
end
scope "/", MyAppWeb do
pipe_through :browser
post "/ds/:module/:event", StarView.Dispatch, [], alias: false
end
An existing Phoenix browser pipeline also needs its normal plugs and the endpoint's JSON parser. The fragment above shows the CSRF ordering, not a replacement for the complete pipeline. Cookie-authenticated mutations need CSRF protection, and each event still needs application authorization. The default route handles POST. If using GET, DELETE, PUT, or PATCH helpers, register those verbs explicitly and check the method in shared event handlers; see action routing. GET must remain read-only.
Bundle the browser client
Import your chosen Datastar ES module from the application's asset entrypoint, for example:
// assets/js/app.js (or the application's public entrypoint)
import "../vendor/datastar.js"
The path is an application-owned, locally acquired module. Preserve its license notice, follow its version/API requirements, and load the client once. The installer's layout includes a free CDN client by default; remove that script when bundling locally. StarView does not ship or grant a license for Datastar Pro.
Keep simple controls as server-rendered HTML with ordinary Datastar signals.
Consider Rocket for reusable controls
that need their own browser state and lifecycle; its API is currently beta.
Rocket provides browser state and lifecycle. StarView.Component colocates
Phoenix function components with server event handlers; it creates no server
process or persistent component state.
Choose your server integration
| Integration | Handler contract | Streaming starts |
|---|---|---|
| Phoenix marker dispatcher | handle_event(event, signals, conn) on a marked controller/component |
Before the callback |
| Optional page routes | StarView.Page mount/render/event callbacks; optional connect/info/disconnect |
Before event/connect callbacks |
Allowlisted StarView.Plug.Dispatch |
handle_event(conn, event, signals) in a modules: entry |
Before the callback unless start: false |
| Manual Plug endpoint | Fetch/validate with your own Plug | When you call StarView.start/1 |
| Long-running subscription | StarView.Stream.open/2, then StarView.Stream.run/2 callbacks |
On successful open/2 |
A dispatchable marker selects a handler; it does not authorize its operations.
The manual endpoint guide explains input
limits, callback orders, auth gates, and why HTTP rejection must happen before
SSE. Once streaming starts, patch the existing error state instead of calling
put_status, setting cookies, or sending another HTTP response.
Assigns, signals, and HTML
| Operation | Server component sees the value | Browser sees the value |
|---|---|---|
Plug.Conn.assign(conn, :key, value) |
Yes | Only if rendered as HTML |
StarView.Controller.signal(conn, :key, value) |
Yes | JSON signal, initially or during SSE |
StarView.patch_signals(conn, map) |
No assign update | JSON signal patch |
Component code uses Phoenix.Component.assign/3 for its assigns map. In
v0.4.0, patch_element/3 supplies missing render metadata for plain Plug
assigns, so the same function component can derive values during initial
rendering and during an event patch. Each event is a new request; load/set every
assign its component requires. See component patches.
Worked examples and diagnostics
- Phoenix counter: complete action, HEEx component, validation, and partial DOM update.
- Actions and quoted values: explicit targets, module signal contract, encoded paths, and double-quote migration.
- Manual Plug endpoints: thread fetched conns and reject bad input before streaming.
- PubSub streams: authentication, subscription cleanup, exact ownership, and fixed-deadline probes.
- Troubleshooting: malformed params, silent client mistakes, CSRF, stream 503s, and transport errors.
The optional AshAuthentication magic-link generator,
mix star_view.setup.auth, was included in v0.3.18 and remains available in
v0.4.0. It renders StarView sign-in pages and exchanges the token through a
normal form POST so session cookies are set before redirecting. It requires
the application's AshAuthentication resource/strategy and domain interfaces.
API and compatibility
Start with the API overview or
HexDocs. Raw/query signal input defaults to a
1,000,000-byte limit and must be an object. Bound already-parsed input with
Plug.Parsers too. Prefer StarView.fetch_signals/2 when raw bodies are involved.
Action strings in v0.4.0 use JSON double quotes. Avoid reverse-parsing them with a single-quote-only string matcher. Use HEEx expression attributes or HTML-escape the whole expression when constructing raw HTML. Expected closed/reset/broken-pipe SSE writes end normally through typed transport errors; timeouts and application exceptions remain errors. Read the changelog when upgrading this pre-1.0 package, especially from Dstar.
License
StarView is MIT licensed. The action-safety and stream-lifecycle implementations include work adapted from dstar, Copyright (c) 2025 Rico Trevisan. Attribution is retained in source and LICENSE. Datastar and any licensed client bundle retain their own terms.
Test emitted updates
StarView.Test adds sse_events/1, patched_signals/1,
assert_patched_signals/2 and assert_patched_element/2 for Plug/Phoenix tests.
These inspect emitted updates; they do not replay the browser's initial state.
mix star_view.setup.stream_page creates a removable PubSub demo page and ticker
with the page/component API and cleanup wiring. See
pages and streams.