Works with any backend npm install Zero dependencies
Kitchen Sink (Everything)

Every major feature combined in one demo: chunked, validated, image-processed, hashed, persistent, with full event logging.

Files0
Speed-
Progress0%
Completed0
(function() {
    var logEl = document.getElementById('log');
    function log(m) { var d = document.createElement('div'); d.className='pkg-log-entry'; d.textContent=new Date().toLocaleTimeString()+' '+m; logEl.appendChild(d); logEl.scrollTop=logEl.scrollHeight; }
    document.getElementById('clear-log').addEventListener('click', function() { logEl.innerHTML=''; });

    var u = new MultipleUpload('#demo', {
        uploadUrl: '/api/upload',
        multiple: true,
        autoUpload: false,
        chunked: true,
        chunkSize: '1MB',
        chunkConcurrency: 2,
        concurrency: 2,
        retries: 2,
        retryBackoff: 'exponential',
        maxFileSize: '50MB',
        allowedExtensions: '.jpg,.jpeg,.png,.gif,.webp,.pdf,.doc,.docx,.xls,.xlsx,.zip,.mp4,.txt',
        preventDuplicates: true,
        showThumbnails: true,
        removable: true,
        confirmRemove: true,
        paste: true,
        fullPageDrop: true,
        autoOrient: true,
        imageResize: { maxWidth: 1920, maxHeight: 1920, quality: 0.9 },
        watermark: { text: 'DEMO', fontSize: 24, position: 'bottom-right', color: 'rgba(0,0,0,0.15)' },
        computeHash: true,
        hashAlgorithm: 'sha256',
        persistState: true,
        persistKey: 'kitchen-sink-demo',
        onFileAdded: function(t) { log('+ ' + t.fileName); document.getElementById('ks-files').textContent = u.tasks.length; },
        onTaskProgress: function(t) {
            document.getElementById('ks-speed').textContent = MultipleUpload.formatSize(t.speed) + '/s';
            document.getElementById('ks-pct').textContent = t.progress + '%';
        },
        onTaskComplete: function(t) { log('Done: ' + t.fileName); document.getElementById('ks-done').textContent = u.getCompletedFiles().length; },
        onTaskError: function(t,e) { log('ERR: ' + t.fileName + ' ' + e); },
        onChunkComplete: function(f,i,n) { log('Chunk ' + (i+1) + '/' + n + ' ' + f.name); },
        onQueueComplete: function(f) { log('=== ALL DONE: ' + f.length + ' files ==='); },
        onHashComputed: function(t,h) { log('Hash: ' + t.fileName + ' ' + h.slice(0,16) + '...'); },
        onValidationError: function(m,n) { log('INVALID: ' + (n||'') + ' ' + m); }
    });
    document.getElementById('btn-upload').onclick = function() { u.upload(); };
    document.getElementById('btn-pause').onclick = function() { u.pause(); };
    document.getElementById('btn-resume').onclick = function() { u.resume(); };
    document.getElementById('btn-cancel').onclick = function() { u.cancel(); };
    document.getElementById('btn-reset').onclick = function() { u.reset(); };
})();

Event Log