arcanedev/gravatar
A Laravel package for generating Gravatar avatar URLs. It offers a simple helper and API for building image links with configurable size, rating, default image, and secure hash handling, making user avatars easy to add across your application.
The package is deliberately thin: it is essentially md5(strtolower(trim($email))) wrapped in a fluent object that appends s=, r=, d= and f= query parameters to https://gravatar.com/avatar/{hash}. Treat the source (src/Gravatar.php) as your documentation — it is shorter than most READMEs and tells you exactly which fluent methods and defaults you actually have in the installed tag.
composer require arcanedev/gravatar
On Laravel 5.5+ the service provider and the Gravatar facade alias are auto-discovered, so you can call the facade after composer install. For non-Laravel usage, instantiate the class directly:
use Arcanedev\Gravatar\Gravatar;
$g = new Gravatar('[email protected]');
echo $g->url(200); // or ->build()/->image() depending on your tag — check src/
If a config file is publishable in your tag (php artisan vendor:publish --provider="Arcanedev\Gravatar\GravatarServiceProvider"), publish it once and set your house defaults (size, rating, fallback image) so call sites stay short.
First realistic use case: replacing an initials/placeholder avatar in a user listing. You already have $user->email; you want a stable image URL, a graceful fallback for users without a Gravatar, and no network call on the hot path. That is exactly what this package is good for — it builds the URL locally, so rendering 50 avatars costs 50 string concatenations, not 50 HTTP requests.
Sanity check before wiring anything up:
php artisan tinker
>>> Gravatar::url('[email protected]', 200);
Load that URL in a browser. If you see your face (or the mystery-person placeholder), the wiring is correct and you can move on to the integration pattern below.
Wrap it once, don't scatter calls. The package's value is the URL format, and that format is stable enough to be a one-line function. Put it behind your own boundary so you can swap to ui-avatars.com, Libravatar, a self-hosted proxy, or a locally stored upload later without touching Blade templates.
// app/Support/Avatar.php
final class Avatar
{
public static function url(?string $email, int $size = 80): string
{
if (blank($email)) {
return asset('img/avatar-placeholder.svg');
}
return Gravatar::url($email, $size);
}
}
Prefer a Blade component over facade calls in views. This is the pattern that survives contact with a real app, because you inevitably need a srcset, a loading="lazy", or a different fallback for mail templates.
{{-- resources/views/components/avatar.blade.php --}}
@props(['user', 'size' => 80])
<img
src="{{ Avatar::url($user->email ?? null, $size) }}"
srcset="{{ Avatar::url($user->email ?? null, $size) }} 1x,
{{ Avatar::url($user->email ?? null, $size * 2) }} 2x"
width="{{ $size }}" height="{{ $size }}"
alt="{{ $user->name }}" loading="lazy" decoding="async"
{{ $attributes->merge(['class' => 'rounded-full']) }}
>
Use it for API resources too, not just Blade. An avatar_url key on a UserResource is one of the cheapest wins you can give a mobile client, and because the URL is computed from md5(email) it is deterministic — safe to cache at the CDN, safe to compare between requests.
Choose the fallback deliberately. The d= parameter is the whole game:
d=mp — mystery person. Safe default, never 404s.d=identicon / d=retro / d=wavatar / d=monsterid / d=robohash — generated geometric avatars, distinct per email but clearly "no photo uploaded".d=404 — returns HTTP 404 for users without a Gravatar. Only use this if you intend to check existence; do not put it in an <img> tag on a page that also needs a fallback, because the browser gets a broken image.d=https://your-app.test/avatars/{hash}.png — your own service. Must be URL-encoded and publicly reachable; see gotchas.Cache the existence check, never the URL. Constructing the URL is free. Verifying that a Gravatar exists is a network round trip per user. If you need "does this user have a real photo" (e.g. to decide between the Gravatar and an initials badge), do it once, off the request path — a queued job, an avatar_checked_at column, a short-TTL cache key like avatar:exists:{md5}. Never call it in a @foreach.
Set defaults once. If your tag ships a config, put size, rating, and the fallback in it, and let call sites pass only what differs. If it does not, define those constants in your Avatar helper. Either way, the day-to-day call site should be Avatar::url($user->email) with no parameters 90% of the time.
The package has been frozen since December 2020 and has very few stars. Its composer.json almost certainly pins illuminate/support to Laravel 5.x–7.x. On Laravel 10/11 you will get a dependency conflict. Two options: add a replace entry / fork the one-file class into app/Support, or skip the package entirely and do 'https://gravatar.com/avatar/'.md5(strtolower(trim($email))) yourself. Because the library is a single md5() plus a query string, this is a genuinely low-risk substitution — do not architect around it. If you keep it, pin the version and note the fork in the upgrade checklist.
Hashing rules. Gravatar's hash is md5 of the email lowercased and trimmed. If you ever build a URL or cache key by hand, replicate that exactly, or the same user gets two different avatars depending on which code path rendered them. Also normalise on write, not on read — one email column, one canonical form.
Privacy is a real concern, not a formality. Every <img> pointing at gravatar.com sends the visitor's IP, User-Agent, and Referer to Automattic, and the md5 hash is a stable identifier across every Gravatar-enabled site. For EU-facing apps this is the kind of thing that belongs in the cookie/third-party disclosure. The clean fix is to proxy and cache the image on your own domain (a small route + Cache::remember + Http::get), which also removes the third-party request from the critical path and lets you set long Cache-Control headers. If you're going to be in production for years, do the proxy.
d=404 existence checks lie under load. Rate limits, timeouts, and transient Gravatar outages all surface as "no avatar," so a false negative permanently downgrades a user to initials if you cache it too aggressively. Use a TTL (a week is fine), and treat network failure as "unknown" rather than "absent."
Custom default images must be encoded. If d= is a URL rather than a keyword, it has to be urlencode()d (https%3A%2F%2F...). Passing a raw URL produces a query string that silently loses the d value, and you'll get the mystery person while debugging your own string building.
Sizing is clamped. Gravatar accepts s from 1 to 2048 and quietly clamps outside that range. Ask for 2x your CSS size for retina (e.g. s=160 for an 80px circle); asking for 4096 gives you 2048 and no error.
The r= rating filter is a filter, not a guarantee. g/pg/r/x lets you cap content, but it depends on what users uploaded; treat it as a best-effort content policy, not a moderation system.
Never hit the network in tests. Anything that constructs a URL is safe and unit-testable. Anything that calls an existence check is not — fake it at your own Avatar boundary, or bind a stub into the container. Do not rely on Http::fake() covering this package: it may use cURL or file_get_contents internally, in which case Laravel's HTTP fake never sees the request and CI will flake.
Extension point is the container, not the class. If you need behaviour the package doesn't offer (self-hosted proxy, Libravatar fallback chain, S3-cached copies), bind your own implementation to an AvatarGenerator interface in a service provider and inject that. The package class has too little surface area to be worth subclassing; the seam that pays off is yours, not theirs.
Watch the facade alias. Gravatar is registered as a root alias. If you later introduce your own App\Support\Gravatar or a model with that name, you'll get confusing "class not found" or wrong-class resolution in views. Since Laravel 9 the alias config has moved anyway — if auto-discovery gets disabled or the alias silently stops resolving, check config/app.php aliases before assuming the package is broken.
How can I help you explore Laravel packages today?