Uploads and frontend integration

# File Upload API guide: architecture, security, and provider selection

Choose and implement a File Upload API by comparing architecture, resumability, direct-to-cloud transfer, security, storage boundaries, and providers.

Published August 24, 2026

## Key takeaways

* Name the byte path explicitly: application relay, direct object storage, direct processing service, or remote-source import.
* Treat resumability, retry, idempotency, and expiry as separate reliability controls with different failure behavior.
* Keep upload authorization on the server, validate observed file properties, and publish only from controlled permanent storage.

Choosing a File Upload API is an architecture decision, not a widget comparison. The right design controls where bytes travel, who can authorize a transfer, how interrupted work resumes, when a file becomes trusted, where durable copies live, and how processing results return to the product.

## In this guide

1. [Define the upload job before choosing a widget](#file-upload-api-guide-section-1)
2. [Choose one of four explicit byte paths](#file-upload-api-guide-section-2)
3. [Design resumability separately from retries](#file-upload-api-guide-section-3)
4. [Authorize intent on the server and distrust the bytes](#file-upload-api-guide-section-4)
5. [Separate durable storage from upload processing](#file-upload-api-guide-section-5)
6. [Make the build-versus-buy decision at the ownership boundary](#file-upload-api-guide-section-6)
7. [Compare providers with failure tests, not feature counts](#file-upload-api-guide-section-7)
8. [Implement a signed Uppy and Transloadit upload](#file-upload-api-guide-section-8)
9. [Connect intake, validation, processing, and export](#file-upload-api-guide-section-9)

## What matters most

* Choose build, buy, or hybrid ownership by testing recovery, security, processing, and operational requirements—not only the happy-path demo.
* Use framework-specific examples only when their lifecycle, server authorization, and recovery behavior can be maintained and tested.

## Define the upload job before choosing a widget

A production upload begins before the first byte moves. The application identifies the user, decides which operation is allowed, describes acceptable file count and size, and creates a record that can outlive a browser tab. Transfer is only one stage. Validation, processing, export, notification, and reconciliation determine whether the product can safely use the file afterward.

Write a completion contract in product terms. “The request returned 200” is weak; “the original and required derivatives are stored under this tenant, the asset record names their versions, and a duplicate callback changes nothing” is testable. Record which failures users may retry, which require a new upload, and which leave an operator-visible state for recovery.

### Control plane

Carries identity, authorization, limits, workflow choice, metadata, status, and result references rather than the file body itself.

### Data plane

Carries file bytes between the user, application, upload service, processing layer, and durable storage destination.

### Trust transition

Marks when an untrusted upload has passed the checks required for processing, storage, preview, or public delivery.

## Choose one of four explicit byte paths

In an application relay, the browser sends the file to your server and your server forwards or stores it. This is understandable and gives the application immediate control, but every byte consumes your ingress, memory or temporary disk, connection time, and egress. It fits small, infrequent files when the existing server can enforce limits and stream safely without buffering entire bodies.

“Direct to cloud” is ambiguous, so name the destination. A browser can upload directly to object storage with short-lived credentials, directly to a processing service such as Transloadit, or ask a service to import an existing remote URL. Direct storage minimizes hops when persistence is the only job. Direct processing keeps the application out of the data path while a workflow validates, transforms, and exports files to owned storage.

### Application relay

Useful for modest workloads and simple policy, but the application owns transfer capacity, timeouts, temporary files, and scaling.

### Direct object storage

Best when the first durable copy is the main outcome and later processing can be triggered reliably from a storage event or queue.

### Direct processing service

Useful when upload and asynchronous validation, derivatives, metadata, or multi-destination export belong to one observable job.

### Remote-source import

Moves bytes server to server, which saves the user’s connection but requires explicit source authorization and fetch limits.

## Design resumability separately from retries

A retry starts an operation again; a resumable transfer continues an existing upload from a server-confirmed byte offset. With tus, the client retains the upload URL, asks the server for `Upload-Offset`, and sends only the remaining bytes. Persist that URL outside transient component state if a refresh or crash should recover, and fingerprint files carefully so one user’s local file is never attached to another upload resource.

Resumability does not make time unlimited or processing idempotent. A Transloadit Assembly still has eight hours from creation to finish uploading, and creating a replacement Assembly can duplicate work unless the application reconciles the old identifier. Define how the client handles pause, offline time, expired resources, changed files, abandoned uploads, and a completion response lost after the server accepted the final bytes.

### Resume identity

Persist the server-issued upload URL together with the authenticated user, local file fingerprint, expected length, and operation record.

### Expiry path

When the upload resource or Assembly has expired, create a fresh operation and retire the stale identifier instead of retrying forever.

### Completion reconciliation

Query durable status after ambiguous network failures so the client does not assume that a missing response means missing bytes.

## Authorize intent on the server and distrust the bytes

Browser code may contain a public Auth Key, but it must never contain the Transloadit Auth Secret or permanent storage credentials. Authenticate the user in your application, select a saved Template on the server, and return short-lived signed parameters with a unique `nonce`. Set `allow_steps_override` to false when the browser has no legitimate reason to replace Steps, because a client-selected Step graph could otherwise change processing or export behavior.

A valid signature proves that the parameter payload was authorized; it does not prove that the uploaded bytes match a filename, extension, declared MIME type, tenant, or moderation policy. Limit request bodies before expensive work, inspect observed file properties, reject unsupported content, scan where the threat model requires it, and keep untrusted output away from public storage until the workflow reaches an approved state.

### Short-lived authorization

Issue upload permission only after application authentication and scope it to a server-selected operation for a limited time.

### Observed properties

Use detected type, dimensions, duration, and other inspected metadata for routing instead of trusting the extension alone.

### Quarantine before publish

Separate receipt from public delivery so invalid, malicious, or policy-rejected files never become an application asset by default.

## Separate durable storage from upload processing

An upload endpoint is not automatically a system of record. Decide which bucket or asset database owns the original, how derivatives relate to it, which identifiers survive renames, and who deletes each copy. If a Transloadit Assembly has no export Step, temporary files are deleted after 24 hours and their access URLs can expire after a few hours. The URLs are limited to short-term retrieval, not embedding or repeated product delivery.

Place export inside the Assembly when workflow success requires both processing and persistence. An export Robot can use stored Template Credentials to write results to the chosen destination as part of the workflow. Alternatively, upload directly to owned storage first and invoke processing from a controlled event. That path gains an early durable copy but adds orchestration and another transfer into the processor.

### Original ownership

State whether the original is retained, for how long, under which tenant key, and whether later workflows may read it again.

### Derivative lineage

Store the source identifier, workflow configuration, output role, dimensions, format, and checksum needed to explain each result.

### Delivery boundary

Serve approved assets from permanent storage and an intentional delivery layer rather than temporary processing URLs.

## Make the build-versus-buy decision at the ownership boundary

Build the transfer path when requirements are narrow and the team is prepared to own the complete lifecycle. A small authenticated form that streams short files into one existing bucket may not justify another platform. The estimate must still include multipart parsing, backpressure, size enforcement, resumability or its deliberate absence, cleanup, abuse controls, observability, upgrades, and support for failures outside the request’s lifetime.

A managed service becomes more attractive as the workflow combines unreliable networks, large files, browser UX, remote sources, media inspection, transformation, or several storage destinations. Buying does not remove application ownership: tenant checks, authorization, asset records, retention, publication, and incident handling remain yours. A hybrid design often works best, with owned storage and business state around a managed transfer and processing layer.

### Build cost

Count engineering, infrastructure, on-call work, protocol maintenance, security review, and user support—not only object-storage fees.

### Buy cost

Model upload bytes, processing operations, retries, storage transfer, minimum charges, support level, and expected growth.

### Hybrid ownership

Keep identity, policy, metadata, and permanent storage in your product while delegating the specialized data path and processing work.

## Compare providers with failure tests, not feature counts

Create a scorecard from the product’s actual workload. Compare browser and mobile clients, open protocol support, maximum file size, concurrency behavior, geographic endpoints, remote imports, processing breadth, storage destinations, credential isolation, webhook verification, status retention, support, and exit options. Mark every feature as required, optional, or irrelevant before looking at vendor pages.

Run the same fixtures through each serious candidate. Interrupt a large upload, reload the page, send a duplicate completion event, revoke storage credentials, decline a file after receipt, exceed a limit, and lose the final response. Measure user-visible recovery, bytes retransmitted, time to durable output, operator evidence, and cleanup. A polished picker says little about these production properties.

### Protocol portability

An open resumable protocol and replaceable clients reduce migration coupling, but workflow and result schemas still require migration planning.

### Operational evidence

Require stable job identifiers, terminal states, timestamps, actionable errors, verified callbacks, and a documented replay procedure.

### Complete economics

Compare transfer, processing, storage, delivery, support, engineering, and failure-recovery costs at representative monthly volume.

## Implement a signed Uppy and Transloadit upload

The browser example lets Uppy manage selection and tus transfer while the Transloadit plugin requests Assembly parameters from your application. The server endpoint must authenticate the current user before returning the object from `calcSignature`. It should select the Template itself instead of accepting arbitrary Steps or a caller-provided storage destination, and it should rate-limit authorization independently from upload traffic.

The Auth Key identifies the Workspace and can be present in the returned parameters; the Auth Secret stays in the server process. In the saved Template, disable Step overrides unless a reviewed use case needs them. Choose whether the UI waits for encoding or returns after upload, then persist the Assembly ID either way so a background worker or verified webhook can reconcile the final result.

Let Uppy request bounded Assembly parameters from your back end

```
import Uppy from '@uppy/core'
import Transloadit from '@uppy/transloadit'

const uppy = new Uppy().use(Transloadit, {
  async assemblyOptions() {
    const response = await fetch('/api/transloadit-params', {
      credentials: 'same-origin',
    })
    if (!response.ok) {
      throw new Error('Could not authorize this upload')
    }
    return response.json()
  },
  waitForEncoding: true,
})

uppy.on('transloadit:complete', (assembly) => {
  console.log(assembly.assembly_id, assembly.results)
})
```

Sign one server-selected Template without exposing the Auth Secret

```
import { randomUUID } from 'node:crypto'
import { Transloadit } from 'transloadit'

function requiredEnvironmentValue(name: string): string {
  const value = process.env[name]
  if (value == null) throw new Error(`Missing environment variable: ${name}`)
  return value
}

const transloadit = new Transloadit({
  authKey: requiredEnvironmentValue('TRANSLOADIT_KEY'),
  authSecret: requiredEnvironmentValue('TRANSLOADIT_SECRET'),
})

export function createAuthorizedUploadParameters() {
  // Call this only after the server has authenticated the request and authorized the operation.
  return transloadit.calcSignature({
    auth: {
      expires: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
      nonce: randomUUID(),
    },
    template_id: requiredEnvironmentValue('TRANSLOADIT_UPLOAD_TEMPLATE_ID'),
  })
}
```

## Connect intake, validation, processing, and export

The Template accepts browser uploads through `/upload/handle`, checks the observed MIME family with `/file/filter`, creates a bounded preview through `/image/resize`, and exports both the accepted original and preview with `/s3/store`. The named Template Credential keeps bucket credentials out of browser parameters, while `allow_steps_override` prevents a caller from replacing that destination through a Step override.

Treat this as a minimal architecture example rather than a universal security policy. Add maximum upload size, file-count policy, virus scanning, moderation, naming, retention, and destination rules from the product’s threat model. Store the Assembly ID and exported result records under the authenticated tenant, and verify that every expected output reached permanent storage before the application marks the asset ready.

Validate, derive, and export files with a locked Template

```
{
  "allow_steps_override": false,
  "steps": {
    ":original": {
      "robot": "/upload/handle"
    },
    "accepted_images": {
      "use": ":original",
      "robot": "/file/filter",
      "accepts": [
        ["${file.mime}", "regex", "^image/"]
      ],
      "error_on_decline": true
    },
    "preview": {
      "use": "accepted_images",
      "robot": "/image/resize",
      "resize_strategy": "fit",
      "width": 1600,
      "height": 1600
    },
    "exported": {
      "use": ["accepted_images", "preview"],
      "robot": "/s3/store",
      "credentials": "my_s3_credentials",
      "path": "uploads/${file.id}/${file.url_name}"
    }
  }
}
```

## Technical details worth knowing

* A browser-to-application upload makes the application server part of the data path, while a direct-storage or direct-processing design keeps file bytes out of that request handler.
* The tus core protocol resumes by reading the server’s `Upload-Offset` with `HEAD` and continuing with `PATCH`; sending the whole file again is a retry, not a resume.
* Uppy’s Transloadit plugin uses tus for file transfer and can request signed Assembly parameters from an application back end through its `assemblyOptions` function.
* Transloadit Signature Authentication signs the JSON-encoded parameters with the Auth Secret on a trusted server; the secret itself must never be sent to browser code.
* A saved Template with `allow_steps_override` set to false prevents an untrusted client from replacing its Steps or selecting a different storage target through Step overrides.
* Without an export Robot, Transloadit deletes temporary files after 24 hours, while their temporary URLs can expire after a few hours and are intended only for limited, short-term retrieval.
* For Transloadit tus uploads, the Assembly is created before file bytes arrive and remains in `ASSEMBLY_UPLOADING` until the declared uploads have finished.
* The Transloadit upload window is eight hours from Assembly creation, so a resumable client still needs a deliberate restart path when the Assembly has expired.

## A practical approach

1. 1\
   Write the byte path, trust transitions, durable owner, and completion contract before selecting an uploader.
2. 2\
   Test relay, direct-storage, and direct-processing options against representative files and network failures.
3. 3\
   Implement short-lived server authorization, resumability, validation, export, and idempotent result handling.
4. 4\
   Load-test the chosen path and rehearse expiry, duplicate callbacks, revoked credentials, and partial failures.

A four-stage media workflow

## When Transloadit is useful

Use Transloadit when uploads need tus resumability, Uppy browser UX, remote-source ingestion, multi-step file processing, or exports to storage you control. Exact parameter contracts live in the `/upload/handle`, `/file/filter`, `/image/resize`, and `/s3/store` Robot documentation.

## Architecture boundary

Transloadit can receive files, run asynchronous processing workflows, and export results, but your application still owns user authentication, tenant authorization, the durable asset record, publication policy, and delivery from permanent storage.

## Frequently asked questions

### What does “direct-to-cloud upload” mean?

It is not one architecture. It can mean browser-to-object-storage, browser-to-processing-service, or server-to-server import from another provider. Name the actual byte destination, authorization mechanism, durable owner, and processing trigger before comparing implementations.

### Should files pass through my application server?

Only when the policy or simplicity benefit outweighs owning the data path. Relaying can fit small, infrequent uploads, but direct storage or direct processing avoids consuming application bandwidth, request time, temporary disk, and connection capacity for every byte.

### Is a retry the same as a resumable upload?

No. A retry normally starts the transfer again, while resumability continues an existing resource from the byte offset confirmed by the server. The client must retain the upload identity and still handle expiry, changed local files, and ambiguous final responses.

### Does Transloadit permanently store uploaded files?

Not unless the workflow exports them to a permanent destination. Without an export Robot, temporary files are deleted after 24 hours and their URLs can expire after a few hours. Production workflows should export to controlled storage or use those URLs only for limited, short-term retrieval into owned infrastructure.

### When should I build an upload API instead of buying one?

Building can be sensible for a narrow path with small files, one storage target, predictable networks, and a team ready to own security and operations. Managed infrastructure earns its cost when resumability, remote sources, large files, processing, multiple destinations, or failure recovery would become a separate product.

### What belongs in a framework-specific upload guide?

A framework guide should focus on maintainable code, lifecycle behavior, server authorization, recovery, and tests for that framework. Use the linked resumable and API references for exact protocol and request contracts, and evaluate providers against the broader architectural requirements above.

## Build the workflow

Move from the concept to a tested Assembly with Robot documentation and working demos.

### Relevant Robots

* [/upload/handle](/docs/robots/upload-handle.md)
* [/file/filter](/docs/robots/file-filter.md)
* [/image/resize](/docs/robots/image-resize.md)
* [/s3/store](/docs/robots/s3-store.md)
* [Explore managed file uploads](/services/handling-uploads.md)
* [Read the resumable upload API reference](/docs/api/resumable-uploads.md)
* [Read the Signature Authentication reference](/docs/api/authentication.md)
* [Read the API documentation](/docs.md)
* [Explore working demos](/demos.md)
* [Create a free workspace](/c/signup/)

Uploads and frontend integration

## Continue with related guides

* [How to accept large uploads that survive a dropped connection](/guides/resumable-uploads-for-large-files.md)\
  Accept multi-gigabyte uploads over tus, continue them after a dropped connection, and keep the Assembly alive long enough to finish.
* [Media automation: from upload to reliable output](/guides/media-automation.md)\
  Automate repeatable media intake, transformation, validation, and export while preserving observability and control.
* [HTML video in production: 10 practical checks](/guides/html-video-production-checklist.md)\
  Ten production checks for HTML video, from source selection and captions to poster images and preprocessing.
* [Three best practices for CSS banner images](/guides/css-banner-image-best-practices.md)\
  Three durable practices for sharp, responsive CSS banner images without unreadable text or accidental crops.
* [Five reliable ways to center an image in HTML](/guides/center-images-in-html.md)\
  Five predictable ways to center images in HTML and how to choose between layout and media preprocessing.
* [Six ways to size a background image with CSS](/guides/stretch-background-images-with-css.md)\
  Six CSS approaches to background sizing, with guidance on avoiding distortion and unnecessary downloads.
