Works with any backend npm install Zero dependencies

Ruby on Rails

Handle MultipleUpload file uploads with Ruby on Rails using Active Storage or direct file handling. This guide covers both standard multipart uploads and chunked uploads.

Routes

Add upload routes in config/routes.rb.

# config/routes.rb
Rails.application.routes.draw do
 post "/api/upload", to: "uploads#create"
 post "/api/upload-chunk", to: "uploads#chunk"
end

CORS Configuration

Add the rack-cors gem and configure it in config/initializers/cors.rb.

# Gemfile
gem "rack-cors"

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
 allow do
 origins "*"
 resource "/api/*",
 headers: %w[
 Content-Type X-Upload-Id X-Chunk-Index
 X-Chunk-Count X-File-Name X-File-Size
 ],
 methods: [:post, :options]
 end
end

Multipart Form Upload

The controller receives the file through Rails params and saves it with a unique GUID.

# app/controllers/uploads_controller.rb
class UploadsController < ApplicationController
 skip_before_action :verify_authenticity_token

 UPLOAD_DIR = Rails.root.join("storage", "uploads")
 CHUNK_DIR = Rails.root.join("storage", "uploads", "chunks")

 def create
 file = params[:file]

 unless file.present?
 return render json: { success: false, error: "No file uploaded" }, status: :bad_request
 end

 file_guid = SecureRandom.uuid
 ext = File.extname(file.original_filename)
 dest_path = UPLOAD_DIR.join("#{file_guid}#{ext}")

 FileUtils.mkdir_p(UPLOAD_DIR)
 File.open(dest_path, "wb") do |f|
 f.write(file.read)
 end

 render json: {
 success: true,
 fileGuid: file_guid,
 fileName: file.original_filename,
 fileSize: file.size
 }
 end
end

Chunked Upload

Each chunk arrives as a raw body with custom headers. Store chunks in a temporary directory and assemble the final file once all chunks have arrived.

# Add to UploadsController
def chunk
 upload_id = request.headers["X-Upload-Id"]
 chunk_index = request.headers["X-Chunk-Index"].to_i
 chunk_count = request.headers["X-Chunk-Count"].to_i
 file_name = request.headers["X-File-Name"] || "unknown"
 file_size = request.headers["X-File-Size"].to_i

 if upload_id.blank? || chunk_count.zero?
 return render json: { success: false, error: "Missing headers" }, status: :bad_request
 end

 session_dir = CHUNK_DIR.join(upload_id)
 FileUtils.mkdir_p(session_dir)

 # Save this chunk from raw body
 chunk_path = session_dir.join("chunk_#{chunk_index}")
 File.open(chunk_path, "wb") do |f|
 f.write(request.body.read)
 end

 # Count received chunks
 received = Dir.glob(session_dir.join("chunk_*")).count

 if received == chunk_count
 # Assemble final file
 file_guid = SecureRandom.uuid
 ext = File.extname(file_name)
 final_path = UPLOAD_DIR.join("#{file_guid}#{ext}")

 FileUtils.mkdir_p(UPLOAD_DIR)
 File.open(final_path, "wb") do |output|
 chunk_count.times do |i|
 cp = session_dir.join("chunk_#{i}")
 output.write(File.binread(cp))
 end
 end

 # Clean up chunks
 FileUtils.rm_rf(session_dir)

 return render json: {
 success: true,
 fileGuid: file_guid,
 fileName: file_name,
 fileSize: file_size
 }
 end

 render json: { success: true, chunksReceived: received }
end

Expected JSON Response

Both endpoints must return this JSON structure on successful upload completion.

{
 "success": true,
 "fileGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
 "fileName": "photo.jpg",
 "fileSize": 204800
}

Client-Side JavaScript

Initialize MultipleUpload on the client, pointing to your Rails API endpoints.

<link rel="stylesheet" href="multipleupload.css" />
<div id="uploader"></div>
<script src="multipleupload.js"></script>
<script>
 var uploader = new MultipleUpload("#uploader", {
 uploadUrl: "/api/upload",
 chunkUploadUrl: "/api/upload-chunk",
 chunkSize: 2 * 1024 * 1024, // 2 MB chunks
 onFileUploaded: function (file, response) {
 console.log("Uploaded:", response.fileName, response.fileGuid);
 }
 });
</script>