docs(storage): signed resumable uploads docs and example (#40793)

This commit is contained in:
Lenny
2025-12-04 02:27:24 -06:00
committed by GitHub
parent 1f018560b3
commit 3001146c89
10 changed files with 214 additions and 0 deletions

View File

@@ -302,6 +302,12 @@ Uppy has integrations with different frameworks:
- [Vue](https://uppy.io/docs/vue/)
- [Angular](https://uppy.io/docs/angular/)
### Presigned uploads
Resumable uploads also supports using signed upload tokens to created time-limited URLs that you can share to your users by invoking the `createSignedUploadUrl` method on the SDK and including the returned token in the `x-signature` header of the resumable upload.
See this [full example using Uppy with signed URLs](https://github.com/supabase/supabase/tree/master/examples/storage/resumable-upload-signed-uppy) for more context.
## Overwriting files
When uploading a file to a path that already exists, the default behavior is to return a `400 Asset Already Exists` error.

View File

@@ -0,0 +1,23 @@
## Resumable Uploads with Supabase Storage and Uppy
This example shows how to use signed urls from [Supabase Storage](https://supabase.io/docs/reference/javascript/storage) with [Uppy](https://uppy.io/) to upload files to Supabase Storage using the TUS protocol (signed resumable uploads).
This works by calling `createSignedUploadUrl()` to get a token for each file, and passing that token via the `x-signature` header when uploading the files
### Running the example
- Start local supabase project `supabase start`
- Open the index.html file and set `SUPABASE_PUBLISHABLE_KEY` to the value output when starting the supabase cli
- Serve the index.html file locally (e.g. with Python Simple HTTP Server or http-server npm package) and start uploading:
```bash
# python http server
python3 -m http.server
# npm http-server
npx http-server
```
### How it works
In index.html the `uppy.on('file-added')` hook calls the [create-upload-token](supabase/functions/create-upload-token/index.ts) function which creates a token for each added file and attaches it to that file's header config as `x-signature`.

View File

@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Signed Resumable Upload Supabase + UppyJS</title>
<link href="https://releases.transloadit.com/uppy/v3.6.1/uppy.min.css" rel="stylesheet" />
<style>
html {
background: #9e44ef;
}
body {
height: 100vh;
background: radial-gradient(72.03% 66.03% at 50% 69.72%, #dbb8bf 0, transparent 100%);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
a {
display: block;
margin: 10px;
text-decoration: none;
}
#logo {
max-width: 150px;
}
#drag-drop-area {
margin-top: 40px;
}
</style>
</head>
<body>
<img id="logo" src="supabase-logo-wordmark--dark.png" />
<div id="drag-drop-area"></div>
<a href="https://supabase.com/docs/guides/storage/uploads/resumable-uploads" target="_blank"
>Read the docs.</a
>
<script type="module">
import {
Uppy,
Dashboard,
Tus,
} from 'https://releases.transloadit.com/uppy/v3.6.1/uppy.min.mjs'
const SUPABASE_PUBLISHABLE_KEY = '' // your project's publishable (anon) key
const PROJECT_URL = 'http://127.0.0.1:54321'
const STORAGE_BUCKET = 'uploads'
async function getSignedUploadToken(filename) {
const res = await fetch(`${PROJECT_URL}/functions/v1/create-upload-token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', apikey: SUPABASE_PUBLISHABLE_KEY },
body: JSON.stringify({ filename }),
})
if (!res.ok) throw new Error('Failed to get upload token')
const data = await res.json()
return data.token
}
var uppy = new Uppy()
.use(Dashboard, {
inline: true,
limit: 10,
target: '#drag-drop-area',
showProgressDetails: true,
})
.use(Tus, {
endpoint: `${PROJECT_URL}/storage/v1/upload/resumable/sign`,
headers: {
apikey: SUPABASE_PUBLISHABLE_KEY,
},
uploadDataDuringCreation: true,
chunkSize: 6 * 1024 * 1024,
allowedMetaFields: ['bucketName', 'objectName', 'contentType', 'cacheControl'],
onError: function (error) {
console.log('Failed because: ' + error)
},
})
uppy.on('file-added', async (file) => {
const supabaseMetadata = {
bucketName: STORAGE_BUCKET,
objectName: file.name,
contentType: file.type,
}
file.meta = {
...file.meta,
...supabaseMetadata,
}
// Important - add signing token header
const token = await getSignedUploadToken(file.name)
uppy.setFileState(file.id, {
tus: {
headers: {
'x-signature': token,
}
}
})
})
uppy.on('complete', (result) => {
console.log('Upload complete! Weve uploaded these files:', result.successful)
})
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,4 @@
# Supabase
.branches
.temp
.env

View File

@@ -0,0 +1,32 @@
# A string used to distinguish different Supabase projects on the same host. Defaults to the
# working directory name when running `supabase init`.
project_id = "resumable-upload-uppy"
[api]
# Disable data API since we are not using the PostgREST client in this example.
enabled = false
[storage]
# The maximum file size allowed for all buckets in the project.
file_size_limit = "50MiB"
[storage.image_transformation]
enabled = false
[storage.buckets.uploads]
public = true
# file_size_limit = "50MiB"
# allowed_mime_types = ["image/png", "image/jpeg"]
# Uncomment to specify a local directory to upload objects to the bucket.
# objects_path = "./buckets/uploads"
[functions.create-upload-token]
enabled = true
verify_jwt = true
import_map = "./functions/create-upload-token/deno.json"
# Uncomment to specify a custom file path to the entrypoint.
# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx
entrypoint = "./functions/create-upload-token/index.ts"
# Specifies static files to be bundled with the function. Supports glob patterns.
# For example, if you want to serve static HTML pages in your function:
# static_files = [ "./functions/create-upload-token/*.html" ]

View File

@@ -0,0 +1,3 @@
# Configuration for private npm package dependencies
# For more information on using private registries with Edge Functions, see:
# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries

View File

@@ -0,0 +1,29 @@
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
import { createClient } from 'jsr:@supabase/supabase-js'
Deno.serve(async (req) => {
const SUPABASE_URL = Deno.env.get('SUPABASE_URL') ?? ''
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
try {
const { filename } = await req.json()
if (!filename) {
return new Response('Missing filename', { status: 400 })
}
const { data, error } = await supabase.storage.from('uploads').createSignedUploadUrl(filename)
if (error) {
return new Response(error.message, { status: 500 })
}
return new Response(JSON.stringify({ token: data.token }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
} catch (error) {
return new Response((error as Error).message, { status: 500 })
}
})

View File

@@ -0,0 +1 @@
CREATE POLICY "allow uploads" ON storage.objects FOR INSERT TO public WITH CHECK (bucket_id = 'uploads');