Works with any backend npm install Zero dependencies

Uploads that survive reloads + crashes

Every in-flight upload persists to IndexedDB automatically. With persistBlobs: true, File objects are stored too - uploads auto-resume after tab crashes or page reloads without re-picking files. Works with chunked, s3, and tus strategies: each preserves its server session (chunk offsets, S3 part ETags, or tus URL) so resume continues from exactly where it stopped.

Live demo. Pick a large file, start uploading, reload the page (F5) mid-upload - the task auto-rehydrates from IndexedDB and continues from the last completed chunk. The server is the site's built-in chunked endpoint.
Configuration
new MultipleUpload('#uploader', {
 uploadUrl: '/api/upload',
 chunked: true,
 chunkSize: 1 * 1024 * 1024,
 persistState: true,
 persistAdapter: 'indexeddb', // or 'localStorage' (no blob storage)
 persistBlobs: true // store File blobs for zero-click resume
});
Rehydrate on load
uploader.on('stateRestored', (state) => {
 state.resumableTasks.forEach(entry => {
 // entry.blob is present when persistBlobs=true.
 // Otherwise pass a re-picked File as the second arg.
 uploader.resumeFromState(entry /* , file */);
 });
});
What gets persisted
  • Always: task id, file metadata, strategy, progress, hash, per-chunk byte counts, S3 uploadId + part ETags, tus session URL.
  • With persistBlobs: true: the File object itself (IndexedDB stores Blobs natively).
  • Never: upload tokens with limited lifetimes - those are re-fetched on resume.
Pluggable adapters

Swap the storage backend by passing a PersistAdapter object with load(key), save(key, state), clear(key) - all returning Promises:

new MultipleUpload('#uploader', {
 persistState: true,
 persistAdapter: {
 load: async (key) => { /* your load logic */ return state; },
 save: async (key, state) => { /* your save logic */ },
 clear: async (key) => { /* your clear logic */ }
 }
});

The built-ins are accessible at MultipleUpload.PersistAdapters.localStorage and MultipleUpload.PersistAdapters.indexedDB if you want to wrap them.