nickurt/laravel-akismet
nickurt/laravel-akismet integrates Akismet spam protection into Laravel applications. Easily validate comments and form submissions, detect spam via the Akismet API, and configure keys and site details through a simple config file and facade.
1. Install and publish config
composer require nickurt/laravel-akismet
php artisan vendor:publish --provider="Nickurt\Akismet\ServiceProvider" --tag="config"
2. Set the two required env vars
AKISMET_API_KEY=your_key
AKISMET_BLOG_URL=https://yoursite.com
api_key is the Akismet key tied to the account, and blog_url must match the URL registered at akismet.com — this is the field people get wrong most often. Open config/akismet.php first; it is small and tells you what else is configurable (typically queue and timeouts).
3. Resolve the facade
The package registers an Akismet facade alias. Inspect vendor/nickurt/laravel-akismet/src/ for the exact FQCN and the available methods — the surface is small (isSpam(), isHam(), submitSpam(), submitHam(), and a check()-style method returning a response object). The facade is the entry point; everything else is sugar.
4. First use case: spam-check a contact form
use Nickurt\Akismet\Facades\Akismet;
public function store(ContactRequest $request)
{
$isSpam = Akismet::isSpam([
'comment_content' => $request->input('message'),
'comment_author' => $request->input('name'),
'comment_author_email'=> $request->input('email'),
]);
if ($isSpam) {
// silently accept, quarantine, or 422
}
Contact::create($request->validated());
}
Defaults such as user_ip, user_agent, referrer, and blog are populated by the package from the current request and config — you only override what you need. Verify the exact signature against the README before shipping; the methods are thin wrappers and the array keys are the raw Akismet field names.
Controller / FormRequest inline check — the most direct pattern. Call isSpam() after validation (so you only send well-formed data) but before persistence. Keep the check on the happy path, not in a middleware, unless you want every route protected.
Dedicated validation rule — wrap isSpam() in a Rule object so controllers stay thin:
class NotSpam implements ValidationRule {
public function validate(string $attr, mixed $value, Closure $fail): void {
if (Akismet::isSpam(['comment_content' => $value])) {
$fail('Your message was flagged.');
}
}
}
Return a generic failure message — telling spammers why they were blocked trains them to evade you.
Check-then-report lifecycle — Akismet only gets smarter if you tell it when it guessed wrong. The pattern that pays off:
isSpam() → store an is_spam boolean column, don't hard-delete.submitHam() with the same payload you originally sent.submitSpam() with the same payload.Persist the payload (or a reconstructable copy — content, author, email, IP) alongside the record so the report calls are accurate weeks later.
Use check() when you need to log — isSpam() collapses everything to a bool. When you're debugging false positives, call the response-returning method to inspect status codes and the raw Akismet verdict, and log it next to the submission.
Queue the reporting calls — submitSpam() and submitHam() are fire-and-forget side effects; they should not block a moderator's HTTP request. If config('akismet.queue') is available, enable it, or wrap the calls in a dispatch()ed job. Do not queue the initial isSpam() check — the user is waiting on that verdict.
Testing — mock the facade so your test suite never hits Akismet:
Akismet::shouldReceive('isSpam')->once()->andReturn(true);
If the underlying implementation uses the Laravel HTTP client, Http::fake() also works and lets you assert on the outgoing payload — useful for catching missing blog_url fields.
blog_url mismatches produce silent failures. A trailing slash, http vs https, or www. difference will cause Akismet to reject the call. The package treats the error as "not spam" in most paths, so your spam filter quietly stops working. Assert it in a health check or a test.
Errors look like "ham". Network timeouts, invalid keys, and quota exhaustion usually surface as a falsy result from isSpam(), not an exception. In production, prefer the response-returning method and check for an error before trusting a false. At minimum, log failures so you can distinguish "Akismet says fine" from "Akismet was unreachable."
Config caching bites. php artisan config:cache freezes AKISMET_API_KEY and AKISMET_BLOG_URL. If you rotate keys or flip the queue flag, re-cache. New env vars won't appear until you do, which produces a "wrong key" symptom that isn't actually wrong.
The submitted data must be re-sent for submitSpam/submitHam. Akismet matches on the content hash, not on an ID. If you mutate the stored comment (sanitizing, truncating) before reporting, the report may not correlate with the original check and gets discarded.
Don't put isSpam() in a loop. Akismet is an external service with rate limits. Bulk-importing 10k comments through isSpam() will get you throttled. Batch imports are better handled offline / with backoff.
queue => true changes the failure mode. Reporting becomes async, so errors surface in the queue worker's logs, not the request. Make sure failed jobs are monitored if you enable it.
GDPR / privacy. You are sending visitor IPs, user agents, and email addresses to a third party. Disclose it in your privacy policy and consider whether the user_ip default is acceptable for your jurisdiction.
Extension points are thin. There's no rich event system here. If you need custom retry logic, error telemetry, or an alternate spam provider, wrap the facade in your own SpamService and bind it in a service provider — then swap implementations later without touching controllers. That wrapper is also the natural place to centralize the "check, log the response, persist the flag" flow described above.
Look at the source, not just the README. With a small package at this star count, the README lags the code. src/Akismet.php is a couple of hundred lines — reading it once will tell you exactly which request fields get auto-filled, how errors are represented, and whether the response object exposes isSpam() or just a status code. That ten-minute read will save you an afternoon of debugging a silent false negative.
How can I help you explore Laravel packages today?