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

Geopattern Laravel Package

redeyeventures/geopattern

Generate unique geometric SVG background patterns from any string. This PHP port of GeoPattern offers multiple pattern types and color themes, making it easy to add lightweight, deterministic decorative backgrounds to Blade views or any PHP app.

Deep Wiki
Context7

Getting Started

Install and first render. The package is framework-agnostic — one composer package, no service provider or migrations required:

composer require redeyeventures/geopattern

The entire public surface is a single class. Look at src/GeoPattern.php first (constructor, setBaseColor(), toSvg()), then src/Generator/ to see the 15+ pattern types (Chevrons, Hexagons, Plaid, SineWaves, Triangles, Xes…). You don't pick a generator — it's chosen deterministically by hashing the seed string.

use RedeyeVentures\GeoPattern\GeoPattern;

$pattern = new GeoPattern($user->name);
$pattern->setBaseColor('#4f46e5');   // optional; brand color or a model attribute

$svg = $pattern->toSvg();            // raw SVG string
// or, if available in your version:
$uri = $pattern->toDataURI();

Your first use case: a deterministic avatar fallback. This is what the package is actually good for. Users without an uploaded avatar get a stable, unique-looking tile derived from their name or email — same input, same image, forever, with zero storage and zero queued jobs. Drop it into a Blade component and you're done:

{{-- resources/views/components/geo-avatar.blade.php --}}
@props(['seed', 'color' => null, 'alt' => ''])

@php
    $pattern = new \RedeyeVentures\GeoPattern\GeoPattern((string) $seed);
    if ($color) {
        $pattern->setBaseColor($color);
    }
    $svg = $pattern->toSvg();
@endphp

<img src="data:image/svg+xml;base64,{{ base64_encode($svg) }}"
     alt="{{ $alt }}" {{ $attributes->merge(['class' => 'rounded-full']) }}>
<x-geo-avatar :seed="$user->email" :color="$team->brand_color" class="h-10 w-10" />

That's the whole day-one loop. Everything below is about doing it at scale without bloating your HTML or fighting the cache.

Implementation Patterns

1. Model accessor + cache keyed on the seed. Generation is cheap but not free, and lists of 50 avatars will re-hash and re-build the same SVG on every request. Since output is pure-function-deterministic, cache aggressively and key on what actually changes the output (seed + base color + package version), not the model ID alone:

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Facades\Cache;

protected function patternSvg(): Attribute
{
    return Attribute::get(function () {
        $seed  = $this->email;
        $color = $this->team?->brand_color;
        $key   = 'geopattern:' . md5($seed . '|' . $color . '|' . self::PATTERN_VERSION);

        return Cache::remember($key, now()->addWeek(), function () use ($seed, $color) {
            $p = new GeoPattern($seed);
            if ($color) {
                $p->setBaseColor($color);
            }
            return $p->toSvg();
        });
    });
}

Define PATTERN_VERSION as a constant and bump it when you upgrade the package — that's your invalidation lever for the determinism gotcha below.

2. Serve as a route, not a data URI, once you have many of them. Base64 data URIs inflate each <img> by ~33% and defeat browser caching across pages. For anything beyond a handful, expose a signed/statically-cacheable endpoint:

Route::get('/pattern/{hash}', function (string $hash) {
    $svg = Cache::rememberForever("geopattern:svg:{$hash}", function () use ($hash) {
        // resolve seed/color from a lookup table or a cached map
        return (new GeoPattern($seed))->toSvg();
    });

    return response($svg, 200, [
        'Content-Type'  => 'image/svg+xml',
        'Cache-Control' => 'public, max-age=31536000, immutable',
    ]);
})->where('hash', '[a-f0-9]{32}');

Because the hash is derived from md5(seed|color|version), the URL changes automatically when any of those change — hence immutable. This is the right pattern for category tiles, project cards, and anything that appears in a @foreach over more than ~20 rows.

3. Pre-generate to disk/S3 for email and Open Graph. Data URIs are stripped by most email clients, and OG image consumers (Slack, Twitter, Facebook) generally won't fetch SVGs at all. For those surfaces, write the SVG at creation time and reference it by URL:

// in an observer or afterCreate hook
$svg  = (new GeoPattern($project->slug))->toSvg();
$path = "patterns/{$project->id}.svg";
Storage::disk('public')->put($path, $svg);
$project->update(['pattern_path' => $path]);

If you genuinely need a PNG (OG cards, PDFs, thumbnails), rasterize in a queued job with Imagick — the package outputs SVG only, and that boundary is deliberate.

4. Derive the base color, don't hardcode it. The package shifts your base color into a background/fill palette. Feeding it $team->brand_color or a stored hue gives you a coherent family of tiles that all look like they belong to your product — which is the actual design win over a generic identicon library.

5. Wrap it in a tiny service so tests can fake it. Nothing here is injectable by default, so bind your own thin wrapper (GeoPatternService::for(string $seed, ?string $color)) in the container. It gives you a single place to swap to a queue, to add the version constant, and to ->fake() in feature tests when you don't want to assert on SVG bytes.

Gotchas and Tips

Determinism is only as stable as your lockfile. The pattern for a given seed is guaranteed only for a given package version, and the last release was March 2021. A composer update that bumps a patch can silently change every avatar, every cached tile, and every image already shared in Slack. Pin the version with an explicit constraint ("redeyeventures/geopattern": "1.0.*"), and if you ever do upgrade, treat it as a visual migration: bump the version constant, invalidate the cache namespace, and regenerate stored files.

PHP 8 compatibility needs verification. With a 2021 release, check composer.json's php constraint and run the suite on your actual PHP version before adopting. If it's tight, an --ignore-platform-req is not a fix; test the generators you actually use.

Validate the base color before it reaches the SVG. The seed is hashed, never emitted — that's safe. But setBaseColor() interpolates your string into the SVG fill attributes. If a color value can ever come from user input or a loosely-typed admin field, whitelist it with a regex (/^#[0-9a-f]{6}$/i) or cast to a known palette. Never pass raw $request->input('color').

Data URIs vs. CSP. If you ship a strict Content-Security-Policy, img-src must allow data: or every fallback avatar turns into a broken-image icon that only shows up in production. The route approach sidesteps this entirely and is another reason to prefer it.

Contrast is your problem, not the package's. The generated fill palette sits at whatever lightness the hash produces. White initials or text overlaid on the tile will be unreadable for a meaningful fraction of seeds. Either put a semi-transparent scrim between them, or generate a large sample of seeds and pick a base color whose derived palette stays in a predictable range for your brand.

Inline SVG (not <img>) when you need to style it. An <img>-embedded SVG is a black box — parent CSS can't recolor it, and currentColor won't inherit. If you need hover states or dark-mode variants, echo {!! $pattern->toSvg() !!} directly into the DOM and target the generated elements, or just keep the <img> and accept a fixed palette (usually the right call).

Don't generate in a loop inside a view without a cache. The generator does a lot of string concatenation and hash math; at 100+ tiles per page it becomes measurable, and it re-runs on every request. The Cache::remember accessor pattern above costs you ten lines and removes the entire problem.

Watch for accidental seed collisions. Seeding off $user->id produces visually unrelated tiles as IDs increment, but seeding off a short field like $user->name means two "John Smith"s get identical avatars. Seed off something unique (email, uuid) and keep the display name separate.

Extension point: the generator registry. If none of the stock patterns fit your brand, the cleanest customization is a subclass or a local copy of a generator in src/Generator/ rather than post-processing the SVG string with regex. The latter breaks the moment upstream changes its element ordering.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route