Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Gravatar Laravel Package

tomshaw/laravel-gravatar

Gravatar avatars, profiles, and QR codes for Laravel. Zero config to start, configurable when you need it, with a Blade directive and component for rendering, a facade and fluent builder for URLs, plus model support, profiles, QR codes, and hashing.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install — nothing else.

composer require tomshaw/laravel-gravatar

No provider registration, no migration, no config publish. Defaults are already sane (size 80, mp default image, rating g, secure URLs, SHA256 hashing).

2. Verify the requirement floor before you commit. The README lists PHP 8.5 and Laravel 13.0. This is the single biggest adoption gate — on a PHP 8.3/Laravel 11 app this package simply will not install. Check composer.json constraints on the repo/packagist before promising it to a team.

3. First use case: a global avatar in a layout. Reach for the component first — it emits the entire <img> and handles things a URL string cannot:

<x-gravatar :email="auth()->user()->email" :size="40" class="rounded-full" />

The directive is the fallback for when you own the markup string (e.g. inside a src attribute you're building conditionally):

<img src="@gravatar(email: $user->email, size: 60, default: 'retro')" alt="">

4. Where to look first.

  • Publish the config immediately so defaults are visible and diffable in code review — don't run blind on package defaults:
    php artisan vendor:publish --tag=gravatar-config
    
  • Read the Parameter reference table. It is the contract; everything else is sugar.
  • Sanity-check with zero network cost: Gravatar::src('[email protected]') and Gravatar::hash('[email protected]') are pure string building. Only has() and profile() touch HTTP.

5. The 60-second smoke test (no Gravatar account needed):

use TomShaw\Gravatar\Facades\Gravatar;

Gravatar::src('[email protected]', size: 200, default: 'retro'); // string
Gravatar::for('[email protected]')->size(200)->identicon();     // Stringable
Gravatar::qrCode('[email protected]', size: 350);               // URL

Implementation Patterns

Pick the right layer for each context

Context Use
Blade layouts, tables, comment lists <x-gravatar /> — forwards attributes, adds 2x srcset, has onerror fallback
Custom markup you already control @gravatar directive (URL only)
PHP: mailables, API resources, notifications, jobs, JSON payloads Gravatar::src() / fluent builder
Domain objects with an email column HasGravatar trait

The decision rule: if you want the <img> tag, use the component; if you want a string, use the facade or builder. Mixing the two (building URLs by hand and string-concatenating parameters) throws away the validation that makes this package worth having.

Model integration with a non-standard email column

Attach the trait, then align the column once in config (or per model if you have outliers):

class User extends Authenticatable
{
    use HasGravatar;

    protected function gravatarEmailColumn(): string
    {
        return 'contact_email';
    }
}
$user->gravatarUrl(size: 200, default: 'retro');
$user->gravatarProfileUrl();
$user->gravatarQrCode(size: 350);

Keep the gravatar-prefixed method names. Never add a gravatar() method to the trait or model — Eloquent routes bare property access through relations and mutators, so $user->gravatar (and therefore any $user->gravatar->something) blows up with a LogicException. When you want the fluent builder on a model instance, start from the facade:

Gravatar::for($user->email)->size(120)->robohash()->url();

Standardise sizes in one place

Avatar sizes proliferate and drift (48 here, 50 there, w-12 in CSS but size: 60 in PHP). Define them once:

enum AvatarSize: int
{
    case Nav = 32;
    case List = 48;
    case Profile = 200;
}
<x-gravatar :email="$user->email" :size="AvatarSize::List->value" class="size-12 rounded-full" />

Because the component also sets width/height attributes from size, this keeps the rendered box and the source image in sync and avoids layout shift.

Treat profile lookups as expensive by design

has() and profile() are the only network calls, and they cache — including misses. So:

  • Don't call Gravatar::profile() inside a Blade loop without pre-warming or chunking; a 50-row table is 50 potential cache hits/misses.
  • Prefer batch enrichment in a queued job or a Cache::remember wrapper in your own service for pages that need full profiles.
  • The built-in TTL (3600s) is the delay before a user who just registered a Gravatar starts showing one. If that matters, shorten the TTL or expose a "refresh my avatar" action that forgets the cache key.
$profile = Gravatar::profile($user->email); // ?Profile
$title = $profile?->jobTitle ?? $user->title; // always null-safe

Enrich UI with profile data, degrading gracefully

Profiles work with no API key, but Gravatar withholds fields from anonymous callers (timezone, languages, contact info, links, interests, payments, gallery) and caps verified accounts at four. Treat every optional field as absent-by-default and reach for get() for anything not surfaced as a property:

$profile?->displayName;
$profile?->verifiedAccounts;   // may be truncated without a key
$profile?->get('timezone');    // null for anonymous callers
$profile?->toArray();          // inspect the raw payload when a field "doesn't exist"

Set GRAVATAR_API_KEY in .env only if you actually render those fields — the key buys you full payloads and a higher rate limit.

Use the fluent builder for conditional / user-driven choices

The builder is the nicest fit when the default image depends on your own data, since each Gravatar default has a shorthand:

$url = Gravatar::for($user->email)
    ->size(200)
    ->when($user->has_avatar, fn ($b) => $b->mp(), fn ($b) => $b->initials())
    ->rating('pg')
    ->url();

It's Stringable, so it echoes straight into Blade without ->url():

<img src="{{ Gravatar::for($user->email)->size(120)->identicon() }}" alt="">

Also useful for pre-flight checks before committing to a layout:

if (Gravatar::for($email)->exists()) { /* ... */ }

Fake the network in tests, leave URLs alone

Gravatar::fake() replaces has() and profile() with canned data. URL building is untouched — correctly, because it never hit the network.

$fake = Gravatar::fake(
    profiles: ['[email protected]' => ['display_name' => 'Ada Lovelace']],
    existing: ['[email protected]'],
);

Two things to internalise:

  1. Emails listed in profiles also report as existing — you don't need to duplicate them in existing.
  2. Assert against lookups, not just return values, when the point of the test is "we didn't hammer the API":
$fake->assertLookedUp('[email protected]');
$fake->assertNothingLookedUp();

Put Gravatar::fake() in a shared test setup so a stray profile call in a feature test can't turn into a real HTTP request in CI.

QR codes for sharing surfaces

Useful beyond novelty: profile cards, printed badges, invite screens, "share my card" modals.

Gravatar::qrCode($user->email, size: 350, version: 3, type: 'gravatar');

version is 1 (standard) or 3 (modern dots); type is default, user (their avatar in the centre), or gravatar (the logo).

Gotchas and Tips

default and fallback are not the same thing — and you probably want both. default is rendered by Gravatar when the address has no avatar, so it still requires Gravatar to be reachable. fallback is what the browser shows when the request fails outright (DNS, outage, dropped connection). Configure fallback as a local asset path and it resolves through asset():

'fallback' => '/img/avatar-placeholder.png',

forceDefault is a string in some places and a boolean in another. The directive / src() take 'y' or 'n'; the component's force-default prop is a real boolean. Passing true to src(forceDefault: true) or "y" to the component will not do what you expect — and the parameter validator may reject it.

Validation throws — plan for it. Anything outside the accepted parameter set raises InvalidArgumentException. That's a feature for typos in your own code, but it's a production hazard if size, rating, or default ever come from a request parameter. Clamp and whitelist at the boundary:

$size = max(1, min(2048, (int) $request->integer('size', 80)));

Hashing: SHA256 is the default and it's the right one. Gravatar now specifies SHA256 and explicitly warns against MD5 (reversible, leaks the address). Use MD5 only if you have pre-existing URLs generated the old way — Gravatar::for($email)->md5()->url() or a global 'hash' => 'md5'. Don't add a "let the user pick the hash" option; there's no legitimate UI reason.

Retina srcset silently disappears above 1024px. The component skips the 2x variant when doubling would exceed Gravatar's 2048px ceiling. If a large hero avatar looks soft on a retina display, that's why — it's correct behaviour, not a bug.

Cached misses mean a new Gravatar account won't appear immediately. Because has() and profile() cache negative results, a user who just signed up at gravatar.com keeps showing the default image until the TTL expires. Either accept the hour, shorten gravatar.cache.ttl, or provide an explicit bust path. Setting ttl to 0 disables caching entirely — do that only in local/dev, or you'll hammer the API on every page.

Normalise emails before they reach the package. Trim it, and be aware Gravatar's identity is the lowercased, trimmed address. If you store mixed-case emails and the user registered with lowercase, you'll get a miss you can't explain from the UI. Normalise at the model/accessor level rather than sprinkling strtolower() at every call site.

Publishing the view is a maintenance commitment. php artisan vendor:publish --tag=gravatar-views gives you the <img> markup, but from then on you own it — package updates to that view won't reach you, including the srcset and onerror logic. Publish only if you genuinely need different markup, and keep the diff minimal so you can eyeball future changes. class and other attributes already forward through, so most styling needs no publish at all.

profile() returns null, not an empty object. Always ?->. Likewise, fields withheld from anonymous callers are null — a $profile->location that "doesn't work" is usually "no API key", not a bug.

Clear config cache after publishing (php artisan config:cache in deploy) — a published config that's been cached will shadow any .env change to GRAVATAR_API_KEY.

Low bus factor — put a seam around it. With one star and zero dependents, this package is a single-maintainer dependency. That's fine for avatars (trivially replaceable), but only if you don't let it leak into fifty Blade files. Consider a thin app-level helper (Avatar::url($user, AvatarSize::List)) that wraps the facade, so swapping the package later is one file rather than a codebase-wide grep. Avoid depending on TomShaw\Gravatar\... classes outside that seam — Profile is the one you're most likely to type-hint, and it's the one you'd have to reimplement.

Extension points, ranked by leverage: published config → the gravatarEmailColumn() override on models → published view → your own wrapper service. There's no custom default-image registration hook, so anything more exotic (S3-backed avatars, third-party providers) belongs in your wrapper, not in the package's config.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource