Standalone, resource-agnostic comments module.
Provides polymorphic commenting for any resource type (posts, entities, tickets, etc.) with unlimited threading, likes/dislikes, and moderation support.
Architecture
Comments are linked to resources via resource_type (string) + resource_uuid (UUID).
No foreign key constraints on the resource side — any module can use comments.
Resource Handler Callbacks
Modules that consume comments can register handlers to receive notifications when comments are created or deleted. Configure in your app:
config :phoenix_kit, :comment_resource_handlers, %{
"post" => PhoenixKitPosts
}Handler modules should implement on_comment_created/3 and on_comment_deleted/3.
Core Functions
System Management
enabled?/0- Check if Comments module is enabledenable_system/0- Enable the Comments moduledisable_system/0- Disable the Comments moduleget_config/0- Get module configuration with statistics
Comment CRUD
create_comment/4- Create a comment on a resourceupdate_comment/2- Update a commentdelete_comment/1- Delete a commentget_comment/2,get_comment!/2- Get by IDlist_comments/3- Flat list for a resourceget_comment_tree/2- Nested tree for a resourcecount_comments/3- Count comments for a resource
Moderation
approve_comment/1- Set status to publishedhide_comment/1- Set status to hiddenbulk_update_status/2- Bulk status changeslist_all_comments/1- Cross-resource listing with filterscomment_stats/0- Aggregate statistics
Like/Dislike
Summary
Functions
Sets a comment's status to published.
Attaches an uploaded file to a comment.
Returns true when comment attachments are enabled in settings.
Bulk-updates status for multiple comment UUIDs.
Checks if a user has disliked a comment.
Checks if a user has liked a comment.
Returns aggregate statistics for all comments.
Counts comments for a resource.
Returns comment counts grouped by resource type.
Creates a comment on a resource.
Soft-deletes a comment by setting its status to "deleted".
Detaches a media row by (comment_uuid, file_uuid).
Detaches a media row by its own uuid.
Disables the Comments module.
User dislikes a comment. Removes any existing like first.
Enables the Comments module.
Checks if the Comments module is enabled.
Gets a single comment by ID with optional preloads.
Gets a single comment by ID with optional preloads.
Gets nested comment tree for a resource.
Gets the Comments module configuration with statistics.
Returns the configured Giphy API key (empty string when unset).
Returns the configured Giphy content rating (g/pg/pg-13/r).
Returns the per-attachment size cap in MB.
Returns the per-comment attachment count cap (default 4).
Returns the configured maximum comment depth.
Returns the configured maximum comment length.
Gets configured resource templates (path + optional display title).
Returns true when the Giphy picker should be shown in the comment form.
Sets a comment's status to hidden.
User likes a comment. Removes any existing dislike first.
Lists all comments across all resource types with filters.
Lists all dislikes for a comment.
Lists all likes for a comment.
Lists media for a comment, ordered by position.
Lists comments for a resource (flat list).
Returns distinct metadata keys grouped by resource type.
Returns distinct resource types that have comments.
Validates a prospective comment before any uploads are consumed.
Resolves resource context (title and admin path) for a list of comments.
Searches Giphy for GIFs matching the query, using the configured API key and rating.
User removes dislike from a comment. Deletes the dislike row and
decrements the counter atomically. Returns {:ok, :undisliked} or
{:error, :not_found}.
User unlikes a comment. Deletes the like row and decrements the counter
atomically. Returns {:ok, :unliked} or {:error, :not_found}.
Updates a comment.
Updates resource templates for resource types.
Types
Functions
Sets a comment's status to published.
@spec attach_media(UUIDv7.t(), UUIDv7.t(), keyword()) :: {:ok, PhoenixKitComments.CommentMedia.t()} | {:error, Ecto.Changeset.t()}
Attaches an uploaded file to a comment.
position defaults to 1; the caller is responsible for assigning
non-colliding positions (the DB has a unique constraint on
(comment_uuid, position)).
@spec attachments_enabled?() :: boolean()
Returns true when comment attachments are enabled in settings.
Bulk-updates status for multiple comment UUIDs.
Routes through update_comment/2 (and delete_comment/1 for the
"deleted" case) so resource-handler callbacks fire per row. Returns
{ok_count, error_count}.
Checks if a user has disliked a comment.
Checks if a user has liked a comment.
Returns aggregate statistics for all comments.
Counts comments for a resource.
Mirrors list_comments/3: deleted rows are excluded unless :status is
set explicitly or include_deleted: true is passed.
Returns comment counts grouped by resource type.
Creates a comment on a resource.
Automatically calculates depth from parent. Invokes resource handler callback if configured.
Parameters
resource_type- Type of resource (e.g., "post")resource_uuid- UUID of the resourceuser_uuid- UUID of commenterattrs- Comment attributes (content, parent_uuid, metadata, etc.). May include:attachment_file_uuids— a list ofPhoenixKit.Modules.Storage.FileUUIDs to attach to the new comment in display order. Comment insert + attachments run in one transaction; any attach failure rolls back the comment too.
Soft-deletes a comment by setting its status to "deleted".
Invokes resource handler callback if configured.
Detaches a media row by (comment_uuid, file_uuid).
Detaches a media row by its own uuid.
Disables the Comments module.
User dislikes a comment. Removes any existing like first.
Returns {:ok, :disliked} when a new dislike row was created, or
{:ok, :already_disliked} when the user had already disliked the comment.
Enables the Comments module.
Checks if the Comments module is enabled.
Gets a single comment by ID with optional preloads.
Returns nil if not found.
Gets a single comment by ID with optional preloads.
Raises Ecto.NoResultsError if not found.
Gets nested comment tree for a resource.
Returns all published comments organized in a tree structure. Deleted
comments with published descendants are preserved as [removed]
placeholders so reply chains stay attached; deleted leaves are pruned.
Gets the Comments module configuration with statistics.
@spec get_giphy_api_key() :: String.t()
Returns the configured Giphy API key (empty string when unset).
@spec get_giphy_rating() :: String.t()
Returns the configured Giphy content rating (g/pg/pg-13/r).
@spec get_max_attachment_size_mb() :: pos_integer()
Returns the per-attachment size cap in MB.
Clamped against the global storage_max_upload_size_mb so an admin
can't accidentally let comment uploads exceed the platform cap.
@spec get_max_attachments() :: pos_integer()
Returns the per-comment attachment count cap (default 4).
Returns the configured maximum comment depth.
Returns the configured maximum comment length.
Gets configured resource templates (path + optional display title).
Returns a map of resource_type => config, where config is either:
- A plain string (legacy path-only format)
- A map with
"path"and optional"title"keys
Examples
%{"shoes" => "/order/shoes/:uuid"}
%{"shoes" => %{"path" => "/order/shoes/:uuid", "title" => ":metadata.name"}}
@spec giphy_enabled?() :: boolean()
Returns true when the Giphy picker should be shown in the comment form.
Requires both the comments_giphy_enabled toggle and a non-empty API key.
Sets a comment's status to hidden.
User likes a comment. Removes any existing dislike first.
Returns {:ok, :liked} when a new like row was created, or
{:ok, :already_liked} when the user had already liked the comment.
Lists all comments across all resource types with filters.
Options
:resource_type- Filter by resource type:status- Filter by status:user_uuid- Filter by user:search- Search in content:page- Page number (default: 1):per_page- Items per page (default: 20)
Lists all dislikes for a comment.
Lists all likes for a comment.
Lists media for a comment, ordered by position.
Lists comments for a resource (flat list).
Soft-deleted comments are excluded by default. Pass include_deleted: true
(or an explicit status:) for admin callers that need them.
Options
:preload- Associations to preload:status- Filter by status:include_deleted- Includestatus == "deleted"rows (default: false)
Returns distinct metadata keys grouped by resource type.
Queries the JSONB metadata column for all keys in use, e.g.:
%{"manga_annotation" => ["chapter", "page", "slug", "source"],
"post" => ["category"]}
Returns distinct resource types that have comments.
@spec precheck_create(String.t(), term(), String.t(), map(), non_neg_integer()) :: :ok | {:error, atom()}
Validates a prospective comment before any uploads are consumed.
Use this in form handlers ahead of Phoenix.LiveView.consume_uploaded_entries/3
so that depth / length / attachment-cap failures don't leak files into
the storage backend. Accepts the same attrs as create_comment/4
except :attachment_file_uuids — pass entry_count instead, which is
how many uploads are currently staged on the LiveView.
Returns :ok or {:error, reason} with the same reasons
create_comment/4 would surface (:invalid_user_uuid,
:max_depth_exceeded, :content_too_long, :attachments_disabled,
:too_many_attachments, :empty_comment).
Resolves resource context (title and admin path) for a list of comments.
Returns a map of {resource_type, resource_uuid} => %{title: ..., path: ...}
by delegating to registered comment_resource_handlers that implement
resolve_comment_resources/1.
Searches Giphy for GIFs matching the query, using the configured API key and rating.
Returns {:ok, [gif_map]} on success or {:error, reason} on failure. Each gif_map
has string keys: "id", "url" (original image), "preview_url" (thumbnail),
"width", "height".
User removes dislike from a comment. Deletes the dislike row and
decrements the counter atomically. Returns {:ok, :undisliked} or
{:error, :not_found}.
User unlikes a comment. Deletes the like row and decrements the counter
atomically. Returns {:ok, :unliked} or {:error, :not_found}.
Updates a comment.
Parameters
comment- Comment to updateattrs- Attributes to update (content, status)
Updates resource templates for resource types.
Accepts both legacy string values and new map values with "path" and "title" keys.