
📤 File upload with drag and drop using Dropzone.js and PHP
Users expect to upload files to a website by simply dragging them into the browser window. The "Choose file" button and file explorer are already perceived as unnecessary steps. But writing drag-and-drop from scratch in JavaScript means several hundred lines of code, progress bars, previews, and error handling. Fortunately, there is a ready-made library that covers all of this out of the box.
Dropzone.js is an open-source JavaScript library for drag-and-drop file uploads. It shows image previews, a progress bar, and does not require jQuery. It works with any server-side language: PHP, Node.js, Python. In this guide, you will find the complete cycle from installation to production setup in six steps.
💡 Quick overview:
- What it does: the Dropzone.js library turns any HTML element into a drop zone for files with AJAX submission to the server.
- How to install: a CDN link (unpkg or jsDelivr) or an npm package; both options are covered in the first step.
- How to handle on the server: a PHP script receives the file via
$_FILES, just like a regular form; the third step includes ready-to-use code. - Where to apply: contact forms with attachments, avatar uploads, bulk image imports into a WordPress gallery.
Step 1: Installing Dropzone.js
The fastest way is to include the library via CDN. Add this tag to your page's <head>:
1 <script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
The CSS file is included separately:
1 <link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" />
If you use a bundler, install via npm:
1 npm install dropzone
And import it in your JS file:
1 import Dropzone from "dropzone";
Note that version 5.9 is the latest stable release as of early 2026. Version six is in beta status and not yet recommended for production. The project repository now lives at github.com/dropzone/dropzone (previously enyo/dropzone).
Step 2: Basic HTML markup and initialization
Create a form with the dropzone class. Dropzone will automatically find it and turn it into a drop zone:
1 <form action="/upload.php" 2 class="dropzone" 3 id="my-dropzone"></form>
That is all. When a user drags a file into this form, the library will send it via an AJAX request to the URL in the action attribute. On the server side, the file will arrive in $_FILES['file'], exactly as with a regular submission through <input type="file">.

If you need a fallback for browsers without JavaScript, add a regular field inside the form:
1 <form action="/upload.php" class="dropzone"> 2 <input name="file" type="file" multiple /> 3 </form>
The "Choose file" button will only be shown when JavaScript is unavailable. With JS enabled, Dropzone will hide it and display its own drop zone.
Step 3: Handling files on the server
A PHP script receives uploads the same way as a regular form. Here is the minimal working version:
1 <?php 2 if (!empty($_FILES)) { 3 $uploadDir = __DIR__ . '/uploads/'; 4 5 if (!is_dir($uploadDir)) { 6 mkdir($uploadDir, 0755, true); 7 } 8 9 $originalName = basename($_FILES['file']['name']); 10 $targetPath = $uploadDir . time() . '_' . $originalName; 11 12 if (move_uploaded_file($_FILES['file']['tmp_name'], $targetPath)) { 13 http_response_code(200); 14 echo json_encode(['status' => 'ok', 'file' => $targetPath]); 15 } else { 16 http_response_code(500); 17 echo json_encode(['status' => 'error', 'message' => 'Failed to save file']); 18 } 19 }
What happens here:
- The script creates an
uploads/folder if it does not exist. time()is added to the original filename, which prevents name conflicts.basename()strips any path-traversal attacks (attempts to escape the folder via../).- Dropzone expects HTTP 200 from the server; otherwise it considers the upload failed and shows an error.
For production, add extension and MIME type validation:
1 $allowed = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'zip']; 2 $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION)); 3 4 if (!in_array($ext, $allowed)) { 5 http_response_code(400); 6 echo json_encode(['status' => 'error', 'message' => 'Invalid file type']); 7 exit; 8 }
Step 4: Limiting the number, size, and type of files
The three most commonly needed Dropzone settings are configured via a JavaScript config:
1 Dropzone.options.myDropzone = { 2 maxFiles: 10, 3 maxFilesize: 5, // In megabytes 4 acceptedFiles: 'image/*,.pdf', 5 dictDefaultMessage: 'Drag files here or click to select' 6 };
Option breakdown:
- maxFiles: how many files the user can upload at once. By default there is no limit; in the example, no more than 10.
- maxFilesize: maximum size of a single file in megabytes. A value of 5 means 5 MB. In older library versions the default was 256 MB; in the current 5.x it is also 256, so explicitly specify your own limit.
- acceptedFiles: a comma-separated string of MIME types. The pattern
image/*allows any images,.pdfadds PDF to the list. See the full list of MIME types in the Dropzone documentation. - dictDefaultMessage: the text the user sees in the empty upload zone (English in this example).
The key myDropzone in Dropzone.options.myDropzone is the camelCase version of the HTML attribute id="my-dropzone". Hyphens are dropped and each subsequent word is capitalized: my-dropzone → myDropzone, file-upload-area → fileUploadArea.
The resizeWidth and resizeHeight options allow you to downsize images on the client before sending. This saves user bandwidth and reduces server load, especially when visitors upload phone photos of 10-15 MB each. Specify both parameters together:
1 Dropzone.options.myDropzone = { 2 resizeWidth: 1200, 3 resizeHeight: 1200, 4 resizeQuality: 0.8, 5 resizeMethod: 'contain' 6 };
The contain method fits the image within the given boundaries without cropping, while crop crops to exact dimensions. A quality of 0.8 is practically indistinguishable from the original but reduces file size by 4-7 times. Important: resizing only works with image files; PDFs and archives are passed through as-is. The browser performs resizing via the Canvas API, so no additional libraries are needed.
Step 5: Manual file submission
By default, Dropzone sends a file to the server immediately after it is added. This is not always convenient: sometimes you need to let the user fill in form fields next to the upload zone and submit everything with a single button.
Disable auto-upload with the autoProcessQueue: false flag and attach submission to an external button:
1 Dropzone.options.myDropzone = { 2 autoProcessQueue: false, 3 maxFilesize: 5, 4 init: function () { 5 const myDropzone = this; 6 const submitBtn = document.querySelector('#btnUpload'); 7 8 submitBtn.addEventListener('click', function () { 9 if (myDropzone.getQueuedFiles().length === 0) { 10 alert('No files to upload'); 11 return; 12 } 13 myDropzone.processQueue(); 14 }); 15 16 this.on('success', function (file, response) { 17 console.log('File uploaded:', file.name); 18 }); 19 } 20 };
The key point here is the processQueue() method. It triggers the submission of all files accumulated in the queue. The success event fires for each file individually, allowing you to update the interface or save the uploaded file's ID to a hidden form field.
Step 6: Sending additional data along with the file
Sometimes you need to attach service parameters to an upload: user ID, document status, selected category. Dropzone has the sending event for this:
1 Dropzone.options.myDropzone = { 2 init: function () { 3 this.on('sending', function (file, xhr, formData) { 4 formData.append('status', 'draft'); 5 formData.append('user_id', currentUserId); 6 }); 7 8 this.on('success', function (file, responseText) { 9 console.log('Done:', responseText); 10 }); 11 } 12 };
The sending event is called right before the XMLHttpRequest is sent. The third argument formData is a standard FormData object to which you can add any key-value pairs via append(). On the server side, these parameters are read from $_POST['status'] and $_POST['user_id'].
Practical scenario: the user selects an album from a dropdown next to the Dropzone area, and the album ID is sent along with each file. The server immediately sorts uploads into the appropriate folders.
Security: three rules you cannot ignore
Before deploying Dropzone to production, check three things.
First. Always validate file type and size on the server side, even if you have already configured acceptedFiles and maxFilesize in JS. Client-side restrictions can be bypassed in a minute via the browser console. Server-side filtering is the only real protection.
Second. Store uploaded files outside the document root or in a folder with script execution disabled. For example, this structure:
1 /public_html ← document root ( index.php ) 2 /uploads ← folder with uploaded files (outside web access)
Then a direct URL to the file will not work; the browser serves the file only through a PHP intermediary script that checks access permissions.
Third. Generate unique filenames on the server. Do not trust the name the client sends: it may contain ../, null bytes, or special characters. basename() + time() + a random string is the minimum level.
Fourth. Check file contents, not just the extension. An attacker can rename shell.php to photo.jpg and bypass a pathinfo() filter. A reliable approach: determine the actual MIME type via finfo_file() (the built-in PHP Fileinfo module) and compare against a whitelist. For images, additionally verify that getimagesize() returns valid dimensions; this filters out corrupted files and disguised scripts. On high-traffic projects, consider offloading uploads to a separate microservice behind a reverse proxy that receives the stream, scans with antivirus (for example, ClamAV), and only then passes it to the main application.
The short demo video above shows the entire process in action: HTML markup, PHP handler, and the result in the browser in three minutes.
⁉️🤔 Frequently asked questions
Does Dropzone.js work without jQuery?
Yes, the library is completely independent of jQuery. Starting with version 5.x, all dependencies have been removed; see the Installation section on dropzone.dev. That said, there is a separate jQuery wrapper for those who prefer
$('.dropzone').dropzone(); it is in the same npm package atdist/min/dropzone-jquery.min.js.
How do I change the error text when a file is too large?
Use the
dictFileTooBigoption. Example:dictFileTooBig: 'File is too big. Maximum size is {{maxFilesize}} MB'. The{{maxFilesize}}placeholder automatically inserts the value from your configuration. The full list of dict keys for localization is in the documentation.
Can files be uploaded in chunks for large volumes?
Yes, Dropzone v5 has built-in support for chunked uploads via the
chunking: true,chunkSize(in bytes), andforceChunkingoptions. The server must be able to reassemble chunks: receive each piece withdzchunkindexanddztotalchunkcountheaders, save them temporarily, and merge them after receiving the last one. A ready-made PHP example for chunk assembly is available in the Dropzone repository.
How do I remove a file preview after a successful upload?
Call
myDropzone.removeFile(file)in thesuccessevent handler. The file will disappear from the interface but remain on the server. If you also need to delete it from the server, add an AJAX request in the same handler beforeremoveFile.
Is Dropzone compatible with WordPress?
Yes, the library can be included via
wp_enqueue_script()in your theme or plugin. A typical WordPress scenario: replacing the standard media uploader on a custom admin page or a frontend form. You just need to specify the correct handler URL, usuallyadmin_url('admin-ajax.php')with a registered AJAX action.
Is Dropzone.js right for your project
If you need drag-and-drop file uploads "here and now," Dropzone.js solves the task faster than any custom code. Here are five reasons the library remains relevant in 2026:
- No dependencies. No jQuery, Bootstrap, or React. Include one JS file, and it works.
- Cross-browser support. Supports browsers down to IE11 and degrades gracefully without JavaScript.
- Ready-made visualization. Image previews, progress bars, success/error icons out of the box, with no CSS layout work.
- Documentation. Up-to-date GitBook and an active GitHub repository (1,700+ stars as of 2026).
- Flexibility. From simple image resizing before upload to chunked uploads of gigabyte-sized files, everything is configurable.
When Dropzone is not the best choice: if you are already using React/Vue and want a native component without external DOM manipulation. The React ecosystem has react-dropzone, Vue has vue-dropzone. But for vanilla JavaScript, jQuery projects, and WordPress sites, Dropzone.js is a proven tool that will not let you down.
Practical integration example. One typical WordPress scenario: an "Upload documents" page in a client portal. The user drags in scans of a passport, contract, and property photos. Dropzone is included via wp_enqueue_script() in the theme's functions.php, and the handler is a custom AJAX action registered via wp_ajax_nopriv_. Each successfully uploaded file is added to the media library via wp_insert_attachment(), and its ID is saved in user meta. The entire frontend code fits in 30 lines, and the server side in 50. For comparison, a custom drag-and-drop with the same capabilities would take 400-600 lines of JavaScript and 2-3 days of development.
As for alternatives: Uppy (from the creators of Transloadit) offers a modular architecture with plugins for React, Svelte, and S3 uploads, but requires more dependencies and configuration. Fine Uploader has not been maintained since 2018; forks exist but with no security guarantees. Dropzone.js wins on the balance of "features per unit of complexity": one file, minimal configuration, and visible results five minutes after integration.
A few additional capabilities worth knowing before you start. Dropzone provides a full event cycle: addedfile, thumbnail, uploadprogress, complete, and queuecomplete. The last one is especially useful: it fires when all files in the queue have been processed, letting you show the user a summary message or redirect to another page. For network failures, there is built-in retry: on an upload error, Dropzone displays a "Retry" button next to the file preview, and the retry goes through the same handler without code duplication. The interface is fully localizable via dict options: dictCancelUpload, dictRemoveFile, dictMaxFilesExceeded, and about two dozen more keys. Russian language support is set up in five minutes by copying an object from the documentation. The library weighs 28 KB compressed (gzip), adding less than 0.1 seconds to page load time on mobile 4G. All these features make Dropzone.js the de facto standard for drag-and-drop uploads in projects without frameworks: one million npm downloads per week as of early 2026, and usage in WordPress, Laravel, and Symfony admin panels.



