Works with any backend npm install Zero dependencies

Browser-direct Google Cloud Storage resumable upload

Third member of the cloud-direct trio (S3, Azure, GCS). The browser uploads each chunk straight to a GCS resumable session URL via Content-Range headers. Bytes bypass your server entirely - your backend only signs the initiate. Survives reloads via IndexedDB: the resumed task probes GCS with a zero-byte PUT, reads Range: bytes=0-N from the 308 response, and continues from byte N+1.

Resume contract: the GCS session URL is persisted in IndexedDB. On resume, a zero-byte PUT to the session URL with Content-Range: bytes */<total> returns 308 Resume Incomplete with the byte offset GCS holds. The client picks up from there. Identical model to tus.
Client config
new MultipleUpload('#uploader', {
 uploadUrl: '/api/upload',
 strategy: 'gcs',
 chunkSize: 8 * 1024 * 1024, // default 8 MiB; auto-rounded to 256 KiB grain
 persistState: true,
 persistAdapter: 'indexeddb',

 // Optional: override the default endpoint paths
 // gcsInitiateUrl: '/my/gcs/initiate',
 // gcsFinalizeUrl: '/my/gcs/finalize',
 // gcsAbortUrl: '/my/gcs/abort',
});
Wire protocol
  • POST /gcs/initiate - your server creates a GCS resumable session via the official SDK, returns { sessionUrl, key }. The browser never sees the GCS access token.
  • PUT <sessionUrl> <- chunked bytes with Content-Range: bytes <s>-<e>/<total>
  • 308 Resume Incomplete on every chunk except the last (with Range: bytes=0-N showing committed offset)
  • 200/201 OK on the final chunk - body is the GCS object metadata
  • POST /gcs/finalize - your bookkeeping hook (record GCS key in DB, etc.)
Node server snippet (initiate)
// /gcs/initiate handler - using @google-cloud/storage
import { Storage } from '@google-cloud/storage';
const storage = new Storage();
const bucket = storage.bucket('your-bucket');

app.post('/gcs/initiate', async (req, res) => {
 const { fileName, fileSize, contentType } = req.body;
 const file = bucket.file(`uploads/${crypto.randomUUID()}/${fileName}`);
 const [sessionUrl] = await file.createResumableUpload({ metadata: { contentType } });
 res.json({ sessionUrl, key: file.name });
});
Bucket CORS
[{
 "origin": ["https://your-site.com"],
 "method": ["PUT", "OPTIONS"],
 "responseHeader": ["Range", "Content-Range", "ETag"],
 "maxAgeSeconds": 3600
}]

Apply with gsutil cors set cors.json gs://your-bucket.

Cloud trio at a glance
StrategyBest forChunk grainConcurrency
's3'S3, MinIO, R2, B2, Wasabi5 MiB min partParallel parts
'azure'Azure Blob4 MiB blocksParallel blocks
'gcs'Google Cloud Storage256 KiB grainSequential (protocol)