Reader Stacks

Image Upload With Live Preview in Laravel

The preview itself happens entirely client-side via the FileReader API — the image is only actually sent to the server once the form is submitted, not as the file is selected.

Image Upload With Live Preview in Laravel

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

@csrf

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.