Skip to content

File Uploads

FileUploadMiddleware handles multipart/form-data file uploads with size validation, MIME type filtering, and configurable storage.

server.post("/upload", FileUploadMiddleware(), request => {
request.file("avatar").flatMap {
case Some(file) =>
Map("filename" -> file.filename, "size" -> file.size).asJson(201)
case None =>
"No file uploaded".asText(400)
}
})
server.post("/upload", FileUploadMiddleware(FileUploadOptions(
storage = DiskStorage(
tempDir = "/tmp",
preserveExtension = true,
createHash = true,
),
maxFileSize = 10 * 1024 * 1024, // 10 MB
maxFiles = 5,
allowedMimes = Set("image/jpeg", "image/png", "application/pdf"),
preserveExtension = true,
createHashOnUpload = false,
onProgress = progress => println(s"${progress.filename}: ${progress.bytesReceived}/${progress.bytesExpected}"),
filter = file => Future.successful(file.size < 5 * 1024 * 1024),
)), uploadHandler)
OptionTypeDefaultDescription
storageStorageEngineDiskStorage()Where to store files
maxFileSizeLong50 MBMaximum file size
maxFilesInt10Maximum number of files
allowedMimesSet[String]Set.empty (all)Allowed MIME types
tempDirString"/tmp"Temporary directory
preserveExtensionBooleantrueKeep original extension
createHashOnUploadBooleanfalseGenerate SHA-256 hash
onProgressUploadProgress => Unitno-opProgress callback
filterUploadedFile => Future[Boolean]_ => truePost-upload filter

After the middleware processes the upload, file data is available through request extension methods:

// Single file by field name
request.file("avatar") // Future[Option[UploadedFile]]
// Multiple files from same field
request.files("photos") // Future[List[UploadedFile]]
// All uploaded files
request.allFiles // Future[Map[String, List[UploadedFile]]]
case class UploadedFile(
fieldname: String, // Form field name
filename: String, // Original filename
encoding: String, // Content encoding
mimetype: String, // MIME type
size: Long, // File size in bytes
path: String, // Path on disk
hash: Option[String] = None, // SHA-256 hash (if enabled)
)

Saves files to disk with UUID-based names:

DiskStorage(
tempDir = "/tmp",
preserveExtension = true,
createHash = true, // SHA-256 hash
)

Implement the StorageEngine trait:

trait StorageEngine {
def store(
fieldname: String,
file: ReadableStream,
filename: String,
encoding: String,
mimetype: String,
): Future[UploadedFile]
def remove(file: UploadedFile): Future[Unit]
}
sealed trait FileUploadError extends ServerError
case class FileTooLargeError(size: Long, limit: Long)
case class UnsupportedMimeTypeError(mime: String)
case class TooManyFilesError(count: Int, limit: Int)
case class FileUploadSystemError(underlying: Throwable)

Under the hood, FileUploadMiddleware uses the built-in MultipartParser — a pure Scala.js implementation with no npm dependencies. The parser handles RFC 2046-compliant multipart boundary detection, header parsing, and body extraction.