Showing an image preview the moment a user selects a file, before actually uploading it, is a pure client-side operation using the browser's FileReader API — the server side of the upload itself follows the same pattern covered elsewhere on this site for handling file uploads generally.
The form markup
The client-side preview script
document.getElementById('image-input').addEventListener('change', function (event) {
const file = event.target.files[0];
const preview = document.getElementById('preview');
if (file) {
const reader = new FileReader();
reader.onload = function (e) {
preview.src = e.target.result;
preview.style.display = 'block';
};
reader.readAsDataURL(file);
} else {
preview.style.display = 'none';
}
});
FileReader.readAsDataURL() converts the selected file into a base64-encoded data URL entirely in the browser, without any network request — this is exactly why the preview appears instantly, before the form is even submitted, since nothing has actually been sent to the server yet at this point.
The Laravel controller handling the actual upload
public function store(Request $request)
{
$request->validate([
'image' => 'required|image|mimes:jpeg,png,jpg,webp|max:2048',
]);
$path = $request->file('image')->store('products', 'public');
Product::create([
'name' => $request->name,
'image' => $path,
]);
return redirect()->route('products.index')->with('success', 'Product created.');
}
Showing an existing image's preview on an edit form
For an edit form, initializing the preview's src to the model's existing image (if one exists) shows the currently saved image before the user selects a replacement — the same JavaScript change-event listener then overwrites it if a new file is chosen.
Validating file type and size before showing the preview
document.getElementById('image-input').addEventListener('change', function (event) {
const file = event.target.files[0];
const errorEl = document.getElementById('image-error');
if (file && file.size > 2 * 1024 * 1024) {
errorEl.textContent = 'Image must be under 2MB.';
event.target.value = '';
return;
}
errorEl.textContent = '';
// ... proceed with FileReader preview as above
});
Checking the file size client-side gives immediate feedback without waiting for a server round-trip — this is a convenience layer only, though; the server-side max:2048 validation rule remains the actual enforced limit, since client-side checks can always be bypassed.
Using a drag-and-drop zone instead of a plain file input
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', (e) => e.preventDefault());
dropZone.addEventListener('drop', function (e) {
e.preventDefault();
document.getElementById('image-input').files = e.dataTransfer.files;
document.getElementById('image-input').dispatchEvent(new Event('change'));
});
Manually setting the file input's files property from the drop event's dataTransfer.files, then dispatching a synthetic change event, reuses the exact same preview logic already wired up for the plain file input — no separate preview code path is needed for drag-and-drop.