Activity feed for tracking business-level actions across the platform.
Provides a simple API for logging and querying activities. Any module can call
Activity.log/1 to record an action. The admin dashboard shows a real-time
activity stream.
Usage
PhoenixKit.Activity.log(%{
action: "post.created",
actor_uuid: user.uuid,
resource_type: "post",
resource_uuid: post.uuid,
metadata: %{"title" => post.title}
})Action naming convention
Use dotted format: resource.verb — e.g., "post.created", "comment.liked",
"user.registered", "password.changed", "role.assigned".
Summary
Functions
Returns a CSS badge class based on the action verb.
Counts activities matching the given filters.
Whether scope may read the WHOLE activity log — every user's actions —
rather than only its own.
Gets a single activity entry by UUID with preloaded associations.
Gets a single activity entry by UUID. Raises if not found.
Renders an activity-metadata VALUE as human-readable text, tolerant of the shapes host apps store.
Lists activities with filtering and pagination.
Returns distinct action types that have been logged.
Lists activities for a specific user (as actor).
Returns distinct modes that have been logged.
Returns distinct modules that have been logged.
Returns distinct resource types that have been logged.
Logs an activity.
Logs a user change with automatic from/to diff extraction from a changeset.
Returns a CSS badge class based on the mode.
Whether entry is scope's OWN activity.
Deletes activities older than the given number of days.
PubSub topic for activity events.
Returns the N most recent activities.
Resolves resource info for entries where resource_type is "user".
Returns the configured retention period in days.
Functions
Returns a CSS badge class based on the action verb.
Counts activities matching the given filters.
@spec full_log_access?(PhoenixKit.Users.Auth.Scope.t() | nil) :: boolean()
Whether scope may read the WHOLE activity log — every user's actions —
rather than only its own.
Administrators qualify: the Admin or Owner role, or any "*" superadmin
role. Everyone else (a custom role that merely holds dashboard) is scoped
to their own actions. The activity LiveViews share this as the single gate
for both the list and the single-entry page.
Gets a single activity entry by UUID with preloaded associations.
Gets a single activity entry by UUID. Raises if not found.
Renders an activity-metadata VALUE as human-readable text, tolerant of the shapes host apps store.
The admin feed and detail page display arbitrary per-module metadata, so this
must never raise Protocol.UndefinedError (String.Chars/to_string on a
Map): a field-change diff carries a nested %{"from" => _, "to" => _} map,
which is rendered as "1 → 2"; any other map is rendered as
"key: value, ...". Legacy entries whose scalar was serialised via inspect
("Decimal.new(\"1\")") are unwrapped to their inner value so old rows read
cleanly instead of leaking Elixir syntax.
Lists activities with filtering and pagination.
Options
:action— filter by action string (exact match or prefix with "post.*"):actor_uuid— filter by who performed the action:resource_type— filter by resource type:resource_uuid— filter by the specific resource's UUID (scope to one resource):target_uuid— filter by who was affected:since— filter activities after this datetime:until— filter activities before this datetime:page— page number (default: 1):per_page— items per page (default: 50):preload— associations to preload (default: [:actor])
Returns distinct action types that have been logged.
Lists activities for a specific user (as actor).
Returns distinct modes that have been logged.
Returns distinct modules that have been logged.
Returns distinct resource types that have been logged.
Logs an activity.
Required fields
:action— dotted action string (e.g., "post.created")
Optional fields
:actor_uuid— who performed the action:resource_type— type of resource acted on:resource_uuid— UUID of the resource:target_uuid— who was affected (e.g., follow target):metadata— map of additional context
Returns {:ok, entry} or {:error, changeset}. Failures are logged but never crash.
Logs a user change with automatic from/to diff extraction from a changeset.
Extracts changed fields from the changeset and builds field_from / field_to
metadata pairs. Skips logging if nothing actually changed.
Options
:actor_uuid— who performed the action (default: the user's own UUID):target_uuid— who was affected (default: nil):mode— "auto" or "manual" (default: "auto"):actor_role— "user" or "admin" (default: "user"):extra_metadata— additional metadata to merge in (default: %{}):skip_fields— fields to exclude from diff (default: [:custom_fields])
Returns a CSS badge class based on the mode.
@spec own_entry?(PhoenixKit.Users.Auth.Scope.t() | nil, PhoenixKit.Activity.Entry.t()) :: boolean()
Whether entry is scope's OWN activity.
"Own" is defined by AUTHORSHIP: the scope's user is the entry's actor_uuid
(the account that performed the action). A record where the user is only the
target_uuid — someone else acted on or for them — is NOT their own and stays
hidden from a non-administrator.
This is the single definition the audit log enforces in two places, and they
must not drift: the list pins actor_uuid to the user (Activity.Index) and
the single-entry page gates on this predicate (Activity.Show). If "own" ever
needs to include target-side records, change it HERE and switch the list's
filter in lock-step — do not fork the rule per view.
Deletes activities older than the given number of days.
PubSub topic for activity events.
Returns the N most recent activities.
Resolves resource info for entries where resource_type is "user".
Returns a map of resource_uuid => %{email: ..., first_name: ..., last_name: ...}.
Batch-queries to avoid N+1.
Returns the configured retention period in days.