A controller-pipeline guard that halts a request unless the resolved billable
is entitled to a feature: (or holds an active plan:).
This is the route-level gate a Phoenix developer reaches for first. It is a
thin transport adapter: init/1 validates the opts (raising at compile time
on ambiguous intent), and call/2 delegates the allow/deny decision to the
always-compiled Accrue.Entitlements.Guard engine. It is pure Plug — there is
NO controller-framework coupling, so it works in any Plug pipeline.
Usage
Gate exactly ONE of a feature: or a plan::
plug Accrue.Plug.RequireEntitlement, feature: :api_access
plug Accrue.Plug.RequireEntitlement, plan: :proThe deny behavior is content-negotiated and host-overridable:
# Custom HTTP status on the opaque deny body (default 403):
plug Accrue.Plug.RequireEntitlement, feature: :api_access, status: 402
# Redirect instead of an opaque body (the target MUST live OUTSIDE this
# gated pipeline to avoid a redirect loop):
plug Accrue.Plug.RequireEntitlement, plan: :pro, on_deny: {:redirect, "/pricing"}
# Resolve the billable explicitly (1-arity fn over the conn). Defaults to
# `conn.assigns.current_scope.user` then `conn.assigns.current_user`:
plug Accrue.Plug.RequireEntitlement, feature: :api_access,
billable: &MyAppWeb.current_org/1Global defaults for on_deny: / billable: live under
config :accrue, :entitlements; a per-guard opt always wins.
The require_feature/1 / require_plan/1 macros in Accrue.Router are sugar
over the single-feature:/plan: form; reach for the explicit plug form
above when you need status: / on_deny: / billable: overrides.
Security
The billable is resolved from server-side assigns only (the host
billable: fn or the default current_scope.user / current_user probe) —
NEVER from request params or headers. The accept header is read for content
negotiation only, never for authorization. The deny path is the fail-closed
default: a nil billable, a raising billable: fn, or any resolution
exception all collapse to a 403. The deny body is opaque — it never echoes the
gated feature / plan name (D-10).