---
title: "SDK File Uploads"
description: "Upload files to a workspace bucket via the Aeontel SDK using the multipart R2 lifecycle."
section: "Libraries"
group: "SDK"
---

# SDK File Uploads

Files live in workspace-scoped R2 buckets and upload through a three-step lifecycle: **create → PUT parts → complete**. The SDK ships a `runUpload` helper that runs the full flow for you.

## One-shot upload

```ts
import Aeontel, { runUpload } from "@aeontel/sdk";

const client = new Aeontel("sec_...");

const { promise } = runUpload(client, {
  workspaceId: "wsp_...",
  blob: new Blob([bytes], { type: "application/pdf" }),
  filename: "invoice.pdf",
  onProgress: (sent, total) => {
    console.log(`${((sent / total) * 100).toFixed(1)}%`);
  },
});

const file = await promise;
console.log(file.id, file.status); // "fil_...", "ready"
```

`runUpload` returns a `{ promise, abort }` handle. Call `abort()` to cancel cleanly — it stops in-flight part PUTs and marks the upload aborted.

## Options

| Option        | Type                    | Notes                                                                |
| ------------- | ----------------------- | -------------------------------------------------------------------- |
| `workspaceId` | string                  | Required — workspace the file belongs to                             |
| `blob`        | `Blob`                  | Required — file bytes (use `File` in the browser)                    |
| `filename`    | string                  | Falls back to `blob.name` if omitted                                 |
| `contentType` | string                  | Falls back to `blob.type`                                            |
| `directoryId` | string                  | Defaults to the workspace root directory                             |
| `concurrency` | number                  | Parallel part PUTs, default 4                                        |
| `onProgress`  | `(sent, total) => void` | Byte-level progress                                                  |
| `onStatus`    | `(status) => void`      | `initiating`, `uploading`, `completing`, `ready`, `error`, `aborted` |

## Manual control

Need to stream bytes from somewhere unusual (S3 proxy, Worker origin)? Drive the lifecycle by hand:

```ts
// 1. Reserve a file row and get presigned part URLs
const init = await client.files.create({
  workspaceId: "wsp_...",
  name: "big.bin",
  contentType: "application/octet-stream",
  size: blob.size,
});

// 2. PUT every part to init.parts[i].url — R2 returns an ETag per part

// 3. Finalize — passes the ETags back so R2 assembles the object
const file = await client.files.completeUpload({
  id: init.file.id,
  parts: etaggedParts,
});
```

For big files, call `client.files.signUploadParts` to mint additional presigned URLs once the initial batch is consumed.
