Guide

Build peer-to-peer file transfer in JavaScript

Send a file to another browser while both users are online. Trystero handles connection setup and chunking; your app handles approval and download controls.

Why send files peer-to-peer?

A server upload works well when recipients come back later. If both users are online now, a WebRTC connection can move the file between them without storing a copy on your backend.

That suits a one-time file drop or sharing an asset during a live session. Some connections need TURN, which still carries traffic and may incur bandwidth costs.

Send a file over a Trystero action

The sender passes a File; the receiver gets a Uint8Array. Attach a filename and type as metadata, then wrap the received bytes in a Blob to create a download URL. Use a connected peer ID as recipientId.

import {joinRoom} from 'trystero'

const room = joinRoom({appId: 'com.example.dropzone'}, transferId)
const files = room.makeAction('file', {
  onReceive: ({byteLength, peerId, metadata, signal}) => {
    if (byteLength > 100 * 1024 ** 2) return false
    return askToAcceptFile({peerId, metadata, byteLength, signal})
  }
})

files.onReceiveProgress = (progress, {peerId, metadata}) => {
  renderIncomingProgress(peerId, metadata?.name ?? 'download', progress)
}

files.onMessage = (bytes, {peerId, metadata}) => {
  const blob = new Blob([bytes], {type: metadata?.type})
  const url = URL.createObjectURL(blob)

  addDownload({
    peerId,
    url,
    name: metadata?.name ?? 'download',
    size: blob.size,
    type: blob.type
  })
}

fileInput.onchange = async () => {
  const file = fileInput.files?.[0]

  if (!file) return

  try {
    await files.send(file, {
      target: recipientId,
      metadata: {name: file.name, type: file.type},
      onProgress: progress => renderSendProgress(file.name, progress)
    })
  } catch (error) {
    showTransferError(error)
  }
}

askToAcceptFile() is your UI: resolve to true or false, and dismiss the prompt if its signal aborts. Large transfers wait for approval before sending their contents. Treat metadata as sender-provided input and revoke object URLs when downloads are removed.

Limits and interrupted transfers

Trystero 0.26 defaults to a 256 MiB receive budget per room. Raise maxReceiveBytes on the receiver for larger files, or lower it to suit your app. Concurrent large transfers queue when the budget is occupied. Each file must fit in memory.

Keep both tabs open and handle failed sends. Progress ranges from 0 to 1; a resolved send promise confirms local sending, not that the recipient saved the file. If you need offline delivery, scanning, or resume after a tab closes, add storage and a delivery protocol to your app.