tomshaw/laravel-gravatar

Gravatar avatars, profiles, and QR codes for Laravel.
9,466 1
Install
composer require tomshaw/laravel-gravatar
Latest Version:v0.8.0
PHP:^8.5
License:MIT
Last Updated:Jul 17, 2026
Links: GitHub  ·  Packagist
Maintainer: Tom Shaw

Laravel Gravatar 📸

Gravatar avatars, profiles, and QR codes for Laravel — zero config to start, configurable when you need it.

GitHub Workflow Status issues forks stars GitHub license


Installation

composer require tomshaw/laravel-gravatar

That's everything — the package auto-registers and every default is sensible. Publish the config only if you want to change them:

php artisan vendor:publish --tag=gravatar-config

Rendering avatars

The directive

@gravatar outputs a URL, so it goes inside a src attribute you write yourself:

<img src="https://raw.githubusercontent.com/tomshaw/laravel-gravatar/HEAD/@gravatar(email: 'email@example.com', size: 60, default: 'retro', rating: 'g')" />

Every parameter is named and optional except email.

The component

<x-gravatar /> emits the whole <img> tag. Because it owns the markup, it can do three things the directive structurally cannot: forward your attributes, add a 2x srcset for high-density displays, and swap in a local image when the browser can't reach Gravatar at all.

<x-gravatar email="{{ $user->email }}" :size="60" default="retro" class="rounded-full" />
<img src="https://secure.gravatar.com/avatar/2a53…8d6dd3.jpg?s=60&d=retro&r=g&f=n"
     srcset="…?s=60&d=retro&r=g&f=n 1x, …?s=120&d=retro&r=g&f=n 2x"
     class="rounded-full" width="60" height="60" />
Prop Default Notes
email Required.
size config Also sets width/height, which your own attributes override.
default rating secure extension hash config As per parameter reference.
force-default false A real boolean here, rather than the directive's 'y'/'n'.
retina true Emits the 2x srcset. Skipped automatically when doubling would exceed Gravatar's 2048px ceiling.
fallback config Local image swapped in via onerror. A relative path resolves through asset().

Note that default and fallback solve different problems. default is rendered by Gravatar when an address has no avatar, so it needs Gravatar to be reachable. fallback is what the browser shows when the request fails outright — a dropped connection, an outage, DNS.

Publish the view to change the markup:

php artisan vendor:publish --tag=gravatar-views

Building URLs

The facade

use TomShaw\Gravatar\Facades\Gravatar;

Gravatar::src('email@example.com', size: 200, default: 'retro');
Gravatar::hash('email@example.com');
Gravatar::profileUrl('email@example.com');
Gravatar::qrCode('email@example.com', size: 350);

Gravatar::has('email@example.com');       // bool     — HTTP
Gravatar::profile('email@example.com');   // ?Profile — HTTP

Fluent builder

Gravatar::for($user->email)->size(200)->retro()->url();
Gravatar::for($user->email)->size(200)->rating('pg')->exists();

Each built-in default image is a shorthand — ->mp(), ->identicon(), ->monsterid(), ->wavatar(), ->retro(), ->robohash(), ->blank(), ->initials(), ->color().

The builder resolves to a URL when cast to a string, so it can be echoed straight into Blade:

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

Models

use TomShaw\Gravatar\Concerns\HasGravatar;

class User extends Authenticatable
{
    use HasGravatar;
}
$user->gravatarUrl(size: 200, default: 'retro');
$user->gravatarProfileUrl();
$user->gravatarQrCode(size: 350);

$user->hasGravatar();      // HTTP
$user->gravatarProfile();  // HTTP

The address is read from the gravatar.email_column config value. Override it per-model:

protected function gravatarEmailColumn(): string
{
    return 'contact_email';
}

Every method is prefixed deliberately. Eloquent resolves bare property access through relations and attribute mutators, so a trait method named gravatar() would make $user->gravatar throw a LogicException. Reach for Gravatar::for($user->email) when you want the fluent builder on a model.

Profiles

profile() returns a Profile, or null when the address has no Gravatar account.

$profile = Gravatar::profile('email@example.com');

$profile->displayName;
$profile->location;
$profile->jobTitle;
$profile->company;
$profile->pronouns;
$profile->verifiedAccounts;

$profile->get('timezone');  // any field, straight from the raw payload
$profile->toArray();        // the whole payload

This works with no API key. Gravatar withholds some fields from anonymous callers — timezone, languages, contact info, links, interests, payments, gallery images — and caps verified accounts at four. Set a key to receive full profiles and a higher rate limit:

GRAVATAR_API_KEY=your-key

Keys are free from gravatar.com/developers.

profile() and has() are the only methods that touch the network, so both cache — misses included, so a missing profile doesn't re-request on every page load. Set gravatar.cache.ttl to 0 to disable.

QR codes

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

version is 1 (standard) or 3 (modern dots). type is default (no logo), user (the user's avatar in the centre), or gravatar (the Gravatar logo).

Hashing

Gravatar specifies SHA256 and explicitly says not to use MD5, which is reversible and can leak the address it was built from. This package defaults to SHA256.

MD5 stays available for URLs generated before the change:

Gravatar::src($email, hash: 'md5');
Gravatar::for($email)->md5()->url();

Or set gravatar.hash to md5 globally.

Testing

Gravatar::fake() answers has() and profile() from canned data and makes no HTTP calls. URL building is left alone, since it never hit the network to begin with.

use TomShaw\Gravatar\Facades\Gravatar;

$fake = Gravatar::fake(
    profiles: ['ada@example.com' => ['display_name' => 'Ada Lovelace']],
    existing: ['grace@example.com'],
);

expect($user->gravatarProfile()->displayName)->toBe('Ada Lovelace');
expect(Gravatar::has('grace@example.com'))->toBeTrue();
expect(Gravatar::has('nobody@example.com'))->toBeFalse();

Emails in profiles also report as existing. Lookups are recorded:

$fake->assertLookedUp('ada@example.com');
$fake->assertNotLookedUp('someone@example.com');
$fake->assertNothingLookedUp();

Configuration

return [
    'hash' => 'sha256',            // or 'md5'
    'size' => 80,
    'default' => 'mp',
    'rating' => 'g',
    'secure' => true,
    'extension' => 'jpg',
    'fallback' => null,            // local image for <x-gravatar />
    'api_key' => env('GRAVATAR_API_KEY'),
    'cache' => [
        'store' => null,           // null uses the app default
        'ttl' => 3600,             // 0 disables
        'prefix' => 'gravatar',
    ],
    'email_column' => 'email',     // read by HasGravatar
];

Values passed at a call site always win over config.

Parameter reference

Accepted by src(), the directive, and (minus email) the component.

Parameter Type Default Accepts
email string Required.
size int 80 12048.
default string mp mp, identicon, monsterid, wavatar, retro, robohash, blank, 404, initials, color, or a publicly reachable URL.
rating string g g, pg, r, x.
secure bool true Uses secure.gravatar.com.
forceDefault string n y always loads the default image.
forceExtension string jpg jpg, jpeg, png, gif, webp, or '' for none.
hash string sha256 sha256, md5.

Anything outside these throws an InvalidArgumentException, so a typo fails loudly at render rather than silently producing a dead URL.

Requirements

  • PHP 8.5
  • Laravel 13.0

Contributing

composer test      # Pest
composer analyse   # PHPStan
composer format    # Pint

License

The MIT License (MIT). See License File for more information.