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

Blurhash Laravel Package

bepsvpt/blurhash

A Laravel/PHP implementation of BlurHash for generating compact image placeholders and decoding them into tiny blurred previews. Add smooth loading states to media-heavy apps with simple, framework-friendly APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer; Laravel auto-discovers Bepsvpt\Blurhash\BlurhashServiceProvider and registers the Blurhash facade.

composer require bepsvpt/blurhash
php artisan vendor:publish --provider="Bepsvpt\Blurhash\BlurhashServiceProvider"

First things to open:

  1. config/blurhash.php — controls the default components_x / components_y (the "resolution" of the hash) and related encode/decode defaults. Tune 4x3 or 4x4 for most photos; lower for portraits, higher for detailed scenes.
  2. The Bepsvpt\Blurhash\Facades\Blurhash facade — the two methods you will actually call are encode() and decode().

First use case — encode an uploaded image and store the hash next to the record:

use Bepsvpt\Blurhash\Facades\Blurhash;

$hash = Blurhash::encode($request->file('photo')->getRealPath());
// "LEHV6nWB2yk8pyo0adR*.7kCMdnj"

Persist that string on your images / posts / users table, ship it to the client, and decode it there with blurhash-js (or Blurhash::decode() on the server, see below) to paint a placeholder while the real image loads.

Implementation Patterns

Store the hash on the model, not in cache. Blurhashes are deterministic — regenerating them on every page view is wasted CPU. Add a blurhash string column, populate it once at upload time, regenerate only when the source image changes.

// migration
$table->string('blurhash', 64)->nullable();

Hook it into the upload pipeline, not the controller. A model observer or an event listener keeps it out of request handlers and works for console/queue imports too:

class ImageObserver
{
    public function saving(Image $image): void
    {
        if ($image->isDirty('path') && $image->path) {
            $image->blurhash = Blurhash::encode(
                Storage::disk($image->disk)->path($image->path)
            );
        }
    }
}

Do the encode on a queue. Encoding is a full image decode plus a DCT — 50–300 ms for a typical photo, more for large ones. For user-facing uploads, dispatch a job, save the model without the hash, and let the job update it:

dispatch(function () use ($image) {
    $image->update(['blurhash' => Blurhash::encode($image->absolutePath())]);
})->afterResponse();

Expose it in API resources / Inertia props alongside the real URL so the frontend can render in one round-trip:

return [
    'url'       => $image->url,
    'blurhash'  => $image->blurhash,
    'width'     => $image->width,
    'height'    => $image->height,
];

Server-side decode for <img src> placeholders. When you cannot (or do not want to) run a JS decoder, Blurhash::decode() returns an encoded image you can inline as a data URI, making the placeholder a zero-round-trip, cacheable asset:

$placeholder = 'data:image/png;base64,' . base64_encode(
    Blurhash::decode($image->blurhash, 32, 32)
);

Keep those decode dimensions tiny (32–64 px) — the browser upscales and applies the blur via CSS. Do not decode to the real display size.

Blade usage with a JS decoder (most flexible, no server CPU):

<div class="relative">
    <canvas
        data-blurhash="{{ $image->blurhash }}"
        class="absolute inset-0 h-full w-full blur-xl scale-110"
    ></canvas>
    <img src="{{ $image->url }}" onload="this.previousElementSibling.remove()" />
</div>

Combine with Spatie Media Library by registering the conversion pipeline on a custom MediaObserver, or simply read $media->getPath() inside a job after the media has been added. Do not compute the hash on a URL — always resolve it to a local path first.

Backfill command for existing rows. Because the operation is idempotent, a small chunked Artisan command is safe to run and re-run:

Post::whereNull('blurhash')->chunkById(100, function ($posts) {
    foreach ($posts as $post) {
        $post->update(['blurhash' => Blurhash::encode($post->imagePath())]);
    }
});

Wrap it in a small service class (e.g. ImagePlaceholderService) that resolves disk → local path, checks the file is readable, resizes if needed, and returns ?string. That is the seam you want for tests and for swapping drivers later.

Gotchas and Tips

  • encode() needs a local filesystem path, not a URL or a stream. For S3/R2/Spaces you must download to a temp file first (Storage::disk('s3')->readStream() into tempnam()), or use Storage::path() only for the local disk. Passing a URL is the single most common first failure.
  • It returns null on failure rather than always throwing. Guard against null before writing to the DB, and log the context (file size, mime) — otherwise you will silently get empty placeholders on a subset of uploads.
  • GD or Imagick must be installed and compiled with the right codecs. WebP/AVIF support depends on your PHP build. Decode failures on AVIF are common on older images. Check php -m and gd_info() first if everything else looks right.
  • Resize before encoding. BlurHash only needs a handful of components; encoding a 4000×3000 JPEG wastes memory and can hit memory_limit on shared hosting. Downscale to ~64×64 with Intervention Image or Imagick first, then encode the thumbnail.
  • Respect the component budget. BlurHash allows 1–9 components per axis and roughly 9 total (e.g. 4x3 = 12 is already at the practical edge). The library validates this; passing components_x => 8, components_y => 8 throws. 4x3, 4x4, 5x4, 3x3 are the sane defaults. More components = sharper placeholder but a longer string and heavier decode.
  • EXIF orientation is not transparently handled. A phone photo stored sideways will produce a hash that looks rotated relative to the displayed image after the browser applies EXIF rotation. Normalize orientation in your upload pipeline before hashing, or the placeholder will visibly snap when the real image arrives.
  • Column size: hashes are typically ~30 characters but the length varies with component count. string(64) is cheap insurance; string(255) costs nothing and avoids future truncation surprises.
  • Deterministic = cheap to cache and dedupe. Two identical images produce identical hashes. If you have a media library with deduplication, hash once per file, not per model. This also makes a blurhash value a safe cache key for a derived placeholder.
  • The hash is not security-sensitive and not reversible. It is a lossy visual signature only — safe to expose in public APIs; never treat it as a fingerprint of the original file.
  • Config caching. If you tune components_x / components_y in config/blurhash.php, remember php artisan config:cache in production. Forgetting this is the usual reason "my config change did nothing".
  • Extension point: to change the pixel source (e.g. feed it a stream, or a crop from an editor), pre-process with Intervention Image and pass the temp file path to encode(). Forking the package for custom drivers is rarely worth it — the facade is thin enough to wrap.
  • Testing: there is no fake or driver swap. Commit a small fixture image (tests/Fixtures/sample.jpg) and assert on the returned string shape (is_string, length, no exceptions) rather than the exact hash, since the hash will change if you bump components or replace the library.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
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