DurableStash
Keep Phoenix LiveView state alive across reconnects, crashes, and redeploys.
A LiveView's assigns live in memory and vanish when the socket drops, the process crashes, or you deploy. DurableStash saves the assigns you pick to object storage and restores them the next time the LiveView mounts. The saved copy belongs to the browser session, so every LiveView the user opens shares it. Under the hood it plugs into LiveStash and stores state through DurableServer.
State survives:
- live navigation (unlike the stock ETS adapter, recovery happens on every mount, not just reconnects)
- WebSocket reconnects (Wi-Fi hiccups)
- LiveView crashes
- redeploys — state lives in object storage, not BEAM memory
State dies with the browser session: cleared cookies, another browser, or TTL expiry mean defaults.
Usage
defmodule MyAppWeb.SomeLive do
use MyAppWeb, :live_view
use LiveStash, adapter: DurableStash, stored_keys: [:count, :username]
def mount(_params, _session, socket) do
socket = assign(socket, count: 0, username: nil)
{_status, socket} = LiveStash.recover_state(socket)
{:ok, socket}
end
def handle_event("increment", _params, socket) do
socket = update(socket, :count, &(&1 + 1))
{:noreply, LiveStash.stash(socket)}
end
end
Scopes
Not all state wants the same recovery policy, so each stored key declares one:
use LiveStash, adapter: DurableStash,
stored_keys: [
theme: :session, # recover on every mount (the default for bare atoms)
draft: :reconnect # recover only on rejoins; cleared on fresh navigation
]
:session— recovered on every mount: live navigation, reconnects, crashes, redeploys. Right for settings the user expects to stick.:reconnect— recovered only when the client rejoins an existing view (_mounts > 0): Wi-Fi drops, LiveView crashes, and redeploys — the browser stays on the page through all of these. A fresh navigation to the view clears the stored values, so starting a new thing starts blank. Right for in-progress form drafts.
See the DurableStash moduledoc for setup (adapter registration, backend
config, the ensure_session_id plug) and all options (:vsn, :migrate,
:secret).
How it works
- A plug puts a random
"sid"into the cookie session; the adapter hashes it (with a secret) into the storage key of oneDurableStash.Sessionprocess. - All LiveViews of a session write through that single actor: per-key diffs, key-wise merge, per-key last-write-wins. Two tabs writing different keys cannot clobber each other; same-key writes are totally ordered.
- Every accepted write syncs to object storage immediately, so a deploy can never lose acknowledged state.
- Values are JSON-normalized at stash time — what you recover in dev is exactly what you would recover after a redeploy in prod.
DurableStash.TestBackend ships with the package: a faithful in-memory
DurableServer.StorageBackend (including etag CAS) for tests and make run
style development without S3 credentials.
Installation
def deps do
[
{:durable_stash, "~> 0.1.0"}
]
end
Credits
DurableStash stands on two lineages:
- the adapter API of LiveStash by Software Mansion
- the durable-process runtime of DurableServer by Chris McCord