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

Croppa Laravel Package

bkwld/croppa

Croppa is a Laravel image thumbnail and cropping package that generates resized, cropped, and cached images through simple URLs. It integrates with Blade and Eloquent, supports retina and quality options, and makes image processing easy and fast.

Deep Wiki
Context7

Getting Started

Install the package and publish its config:

composer require bkwld/croppa
php artisan vendor:publish --provider="Bkwld\Croppa\CroppaServiceProvider"

Laravel's package discovery registers the service provider and the Croppa facade alias automatically. On older skeletons, add Bkwld\Croppa\CroppaServiceProvider to config/app.php and alias Croppa in the aliases array.

Verify your environment before anything else:

  • php -m | grep -E 'gd|imagick' — Croppa needs an image driver; GD is the default.
  • The directory holding your source images (typically public/uploads) must be writable by PHP, not just readable — thumbnails are written next to the originals.
  • Open config/croppa.php first. That file is the whole surface area: driver choice, JPEG quality, PNG compression, whether upscaling is allowed, and any path/permission overrides.

First use case: drop photo.jpg into public/uploads/ and hit /uploads/photo-300x200.jpg in a browser. If a cropped thumbnail appears, you're done. In a Blade view, generate that URL instead of hand-writing it:

<img src="{{ Croppa::url('uploads/photo.jpg', 300, 200) }}" alt="">

Croppa::url($path, $width, $height, $options) is the only API you'll use 95% of the time. The URL it returns encodes everything Croppa needs: source path, target dimensions, and modifier flags. The default behaviour is a hard center crop to exactly the requested dimensions — this is the single most important thing to internalise, because it's the opposite of resize-style "fit inside" semantics that most developers assume.

If you get a 404 instead of an image, look at route ordering: Croppa works by registering a catch-all route that matches thumbnail-shaped URLs. A broad Route::get('{slug}') or a Route::fallback() placed before it will swallow the request. In nginx, confirm try_files falls through to index.php for non-existent files, otherwise Laravel never sees the request.

Implementation Patterns

The URL grammar. Every thumbnail URL is {path}-{width}x{height}-{modifiers}.{ext}. Modifiers are dash-separated and order-independent:

uploads/photo-300x200.jpg              # hard crop, center anchor
uploads/photo-300x200-resize.jpg       # fit inside the box, preserving ratio
uploads/photo-300x200-pad.jpg          # letterbox/pillarbox to exact size
uploads/photo-300x200-q90.jpg          # override global JPEG quality
uploads/photo-300x200-1x1.jpg           # explicit crop anchor

resize and pad are the two you'll reach for constantly. Use resize when the container has a fixed width but flexible height (long-form article images). Use pad when the layout demands rigid uniformity (product grids, avatar tiles) and you can live with background bands. Only use the default crop when the source is known to have a centred subject.

Wrap the facade once. Rather than scattering Croppa::url() calls through views, put a small presenter or accessor on the model that owns the file:

class Photo extends Model
{
    public function thumb(int $w, int $h, array $opts = []): string
    {
        return Croppa::url("uploads/{$this->filename}", $w, $h, $opts);
    }
}

This gives you one place to enforce a consistent quality policy, add a ?v={$this->updated_at->timestamp} cache-buster, and swap in a placeholder when the source is missing (File::exists(public_path(...))).

Responsive images. Build srcset by mapping the same source over a size ladder. Every distinct width×height×modifier combination is a separate generated file, so keep the ladder short — 3–4 breakpoints is usually enough:

<img
  src="{{ $photo->thumb(400, 300) }}"
  srcset="{{ $photo->thumb(400,300) }} 400w,
          {{ $photo->thumb(800,600) }} 800w,
          {{ $photo->thumb(1600,1200) }} 1600w"
  sizes="(max-width: 800px) 100vw, 800px" alt="">

For art direction — where a portrait source needs a genuinely different crop on mobile — use <picture> with distinct crop anchors per source rather than trying to force one crop to work everywhere.

Cache warming. Generation is synchronous on first request. For high-traffic landing pages, walk the known set of URLs during deploy and prime them, either with a small artisan command that file_get_contents() each Croppa::url() output, or by having your build step request them. This turns a 200–800ms cold hit into a static file serve.

Invalidation. Thumbnails are keyed by filename and dimensions, not by content. If you overwrite photo.jpg with a new version under the same name, every existing thumbnail is stale. Hook your model's saved and deleted events and call Croppa::delete($urlOrPath) (and Croppa::deleteAll() for bulk purges). If you'd rather not think about it, the ?v= cache-buster on the URL sidesteps the problem at the cost of orphaned files.

API resources. Return the generated URL from your JSON resources, never the raw storage path — clients shouldn't need to know the URL grammar, and you keep the freedom to change dimensions later.

Gotchas and Tips

No upscaling by default. Asking for 1200x1200 from a 400×400 source will not give you a blurry 1200px file; it gives you the source dimensions (or an error depending on config). Check the upscale flag in config/croppa.php before assuming a missing size is a bug. Defensive UI code should clamp requested dimensions to the source's real dimensions.

Memory ceilings. GD loads the full decoded source into memory. A 6000×4000 upload is ~96MB in RAM before any resizing. If you see blank images or silent failures on large uploads, raise memory_limit for the PHP-FPM pool or constrain upload dimensions at validation time. Imagick is slower but more forgiving here.

Permissions are the #1 failure mode. Directory writable by PHP, but the web server user must also be able to read the generated files. On shared hosting with a separate www-data and deploy user, this is where thumbnails mysteriously 404 after generation appears to succeed. Check the filesystem before you check the code.

Static files shadow the route. Once a thumbnail exists on disk, nginx/Apache serves it without ever invoking Laravel — which is the whole performance win, but it also means config changes (quality, driver) do not affect already-generated files. After changing croppa.php, delete the generated thumbnails or the change appears to do nothing. Same trap applies to swapping the source image.

Croppa is not an access control layer. The originals live in public/ and remain directly fetchable; the thumbnail route only pattern-matches dimensions. If the images are private, Croppa is the wrong tool — you need a signed-URL controller, not a URL convention.

Local filesystem only. The URL-routing model assumes files under your public path. There is no S3/remote-disk driver; for cloud storage you'd have to generate locally and push, or bypass Croppa entirely. Plan for this before you design around it.

Path handling. Filenames with spaces or non-ASCII characters need encoding before they hit the URL, and deeply nested paths make ugly URLs. Sanitising upload filenames on the way in saves a lot of debugging on the way out. Also watch for trailing-slash variations producing duplicate cache entries.

Clean up with the model, not by hand. Orphaned thumbnails accumulate silently — every renamed file leaves a copy behind. Wiring Croppa::delete() into a deleting observer is a two-line change that prevents a disk-usage incident eighteen months later.

Testing. Feature tests that request thumbnail URLs write real files to disk. Use Storage::fake() or an isolated temp public directory, and clean up in tearDown(); otherwise your test suite slowly fills with generated JPEGs. For unit tests, asserting on Croppa::url() output is cheap and doesn't touch the filesystem at all — prefer that.

Extension points are thin, by design. There's no event system or driver interface to hook. Customisation happens through the config file and by wrapping the facade in your own service. If you need per-tenant quality settings or watermarking, that belongs in a decorator around Croppa::url() plus a post-processing step, not in the package.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
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