---
myst:
  html_meta:
    "description": "plone.restapi supports the TUS Open Protocol for resumable file uploads. There is a @tus-upload endpoint to upload a file, and a @tus-replace endpoint to replace an existing file."
    "property=og:description": "plone.restapi supports the TUS Open Protocol for resumable file uploads. There is a @tus-upload endpoint to upload a file, and a @tus-replace endpoint to replace an existing file."
    "property=og:title": "TUS resumable upload"
    "keywords": "Plone, plone.restapi, REST, API, TUS, resumable, upload"
---

# TUS resumable upload

`plone.restapi` supports the [TUS Open Protocol](https://tus.io/) for resumable file uploads.
There is a `@tus-upload` endpoint to upload a file, and a `@tus-replace` endpoint to replace an existing file.


## Creating an Upload URL

```{note}
`POST` requests to the `@tus-upload` endpoint are allowed on all `IFolderish` content types, for example, `Folder`.
```

To create a new upload, send a `POST` request to the `@tus-upload` endpoint:

```{eval-rst}
..  http:example:: curl httpie python-requests
    :request: ../../../src/plone/restapi/tests/http-examples/tusupload_post.req
```

The server will return a temporary upload URL in the `Location` header of the response:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusupload_post.resp
:language: http
```

The file can then be uploaded in the next step to that temporary URL.


## Uploading a File

```{note}
PATCH requests to the `@tus-upload` endpoint are allowed on all IContentish content types.
```

Once a temporary upload URL has been created, a client can send a `PATCH` request to upload a file.
The file content should be sent in the body of the request:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusupload_patch_finalized.req
:language: http
```

When just a single file is uploaded at once, the server will respond with a {term}`204 No Content` response after a successful upload.
The HTTP `Location` header contains he URL of the newly created content object:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusupload_patch_finalized.resp
:language: http
```


## Partial Upload

TUS allows partial upload of files.
A partial file is also uploaded by sending a `PATCH` request to the temporary URL:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusupload_patch.req
:language: http
```

The server will also respond with a {term}`204 No content` response.
Though instead of providing the final file URL in the `Location` header, the server provides an updated `Upload-Offset` value, telling the client the new offset:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusupload_patch.resp
:language: http
```

When the last partial file has been uploaded, the server will contain the final file URL in the `Location` header.

## Replacing Existing Files

TUS can also be used to replace an existing file by sending a `POST` request to the `@tus-replace` endpoint instead:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusreplace_post.req
:language: http
```

The server will respond with a {term}`201 Created` status and return the URL of the temporarily created upload resource in the `Location` header of the response:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusupload_post.resp
:language: http
```

The file can then be uploaded to that URL using the `PATCH` method in the same way as creating a new file:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusreplace_patch.req
:language: http
```

The server will respond with a {term}`204 No Content` response and the final file URL in the HTTP `Location` header:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusreplace_patch.resp
:language: http
```


## Asking for the Current File Offset

To ask the server for the current file offset, the client can send a `HEAD` request to the upload URL:

```{eval-rst}
..  http:example:: curl httpie python-requests
    :request: ../../../src/plone/restapi/tests/http-examples/tusupload_head.req
```

The server will respond with a {term}`200 OK` status and the current file offset in the `Upload-Offset` header:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusupload_head.resp
:language: http
```


## Configuration and Options

The current TUS configuration and a list of supported options can be retrieved sending an `OPTIONS` request to the `@tus-upload` endpoint:

```{eval-rst}
..  http:example:: curl httpie python-requests
    :request: ../../../src/plone/restapi/tests/http-examples/tusupload_options.req
```

The server will respond with a {term}`204 No content` status and HTTP headers containing information about the available extensions and the TUS version:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/tusupload_options.resp
:language: http
```


## CORS Configuration

If you use CORS and want to make it work with TUS, you have to make sure the TUS-specific HTTP headers are allowed by your CORS policy:

```xml
<plone:CORSPolicy
  allow_origin="http://localhost"
  allow_methods="DELETE,GET,OPTIONS,PATCH,POST,PUT"
  allow_credentials="true"
  allow_headers="Accept,Authorization,Origin,X-Requested-With,Content-Type,Upload-Length,Upload-Offset,Tus-Resumable,Upload-Metadata,Lock-Token"
  expose_headers="Upload-Offset,Location,Upload-Length,Tus-Version,Tus-Resumable,Tus-Max-Size,Tus-Extension,Upload-Metadata"
  max_age="3600"
  />
```

See the `plone.rest` documentation for more information on how to configure CORS policies.

See <https://tus.io/protocols/resumable-upload.html#headers> for a list and description of the individual headers.


## Temporary Upload Directory

During upload, files are stored in a temporary directory that by default is located in the `CLIENT_HOME` directory.
If you are using a multi ZEO client setup without session stickiness you *must* configure this to a directory shared by all ZEO clients by setting the `TUS_TMP_FILE_DIR` environment variable, for example `TUS_TMP_FILE_DIR=/tmp/tus-uploads`.
