Bandera ships an optional LiveView dashboard for browsing and managing flags. It
lives in the core library but is inert unless your app depends on
phoenix_live_view. It ships no JavaScript and needs no asset-pipeline
changes: it inherits your app's layout and runs on your existing LiveView
socket.
Install in a Phoenix app
The dashboard is a LiveView that runs inside your app. It needs LiveView wired up
(a socket + app.js), Bandera configured with a storage backend, and a route
mounted behind your auth. Apps generated with mix phx.new --live already have
the LiveView pieces (step 2).
1. Add the dependencies
# mix.exs
def deps do
[
{:bandera, "~> 0.1"},
{:phoenix_live_view, "~> 1.0"}
]
endRun mix deps.get. The dashboard only compiles when phoenix_live_view is
present — without it, Bandera still works as a plain flag library.
2. Make sure LiveView is wired up
The dashboard reuses your app's LiveView socket and JavaScript; it ships none of
its own. If you generated your app with --live this is already done. Otherwise
confirm both:
# lib/my_app_web/endpoint.ex
socket "/live", Phoenix.LiveView.Socket// assets/js/app.js
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
liveSocket.connect()Mount the dashboard under a pipeline whose layout loads that app.js (the default
:browser pipeline does). Without the socket the page still renders, but toggles,
search, and gate editing won't work.
3. Configure a storage backend
The dashboard reads and writes real flags through Bandera, so configure a persistence backend (defaults to in-memory, which is per-node and resets on restart — fine for trying it out, not for production). For example, Ecto:
# config/runtime.exs
config :bandera,
store: Bandera.Store.TwoLevel,
persistence: [adapter: Bandera.Store.Persistent.Ecto, repo: MyApp.Repo]See the README for all backends (Ecto, Redis, in-memory) and cross-node cache-bust notifications.
4. Mount the route behind your auth
# lib/my_app_web/router.ex
import Bandera.Dashboard.Router
scope "/admin" do
pipe_through [:browser, :require_admin] # YOUR authentication/authorization
bandera_dashboard "/flags"
endThe dashboard can toggle production features, so never mount it on an
unauthenticated route. You can also hook authorization into the LiveView's own
mount lifecycle with :on_mount:
bandera_dashboard "/flags", on_mount: {MyAppWeb.AdminAuth, :ensure_admin}Mount it more than once (e.g. per environment) by passing a distinct
:live_session_name.
5. Visit it
Start your server and open /admin/flags. Flags are created in code/IEx (e.g.
Bandera.enable(:my_flag)); the dashboard manages their gates from there.
The dashboard sets no root layout — it inherits the one from your pipeline. By
default its CSS is self-contained and inlined, with bandera--prefixed selectors,
so it needs no asset-pipeline changes and won't clash with your styles. See
Theming to retheme it or switch to daisyUI.
Grouping
Flags are grouped by a name-prefix convention: the part of the flag's name before
the first separator is the group. The separator defaults to "_":
config :bandera, dashboard: [group_separator: "_"]So :billing_checkout_v2 and :billing_invoices appear under billing. Flags
with no separator (e.g. :beta) appear under Ungrouped. Set
group_separator: nil to disable grouping.
Theming
The dashboard has two themes, chosen by config:
config :bandera, dashboard: [theme: :standalone] # default:standalone (default)
A self-contained, inlined stylesheet with bandera--prefixed selectors. No asset
pipeline, no daisyUI, no clashes. Colors, radii, and fonts are read as CSS custom
properties with built-in fallbacks, so you can retheme with a single rule — no
specificity battles, no !important:
:root {
--bandera-primary: #0ea5e9; /* buttons, group headers, focus rings */
--bandera-radius: 6px; /* cards and inputs */
--bandera-surface: #ffffff; /* card background */
--bandera-border: #e2e8f0;
/* also: --bandera-fg, --bandera-muted, --bandera-surface-2,
--bandera-radius-sm, --bandera-shadow, --bandera-primary-fg,
--bandera-success, --bandera-off, --bandera-danger,
--bandera-danger-border, --bandera-danger-bg, --bandera-font */
}The defaults are a fixed light palette. For dark mode, set the variables (e.g.
inside your own @media (prefers-color-scheme: dark)), or use the :daisyui
theme below.
:daisyui
config :bandera, dashboard: [theme: :daisyui]In this mode the dashboard emits daisyUI component classes (btn, input,
select, alert, …) and no inlined <style>, so it inherits your app's daisyUI theme
(including dark mode and brand colors). This requires your app to build daisyUI
itself, and — because Bandera ships compiled templates that Tailwind doesn't scan
by default — you must add Bandera to your Tailwind sources so the classes are
generated:
/* assets/css/app.css (Tailwind v4 / Phoenix 1.8) */
@source "../../deps/bandera/lib";(Tailwind v3: add "../deps/bandera/lib/**/*.ex" to content in
tailwind.config.js.) Without this, the dashboard renders unstyled.
Live updates across nodes
If you've configured Bandera's PhoenixPubSub cache-bust notifications, open
dashboards refresh automatically when another node or operator changes a flag.
What you can manage
Per flag: the boolean on/off toggle, per-actor gates, per-group gates, and a
percentage rollout (of actors or of time), plus clearing the whole flag. Search
filters the list as you type. (Creating brand-new flags from the UI is not in this
version — flags are created in code/IEx via Bandera.enable/2.)