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

Avatar Laravel Package

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+.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  1. Version vs. your stack. 7.x requires PHP >= 8.3 and Intervention Image ^4.0 (Laravel 10–13). 6.x is PHP >= 8.1 / Intervention ^3.0 (Laravel 10–12). On PHP 8.1–8.2 you must pin ^6.5.
  2. Image driver. The config default is gd. Confirm the GD extension (with FreeType) or Imagick is actually compiled into your PHP; without FreeType, imagettftext-style rendering silently fails.
  3. Font paths. The default 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

Implementation Patterns

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());

Gotchas and Tips

  • Colours are deterministic, not random. Background/foreground/font/theme are selected by hashing the input string — the same name always yields the same avatar, which is exactly what you want. The flip side: adding or removing an entry in backgrounds or themes reshuffles every existing user's colour. Treat the colour palette as data you shouldn't casually edit.
  • Base64 output is cached internally. Release 7.0.0 fixed a stale 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.
  • Quality argument is for lossy formats only. save('x.png', 100) ignores quality; save('x.jpg', 90) honours it. Format is inferred from the extension — no format parameter.
  • Non-ASCII is font-dependent. Names with ā, Ě, ǽ 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.
  • Border radius is SVG-only. setBorder(..., $radius) and shape roundness behave differently between raster and SVG; if your design demands a rounded PNG, crop/round the Intervention image yourself.
  • Watch the XSS surface on inline SVG. {!! ... !!} 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.
  • Don't store base64 in the database. A 100×100 PNG data-URI is several KB; a text column of these for 100k users is a self-inflicted wound. Store a path (or nothing) and generate.
  • Don't snapshot the bytes in tests. Encoding metadata (pHYs/DPI) differs between GD and Imagick and across versions — the package itself had to loosen its snapshot tests in 7.0.0. Assert str_starts_with($out, 'data:image/png;base64,'), or that Storage::exists() is true.
  • Config cache. After touching config/laravolt/avatar.php, run php artisan config:cache again; otherwise your font or theme change appears to be ignored.
  • Lumen needs a manual register. $app->register(Laravolt\Avatar\LumenServiceProvider::class); — auto-discovery doesn't apply.
  • Plain PHP is first-class. 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.

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.
mydnic/volet-feature-board-filament-plugin
wmde/email-address
refinery29/test-util
dees040/laravel-api-responses
teamzac/geocoder
ideatica/config-writer
keaton-php/pipe-dream
jacksonwebservices/laravel-user-suite
maksimru/conditional-laravel-debugbar
gismatul/nova-support-chat
gismatul/laravel-support-chat
networkrailbusinesssystems/oracle-api
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
refinery29/php-cs-fixer-config
parsnick/steak