saade/filament-facehash
Generate unique, deterministic SVG avatar faces for Filament panels. The same name always produces the same face. A drop-in replacement for default user avatars, no GD or Imagick needed, with customizable size, colors and variants plus a trait for Blade views and notifications.
Architecture fit
Facehash is a presentation-layer fallback, not a data-layer commitment. It contributes three artifacts: a class implementing Filament's AvatarProvider contract (wired through ->defaultAvatarProvider(FacehashProvider::class)), a plugin object carrying the fluent styling surface (size(), variant(), initial(), colors()), and a HasFacehashAvatar trait exposing facehash_avatar_url for Blade, Inertia props, and in-app notifications. There are no migrations, no published config file, no storage bucket, no queue worker, and no GD/Imagick extension in the runtime image — output is a pure-SVG data: URI generated in PHP at render time.
The architecturally decisive detail for a TPM is that defaultAvatarProvider() is the fallback seam. A model already implementing Filament's HasAvatar with an uploaded URL still wins, so Facehash fills a gap rather than replacing a pipeline. That keeps blast radius small, keeps the upload path untouched, and makes rollback a one-line deletion per panel. It is the strongest argument for approving a contained pilot rather than a platform initiative.
Integration feasibility
Integration is two lines in a panel provider plus composer require. Effort is genuinely small: roughly half a dev-day for a pilot panel including CSP verification and a determinism test, and one to two dev-days for an org-wide rollout that adds a centralized plugin factory, a documented name-derivation policy, and CI coverage. Because there is no schema change, no backfill, and no data migration, there is no DBA coordination, no maintenance window, and no dual-write period — the usual cost drivers of a rollout are simply absent.
The one non-code dependency is the security/content layer: avatars are data:image/svg+xml;base64,... URIs and will be silently blocked without img-src data: in the Content-Security-Policy. That work sits with whoever owns middleware, web server config, or the CDN-injected headers — identify that owner before scheduling the pilot, not during it.
Technical risk
saade/facehash, by the same author. Pinning the adapter does not pin the generator. Size the risk accordingly and avoid citing this package as a platform standard.Key questions
defaultAvatarProvider()?img-src data: be confirmed before the pilot goes live?composer update? If yes, we need an explicit constraint on saade/facehash, not just the adapter.AvatarProvider reimplementation that is our exit hatch?alt text on the Blade path, contrast between the initial and the generated palette?Stack fit
Laravel + Filament panels, Livewire-rendered surfaces, and anything that can render an <img src> from a model accessor. It requires no image extension in the container, which is a genuine operational win over GD/Imagick-based avatar libraries — no rebuild of base images, no memory-limit tuning for image work, no additional attack surface from image parsers. It fits the panel, in-app notification, and Blade/Inertia surfaces; it does not fit email.
Migration path
Adoption is additive, not a migration. Sequence the pilot on one low-traffic panel; existing uploaded avatars continue to win through the fallback seam, so no user-visible regression is possible for users who already have photos. Reverting is deleting one line from one provider — no data cleanup, because no data was written. This is about as reversible as a change gets, which is what makes a fast yes/low-commitment decision defensible.
Compatibility
Two verification points, both cheap: Filament version support for defaultAvatarProvider(), and PHP version against the package's constraint. Add an explicit pin on the transitive saade/facehash generator if face stability matters, and run the determinism test in CI so a dependency bump that reshuffles every avatar surfaces as a test failure rather than a helpdesk ticket. Confirm no published config to merge into existing config-cache workflows — there isn't one, so config-cache is unaffected.
Sequencing
?: "user-{$this->id}" fallback so empty names never collide.Facehash::make() factory, determinism test, assertSee('data:image/svg+xml') on a resource test.< or &; verify escaping and initial rendering against the playground.Total: under three dev-days end to end. That is small enough to run inside a single sprint alongside other work.
Maintenance
The ongoing code surface is small — a plugin registration and a factory — and there is no infrastructure to maintain. The real maintenance item is dependency governance: track both packages, pin the generator, and treat a face reshuffle as a cosmetic regression worth a test. Budget for the exit hatch: roughly one class implementing Filament's AvatarProvider that calls the underlying generator and returns a data URI. Knowing that cost — hours, not weeks — is what defuses the single-maintainer concern in an architecture review. If the package stalls, we reimplement the adapter and keep the generator, or replace both with flat initials, which is the acceptable degraded state.
Support
There is no vendor SLA and effectively one maintainer, so internal ownership is not optional. Support-facing artifacts: a one-paragraph note that identical display names produce identical faces by design, that renaming a user changes their face, and a runbook line for "avatars are blank" that checks CSP first, then php artisan optimize:clear and a hard refresh, before anyone reads source. A 30-minute briefing covers the helpdesk. No new on-call dimension: nothing here pages anyone, and nothing degrades user-facing functionality beyond a missing image.
Scaling
Generation is CPU work in PHP on every avatar render, inlined into the response. There is no way to offload it to a CDN — a data URI cannot be cached separately from the document that contains it — so cost scales with rows rendered × faces per row. For a user list this is trivial; for a dense 50-row table it is real payload on every Livewire round trip. Mitigations are cheap and local: shorten the column on wide tables, memoize the accessor on the model instance within a request, and skip the initial where the name is already adjacent. Note the favorable trade: we consume application CPU that would otherwise be image-processing memory, and we add zero storage. For most panels this is a net reduction in resource footprint versus an upload pipeline.
Failure modes
data: URIs — every avatar silently disappears, in production only. Highest-probability, highest-visibility failure; gated in the pilot.jobs table. Rule: pass the user or ID, resolve inside render.AvatarProvider seam; no user is locked out.Ramp-up
Low. A TPM can validate the value proposition in a browser using the public playground in minutes, and a developer reaches productive use in under half a day — the primary source file (FacehashPlugin.php) is the entire day-to-day surface. QA's ramp is the name sweep plus a look at the playground; support's is the briefing note. The absence of migrations, config files, and extension requirements means no environment provisioning step and no "works on my machine" class of setup bug. The realistic ramp-up risk is not learning the package, it is remembering the CSP gate and the name-derivation policy — both of which belong in the rollout checklist rather than in anyone's head.
How can I help you explore Laravel packages today?