ajhaupt7/image-upload-preview
A Laravel package that adds instant image previews to file uploads, letting users see thumbnails before submitting. Useful for Blade or Livewire forms to confirm selections and reduce upload mistakes with minimal setup.
Install the field into an existing Nova application:
composer require ajhaupt7/image-upload-preview
php artisan storage:link
The storage:link step matters more than usual: the whole point of the field is showing the image back to the user immediately, so the disk you point it at must be publicly reachable.
Use it exactly like Nova's built-in Image field — Ajhaupt7\ImageUploadPreview\ImageUploadPreview extends Laravel\Nova\Fields\Image, so every method you already know (disk, path, rules, deletable, prunable, thumbnail, storeOriginalName, etc.) is available. The only difference is the Vue component, which adds a dropzone and a live preview.
use Ajhaupt7\ImageUploadPreview\ImageUploadPreview;
public function fields(Request $request)
{
return [
ImageUploadPreview::make('Avatar')
->disk('public')
->path('avatars')
->rules('required', 'image', 'max:2048')
->rounded(),
];
}
Where to look first:
vendor/ajhaupt7/image-upload-preview/src/ImageUploadPreview.php — the PHP field class, ~50 lines. Read it before doing anything clever; it will tell you exactly which custom builder methods exist (typically rounded(), preview() and preview dimension helpers) and which defaults it sets.vendor/ajhaupt7/image-upload-preview/resources/js/ — the Vue component if you need to understand why a preview isn't rendering.Laravel\Nova\Fields\Image for everything inherited.First real use case: a User resource avatar. Drop the field in, point it at the public disk, add ->rounded(), and you have a circular drag-and-drop avatar uploader on the create and update forms in about two minutes.
Avatar / single-image profile field. The cleanest fit. Keep it on public, give it a path() subfolder, validate image, and use ->rounded() for the circular preview. Pair with ->thumbnail() so the index view stays light:
ImageUploadPreview::make('Avatar')
->disk('public')
->path('avatars/' . $this->id)
->rules('required', 'image', 'max:2048')
->rounded()
->thumbnail(fn ($value) => $value->thumbnail(64)),
Use it only where drag-and-drop earns its keep. Because it is a drop-in replacement for Image, you can mix and match in the same resource: ImageUploadPreview for the hero/avatar/product photo where the UX matters, plain Image::make() for secondary gallery slots. No reason to migrate every image field.
Prunable + deletable cleanup. The dropzone makes users eager to re-upload, so stale files pile up fast. Always add ->prunable() and run nova:prune on a schedule. Without it you'll quietly accumulate orphaned images in storage/app/public.
Validation should mirror the dropzone promise. The component accepts whatever you drop, but the server decides. Always include image in the rules (not just mimes) so the getimagesize check runs, and set max: in kilobytes to something below upload_max_filesize / post_max_size. A 10 MB drop that dies at PHP's limit produces a silent-looking failure rather than a Nova validation error.
Local fallback if the package stalls. Because it subclasses Image, swapping back is a one-line change. I'd treat the third-party field as sugar and keep the fallback documented in the resource's docblock — see the gotchas below.
Compose with default() and required() on create. A common pattern for onboarding:
ImageUploadPreview::make('Avatar')
->disk('public')
->path('avatars')
->rules('nullable', 'image', 'max:2048')
->default(fn () => 'avatars/default.png'),
S3 / remote disks. Works, but the preview is a signed/temporary URL. Set the disk to public visibility (or configure AWS_URL), and add a CORS rule on the bucket allowing GET from your Nova origin, otherwise the <img> renders as a broken thumbnail with no obvious error.
composer.json lists an explicit Nova version constraint. If you upgrade Nova major versions (3 → 4 → 5), expect the Vue component to break before any PHP error appears — usually as a blank field or a field that renders but never previews. Check the constraint first, and test the create form, not just the index.storage:link = no previews. The classic symptom is a successful upload, no validation errors, and a broken-image icon. php artisan storage:link and confirm the target disk is public visibility.URL.createObjectURL, which has nothing to do with what's on disk. If the form later fails validation, the in-memory preview disappears and the old image comes back. Don't mistake this for a lost upload — the file was never uploaded until submit.Image subclass, not a gallery. Multiple image uploads need a separate field, a repeater, or a custom tool.->thumbnail(fn ($value) => $value->thumbnail(64)) and use Nova's built-in nova:prune pipeline rather than rolling your own.php artisan nova:publish / hard refresh), Nova version mismatch, or a wrong disk visibility. Check the browser console first — the Vue component logs its own failures more helpfully than PHP will.multiple. Don't try to hack multiple onto it. The backend Image field handles one file; the browser will happily accept the drop and then fail somewhere unhelpful.ImageUploadPreview.php and the Vue component into nova-components/image-upload-preview and own it — the class is tiny and the upgrade path is then under your control.hideFromIndex) uses the standard Image API. There's no config file to publish, so don't go looking for one.How can I help you explore Laravel packages today?