laravolt/avatar
Generate unique, colorful initials-based avatars for any user in Laravel or plain PHP. Output as base64 data-URI directly in views, save to PNG/JPG with custom quality, or use Gravatar. Customizable config, auto-discovery, works with Laravel 10-13 and PHP 8.3+.
laravolt/avatar is a single-purpose utility: it turns an arbitrary string (name, email, username) into a deterministic initials avatar, either rasterised (PNG/JPG via Intervention Image) or as SVG, plus a Gravatar URL builder. There are no migrations, no models, no views, and no HTTP routes — it is a renderer with a fluent chain. That makes adoption almost free, and also means every integration decision (caching, storage, fallback) is yours.
composer require laravolt/avatar
php artisan vendor:publish --provider="Laravolt\Avatar\ServiceProvider" # optional, config/laravolt/avatar.php
Auto-discovery registers the service provider and the Avatar facade. Check three things before writing code:
^6.5.gd. Confirm the GD extension (with FreeType) or Imagick is actually compiled into your PHP; without FreeType, imagettftext-style rendering silently fails.fonts array uses __DIR__-relative paths. After publishing to config/laravolt/avatar.php, verify they still resolve (php artisan tinker → Avatar::create('Test')->toBase64()), otherwise replace them with absolute paths or copy TTFs into storage/fonts.First use case — a Blade fallback for users without an uploaded photo:
@if ($user->avatar_path)
<img src="{{ Storage::url($user->avatar_path) }}" alt="{{ $user->name }}">
@else
<img src="{{ Avatar::create($user->name)->toBase64() }}" alt="{{ $user->name }}">
@endif
Model accessor / API resource — centralise the chain. Never scatter Avatar::create(...) calls through views; wrap it once so config, sizing, and caching policy live in one place.
// app/Support/AvatarPresenter.php
public static function url(User $user, int $size = 100): string
{
if ($user->avatar_path) {
return Storage::disk('public')->url($user->avatar_path);
}
return Avatar::create($user->name ?: $user->email)
->setDimension($size)
->setFontSize((int) ($size * 0.48))
->toBase64();
}
Two rendering strategies — pick deliberately.
| Strategy | When | Cost |
|---|---|---|
toBase64() inline in HTML |
Small/medium apps, dashboards, email-free contexts | Bytes in every response; no HTTP request; no cache headers |
save() to disk + CDN URL |
Medium/large, lists, mobile apps | One-time generation, then zero runtime cost |
Avatar::create($user->name)->save("avatars/{$user->id}.png");
Avatar::create($user->name)->setDimension(400, 400)->save("avatars/{$user->id}.jpg", 90);
$user->update(['avatar_path' => "avatars/{$user->id}.png"]);
Backfill in a queued job with chunkById, not each() — rendering is CPU-bound and you don't want 50k avatars generated inside a web request.
Gravatar first, initials as fallback. toGravatar() only builds a URL — it never checks whether the hash exists. Use the d= parameter for the fallback, or do a client-side onerror swap to the initials avatar:
$url = Avatar::create($user->email)->toGravatar(['d' => '404', 'r' => 'pg', 's' => 200]);
// d=404 => Gravatar returns 404 when no avatar exists, so the browser onerror fires
SVG when you want themes to be cheap. toSvg() returns markup you can inline, so you can recolour with CSS, avoid a raster round-trip, and keep it crisp at any size:
{!! Avatar::create($user->name)->setResponsive()->toSvg() !!}
Combine with setTheme(['grayscale-light', 'grayscale-dark']) for consistent palettes per UI context, and setFontFamily('Laravolt') when you have loaded the font via @font-face or Google Fonts — SVG text renders in the browser's font context, so an unloaded family silently falls back.
Runtime overrides over config edits. Prefer chaining (setDimension, setBackground, setShape('square'), setBorder(1, '#aabbcc', 10), setChars(3)) so a single config stays sane across contexts. setChars() matters for single-word names and CJK, where the first two characters are often meaningless.
Escape hatch. getImageObject() returns the Intervention image instance — use it to add a status dot, watermark, or stream directly to S3:
$image = Avatar::create($user->name)->setDimension(256)->getImageObject();
Storage::disk('s3')->put("avatars/{$user->id}.png", (string) $image->encode());
backgrounds or themes reshuffles every existing user's colour. Treat the colour palette as data you shouldn't casually edit.cacheKey() bug where toBase64() computed the key before buildInitial(), so every avatar shared an empty-initials key. If you're on 6.x and seeing identical/cross-contaminated avatars under load, upgrade. When you change config, clear your app cache too — the internal cache and yours can disagree.theme => ['*'] means "any defined theme", not "no theme". With the shipped config, a user might land on grayscale. If you want a single consistent look, set one theme explicitly, or delete the themes you don't want from themes.save('x.png', 100) ignores quality; save('x.jpg', 90) honours it. Format is inferred from the extension — no format parameter.ā, Ě, ǽ render as tofu unless the TTF covers them. Set 'ascii' => true to transliterate via Stringy (unmappable characters are dropped). Do this before your first user registers, not after.uppercase defaults to false. Many teams expect JW, get jw. Set it in config rather than calling strtoupper() yourself — and remember it affects the base64 cache key.setBorder(..., $radius) and shape roundness behave differently between raster and SVG; if your design demands a rounded PNG, crop/round the Intervention image yourself.{!! ... !!} bypasses Blade escaping, and setFontFamily() is interpolated into the markup. Never pass user-controlled input to setFontFamily() or any colour setter; keep them hardcoded constants or config values.text column of these for 100k users is a self-inflicted wound. Store a path (or nothing) and generate.str_starts_with($out, 'data:image/png;base64,'), or that Storage::exists() is true.config/laravolt/avatar.php, run php artisan config:cache again; otherwise your font or theme change appears to be ignored.$app->register(Laravolt\Avatar\LumenServiceProvider::class); — auto-discovery doesn't apply.new Avatar($configArray) works with an ordinary array; useful in scripts, seeders outside Laravel, or a small worker that pre-renders avatars.Overall assessment: a low-risk, high-leverage utility. Its surface area is one facade and ~15 chainable setters, the API has been stable since 3.x, and the maintenance focus is dependency currency (Intervention v4, Laravel 13) rather than features. The real work is architectural, not API-level — deciding generate-on-demand vs. persist-to-disk, keeping the colour palette stable, and making sure the image driver and font paths exist in every environment (local, CI, container image). Get those three right on day one and you'll never think about this package again.
How can I help you explore Laravel packages today?