When it fires: Fires with the prepared comment attributes just before the row is inserted.
Read-only: the attributes are passed by value, so mutating them changes nothing. The filter that runs on the very next line, fluent_community/comment/comment_data, is the one that can alter them. Everything is already resolved at this point — rendered HTML, media, parent_id for replies, is_admin, and meta.mentioned_user_ids — which makes this a convenient point for validation logging or for throwing to abort the request.
When it fires: Runs immediately before a comment row is deleted, while it and its relations are still readable.
The only place to capture a comment before it disappears — fluent_community/comment_deleted receives just the ID. Attached media is announced separately through fluent_community/comment/media_deleted right after this hook, and the post's comment count is recalculated after the delete, so the count on $comment->post is still the pre-delete value here.
When it fires: Runs at the top of the create-comment endpoint so rate limiters can abort the request.
Core attaches RateLimitHandler::maybeLimitComment(), which throws once the member has exceeded fluent_community/rate_limit/comments_per_minute within the last 60 seconds. It fires before the comment text is validated and before the target post is even loaded, so the user is all you get. Throw to refuse; there is no return value.
When it fires: Runs after a published comment or reply has been stored and its media attached.
Comments held for moderation never reach it — those fire fluent_community/comment/new_comment_{status} instead. A type-scoped twin, fluent_community/comment_added_{feed->type}, fires immediately before it, so listening to both double-handles the same comment. Note the third argument is only supplied by CommentsController::store(); the Pro moderation-approval path passes just two.
When it fires: Post-type-scoped twin of fluent_community/comment_added, suffixed with the parent post's type.
The suffix is $feed->type, so the live names are fluent_community/comment_added_text for ordinary posts and fluent_community/comment_added_document for course lessons. It fires immediately before the generic fluent_community/comment_added, so listening to both double-handles the same comment. Pro fires it a second time from the moderation-approval path when a held comment is published.
When it fires: Action Scheduler task that sends the comment notification emails for one comment.
Scheduled job
This action is not fired inline. It is registered as a recurring background job and runs on a schedule, so the source below is where the job is scheduled, not where it fires. Hook it with add_action() as usual.
Queued for immediate execution from EmailNotificationHandler::handleNewCommentEvent() whenever the comment mentions somebody, the post author wants comment mail, or a thread participant wants reply mail. The handler re-queues this same action when it approaches its run-time budget, passing the last notified user ID as the cursor, so it fires repeatedly for a busy thread.
When it fires: Runs after a comment row has been deleted and the post's comment count recalculated.
The first argument is the comment ID, not a model — the row is already gone by the time the hook runs, so anything you need from the comment must be captured earlier via fluent_community/before_comment_delete. Attached media is announced beforehand through fluent_community/comment/media_deleted.
When it fires: Post-type-scoped twin of fluent_community/comment_deleted.
Suffixed with $feed->type, and fired immediately before the generic hook, so both run for every deletion. As with the generic hook the first argument is an ID, not a model — the row is already gone. The post's comments_count has been recounted and saved without bumping updated_at.
When it fires: Runs after an edited comment is saved, provided the save changed something.
Guarded by a dirty check, so a no-op edit is silent. Media attached to the comment is reconciled first, and any media dropped by the edit is announced separately through fluent_community/comment/media_deleted. The type-scoped fluent_community/comment_updated_{feed->type} fires directly after this one.
When it fires: Post-type-scoped twin of fluent_community/comment_updated.
Suffixed with $feed->type and fired immediately after the generic hook, under the same dirty-check guard — an edit that changed nothing fires neither. Media dropped by the edit has already been announced through fluent_community/comment/media_deleted.
When it fires: Signals that media attached to a comment should be detached and cleaned up.
A request to clean up rather than a report that a delete happened: core's CleanupHandler::queueMediaDelete() is what removes local files and deactivates remote ones. Two call sites, and they pass different things — editing a comment passes only the media rows the edit dropped, whereas deleting a comment passes the whole media relation. It is not fired at all when an edit drops no media.
When it fires: Status-scoped action for a comment that was not published, where the suffix is the comment status.
In practice the live name is fluent_community/comment/new_comment_pending, fired when moderation holds a comment back; Pro listens there to attach the flag record. A held comment fires neither fluent_community/comment_added nor its type-scoped twin, so if you are counting comments you must handle this branch as well.
When it fires: Fires when a member likes a comment.
Guarded by wasRecentlyCreated, so re-sending the same like is a no-op that does not fire again. The comment's incremented reactions_count is already saved. Comment reactions have no type dimension — unlike post reactions there is no bookmark variant. Core uses it to raise the reply-liked notification.
When it fires: Fires when a member withdraws a like from a comment.
Only fires when a row was actually deleted, so a stray un-like is silent. The reaction is gone by then, and unlike the add side no reaction model is passed, so the acting user is not available from the arguments.
When it fires: Fires after a comment is changed through the moderator patch endpoint.
A different event from fluent_community/comment_updated, which covers author edits to the body. This one only ever runs for the pin toggle: the patch endpoint accepts is_sticky alone, requires moderator or admin rights, and refuses to pin a reply. Pinning a comment un-pins every other comment on the post first, and those bulk un-pins do not fire the hook.
When it fires: Fires after the post author has been notified about a new comment.
Takes a single associative array rather than positional arguments — the shape is shared by all four notification/comment/* hooks, and key repeats the hook name so one callback can serve several. Skipped when the commenter is the author, and skipped when the author was @-mentioned, in which case fluent_community/notification/comment/notifed_to_mentions covers them instead. created distinguishes a new notification row from an existing one that was updated and marked unread again. The bundled push notification module listens here.
When it fires: Fires after the users @-mentioned in a comment have been notified.
Runs for both top-level comments and replies, and always creates a fresh notification rather than updating an existing one, so there is no created key in the payload. It runs before the author and thread notifications, and mentioned users are then subtracted from those recipient lists, so a mentioned reader gets exactly one notification.
When it fires: Fires after other participants on a post have been notified about a new top-level comment.
The odd one out of the four. Its notification value is the raw attribute array used as a template, not a Notification model — a separate row is created per recipient — so code that reads $notification->content will fatal here. It only runs for top-level comments, never replies, and the bundled push notification module deliberately leaves it unsubscribed. user_ids merges freshly notified users with subscribers of pre-existing notifications that were refreshed.
When it fires: Fires after participants in a reply thread have been notified of a new reply.
Note the misspelling in the hook name; it is part of the public surface and is documented as written. Only reached for replies, that is comments with a parent_id. Two call sites: one updates an existing child_comment_added notification and re-marks it unread, the other creates a new one. Neither passes a created key, so compare $notification->wasRecentlyCreated if you need to tell them apart.
Comments Actions
19 unique action hooks currently map to this category, across 27 call sites.
Hook Inventory
fluent_community/before_comment_createfluent-community/app/Http/Controllers/CommentsController.php:138fluent_community/before_comment_deletefluent-community/app/Http/Controllers/CommentsController.php:595fluent_community/check_rate_limit/create_commentfluent-community/app/Http/Controllers/CommentsController.php:77fluent_community/comment_addedfluent-community-pro/app/Http/Controllers/ModerationController.php:211fluent_community/comment_added_{feed}fluent-community-pro/app/Http/Controllers/ModerationController.php:210fluent_community/comment_added_asyncfluent-community/app/Hooks/Handlers/EmailNotificationHandler.php:231fluent_community/comment_deletedfluent-community/app/Http/Controllers/CommentsController.php:608fluent_community/comment_deleted_{feed}fluent-community/app/Http/Controllers/CommentsController.php:607fluent_community/comment_updatedfluent-community/app/Http/Controllers/CommentsController.php:272fluent_community/comment_updated_{feed}fluent-community/app/Http/Controllers/CommentsController.php:273fluent_community/comment/media_deletedfluent-community/app/Http/Controllers/CommentsController.php:266fluent_community/comment/new_comment_{comment}fluent-community/app/Http/Controllers/CommentsController.php:170fluent_community/comment/react_addedfluent-community/app/Http/Controllers/CommentsController.php:653fluent_community/comment/react_removedfluent-community/app/Http/Controllers/CommentsController.php:665fluent_community/comment/updatedfluent-community/app/Http/Controllers/CommentsController.php:330fluent_community/notification/comment/notifed_to_authorfluent-community/app/Hooks/Handlers/NotificationEventHandler.php:334fluent_community/notification/comment/notifed_to_mentionsfluent-community/app/Hooks/Handlers/NotificationEventHandler.php:556fluent_community/notification/comment/notifed_to_other_usersfluent-community/app/Hooks/Handlers/NotificationEventHandler.php:521fluent_community/notification/comment/notifed_to_thread_commetenterfluent-community/app/Hooks/Handlers/NotificationEventHandler.php:626fluent_community/before_comment_createRead-only: the attributes are passed by value, so mutating them changes nothing. The filter that runs on the very next line,
fluent_community/comment/comment_data, is the one that can alter them. Everything is already resolved at this point — rendered HTML, media,parent_idfor replies,is_admin, andmeta.mentioned_user_ids— which makes this a convenient point for validation logging or for throwing to abort the request.Parameters
$commentDataarray$feed\FluentCommunity\App\Models\FeedCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:138$commentData(mixed)$feed(Feed)Example
Related:
fluent_community/comment/comment_data·fluent_community/comment_addedfluent_community/before_comment_deleteThe only place to capture a comment before it disappears —
fluent_community/comment_deletedreceives just the ID. Attached media is announced separately throughfluent_community/comment/media_deletedright after this hook, and the post's comment count is recalculated after the delete, so the count on$comment->postis still the pre-delete value here.Parameters
$comment\FluentCommunity\App\Models\CommentCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:595$comment(Comment)Example
Related:
fluent_community/comment_deleted·fluent_community/comment/media_deletedfluent_community/check_rate_limit/create_commentCore attaches
RateLimitHandler::maybeLimitComment(), which throws once the member has exceededfluent_community/rate_limit/comments_per_minutewithin the last 60 seconds. It fires before the comment text is validated and before the target post is even loaded, so the user is all you get. Throw to refuse; there is no return value.Parameters
$user\FluentCommunity\App\Models\UserCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:77$user(User)Example
Related:
fluent_community/rate_limit/comments_per_minute·fluent_community/check_rate_limit/create_postfluent_community/comment_addedComments held for moderation never reach it — those fire
fluent_community/comment/new_comment_{status}instead. A type-scoped twin,fluent_community/comment_added_{feed->type}, fires immediately before it, so listening to both double-handles the same comment. Note the third argument is only supplied byCommentsController::store(); the Pro moderation-approval path passes just two.Parameters
$comment\FluentCommunity\App\Models\Comment$feed\FluentCommunity\App\Models\Feed$mentionedUsersarrayCall Sites
fluent-community-pro/app/Http/Controllers/ModerationController.php:211$content(mixed)$feed(Feed)fluent-community/app/Http/Controllers/CommentsController.php:181$comment(Comment)$feed(Feed)Arr::get($mentions, 'users', [])(array)Example
Related:
fluent_community/comment_updated·fluent_community/comment_deletedfluent_community/comment_added_{feed}fluent_community/comment_added, suffixed with the parent post's type.The suffix is
$feed->type, so the live names arefluent_community/comment_added_textfor ordinary posts andfluent_community/comment_added_documentfor course lessons. It fires immediately before the genericfluent_community/comment_added, so listening to both double-handles the same comment. Pro fires it a second time from the moderation-approval path when a held comment is published.Parameters
$comment\FluentCommunity\App\Models\Comment$feed\FluentCommunity\App\Models\FeedCall Sites
fluent-community-pro/app/Http/Controllers/ModerationController.php:210$content(mixed)$feed(Feed)fluent-community/app/Http/Controllers/CommentsController.php:180$comment(Comment)$feed(Feed)Example
Related:
fluent_community/comment_addedfluent_community/comment_added_asyncScheduled job
This action is not fired inline. It is registered as a recurring background job and runs on a schedule, so the source below is where the job is scheduled, not where it fires. Hook it with
add_action()as usual.Queued for immediate execution from
EmailNotificationHandler::handleNewCommentEvent()whenever the comment mentions somebody, the post author wants comment mail, or a thread participant wants reply mail. The handler re-queues this same action when it approaches its run-time budget, passing the last notified user ID as the cursor, so it fires repeatedly for a busy thread.Parameters
$commentIdint$lastUserIdintCall Sites
fluent-community/app/Hooks/Handlers/EmailNotificationHandler.php:231fluent-community/app/Hooks/Handlers/EmailNotificationHandler.php:245fluent-community/app/Hooks/Handlers/EmailNotificationHandler.php:260fluent-community/app/Hooks/Handlers/EmailNotificationHandler.php:372Example
Related:
fluent_community/comment_added·fluent_community/email_notify_new_postsfluent_community/comment_deletedThe first argument is the comment ID, not a model — the row is already gone by the time the hook runs, so anything you need from the comment must be captured earlier via
fluent_community/before_comment_delete. Attached media is announced beforehand throughfluent_community/comment/media_deleted.Parameters
$commentIdint$feed\FluentCommunity\App\Models\FeedCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:608$commentId(int)$feed(Feed)Example
Related:
fluent_community/comment_addedfluent_community/comment_deleted_{feed}fluent_community/comment_deleted.Suffixed with
$feed->type, and fired immediately before the generic hook, so both run for every deletion. As with the generic hook the first argument is an ID, not a model — the row is already gone. The post'scomments_counthas been recounted and saved without bumpingupdated_at.Parameters
$commentIdint$feed\FluentCommunity\App\Models\FeedCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:607$commentId(int)$feed(Feed)Example
Related:
fluent_community/comment_deleted·fluent_community/before_comment_deletefluent_community/comment_updatedGuarded by a dirty check, so a no-op edit is silent. Media attached to the comment is reconciled first, and any media dropped by the edit is announced separately through
fluent_community/comment/media_deleted. The type-scopedfluent_community/comment_updated_{feed->type}fires directly after this one.Parameters
$comment\FluentCommunity\App\Models\Comment$feed\FluentCommunity\App\Models\FeedCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:272$comment(Comment)$feed(Feed)Example
Related:
fluent_community/comment_addedfluent_community/comment_updated_{feed}fluent_community/comment_updated.Suffixed with
$feed->typeand fired immediately after the generic hook, under the same dirty-check guard — an edit that changed nothing fires neither. Media dropped by the edit has already been announced throughfluent_community/comment/media_deleted.Parameters
$comment\FluentCommunity\App\Models\Comment$feed\FluentCommunity\App\Models\FeedCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:273$comment(Comment)$feed(Feed)Example
Related:
fluent_community/comment_updatedfluent_community/comment/media_deletedA request to clean up rather than a report that a delete happened: core's
CleanupHandler::queueMediaDelete()is what removes local files and deactivates remote ones. Two call sites, and they pass different things — editing a comment passes only the media rows the edit dropped, whereas deleting a comment passes the wholemediarelation. It is not fired at all when an edit drops no media.Parameters
$media\FluentCommunity\Framework\Database\Orm\CollectionMediarows to clean up.Call Sites
fluent-community/app/Http/Controllers/CommentsController.php:266$otherMedias(mixed)fluent-community/app/Http/Controllers/CommentsController.php:598$comment->media(Comment)Example
Related:
fluent_community/feed/media_deleted·fluent_community/handle_remove_bulk_mediafluent_community/comment/new_comment_{comment}In practice the live name is
fluent_community/comment/new_comment_pending, fired when moderation holds a comment back; Pro listens there to attach the flag record. A held comment fires neitherfluent_community/comment_addednor its type-scoped twin, so if you are counting comments you must handle this branch as well.Parameters
$comment\FluentCommunity\App\Models\Comment$feed\FluentCommunity\App\Models\FeedCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:170$comment(Comment)$feed(Feed)Example
Related:
fluent_community/comment_added·fluent_community/comment/new_comment_responsefluent_community/comment/react_addedGuarded by
wasRecentlyCreated, so re-sending the same like is a no-op that does not fire again. The comment's incrementedreactions_countis already saved. Comment reactions have no type dimension — unlike post reactions there is no bookmark variant. Core uses it to raise the reply-liked notification.Parameters
$reaction\FluentCommunity\App\Models\Reaction$comment\FluentCommunity\App\Models\Comment$feed\FluentCommunity\App\Models\FeedCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:653$reaction(mixed)$comment(Comment)$feed(Feed)Example
Related:
fluent_community/comment/react_removed·fluent_community/feed/react_addedfluent_community/comment/react_removedOnly fires when a row was actually deleted, so a stray un-like is silent. The reaction is gone by then, and unlike the add side no reaction model is passed, so the acting user is not available from the arguments.
Parameters
$comment\FluentCommunity\App\Models\Comment$feed\FluentCommunity\App\Models\FeedCall Sites
fluent-community/app/Http/Controllers/CommentsController.php:665$comment(Comment)$feed(Feed)Example
Related:
fluent_community/comment/react_addedfluent_community/comment/updatedA different event from
fluent_community/comment_updated, which covers author edits to the body. This one only ever runs for the pin toggle: the patch endpoint acceptsis_stickyalone, requires moderator or admin rights, and refuses to pin a reply. Pinning a comment un-pins every other comment on the post first, and those bulk un-pins do not fire the hook.Parameters
$comment\FluentCommunity\App\Models\Comment$dirtyarraygetDirty(); in practice justis_sticky.Call Sites
fluent-community/app/Http/Controllers/CommentsController.php:330$comment(Comment)$dirty(mixed)Example
Related:
fluent_community/comment_updated·fluent_community/comment/patch_comment_responsefluent_community/notification/comment/notifed_to_authorTakes a single associative array rather than positional arguments — the shape is shared by all four
notification/comment/*hooks, andkeyrepeats the hook name so one callback can serve several. Skipped when the commenter is the author, and skipped when the author was @-mentioned, in which casefluent_community/notification/comment/notifed_to_mentionscovers them instead.createddistinguishes a new notification row from an existing one that was updated and marked unread again. The bundled push notification module listens here.Parameters
$eventDataarrayuser_ids,notification(aNotificationmodel),key,comment,feed,created.Call Sites
fluent-community/app/Hooks/Handlers/NotificationEventHandler.php:334array (6 keys: user_ids, notification, key, …)(array)fluent-community/app/Hooks/Handlers/NotificationEventHandler.php:360array (6 keys: user_ids, notification, comment, …)(array)Example
Related:
fluent_community/notification/comment/notifed_to_mentions·fluent_community/notification/comment/notifed_to_thread_commetenterfluent_community/notification/comment/notifed_to_mentionsRuns for both top-level comments and replies, and always creates a fresh notification rather than updating an existing one, so there is no
createdkey in the payload. It runs before the author and thread notifications, and mentioned users are then subtracted from those recipient lists, so a mentioned reader gets exactly one notification.Parameters
$eventDataarrayuser_ids,notification(aNotificationmodel),key,comment,feed.Call Sites
fluent-community/app/Hooks/Handlers/NotificationEventHandler.php:556array (5 keys: user_ids, notification, key, …)(array)Example
Related:
fluent_community/notification/comment/notifed_to_authorfluent_community/notification/comment/notifed_to_other_usersThe odd one out of the four. Its
notificationvalue is the raw attribute array used as a template, not aNotificationmodel — a separate row is created per recipient — so code that reads$notification->contentwill fatal here. It only runs for top-level comments, never replies, and the bundled push notification module deliberately leaves it unsubscribed.user_idsmerges freshly notified users with subscribers of pre-existing notifications that were refreshed.Parameters
$eventDataarrayuser_ids,key,notification(a plain attribute array, not a model),comment,feed.Call Sites
fluent-community/app/Hooks/Handlers/NotificationEventHandler.php:521array (5 keys: user_ids, key, notification, …)(array)Example
Related:
fluent_community/notification/comment/notifed_to_authorfluent_community/notification/comment/notifed_to_thread_commetenterNote the misspelling in the hook name; it is part of the public surface and is documented as written. Only reached for replies, that is comments with a
parent_id. Two call sites: one updates an existingchild_comment_addednotification and re-marks it unread, the other creates a new one. Neither passes acreatedkey, so compare$notification->wasRecentlyCreatedif you need to tell them apart.Parameters
$eventDataarrayuser_ids,notification(aNotificationmodel),key,comment,feed.Call Sites
fluent-community/app/Hooks/Handlers/NotificationEventHandler.php:626array (5 keys: user_ids, notification, key, …)(array)fluent-community/app/Hooks/Handlers/NotificationEventHandler.php:663array (5 keys: user_ids, notification, key, …)(array)Example
Related:
fluent_community/notification/comment/notifed_to_author