Skip to main content
Every comment posted through Livewire or the API passes through the spam pipeline in CommentObserver on create and on body edit. Checkers run in spam.checkers order. The worst verdict wins (deny > review > allow). A deny short-circuits remaining checkers. Reasons merge. When an external checker throws or times out, spam.fail_mode is allow (fail open) or review (hold). Failures are report()ed.

Built-in checkers

heuristic (default, no network)

Configured under spam.heuristic: With guests.stricter_spam (default true):
  • Guest missing email while guest.require_email → deny guest_missing_email
  • Guest with any link → review guest_with_link
  • Duplicate window also matches guest email + IP

akismet

Set AKISMET_KEY. Posts to {key}.rest.akismet.com/1.1/comment-check with IP, user agent, author, email, body, and comment_type reply or comment.
  • Akismet true + header X-akismet-pro-tip: discard → deny akismet_discard
  • Akismet true otherwise → review akismet_spam
  • Anything else, or missing key → allow (skips)
The installer can write the key to .env. Blank key at runtime is a no-op.

toxicity

Google Perspective Comment Analyzer. See AI toxicity. Add 'toxicity' to spam.checkers and set COMMENTIFY_PERSPECTIVE_KEY. Billed per call.

Logging

Non-allow verdicts persist to commentify_spam_logs when spam.log is true: user_id, ip, body_hash (SHA-256), 250-char excerpt, decision, reasons, checker. The pipeline runs before the row exists, so logs store a hash rather than a comment id.

”Not spam” in Filament

On the Spam Log resource, Not spam re-approves the matching held comment (hash + author) and submits ham to Akismet when configured. Unmatched logs still write an audit row (not_spam_unmatched). See Filament.

Custom checkers

Implement Usamamuneerchaudhary\CommentifyPro\Contracts\SpamChecker and list the class name in spam.checkers (built-in aliases heuristic, akismet, toxicity are resolved first; anything else is treated as a FQCN):
CommentCandidate includes body, author, ip, userAgent, parentId, guestName, guestEmail, plus links(), isGuest(), and authorFrom().
Pair guests with require_approval and heuristic. Humans should not be the first line of defence at 2am.
Last modified on November 1, 2026