Per-user notification preferences.
Preferences live inside the user's existing custom_fields JSONB column
under the "notification_preferences" key — a flat %{type_key => boolean} map. Unset keys default to the type's own :default flag via
PhoenixKit.Notifications.Types.default_for/1, so behaviour before a
user has opted in anywhere is unchanged.
The filter function user_wants?/2 is called once per notification
fan-out from PhoenixKit.Notifications.maybe_create_from_activity/1. It's
designed to fail open: any lookup error, unknown action, or malformed
prefs map returns true so the system never silently drops a
notification due to a bad row.
Summary
Functions
Returns the user's raw preference map.
Saves a preference map into the user's custom_fields.
Answers "would this user want a notification for this action?"
Functions
@spec get(PhoenixKit.Users.Auth.User.t() | String.t()) :: %{ optional(String.t()) => boolean() }
Returns the user's raw preference map.
Accepts either a loaded %User{} (zero DB work) or a UUID (one lookup).
Missing preferences return %{}.
@spec update(PhoenixKit.Users.Auth.User.t(), %{optional(String.t()) => boolean()}) :: {:ok, PhoenixKit.Users.Auth.User.t()} | {:error, Ecto.Changeset.t()}
Saves a preference map into the user's custom_fields.
prefs is the full map of %{type_key => boolean} — callers should
include entries for every rendered toggle, since the storage is a
replace, not a merge-at-the-key level. Other custom-field keys are
preserved (we merge at the custom_fields level, not inside it).
Answers "would this user want a notification for this action?"
Returns true on any ambiguity so new actions, unknown prefs, or a
failing user lookup never cause a silent drop.
Order of checks:
- Unknown action (no registered type) →
true(fail open) - Prefs map has the type key set to
trueorfalse→ that value - No entry → the type's
:defaultfromTypes.default_for/1 - Any raise → logged as a warning and returns
true