Skip to main content


The @uppy/tus plugin brings resumable file uploading with Tus to Uppy by wrapping the tus-js-client.

When should I use it?


Not sure which uploader is best for you? Read “Choosing the uploader you need”.

Tus is an open protocol for resumable uploads built on HTTP. This means accidentally closing your tab or losing connection let’s you continue, for instance, your 10GB upload instead of starting all over.

Tus supports any language, any platform, and any network. It requires a client and server integration to work. You can checkout the client and server implementations to find the server in your preferred language. You can store files on the Tus server itself, but you can also use service integrations (such as S3) to store files externally. If you don’t want to host your own server, see “Are there hosted Tus servers?”.

If you want reliable, resumable uploads: use @uppy/tus to connect to your Tus server in a few lines of code.


npm install @uppy/tus


A quick overview of the complete API.

import Uppy from '@uppy/core';
import Dashboard from '@uppy/dashboard';
import Tus from '@uppy/tus';

import '@uppy/core/dist/style.min.css';
import '@uppy/dashboard/dist/style.min.css';

new Uppy()
.use(Dashboard, { inline: true, target: 'body' })
.use(Tus, { endpoint: '' });




All options are passed to tus-js-client and we document the ones here that are required, added, or changed. This means you can also pass functions like onAfterResponse.

We recommended taking a look at the API reference from tus-js-client to know what is supported.


A unique identifier for this plugin (string, default: 'Tus').


URL of the tus server (string, default: null).


An object or function returning an object with HTTP headers to send along requests (object | function, default: null).

Keys are header names, values are header values.

const headers = {
authorization: `Bearer ${window.getCurrentUserToken()}`,

Header values can also be derived from file data by providing a function. The function receives an Uppy file and must return an object where the keys are header names, and values are header values.

const headers = (file) => {
return {
authorization: `Bearer ${window.getCurrentUserToken()}`,
expires: file.meta.expires,


A number indicating the maximum size of a PATCH request body in bytes (number, default: Infinity).


Do not set this value unless you are forced to. The two valid reasons are described in the tus-js-client docs.


Configure the requests to send Cookies using the xhr.withCredentials property (boolean, default: false).

The remote server must accept CORS and credentials.


When uploading a chunk fails, automatically try again after the defined millisecond intervals (Array<number>, default: [0, 1000, 3000, 5000]).

By default, we first retry instantly; if that fails, we retry after 1 second; if that fails, we retry after 3 seconds, etc.

Set to null to disable automatic retries, and fail instantly if any chunk fails to upload.

onBeforeRequest(req, file)

Behaves like the onBeforeRequest function from tus-js-client but with the added file argument.

onShouldRetry: (err, retryAttempt, options, next)

When an upload fails onShouldRetry is called with the error and the default retry logic as the last argument (function).

The default retry logic is an exponential backoff algorithm triggered on HTTP 429 (Too Many Requests) errors. Meaning if your server (or proxy) returns HTTP 429 because it’s being overloaded, @uppy/tus will find the ideal sweet spot to keep uploading without overloading.

If you want to extend this functionality, for instance to retry on unauthorized requests (to retrieve a new authentication token):

import Uppy from '@uppy/core';
import Tus from '@uppy/tus';
new Uppy().use(Tus, {
endpoint: '',
async onBeforeRequest(req) {
const token = await getAuthToken();
req.setHeader('Authorization', `Bearer ${token}`);
onShouldRetry(err, retryAttempt, options, next) {
if (err?.originalResponse?.getStatus() === 401) {
return true;
return next(err);
async onAfterResponse(req, res) {
if (res.getStatus() === 401) {
await refreshAuthToken();


Pass an array of field names to limit the metadata fields that will be added to uploads as Tus Metadata (Array, default: null).

  • Set this to ['name'] to only send the name field.
  • Set this to null (the default) to send all metadata fields.
  • Set this to an empty array [] to not send any fields.


Limit the amount of uploads going on at the same time (number, default: 20).

Setting this to 0 means no limit on concurrent uploads (not recommended).

Frequently Asked Questions


The Tus website has extensive FAQ section, we recommend taking a look there as well if something is unclear.

How is file meta data stored?

Tus uses unique identifiers for the file names to prevent naming collisions. To still keep the meta data in place, Tus also uploads an extra .info file with the original file name and other meta data:

"ID": "00007a99d16d4eeb5a3e3c080b6f69da+JHZavdqPSK4VMtarg2yYcNiP8t_kDjN51lBYMJdEyr_wqEotVl8ZBRBSTnWKWenZBwHvbLNz5tQXYp2N7Vdol.04ysQAuw__suTJ4IsCljj0rjyWA6LvV4IwF5P2oom2",
"Size": 1679852,
"SizeIsDeferred": false,
"Offset": 0,
"MetaData": {
"filename": "cat.jpg",
"filetype": "image/jpeg"
"IsPartial": false,
"IsFinal": false,
"PartialUploads": null,
"Storage": {
"Bucket": "your-bucket",
"Key": "some-key",
"Type": "s3store"

How do I change files before sending them?

If you want to change the file names, you want to do that in onBeforeFileAdded.

If you want to send extra headers with the request, use headers or onBeforeRequest.

How do I change (or move) files after sending them?

If you want to preserve files names, extract meta data, or move files to a different place you generally can with hooks or events. It depends on the Tus server you use how it’s done exactly. tusd, for instance, exposes hooks and tus-node-server has events.

Which server do you recommend?

Transloadit runs tusd in production, where it serves millions of requests globally. So we recommend tusd as battle-tested from our side, but other companies have had success with other implementations so it depends on your needs.

Are there hosted Tus servers?

All Transloadit plans come with a hosted tusd server. You don’t have to do anything to leverage it, using @uppy/transloadit automatically uses Tus under the hood.

Why Tus instead of directly uploading to AWS S3?

First: reliable, resumable uploads. This means accidentally closing your tab or losing connection let’s you continue, for instance, your 10GB upload instead of starting all over.

Tus is also efficient with lots of files (such as 8K) and large files. Uploading to AWS S3 directly from the client also introduces quite a bit of overhead, as more requests are needed for the flow to work.