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

Nova Image Cropper Laravel Package

r64/nova-image-cropper

A Laravel Nova field for cropping images directly in the admin panel. Upload, adjust the crop area, and save precise dimensions for avatars, banners, thumbnails, and other image assets with a simple, intuitive workflow.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the ground first. This package is a Nova field and nothing else — no Nova, no value. Check your Laravel/Nova majors and PHP version against the release's composer.json before installing. Cropping is client-side, so there is no GD or Imagick requirement; the server receives a finished file through Nova's normal upload path.

composer require r64/nova-image-cropper

Hard-pin it ("r64/nova-image-cropper": "1.0.2") rather than floating ^1.0. With 55 stars, zero dependents, and a single maintainer, a floating constraint is how a Nova bump silently breaks you.

Add a column. It's a plain string column — nothing exotic:

Schema::table('products', function (Blueprint $table) {
    $table->string('hero_image')->nullable()->after('name');
});

First use case: a fixed 1:1 avatar. Wire it into a throwaway resource, not a real one:

use R64\NovaImageCropper\ImageCropper;

public function fields(Request $request)
{
    return [
        ImageCropper::make('Avatar')
            ->disk('public')
            ->cropWidth(400)
            ->cropHeight(400),
    ];
}

Run php artisan storage:link once if you're on the public disk; for S3, point ->disk('s3') at a configured disk with explicit visibility.

Where to look first, in order:

  1. vendor/r64/nova-image-cropper/src/ — the field class is small. Read it end-to-end. You'll find the $component name, how the disk resolves by default, and how sizing props reach Vue. Trust this over the README: sizing and preview method names have shifted between releases.
  2. vendor/r64/nova-image-cropper/resources/js/ — the Vue wrapper around Cropper.js. Confirm whether Cropper is bundled or peer-depended, and check its EXIF handling.
  3. dist/ — confirm the field registers via Nova.booting, and whether a publish/rebuild step is expected.
  4. The README last, as a cross-check.

Prove the round-trip before touching real data. Three fixtures on the throwaway resource: a small PNG, a large JPEG, and an EXIF-rotated phone photo. Crop → save → re-edit → save. If that loop is clean, you understand the field's persistence behaviour, which is the thing you actually need to know.

Implementation Patterns

Centralise sizing from day one. The most common long-term defect with this package is dimension drift — crop, preview, and thumb values hard-coded differently in each resource, producing layouts the front-end can't predict. Put the numbers in config:

// config/images.php
return [
    'hero'   => ['crop_width' => 1600, 'crop_height' => 900, 'preview_width' => 400, 'preview_height' => 225],
    'avatar' => ['crop_width' => 400,  'crop_height' => 400, 'preview_width' => 100, 'preview_height' => 100],
    'thumb'  => ['crop_width' => 300,  'crop_height' => 200, 'preview_width' => 150, 'preview_height' => 100],
];

and build the field through a helper so resources never own the numbers:

// app/Nova/Concerns/BuildsImageCrops.php
protected function crop(string $column, string $preset, ?string $label = null): ImageCropper
{
    $cfg = config("images.{$preset}");

    return ImageCropper::make($column, $label ?? Str::headline($column))
        ->disk(config('filesystems.default'))
        ->cropWidth($cfg['crop_width'])
        ->cropHeight($cfg['crop_height'])
        ->previewWidth($cfg['preview_width'])
        ->previewHeight($cfg['preview_height']);
}

Resources become $this->crop('hero_image', 'hero', 'Hero Image'), and the front-end can read the same config/images.php keys. This helper is also the seam that makes the package replaceable — a method rename breaks one file, not fifteen.

Fixed dimensions vs. aspect ratio. Fixed cropWidth/cropHeight is a hard output contract — use it whenever downstream CSS assumes exact pixels (gallery grids, hero banners). Aspect-ratio-only mode suits variable sources where the editor picks framing; then treat the file as variable-dimension and make the front-end tolerate it. Don't mix the two across resources in the same visual family.

Design for non-destructive crops. Verify on your installed version whether the field overwrites the original upload or writes a fresh file. If it overwrites, solve that before rollout, not after. The safe pattern is two columns:

Image::make('Original', 'source_image')->disk('s3')->hideWhenUpdating(),
$this->crop('hero_image', 'hero', 'Hero Crop'),

Everything downstream is plain Nova file handling, so thumbnails, WebP, and responsive variants belong in your pipeline — keep the package at the UI layer and the exit path stays cheap.

Validate the cropped payload. Rules apply to whatever the browser posts, which is the crop, so dimension assertions are trustworthy:

$this->crop('hero_image', 'hero')
    ->rules('required', 'image', 'max:4096')
    ->creationRules('dimensions:width=1600,height=900');

Use dimensions: to assert the preset landed, and max: to catch oversized sources before they hit PHP memory limits.

Multi-field models. Avatar + hero + gallery on one model is fine — separate columns, separate field instances, one preset each. Cache-bust on regeneration (?v={updated_at}) so editors don't see a stale crop after re-uploading.

Testing. Storage::fake() plus a Nova/Livewire field test covers most of it:

Storage::fake('public');
// post the cropped blob through the resource update endpoint
$image = getimagesize(Storage::disk('public')->path($product->hero_image));
$this->assertSame([1600, 900], $image);

That dimension assertion is the one test you never delete — it's the only reliable guard against a silent no-op crop. Add a rotated fixture so EXIF bugs fail loudly.

Sequencing that avoids incidents: one resource wired → disk/storage config → sizing centralisation → derivative policy → overwrite/backfill decision → observability. Don't invert the last two.

Gotchas and Tips

The failure mode is a silent no-op crop. Coordinates persist, the UI looks happy, the stored image is unchanged — or unchanged in dimensions. It looks like success, which is worse than an error. The dimension assertion plus one manual check on a fresh resource during the spike is the cheapest insurance you'll ever buy.

Destructive overwrite is the data-loss risk. If your version replaces the original in place, an editor re-cropping a hero permanently destroys the source. Keep the original column, or hideWhenUpdating() the source field. Verify the behaviour against your installed version rather than assuming.

Nova coupling is the top technical risk. Nova majors have changed field APIs and asset build conventions. Add a CI job that boots Nova and instantiates the field (app(ImageCropper::class)) so an upgrade fails loudly instead of silently unregistering the component. Record the Nova + PHP + disk matrix in the runbook.

EXIF orientation. Phone photos arrive rotated; Cropper.js can crop rotated bounds and hand you a visually sideways file. Check the handling in the Vue source and keep a rotated fixture in tests.

Asset pipeline debugging. If the field renders as a plain text input, the Vue component didn't register. Check the console for Nova.booting errors, confirm dist/ exists (some versions expect a publish/rebuild), and verify your Nova asset compilation picked it up. This spans PHP field code, Vue registration, and Nova's build — one owner beats many here.

403s after deploy are almost always disk visibility, not the package. Health-check the disk, set visibility explicitly for S3-compatible storage, and put crops on object storage for multi-node deployments — local-disk crops diverge between nodes.

Upload limits bite before the crop does. A 20MB phone photo still has to clear client_max_body_size, post_max_size, and upload_max_filesize before any browser-side cropping happens. Set explicit limits and a max: rule so editors get a clear validation error instead of a hung upload.

Re-edit idempotency. Confirm that saving repeatedly without touching the crop doesn't re-encode each time — repeated lossy re-encodes degrade quality even with identical bounds.

Treat it as vendored-with-benefits. No SLA, issues at the maintainer's discretion. Keep the wrapper thin enough that forking is a contained afternoon, budget a small recurring allocation for the annual Nova upgrade, and state the escalation path out loud ("we fix it or we fork it") before you need it.

Extension point. The cleanest customisation seam is your own field subclass or the crop() helper — never edits inside vendor/. That keeps you upgradable while owning the contract your resources and front-end depend on.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
cgv/localized-resource