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

Laravel Image Transform Url Laravel Package

ace-of-aces/laravel-image-transform-url

Generate image transformation URLs in Laravel with a fluent API, including resizing, cropping, format conversion, quality, and signed URLs for secure on-the-fly image delivery through your transformation service.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require ace-of-aces/laravel-image-transform-url
php artisan vendor:publish --tag="image-transform-url-config"

Check config/image-transform-url.php first — that is where the driver, signing secret, cache path/disk, allowed formats, and default quality live. Almost every "why does my URL look like that" question is answered by this file.

Pick your driver before anything else. As of v2.1.0 there are three:

Driver Requires Use when
gd PHP GD ext Default, zero extra install, fine for small/medium images
imagick PHP Imagick ext You need finer control over quality/filters already tuned in Imagemagick
vips libvips binary + php-vips ext New in v2.1.0 — large source images, high-throughput endpoints, AVIF/HEIC, low memory ceilings
// config/image-transform-url.php
'driver' => env('IMAGE_TRANSFORM_DRIVER', 'gd'),

First use case — a thumbnail URL in a Blade view instead of a raw storage URL:

<img src="{{ image_transform_url($post->cover_path, ['w' => 640, 'fm' => 'webp']) }}" alt="{{ $post->title }}">

Ship that behind one model accessor before wiring up srcset everywhere — it gives you a single place to change sizes later.

Implementation Patterns

Centralise URL generation in the model, not the view. Transform parameters are presentation decisions that change often; scattering ['w' => 300, 'h' => 300, 'fit' => 'crop'] across templates means a redesign becomes a grep exercise.

class Post extends Model
{
    public function coverUrl(int $width = 640): string
    {
        return image_transform_url($this->cover_path, [
            'w'  => $width,
            'fm' => 'webp',
            'q'  => 80,
        ]);
    }
}

Responsive images via srcset. Generate a small ladder and let the browser choose — this is where the package earns its keep:

<img
    src="{{ $post->coverUrl(640) }}"
    srcset="@foreach([320, 640, 960, 1280] as $w){{ $post->coverUrl($w) }} {{ $w }}w @if(!$loop->last), @endif @endforeach"
    sizes="(max-width: 768px) 100vw, 640px"
>

If the package exposes a Blade component or a fluent builder, prefer it over hand-rolled strings — it keeps the signature/expiry logic in one place.

Signed URLs. If you enable signing, generation and verification must share the same secret. Set it via env, not the published config, so it does not drift between local, staging, and production:

IMAGE_TRANSFORM_SECRET=${APP_KEY}
# or a dedicated secret, as long as every app server and any CDN/edge that re-signs has it

Rotate deliberately: changing the secret instantly invalidates every URL cached by browsers and your CDN.

Cache strategy. On-demand transformation is expensive the first time and cheap afterwards. Two patterns work well:

  • Pre-warm on write — dispatch a job after upload that requests the handful of sizes your layout actually uses. Users never hit a cold transform.
  • Let the edge do it — leave URLs unsigned and immutable (params in the path/query), serve Cache-Control: public, max-age=31536000, immutable, and purge on asset replacement. Immutability is only safe if a given URL always maps to the same bytes, so version the path or filenames.

Driver parity is a test, not a promise. If you switch to vips in production but CI runs gd, you will ship differences in colour handling, chroma subsampling, and default quality. Run at least one visual/dimension assertion per driver, or pin CI to the driver you deploy.

// tests/Feature/ImageTransformTest.php
it('renders the configured driver at the requested width', function () {
    $url = image_transform_url('fixtures/wide.jpg', ['w' => 400]);
    $response = $this->get($url);

    $response->assertOk();
    expect(getimagesizefromstring($response->streamedContent())[0])->toBe(400);
});

Migrating an existing install to vips. Treat it as a rollout, not a config flip: keep the old driver serving existing cached keys, point new uploads at vips, compare output on a sample of real assets, then retire the old driver. Because v2.1.0 is a minor release, both drivers coexist — you can run them side by side via separate config entries or environments without forking.

Also do the Composer bump. v2.1.0 fixes a dependency conflict (PR-linked commit d56196e). If you previously had to constrain a transitive dependency with a conflict block or an inline "replace" to get the package installed, remove that workaround and run composer update ace-of-aces/laravel-image-transform-url --with-dependencies — leaving the pin in place is the most likely way to hit the old resolution problem again on a fresh clone.

Gotchas and Tips

Vips is not a drop-in for GD at the edges. libvips resamples differently (Lanczos by default in most paths) and its quality scale does not map 1:1 onto GD's. After switching, re-check: default q values, EXIF orientation handling, palette/PNG transparency, and animated GIFs (libvips needs explicit handling for animation; GD usually flattens). Output will look fine at 80% and subtly wrong at 60%.

Install order for Vips. PHP extension first, then the native library, then verify — php -m | grep vips says nothing about whether libvips.so loads. Add a boot-time or health-check assertion so the container fails loudly at deploy rather than returning 500s on the first image request:

if (config('image-transform-url.driver') === 'vips' && ! extension_loaded('vips')) {
    throw new RuntimeException('vips driver configured but php-vips extension is missing.');
}

Also pin libvips in your Dockerfile — distro versions vary enough to change output between staging and production.

404s are usually path resolution, not the driver. The package resolves source images against a configured disk/root. A relative path that works with Storage::url() may not resolve here. Log the resolved absolute path on failure; that single line resolves most reports.

Transform params are cache keys. Adding a parameter (or changing whether signing is on) changes the URL, which orphans every cached variant — that is correct behaviour, but it silently doubles your storage until old files are pruned. Check for a prune/clear command before and after a param change, and schedule it rather than running it manually.

Signed URL expiry bites the CDN, not the browser. A short TTL means your CDN re-fetches constantly and can start serving 403s to users from cached error responses. Keep TTL comfortably longer than your edge cache TTL, or leave signing off for public assets and rely on path validation instead.

Debug checklist, in order: (1) is the route registered — php artisan route:list | grep -i image; (2) is the driver actually loaded; (3) does the source path resolve to a readable file; (4) are the storage/cache directories writable by the web user (not just you); (5) does the signature verify — a signature mismatch usually presents as a 403 with no other detail.

Extension points worth knowing. If the package exposes a driver contract or a manager, register a custom driver in a service provider rather than editing vendor config — that is where you put an S3-backed or remote-proxy source, and it survives package upgrades. Likewise, keep any custom fit/crop logic in your own class implementing the contract so a future driver addition does not require you to re-verify bespoke code.

Minor note on the release metadata: v2.1.0's changelog link points at compare/v2.0.0...v2.0.1. If you are auditing what actually changed, diff against the v2.1.0 tag directly — the auto-generated compare range understates the release.

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.
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
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource