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

Thumbor Php Laravel Package

beeyev/thumbor-php

A Laravel-friendly PHP client for Thumbor, the open-source image processing service. Build signed and unsigned Thumbor URLs fluently, with support for resizing, cropping, filters, and security keys. Works cleanly with Laravel and plain PHP projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Requirements: PHP 8.0+ (2.1.0 dropped 7.x — pin ^2.0.3 if you're stuck on 7.4), Composer, and a reachable Thumbor server with a SECURITY_KEY. The package is a URL builder only; it never talks to Thumbor over HTTP, so nothing works until a Thumbor instance (usually Docker) is running.

composer require beeyev/thumbor-php

Where to look first:

  1. The Thumbor client class — constructor takes the security key and the server base URL.
  2. The fluent builder returned by $thumbor->get($source) — this is where all your day-to-day work happens (resize, fitIn, smartCrop, addFilter, format, quality).
  3. getUrl() (and __toString()), which returns the finished signed URL.

Minimal first use case — turning a stored image path into a signed, resized thumbnail:

use Beeyev\Thumbor\Thumbor;

$thumbor = new Thumbor(
    config('services.thumbor.key'),
    config('services.thumbor.url'), // https://img.example.com
);

$url = $thumbor->get('https://cdn.example.com/products/123.jpg')
    ->resize(600, 400)
    ->format('webp')
    ->quality(80)
    ->getUrl();

Put the two values in config/services.php (or a dedicated config/thumbor.php) and source them from .env:

'thumbor' => [
    'url' => env('THUMBOR_URL', 'http://localhost:8888'),
    'key' => env('THUMBOR_KEY', ''),
],

Then bind it once as a singleton in AppServiceProvider::register() so you inject a typed dependency instead of new Thumbor(...) in every controller:

$this->app->singleton(Thumbor::class, fn () => new Thumbor(
    config('services.thumbor.key'),
    config('services.thumbor.url'),
));

First real feature to ship: product/post thumbnails in a Blade view, because it proves the full chain (config → signature → thumbor → source reachability) in one go.

Implementation Patterns

Wrap it in a small service, not in views. The package API is a fluent URL grammar; if you sprinkle it through Blade templates you've coupled your whole app to it. A thin App\Support\ImageUrl class that accepts (string $source, int $width, int $height, array $options = []) and returns a string pays for itself the first time you change CDN or add a default filter.

Blade usage via a helper or component. Inject the wrapper into a <x-thumb-image> Blade component so templates only speak in image + size:

<x-thumb-image :src="$product->image_url" width="600" height="400" class="rounded" />

Responsive images / srcset. The builder is deterministic, so generating a set of breakpoints is a loop, not a query:

$srcset = collect([320, 640, 1280])
    ->map(fn ($w) => $this->imageUrl->resize($w, null) . " {$w}w")
    ->implode(', ');

This is the single highest-value pattern for Laravel apps — one source image in the DB, N derivatives at render time.

API resources. Emit derived URLs from your JsonResource classes rather than storing them. Store the source path only. Signed URLs are cheap to rebuild and would go stale if you rotate THUMBOR_KEY or move servers.

Default policy object. Thumbor URL params are order-sensitive for caching; centralise them. A ThumborPreset enum-ish set of methods (thumbnail(), hero(), avatar()) that applies fitIn + format('webp') + quality(80) consistently stops every developer inventing their own crop behaviour and fragmenting your CDN cache.

Queues and jobs. Never pass the client or builder into a job payload — pass the source string and scalar params, and rebuild inside the job. The builder holds state and won't survive a sane serialization boundary.

Testing. You're testing strings, not I/O. Assert on the generated URL (snapshot or substring assertions on filter segments). One integration smoke test that Http::get($url)->successful() against a real/dev Thumbor catches filter-name typos that the builder happily accepts.

Local dev. Add a Thumbor service to docker-compose.yml, mount an uploads volume so the server can reach your sample images, and set THUMBOR_URL/THUMBOR_KEY to match its SECURITY_KEY. Also set them in .env.testing so CI doesn't need a live server for URL assertions.

Gotchas and Tips

  • 2.1.0 requires PHP 8.0+. Upgrading is otherwise a no-op at the API level — no method signatures changed, just language-level modernisation and a CI matrix of 8.0/8.2/8.3/8.4/8.5. Check composer.json on any app still on 7.4 before bumping.
  • A mismatched security key fails far downstream. If your key and Thumbor's SECURITY_KEY differ, the URL builds fine and thumbor returns 400/404. Classic causes: trailing whitespace in .env, the key quoted in .env but the quotes surviving, or an empty key on one side only (empty key = unsigned URLs, which work until you enable signing).
  • The source image must be reachable by the Thumbor server. localhost, relative paths, and private S3 objects will 404 even though they work in your browser. If your images live on a private bucket, put Thumbor behind signed S3 URLs or object storage it can read.
  • Don't pre-encode the source URL. Pass the raw URL/path; the client handles encoding and the signature covers the encoded form. Passing an already-urlencoded URL produces a valid-looking but permanently 400ing link — and it's the most common "it works in Postman" bug with this package.
  • Filter/parameter order changes the URL, and therefore your cache key. Standardise argument order in your wrapper or you'll get near-duplicate cache entries for the same visual output.
  • Invalid filter names don't throw locally. addFilter('brigthness', 60) is accepted by the builder and rejected by Thumbor at fetch time. Keep one real smoke test, or validate filter names against a known list in your wrapper.
  • Numeric request input needs casting. Sizes often come from query strings; "0" and 0 mean different things in Thumbor's grammar (0 = auto/original in several params). Cast to int and validate ranges before they hit the builder.
  • env() outside config files is a trap, as always: once php artisan config:cache runs, a runtime env('THUMBOR_KEY') returns null and you get silently unsigned URLs. Read only from config().
  • Server URL formatting. Include the port, use https:// in production (mixed-content blocking is silent until you open devtools), and drop the trailing slash.
  • No Laravel service provider or publishable config ships with it — it's a framework-agnostic client. Don't go looking for vendor:publish; write the six-line binding above.
  • Extension points: subclass or decorate the client to inject default filters/quality, or wrap the builder in a Preset/ImageUrl service as described above. That decorator is also where you'd swap CDN hostnames per environment.
  • Debugging recipe: take the built URL, curl -I it directly, and compare against an unsigned URL (empty key both sides) if it 4xxs. If the unsigned one works, the problem is always encoding or key — never the builder.
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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle