Files reference
`useR2Files` and file display helpers.
On this page
The useR2Files hook handles uploads, listings, deletions, and signed URLs against the app's R2 bucket. All operations route through the platform's file gateway, so end users never touch raw R2 credentials. For patterns and worked examples, see the file uploads guide.
import { useR2Files, isImageFile, formatFileSize } from 'deepspace'
import type { R2FileInfo, R2Scope } from 'deepspace'
useR2Files(options?)#
type R2UploadResult = {
success: boolean
key?: string
url?: string
name?: string
error?: string
}
function useR2Files(options?: R2Scope): {
upload: (file: File | Blob, name?: string) => Promise<R2UploadResult>
uploadBase64: (base64Data: string, name: string, mimeType?: string) => Promise<R2UploadResult>
deleteFile: (fileOrKey: R2FileInfo | string) => Promise<{ success: boolean; error?: string }>
downloadFile: (fileOrKey: R2FileInfo | string, fileName?: string) => Promise<{ success: boolean; error?: string }>
readFile: (fileOrKey: R2FileInfo | string) => Promise<Response>
list: (prefix?: string) => Promise<R2FileInfo[]>
getUrl: (fileOrKey: R2FileInfo | string) => string
isUploading: boolean
}
options is the R2Scope itself. Two scopes are valid - 'self' (the default) and 'app' - and the choice decides both where files live and who can read them. See Scopes. Every method that takes a file accepts either an R2FileInfo object from list() or a raw key string.
upload accepts a File (from <input type="file"> or a drag-drop event) or a Blob and an optional display name. Returns R2UploadResult - check success and read the key field.
const { upload, isUploading } = useR2Files()
async function onFileChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0]
if (!file) return
const result = await upload(file, file.name)
if (!result.success) return console.error(result.error)
console.log('uploaded:', result.key)
}
Use when you have data as a Base64 string - for example, from <canvas> toDataURL(). name is required; mimeType is optional.
const { uploadBase64 } = useR2Files()
const canvas = canvasRef.current!
const dataUrl = canvas.toDataURL('image/png')
const base64 = dataUrl.split(',')[1]
const result = await uploadBase64(base64, 'drawing.png', 'image/png')
list() is an async function - call it and store the result in component state rather than expecting a reactive array. Pass a sub-prefix to filter.
import { useState, useEffect } from 'react'
const { list } = useR2Files()
const [files, setFiles] = useState<R2FileInfo[]>([])
async function refresh() {
setFiles(await list())
}
useEffect(() => { refresh() }, [])
Removes the file from R2 and broadcasts the change. Accepts either the full R2FileInfo from list() or a raw key string. There is no recycle bin - deletes are immediate and irreversible.
const { deleteFile } = useR2Files()
await deleteFile(file) // R2FileInfo from list()
await deleteFile('reports/q1.pdf') // or a raw key
getUrl() returns a plain URL (no auth attached - only usable for unauthenticated reads). downloadFile() triggers a browser-side blob download and returns { success, error? }. readFile() returns the raw Response so you can call .text(), .blob(), .arrayBuffer(), .json(), etc.
const { getUrl, downloadFile, readFile } = useR2Files()
<img src={getUrl(file)} alt="" />
await downloadFile(file) // triggers Save As…
await downloadFile('reports/q1.pdf', 'q1.pdf') // explicit filename
const response = await readFile(file)
const text = await response.text()
list() is an async function - call it and store the result in component state rather than reading a reactive array.
R2FileInfo#
type R2FileInfo = {
key: string
size: number
uploaded: string
url: string
originalName?: string
uploadedBy?: string
}
Scopes#
type R2Scope = { scope?: 'self' | 'app' }
Scope decides where files live and, crucially, who can read them.
| Scope | Prefix | Reads |
|---|---|---|
'self' (default) | apps/<app>/users/<userId>/… | Require the caller's auth token. Not usable from a plain <img> or an unauthenticated request. |
'app' | apps/<app>/… | Public. No auth header needed - the returned URL works directly as an <img src>. |
// Per-user files (default) - private to the signed-in user
const { upload, downloadFile } = useR2Files()
await upload(myFile, 'photo.png')
// App-shared files - public reads, embeddable in pages
const { upload } = useR2Files({ scope: 'app' })
const r = await upload(file, `avatars/${userId}.png`)
// r.url is a plain, anon-readable URL
getUrl() attaches no auth token, so it works for 'app'-scope files but not 'self'-scope ones. For private files use readFile or downloadFile, which send the Authorization header.
Both scopes are per-app: the platform derives the bucket prefix server-side, so a key can never address another app. For finer namespacing within a scope (per-room, per-project), encode it into the key.
Display helpers#
| Helper | Signature |
|---|---|
isImageFile(mimeType: string) | Returns true for image/* MIMEs |
formatFileSize(bytes: number) | Returns '1.2 MB', '456 KB', etc. |
Local dev limitation#
See also#
- File uploads guide - patterns and worked examples.
- Custom bindings - declare your own R2 bucket with custom permissions.