StarView logo

StarView — Datastar SSE for Plug and Phoenix

Version Downloads Requires Erlang/OTP 27+ MIT license

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.

Start with Thinking in Phoenix, Datastar and StarView for the controller/backend model and the inspiration from Jacob O'Bryant's Biff. StarView 0.6.0 adds optional coalesced reactive rendering to the existing stream lifecycle.

The separate experimental STAR template compiler uses ~STAR and a StarView-owned tree for initial HTML and selected element patches. It currently requires the tested LiveView 1.2.12 parser adapter. Opt-in checked events validate literal local helpers such as post("save") against the actual handler names and methods.

The checkout includes a copyable Datastar agent skill at skills/datastar/, bundled in Hex builds, for Codex, Claude Code and other skill-capable agents.

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.6.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

The optional AshAuthentication magic-link generator, mix star_view.setup.auth, 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.