Storage Uploads

Supabase Storage provides two methods for uploading files:

  • Standard Uploads
  • Resumable Uploads

Standard Upload#

The standard file upload method is ideal for small files that are not larger than 6MB.

It uses the traditional multipart/form-data format and is simple to implement using the supabase-js SDK. Here's an example of how to upload a file using the standard upload method:


You can upload up to 5GB file size using the standard upload method.

For files greater than 6 MB in size, use TUS Resumable Upload for better reliability.

import { createClient } from '@supabase/supabase-js'

// Create Supabase client
const supabase = createClient('your_project_url', 'your_supabase_api_key')

// Upload file using standard upload
async function uploadFile(file) {
  const { data, error } = await supabase.storage.from('bucket_name').upload('file_path', file)
  if (error) {
    // Handle error
  } else {
    // Handle success

Resumable Upload#


Resumable upload is in Beta. We are rolling this feature gradually, please contact us if you want to be prioritized.

The Resumable upload method is recommended for uploading large files that may exceed 6MB in size or for scenarios where network stability is a concern or if you simply want to have a progress bar for your uploads.

Supabase Storage implements the TUS protocol to enable resumable uploads. TUS stands for The Upload Server and is an open protocol for supporting resumable uploads. The protocol allows the upload process to be resumed from where it left off in case of interruptions. This method can be implemented using the tus-js-client library, or other client-side libraries like Uppy-js that support the TUS protocol.

Here's an example of how to upload a file using tus-js-client:

const tus = require('tus-js-client')

const projectId = ''
const token = ''

function uploadFile(bucketName, fileName, file) {
  return new Promise((resolve, reject) => {
    var upload = new tus.Upload(file, {
      endpoint: `https://${projectId}.supabase.co/storage/v1/upload/resumable`,
      retryDelays: [0, 3000, 5000, 10000, 20000],
      headers: {
        authorization: `Bearer ${token}`,
        'x-upsert': 'true', // optionally set upsert to true to overwrite existing files
      uploadDataDuringCreation: true,
      metadata: {
        bucketName: bucketName,
        objectName: fileName,
        contentType: 'image/png',
        cacheControl: 3600,
      chunkSize: 6 * 1024 * 1024, // NOTE: it must be set to 6MB (for now) do not change it
      onError: function (error) {
        console.log('Failed because: ' + error)
      onProgress: function (bytesUploaded, bytesTotal) {
        var percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2)
        console.log(bytesUploaded, bytesTotal, percentage + '%')
      onSuccess: function () {
        // console.log(upload)
        console.log('Download %s from %s', upload.file.name, upload.url)

    // Check if there are any previous uploads to continue.
    return upload.findPreviousUploads().then(function (previousUploads) {
      // Found previous uploads so we select the first one.
      if (previousUploads.length) {

      // Start the upload

Upload URL#

When uploading using the resumable upload endpoint, the TUS server creates a unique URL for each upload, even for multiple uploads to the same path. All chunks will be uploaded to this URL using the PATCH method.

This URL will be valid for up to 24 hours. If the upload is not completed within 24 hours, the URL will expire and you'll need to start the upload again. The TUS client library will automatically create a new URL if the previous one expires.


When two or more clients try to upload to the same Upload URL only one of them will succeed. The other clients will receive a 409 Conflict error. Only 1 client can upload to the same Upload URL at a time which prevents data corruption.


We do not yet support checksum validation for the uploaded chunks. This means if a client changes the file mid way through the upload, the final upload will be an amalgamation of both the files. This has to be done intentionally by the client and is unlikely to happen in normal circumstances.

When two or more clients upload a file to the same path using different upload URLs, the first client to complete the upload will succeed and the other clients will receive a 409 Conflict error.

If you provide the x-upsert header the last client to complete the upload will succeed instead.

UppyJS Example#

You can check a full example using UppyJS Here

Framework integration for UppyJS:

Overwriting Files#

When uploading a file to a path that already exists, the default behavior is to return a 409 Conflict error. If you want to overwrite a file on a specific path you can set the x-upsert header to true.

We do advise against overwriting files when possible, as the CDN will take sometime to propagate the changes to all the edge nodes leading to stale content. Uploading a file to a new path is the recommended way to avoid propagation delays and stale content.

If you want to know more about our CDN, check the CDN guide.

Access Control with RLS Policies#

Both the standard file upload and TUS resumable file upload methods in Supabase Storage support Row-Level Security (RLS) policies. RLS allows you to define fine-grained access control rules to restrict access to files based on user roles, permissions, or other criteria. This ensures that your files are secure and accessible only to authorized users.

For more information on RLS policies, see the Storage Access Control guide.


Supabase Storage provides multiple options for uploading files, including standard file upload and TUS resumable file upload, both of which use RLS policies for access control. Additionally, there are client-side libraries like Uppy-js that make it easy to implement TUS uploads in your application. Choose the upload method that best fits your needs based on file size, network stability, and other requirements.