multipleupload
v1.4.0
Published
Full-featured file upload component for any backend. Pluggable strategies (direct-to-S3 multipart, direct-to-Azure Blob, tus 1.0.0, server-side URL import), chunked uploads with parallel chunks, IndexedDB-backed resume (Golden Retriever equivalent), Web W
Maintainers
Keywords
Readme
MultipleUpload
A standalone JavaScript file upload component that works with any backend. No runtime dependencies, no framework required.
npm install multipleuploadTransports
Pick one with a single strategy option — the UI, queue, retry and progress model
stay identical across all of them.
| strategy | what it does |
|---|---|
| single | one multipart/form-data POST |
| chunked | splits the file, parallel chunks, per-chunk retry, server-side assembly |
| s3 | browser uploads parts straight to S3 (or MinIO, R2, B2, Wasabi) via your presigned URLs |
| azure | PutBlock + PutBlockList against a blob SAS URL |
| gcs | GCS resumable session, Content-Range chunks |
| tus | tus 1.0 client (Core + Creation + Termination); interops with tusd |
| urlImport | server fetches a pasted URL and streams it through your storage |
With s3, azure and gcs the bytes never touch your app server — it only signs.
Features
- Resume that survives a crash — every in-flight upload persists to IndexedDB;
with
persistBlobs: truetheFileobjects too, so a tab crash or reload picks up from the last confirmed chunk - Instant upload — opt-in SHA-256 content dedupe; a match completes with zero bytes transferred (the server hashes its own storage; client-claimed hashes are never trusted)
- Remote sources — Google Drive, Dropbox, OneDrive, Box, Unsplash, and a self-hosted Companion broker that keeps OAuth tokens server-side
- Image pipeline — resize, compress, watermark, EXIF auto-orient, AVIF/WebP output, and an inline editor with interactive crop, rotate/flip and annotation
- Folder uploads —
webkitdirectoryand directory drops preserve each file's relative path, sanitized server-side - Every input source — drag-and-drop, full-page drop overlay, clipboard paste, webcam, screen and audio capture
- Validation — extensions, MIME with magic-byte sniffing, size, image dimensions, duplicates, plus your own rules; re-checked server-side
- Web Worker hashing — SHA-256/CRC32 off the main thread, CSP-safe fallback
- 30 built-in locales including RTL, plus dark mode and CSS-variable theming
- Framework wrappers for React, Vue, Svelte and Angular — or use it plain
- Node/Express middleware included for single, chunked and scan endpoints
- ESM, CommonJS and UMD from one engine build, with TypeScript declarations
Quick Start
CDN
<link rel="stylesheet" href="https://unpkg.com/multipleupload/dist/multipleupload.css">
<script src="https://unpkg.com/multipleupload/dist/multipleupload.js"></script>unpkg serves raw paths and ignores the exports map, so the dist/ segment is required.
Licensed deployments can place a sibling multipleupload.lic next to multipleupload.js. The runtime auto-loads that file by default.
npm
npm install multipleuploadBoth module systems are supported from the same engine build — there is only one copy of the runtime in the package, so bundlers never duplicate it.
// ESM (Vite, webpack, Rollup, Next.js, Node ESM)
import { MultipleUpload } from 'multipleupload';
import 'multipleupload/multipleupload.css';
// or a default import
import MultipleUpload from 'multipleupload';// CommonJS
const MultipleUpload = require('multipleupload');
// destructuring works too
const { MultipleUpload } = require('multipleupload');TypeScript resolves types under node, node16/nodenext, and bundler
module resolution.
Usage
<div id="uploader" class="mu-uploader">
<button type="button" class="mu-select-btn">Select Files</button>
<input type="file" class="mu-file-input" multiple style="display:none" />
<div class="mu-dropzone">
<div class="mu-dropzone-text">Drag & drop files here</div>
</div>
<div class="mu-preview-area"></div>
<div class="mu-progress" style="display:none">
<div class="mu-progress-bar"><div class="mu-progress-fill"></div></div>
<div class="mu-progress-text"></div>
</div>
<div class="mu-queue"></div>
</div>
<script>
var uploader = new MultipleUpload('#uploader', {
uploadUrl: '/api/upload',
multiple: true,
maxFileSize: 100 * 1024 * 1024,
onComplete: function(files) {
console.log('All uploads complete:', files);
}
});
</script>Licensed File Deployment
Licensed customers can replace dist/multipleupload.lic with the multipleupload.lic downloaded from Client Center. multipleupload.js auto-loads a sibling multipleupload.lic, or you can point to a custom location with licenseUrl.
<script src="/assets/multipleupload.js"></script>
<script>
var uploader = new MultipleUpload('#uploader', {
uploadUrl: '/api/upload',
licenseUrl: '/licenses/multipleupload.lic'
});
</script>Framework Wrappers
Each wrapper owns the lifecycle (mount, update, tear-down) and forwards every core
event, so you never hand-wire useEffect cleanup.
import { useUploader } from 'multipleupload/react';
import { useUploader } from 'multipleupload/vue';
import { uploader } from 'multipleupload/svelte'; // Svelte action
import { UploaderService } from 'multipleupload/angular';The framework packages are peerDependencies — install only the one you use.
Node Middleware
import multipleupload from 'multipleupload/server';
// single + chunked endpoints, relative-path sanitizing, optional instant-upload probe
app.use('/api/upload', multipleupload({ uploadDir: './uploads' }));multipleupload/server/companion hosts the OAuth broker for the remote sources, and
multipleupload/server/adapters provides the storage destinations.
Backend-Agnostic Options
new MultipleUpload('#uploader', {
headers: function(task) {
return {
'Authorization': 'Bearer YOUR_TOKEN',
'X-Upload-Id': task.id
};
},
withCredentials: true,
fieldName: 'attachment',
chunkUrl: '/api/upload/chunk',
chunkCompleteUrl: '/api/upload/chunk/complete',
deleteUrl: '/api/files/delete',
responseParser: function(responseText, task) {
var data = JSON.parse(responseText);
return {
success: true,
fileGuid: data.id,
fileName: data.name || task.fileName,
fileSize: data.size
};
},
transformPipeline: [
function(file) {
return file;
}
],
allowedMimeTypes: 'image/*,application/pdf',
validateMimeByMagic: true,
customValidation: function(file, uploader) {
if (uploader.getTasksByStatus('completed').length > 25) {
return 'Too many completed files in the queue.';
}
}
});Folder Uploads (Relative Paths)
Folder drops and webkitdirectory picks populate task.relativePath
('photos/2026/beach.jpg', '' for plain files). The path is sent as the
relativePath form field (plus an X-Upload-Relative-Path header) on single
uploads and inside the chunk-complete body on chunked uploads. The bundled Node
middleware sanitizes it (traversal removed) and echoes it on onComplete /
response info.relativePath so your app can recreate the tree — always join it
under a fixed root.
Instant Upload (Content Dedupe, Opt-In)
// Server (Node middleware): hashes stored uploads with SHA-256 server-side
// and answers GET /exists?hash=&size=. Off by default — the probe reveals
// whether a given content hash exists on this server.
app.use('/api/upload', multipleupload({ uploadDir: './uploads', instantUpload: true }));
// Client: probe before transferring; a confirmed match completes instantly
// with the existing file's id and zero bytes transferred.
new MultipleUpload('#uploader', {
instantUpload: true,
onInstantUpload: function(task, result) {
console.log(task.fileName + ' deduped -> ' + result.fileGuid);
}
});Client-claimed hashes are never used for dedupe — the server hashes content itself. Renditions and encrypted uploads never probe.
License
Commercial license. See multipleupload.com for details.
