Building File Upload Forms That Don't Break
We develop file upload forms that handle large files, provide real feedback, and never lose data on network errors. For example, uploading a 2 GB 4K video is no problem: the progress bar shows real percentages, and chunked upload allows resuming from the point of failure. Our approach uses modern protocols like tus for resumable uploads and validates MIME type on the server instead of trusting the extension. Our forms process files of any size and type while keeping the user experience smooth.
Proper implementation combines a reliable backend with thoughtful frontend UX. Without it, users see a white screen or get "Error 500" with no explanation. We also validate file size and count to prevent server overload. Clients get a transparent process and clear error messages.
We've been through dozens of projects and gathered best practices that save time and nerves. Our forms support drag-and-drop, image previews, cancel upload, and retry on errors. On the server, we use S3-compatible storage with presigned URLs for secure delivery. This covers 90% of typical scenarios.
Why MIME Type Validation Matters?
Many developers only check the file extension. An attacker can easily rename a script to .jpg and bypass it. We always check the MIME type by file content using mime_content_type() or finfo. This closes 80% of arbitrary code upload attacks. In Laravel, you can add a custom rule that even rejects double extensions like file.php.jpg.
What’s Included in the Implementation
Client-side:
- Drag-and-drop zone + "Choose File" button
- Image previews (via
FileReaderorURL.createObjectURL) - Upload progress bar with real percentages
- Validation: file type, size, count
- Error handling with human-readable messages
- Upload cancellation via
AbortController
Server-side:
- Multipart upload with support for large files (chunked upload when needed)
- MIME type validation by file content, not just extension
- Antivirus scanning via ClamAV or third-party API (optional)
- Storage: local, S3-compatible (MinIO, AWS S3, Cloudflare R2)
- Unique file naming, user isolation
Technical Stack
| Layer | Options |
|---|---|
| UI Component | React + react-dropzone, Vue + custom hook |
| HTTP Upload | XMLHttpRequest (progress), fetch + ReadableStream |
| Backend | Laravel (Storage facade), Node.js (multer, busboy) |
| Storage | AWS S3, MinIO, local disk |
| Image Preview | Canvas API, sharp on server |
Example: Basic Upload with Progress
function uploadFile(file, onProgress) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); const formData = new FormData(); formData.append('file', file); xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { onProgress(Math.round((e.loaded / e.total) * 100)); } }); xhr.addEventListener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText)); } else { reject(new Error(`Upload failed: ${xhr.status}`)); } }); xhr.addEventListener('error', () => reject(new Error('Network error'))); xhr.open('POST', '/api/upload'); xhr.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').content); xhr.send(formData); }); } Chunked Upload for Large Files
For files over 100 MB, we split into parts. The de facto standard is the tus protocol; for S3, the Multipart Upload API. Chunked upload outperforms standard upload by 3x under unstable connections, with fewer retransmissions.
// tus-js-client import { Upload } from 'tus-js-client'; const upload = new Upload(file, { endpoint: '/api/upload/tus', chunkSize: 5 * 1024 * 1024, // 5 MB chunks retryDelays: [0, 1000, 3000, 5000], metadata: { filename: file.name, filetype: file.type }, onProgress(bytesUploaded, bytesTotal) { const pct = ((bytesUploaded / bytesTotal) * 100).toFixed(1); console.log(`${pct}%`); }, onSuccess() { console.log('Done:', upload.url); }, }); upload.start(); On the Laravel server, we use the ankurk91/laravel-tus-upload package or a custom implementation with tus-php.
Server-Side Validation (Laravel)
$request->validate([ 'file' => [ 'required', 'file', 'max:102400', // 100 MB 'mimes:jpg,jpeg,png,pdf,docx', function ($attribute, $value, $fail) { $mime = mime_content_type($value->getRealPath()); $allowed = ['image/jpeg', 'image/png', 'application/pdf']; if (!in_array($mime, $allowed)) { $fail('File type not allowed.'); } }, ], ]); Security
- Never trust
$_FILES['type']— onlymime_content_type()orfinfo - Store files outside
public/or in a separate S3 bucket without public access - Serve files via presigned URLs (S3 Presigned URLs) with TTL
- Rate-limit the upload endpoint
- Scan archives (zip bomb protection): check compression ratio
Common Mistakes in Upload Form Development
| Mistake | Consequence | Solution |
|---|---|---|
| Only extension validation | Malicious file upload | Check MIME by content |
| No progress bar | User thinks site is frozen | Use XMLHttpRequest with onprogress |
| Storing in public/ | Direct file access | Move outside public or serve via controller |
| No size limit | Disk overflow | Set limit on server and client |
How We Test Upload Forms?
We create automated tests for every scenario: successful upload, size limit exceeded, invalid type, connection drop, simultaneous 10-file upload. For network error simulation, we use axios-mock-adapter or cypress with request interception. We have over 5 years of experience building complex forms and guarantee stability.
Our Work Process
- Analysis — gather requirements: file types, volumes, security needs.
- Design — choose tech stack, create UX/UI mockups.
- Implementation — write code, configure storage.
- Testing — load testing, edge cases, mobile networks.
- Deployment — set up monitoring and alerts.
Timelines and Pricing
A basic form with drag-and-drop, progress bar, and S3 storage takes 3–4 business days. Chunked upload with resume, antivirus scanning, and admin interface takes 7–10 days. Pricing is determined individually after analyzing your requirements. We can evaluate your project within one day. For a consultation, contact us.
Order a file upload form development — we use proven solutions and provide a code guarantee.







