Backpex.Preferences.Adapters.Session (Backpex v0.23.0)

Copy Markdown View Source

Session-backed Backpex.Preferences adapter.

Stores all preferences as a single nested map under one Phoenix session key ("backpex_preferences"). Exact storage characteristics depend on the host app's Plug.Session backend (cookie, ETS, Redis, ...). The default cookie store has a ~4KB limit; prefer a database-backed adapter when you expect bulky per-user data.

Reference implementation for the Backpex.Preferences.Adapter behavior — a reasonable template when writing your own adapter.

Size limit

With the default :cookie session store the whole session — Backpex's preferences plus everything the host app keeps in it, such as phx.gen.auth's user token — travels in one Set-Cookie header, and Plug.Conn refuses to send a header longer than 4096 bytes: it raises Plug.Conn.CookieOverflowError, the request 500s, and the write is lost. put/4 therefore estimates the size of that header and refuses a write that would breach the budget with {:error, :too_large}, leaving the stored value untouched; Backpex.PreferencesController turns that into a 422 so the browser can stop carrying the write. A warning is logged well before the ceiling.

Options

  • :max_bytes — the budget for the session cookie's whole Set-Cookie header (name, signed value and attributes), in bytes (default: 4096, Plug.Conn's limit). Use :infinity for a server-side session store (ETS, Redis, ...), which has no such cap:

    config :backpex, Backpex.Preferences,
      adapters: [
        {:default, Backpex.Preferences.Adapters.Session, max_bytes: :infinity}
      ]

The estimate covers the whole session, not just Backpex's subtree. It counts the ~4/3 growth of Base64 and reserves 256 bytes for the rest of the header: the signature (or the encryption envelope, with :encryption_salt), the cookie name and the attributes Plug.Session adds (path, HttpOnly, SameSite, secure, max-age/expires, domain). The adapter cannot see the Plug.Session options, so this is deliberately approximate: it is a budget check, not an exact reproduction of the store's encoding, and it is conservative for typical options.

If your options are unusually long (a long :key or :domain, :extra attributes), work out the tail your app actually sends:

# `key` and the cookie options (`:domain`, `:max_age`, `:secure`,
# `:same_site`, `:extra`, ...) as passed to `Plug.Session`
header = Plug.Conn.Cookies.encode(key, Map.new([value: ""] ++ cookie_opts))

# 52 for a signed cookie (the default); about 58 instead with
# `:encryption_salt`, where the value is encrypted, not also signed
tail = byte_size(header) + 52

and lower :max_bytes by however much tail exceeds 256. The warning threshold is 75% of :max_bytes and moves with it. Prefer a database-backed adapter over raising :max_bytes when per-user data genuinely does not fit.

Write-path limitations

put/4 returns {:error, :requires_http} for any source other than :controller. Plug.Session cannot write to the Phoenix session outside an HTTP request cycle. The dispatcher handles this by falling back to push_event/3, which round-trips the write through the browser and the preferences controller.

nil values

get/3 maps a stored nil to {:ok, :not_found}. Store a tagged value such as %{"value" => nil} if application code must distinguish an explicit nil from an absent preference.

Summary

Functions

Returns the Phoenix session key used to store the preferences tree.

Functions

session_key()

Returns the Phoenix session key used to store the preferences tree.