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_emaildeny 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: discarddeny 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 September 12, 2026