Skip to main content
Tusky uses the tus open protocol for resumable file uploads. This means uploads can be interrupted — by network failures, browser crashes, or intentional pauses — and resumed exactly where they left off, without re-uploading data that was already transferred.

How resumable uploads work

1

Create the upload

Send a POST request to the upload endpoint with file metadata (size, filename, environment ID). Tusky returns a unique upload URL in the Location header.
2

Upload chunks

Send PATCH requests to the upload URL with chunks of file data. Each chunk includes an Upload-Offset header indicating where in the file this chunk belongs.
3

Resume on failure

If the connection drops mid-upload, send a HEAD request to the upload URL to check how many bytes the server has received. Then resume by sending a PATCH with the next chunk from that offset.
4

Upload completes

When the server has received all bytes (offset equals the total file size), the upload is complete. Tusky processes the file — encrypting (if applicable), batching into quilts, and publishing to Walrus.

Why resumable uploads?

Reliability

Network interruptions don’t mean starting over. Resume from the last successful byte, even after a complete connection loss.

Large files

Upload files up to 2.5 GB without worrying about timeouts. Chunks are small enough to survive unstable connections.

Mobile friendly

Mobile networks are inherently unreliable. Resumable uploads ensure files get through even on spotty cellular connections.

Progress tracking

Track upload progress byte-by-byte. Show accurate progress bars and estimated completion times in your UI.

tus protocol overview

Tusky implements the tus v1.0.0 protocol with the Creation and Creation With Upload extensions. Every request must include the Tus-Resumable: 1.0.0 header.

Key headers

HeaderDirectionDescription
Tus-ResumableRequest & ResponseProtocol version. Always 1.0.0.
Upload-LengthRequest (POST)Total file size in bytes.
Upload-OffsetRequest (PATCH) / ResponseCurrent byte offset. Client sends where the chunk starts; server responds with the new offset after writing.
Upload-MetadataRequest (POST)Base64-encoded key-value pairs: filename, filetype, environmentId.
Content-TypeRequest (PATCH)Must be application/offset+octet-stream for chunk data.
LocationResponse (POST)The URL to send subsequent PATCH and HEAD requests to.

Upload lifecycle

Client                                    Server
  │                                          │
  │  POST /uploads                           │
  │  Upload-Length: 10485760                  │
  │  Upload-Metadata: filename ..., environmentId  │
  │ ────────────────────────────────────────▶ │
  │                                          │
  │  201 Created                             │
  │  Location: /uploads/upload_abc123        │
  │ ◀──────────────────────────────────────── │
  │                                          │
  │  PATCH /uploads/upload_abc123            │
  │  Upload-Offset: 0                        │
  │  Content-Type: application/offset+...    │
  │  [chunk 1 data: 0 – 5MB]                │
  │ ────────────────────────────────────────▶ │
  │                                          │
  │  204 No Content                          │
  │  Upload-Offset: 5242880                  │
  │ ◀──────────────────────────────────────── │
  │                                          │
  │  ❌ Connection lost                       │
  │                                          │
  │  HEAD /uploads/upload_abc123             │
  │ ────────────────────────────────────────▶ │
  │                                          │
  │  200 OK                                  │
  │  Upload-Offset: 5242880                  │
  │ ◀──────────────────────────────────────── │
  │                                          │
  │  PATCH /uploads/upload_abc123            │
  │  Upload-Offset: 5242880                  │
  │  [chunk 2 data: 5MB – 10MB]             │
  │ ────────────────────────────────────────▶ │
  │                                          │
  │  204 No Content                          │
  │  Upload-Offset: 10485760 ✅ Complete      │
  │ ◀──────────────────────────────────────── │

Using the SDK

The Tusky SDK handles resumable uploads automatically — chunking, offset tracking, and resume logic are all built in.
import { Tusky } from "@tusky-io/ts-sdk";

const tusky = new Tusky({ apiKey: process.env.TUSKY_API_KEY });

const fileId = await tusky.file.upload("env_abc123", "./large-video.mp4", {
  onProgress: (bytesUploaded, bytesTotal) => {
    const pct = ((bytesUploaded / bytesTotal) * 100).toFixed(1);
    console.log(`${pct}% uploaded`);
  },
});
The SDK automatically resumes interrupted uploads on retry.

Browser uploads with Uppy

For browser-based applications, Tusky integrates with Uppy — a modular file uploader that supports tus out of the box. Uppy adds a polished upload UI with drag-and-drop, progress bars, and powerful plugins.

Golden Retriever

The Golden Retriever plugin protects users from data loss by caching selected files in the browser.
  • Automatic recovery: If the browser crashes, the tab closes, or the user navigates away, Golden Retriever restores the file selection and upload progress when the user returns.
  • Storage layers: Uses IndexedDB for files under 5 MB and optional Service Worker for larger files. Metadata and state are stored in LocalStorage.
  • Zero configuration: Install the plugin and it works automatically — no user action required.
import Uppy from "@uppy/core";
import GoldenRetriever from "@uppy/golden-retriever";
import Tus from "@uppy/tus";

const uppy = new Uppy()
  .use(Tus, { endpoint: "https://api.tusky.io/v2/uploads" })
  .use(GoldenRetriever);
Golden Retriever is especially valuable when users spend time selecting and configuring multiple files before uploading. It prevents frustrating data loss from accidental page closures.

Compressor

The Compressor plugin optimizes images before upload, reducing file sizes by up to 60%.
  • Automatic: Compresses JPEG and PNG images transparently before they are uploaded — no user action required.
  • Configurable quality: Default quality is 0.6 (60%). Adjust to balance size vs. visual quality.
  • Bandwidth savings: Particularly valuable for mobile users on slow or metered connections.
  • Parallel processing: Compresses up to 10 images concurrently by default.
import Uppy from "@uppy/core";
import Compressor from "@uppy/compressor";
import Tus from "@uppy/tus";

const uppy = new Uppy()
  .use(Tus, { endpoint: "https://api.tusky.io/v2/uploads" })
  .use(Compressor, { quality: 0.6 });
The Compressor plugin only affects image files. Non-image files pass through unmodified. The original file is replaced in the upload queue with the compressed version, and Uppy displays the total bytes saved.

Combining plugins

For a production browser upload experience, combine tus, Golden Retriever, and Compressor:
import Uppy from "@uppy/core";
import Dashboard from "@uppy/dashboard";
import Tus from "@uppy/tus";
import GoldenRetriever from "@uppy/golden-retriever";
import Compressor from "@uppy/compressor";

const uppy = new Uppy()
  .use(Dashboard, { inline: true, target: "#upload-area" })
  .use(Tus, {
    endpoint: "https://api.tusky.io/v2/uploads",
    headers: { Authorization: "Bearer YOUR_API_KEY" },
    chunkSize: 5 * 1024 * 1024, // 5 MB chunks
  })
  .use(GoldenRetriever)
  .use(Compressor, { quality: 0.6 });

uppy.on("complete", (result) => {
  console.log("Uploads complete:", result.successful);
});

What’s next?