Introduction to File Uploads
File uploads allow users to select and send files from their device to a web server. The <input type="file"> element provides the interface for file selection, while proper form configuration ensures successful file transmission.
Basic File Upload Setup
For file uploads to work properly, you need specific form attributes:
html
<form action="/upload" method="POST" enctype="multipart/form-data"> <label for="file-upload">Choose file:</label> <input type="file" id="file-upload" name="file-upload"> <button type="submit">Upload</button> </form>
Key Requirements:
- method="POST" - Files cannot be uploaded with GET method
- enctype="multipart/form-data" - Essential for binary file data transmission
File Input Attributes
Accepting Specific File Types
html
<!-- Accept only images --> <input type="file" name="image" accept="image/*"> <!-- Accept specific image formats --> <input type="file" name="photo" accept="image/png, image/jpeg, image/gif"> <!-- Accept documents --> <input type="file" name="document" accept=".pdf,.doc,.docx"> <!-- Accept audio files --> <input type="file" name="audio" accept="audio/*"> <!-- Accept video files --> <input type="file" name="video" accept="video/mp4, video/webm"> <!-- Mixed file types --> <input type="file" name="media" accept="image/*, video/*, .pdf">
Multiple File Selection
html
<!-- Allow multiple file selection --> <input type="file" name="files" multiple> <!-- Multiple images --> <input type="file" name="photos" accept="image/*" multiple>
Capture from Device Camera (Mobile)
html
<!-- Camera for photos --> <input type="file" name="photo" accept="image/*" capture="environment"> <!-- Front camera for selfies --> <input type="file" name="selfie" accept="image/*" capture="user"> <!-- Video recording --> <input type="file" name="video" accept="video/*" capture="environment">
Complete File Upload Examples
Basic Image Upload with Preview
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Image Upload with Preview</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 20px auto;
padding: 20px;
}
.upload-container {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 20px;
text-align: center;
margin-bottom: 20px;
transition: border-color 0.3s;
}
.upload-container:hover {
border-color: #007bff;
}
.upload-container.dragover {
border-color: #007bff;
background-color: #f8f9fa;
}
.file-input {
display: none;
}
.upload-label {
cursor: pointer;
color: #007bff;
font-weight: bold;
}
.preview-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 20px;
}
.preview-item {
position: relative;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
.preview-image {
width: 150px;
height: 150px;
object-fit: cover;
}
.remove-btn {
position: absolute;
top: 5px;
right: 5px;
background-color: #dc3545;
color: white;
border: none;
border-radius: 50%;
width: 25px;
height: 25px;
cursor: pointer;
font-size: 12px;
}
.file-info {
padding: 10px;
background-color: #f8f9fa;
border-radius: 4px;
margin-bottom: 10px;
}
.error {
color: #dc3545;
margin-top: 10px;
}
button {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
</style>
</head>
<body>
<h1>File Upload with Preview</h1>
<form id="upload-form" enctype="multipart/form-data">
<div class="upload-container" id="upload-container">
<label for="file-input" class="upload-label">
📁 Click to select files or drag and drop here
</label>
<input type="file"
id="file-input"
class="file-input"
name="files"
accept="image/*"
multiple>
<p>Supported formats: JPG, PNG, GIF (max 5MB each)</p>
</div>
<div class="error" id="error-message"></div>
<div class="preview-container" id="preview-container"></div>
<button type="submit" id="upload-btn" disabled>Upload Files</button>
</form>
<script>
const fileInput = document.getElementById('file-input');
const uploadContainer = document.getElementById('upload-container');
const previewContainer = document.getElementById('preview-container');
const errorMessage = document.getElementById('error-message');
const uploadBtn = document.getElementById('upload-btn');
const form = document.getElementById('upload-form');
let selectedFiles = [];
const maxFileSize = 5 * 1024 * 1024; // 5MB
const maxFiles = 10;
// File selection handler
fileInput.addEventListener('change', handleFiles);
// Drag and drop handlers
uploadContainer.addEventListener('dragover', (e) => {
e.preventDefault();
uploadContainer.classList.add('dragover');
});
uploadContainer.addEventListener('dragleave', () => {
uploadContainer.classList.remove('dragover');
});
uploadContainer.addEventListener('drop', (e) => {
e.preventDefault();
uploadContainer.classList.remove('dragover');
const files = Array.from(e.dataTransfer.files);
processFiles(files);
});
function handleFiles(e) {
const files = Array.from(e.target.files);
processFiles(files);
}
function processFiles(files) {
errorMessage.textContent = '';
// Validate total number of files
if (selectedFiles.length + files.length > maxFiles) {
showError(`Maximum ${maxFiles} files allowed`);
return;
}
files.forEach(file => {
// Validate file type
if (!file.type.startsWith('image/')) {
showError(`${file.name} is not an image file`);
return;
}
// Validate file size
if (file.size > maxFileSize) {
showError(`${file.name} exceeds 5MB limit`);
return;
}
// Check for duplicates
if (selectedFiles.some(f => f.name === file.name && f.size === file.size)) {
showError(`${file.name} is already selected`);
return;
}
selectedFiles.push(file);
createPreview(file, selectedFiles.length - 1);
});
updateUploadButton();
}
function createPreview(file, index) {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';
previewItem.dataset.index = index;
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
previewItem.innerHTML = `
<img src="${e.target.result}" alt="${file.name}" class="preview-image">
<button type="button" class="remove-btn" onclick="removeFile(${index})">×</button>
<div class="file-info">
<strong>${file.name}</strong><br>
<small>${formatFileSize(file.size)}</small>
</div>
`;
};
reader.readAsDataURL(file);
}
previewContainer.appendChild(previewItem);
}
function removeFile(index) {
selectedFiles.splice(index, 1);
updatePreviews();
updateUploadButton();
errorMessage.textContent = '';
}
function updatePreviews() {
previewContainer.innerHTML = '';
selectedFiles.forEach((file, index) => {
createPreview(file, index);
});
}
function updateUploadButton() {
uploadBtn.disabled = selectedFiles.length === 0;
uploadBtn.textContent = selectedFiles.length > 0
? `Upload ${selectedFiles.length} file(s)`
: 'Upload Files';
}
function showError(message) {
errorMessage.textContent = message;
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Form submission
form.addEventListener('submit', function(e) {
e.preventDefault();
if (selectedFiles.length === 0) {
showError('Please select at least one file');
return;
}
// Create FormData with selected files
const formData = new FormData();
selectedFiles.forEach((file, index) => {
formData.append('files', file);
});
// Simulate upload (replace with actual upload logic)
uploadBtn.disabled = true;
uploadBtn.textContent = 'Uploading...';
setTimeout(() => {
alert('Files uploaded successfully!');
selectedFiles = [];
previewContainer.innerHTML = '';
fileInput.value = '';
updateUploadButton();
}, 2000);
});
</script>
</body>
</html>
Document Upload with Progress
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document Upload with Progress</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 20px auto;
padding: 20px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="file"] {
width: 100%;
padding: 10px;
border: 2px solid #ddd;
border-radius: 4px;
background-color: #f9f9f9;
}
.file-list {
margin-top: 15px;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 10px;
background-color: #f8f9fa;
}
.file-details {
flex: 1;
}
.file-name {
font-weight: bold;
margin-bottom: 2px;
}
.file-size {
color: #666;
font-size: 0.9em;
}
.progress-container {
width: 100%;
background-color: #e9ecef;
border-radius: 4px;
margin-top: 5px;
height: 20px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #007bff;
width: 0%;
transition: width 0.3s;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 0.8em;
}
.remove-file {
background-color: #dc3545;
color: white;
border: none;
border-radius: 4px;
padding: 5px 10px;
cursor: pointer;
margin-left: 10px;
}
button {
background-color: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.error {
color: #dc3545;
font-size: 0.9em;
margin-top: 5px;
}
.success {
color: #28a745;
font-size: 0.9em;
margin-top: 5px;
}
</style>
</head>
<body>
<h1>Document Upload</h1>
<form id="document-form">
<div class="form-group">
<label for="document-input">Select Documents:</label>
<input type="file"
id="document-input"
name="documents"
accept=".pdf,.doc,.docx,.txt,.rtf"
multiple>
<small>Accepted formats: PDF, DOC, DOCX, TXT, RTF (max 10MB each)</small>
</div>
<div class="file-list" id="file-list"></div>
<button type="submit" id="upload-btn" disabled>Upload Documents</button>
</form>
<script>
const documentInput = document.getElementById('document-input');
const fileList = document.getElementById('file-list');
const uploadBtn = document.getElementById('upload-btn');
const form = document.getElementById('document-form');
let selectedFiles = [];
const maxFileSize = 10 * 1024 * 1024; // 10MB
const allowedTypes = ['.pdf', '.doc', '.docx', '.txt', '.rtf'];
documentInput.addEventListener('change', handleFileSelection);
function handleFileSelection(e) {
const files = Array.from(e.target.files);
processDocumentFiles(files);
}
function processDocumentFiles(files) {
files.forEach(file => {
// Validate file type
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
if (!allowedTypes.includes(fileExtension)) {
showError(`${file.name} is not a supported document format`);
return;
}
// Validate file size
if (file.size > maxFileSize) {
showError(`${file.name} exceeds 10MB limit`);
return;
}
// Check for duplicates
if (selectedFiles.some(f => f.name === file.name && f.size === file.size)) {
showError(`${file.name} is already selected`);
return;
}
const fileObj = {
file: file,
id: Date.now() + Math.random(),
progress: 0,
uploaded: false
};
selectedFiles.push(fileObj);
addFileToList(fileObj);
});
updateUploadButton();
}
function addFileToList(fileObj) {
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.dataset.id = fileObj.id;
fileItem.innerHTML = `
<div class="file-details">
<div class="file-name">${fileObj.file.name}</div>
<div class="file-size">${formatFileSize(fileObj.file.size)}</div>
<div class="progress-container" style="display: none;">
<div class="progress-bar">0%</div>
</div>
</div>
<button type="button" class="remove-file" onclick="removeFile('${fileObj.id}')">Remove</button>
`;
fileList.appendChild(fileItem);
}
function removeFile(id) {
selectedFiles = selectedFiles.filter(f => f.id != id);
const fileItem = document.querySelector(`[data-id="${id}"]`);
if (fileItem) {
fileItem.remove();
}
updateUploadButton();
}
function updateUploadButton() {
uploadBtn.disabled = selectedFiles.length === 0;
uploadBtn.textContent = selectedFiles.length > 0
? `Upload ${selectedFiles.length} document(s)`
: 'Upload Documents';
}
function updateProgress(id, progress) {
const fileItem = document.querySelector(`[data-id="${id}"]`);
if (fileItem) {
const progressContainer = fileItem.querySelector('.progress-container');
const progressBar = fileItem.querySelector('.progress-bar');
progressContainer.style.display = 'block';
progressBar.style.width = `${progress}%`;
progressBar.textContent = `${progress}%`;
if (progress === 100) {
progressBar.style.backgroundColor = '#28a745';
progressBar.textContent = 'Complete';
}
}
}
function simulateUpload(fileObj) {
return new Promise((resolve) => {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 20;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
fileObj.uploaded = true;
updateProgress(fileObj.id, progress);
resolve();
} else {
updateProgress(fileObj.id, Math.floor(progress));
}
}, 200);
});
}
function showError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'error';
errorDiv.textContent = message;
form.appendChild(errorDiv);
setTimeout(() => errorDiv.remove(), 5000);
}
function showSuccess(message) {
const successDiv = document.createElement('div');
successDiv.className = 'success';
successDiv.textContent = message;
form.appendChild(successDiv);
setTimeout(() => successDiv.remove(), 5000);
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Form submission with progress
form.addEventListener('submit', async function(e) {
e.preventDefault();
if (selectedFiles.length === 0) {
showError('Please select at least one document');
return;
}
uploadBtn.disabled = true;
uploadBtn.textContent = 'Uploading...';
try {
// Upload files sequentially with progress
for (const fileObj of selectedFiles) {
if (!fileObj.uploaded) {
await simulateUpload(fileObj);
}
}
showSuccess('All documents uploaded successfully!');
// Reset form after successful upload
setTimeout(() => {
selectedFiles = [];
fileList.innerHTML = '';
documentInput.value = '';
updateUploadButton();
}, 2000);
} catch (error) {
showError('Upload failed. Please try again.');
uploadBtn.disabled = false;
uploadBtn.textContent = 'Retry Upload';
}
});
</script>
</body>
</html>
File Upload Security Best Practices
Client-Side Validation
html
<script>
function validateFile(file) {
const maxSize = 5 * 1024 * 1024; // 5MB
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif'];
// Check file size
if (file.size > maxSize) {
return { valid: false, message: 'File size exceeds 5MB limit' };
}
// Check MIME type
if (!allowedTypes.includes(file.type)) {
return { valid: false, message: 'Invalid file type' };
}
// Check file extension
const extension = '.' + file.name.split('.').pop().toLowerCase();
if (!allowedExtensions.includes(extension)) {
return { valid: false, message: 'Invalid file extension' };
}
// Additional checks
if (file.name.length > 255) {
return { valid: false, message: 'Filename too long' };
}
return { valid: true, message: 'File is valid' };
}
</script>
File Upload with Fetch API
html
<script>
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'User uploaded file');
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error('Upload failed:', error);
throw error;
}
}
// Usage with progress tracking
function uploadWithProgress(file, progressCallback) {
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) {
const percentComplete = (e.loaded / e.total) * 100;
progressCallback(Math.round(percentComplete));
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`Upload failed with status ${xhr.status}`));
}
});
xhr.addEventListener('error', () => {
reject(new Error('Upload failed'));
});
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
}
</script>
Common File Upload Patterns
Profile Picture Upload
html
<div class="profile-upload">
<div class="current-avatar">
<img src="/default-avatar.png" alt="Current profile picture" id="avatar-preview">
</div>
<input type="file" id="avatar-input" name="avatar" accept="image/*" style="display: none;">
<label for="avatar-input" class="upload-btn">Change Photo</label>
</div>
<script>
document.getElementById('avatar-input').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('avatar-preview').src = e.target.result;
};
reader.readAsDataURL(file);
}
});
</script>
Bulk File Upload
html
<div class="bulk-upload">
<input type="file" id="bulk-input" multiple accept=".csv,.xlsx,.json">
<div class="upload-summary" id="upload-summary"></div>
</div>
<script>
document.getElementById('bulk-input').addEventListener('change', function(e) {
const files = Array.from(e.target.files);
const summary = document.getElementById('upload-summary');
summary.innerHTML = `
<p>Selected ${files.length} files:</p>
<ul>
${files.map(file => `<li>${file.name} (${formatFileSize(file.size)})</li>`).join('')}
</ul>
<p>Total size: ${formatFileSize(files.reduce((sum, file) => sum + file.size, 0))}</p>
`;
});
</script>
Important Security Considerations
- Always validate on server-side - Client-side validation can be bypassed
- Check file extensions AND MIME types - Both can be spoofed but provide layers of security
- Limit file sizes - Prevent server overload and storage issues
- Sanitize filenames - Remove dangerous characters and limit length
- Store uploads outside web root - Prevent direct execution of uploaded files
- Scan for malware - Use antivirus scanning for uploaded files
- Implement rate limiting - Prevent abuse and DoS attacks