File Upload Reference

Use useFileUpload to upload user-selected files to blob storage and receive a public URL that can be displayed, persisted, passed to AI hooks, or downloaded later.

File URLs are unguessable, but anyone with the URL can access the file without signing in. App visibility does not restrict access to a file URL. Uploads and AI attachments remain in the user's account storage until deleted, including uploads made in preview. See file privacy.

useFileUpload

Returns

  • uploadFile (function): Upload a File and resolve to the result shape below or undefined.
  • isLoading (boolean): Whether an upload is in progress.
  • error (Error | null): Current upload error.
  • file (object | null): Last uploaded file result.

Result Shape

  • name (string): Original file name.
  • contentType (string): MIME type.
  • url (string): Public URL to the uploaded file.

Supported File Types

Common supported MIME types include:

  • Images: image/png, image/jpeg, image/webp, image/heic, and image/heif.
  • Audio: audio/mpeg, audio/mp3, audio/wav, audio/aiff, audio/x-aiff, audio/aac, audio/ogg, and audio/flac.
  • Video: video/mp4, video/mpeg, video/mov, video/avi, video/x-flv, video/mpg, video/webm, video/wmv, and video/3gpp.
  • Documents: application/pdf.

Example

Import the hook from the file upload module:

import { useFileUpload } from '@/hooks/use-file-upload'
import { usePersistentItem } from '@/hooks/use-persistent-item'

export default function App() {
  const [uploadedImageUrl, setUploadedImageUrl] = usePersistentItem<
    string | null
  >('uploadedImageUrl', null)
  const [selectedFile, setSelectedFile] = React.useState<File | null>(null)
  const { uploadFile, isLoading, error } = useFileUpload()

  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setSelectedFile(event.target.files?.[0] ?? null)
  }

  const handleUpload = async () => {
    if (!selectedFile) return

    const result = await uploadFile(selectedFile)
    if (result) {
      setUploadedImageUrl(result.url)
      setSelectedFile(null)
    }
  }

  return (
    <div>
      <div className="flex gap-2">
        <input
          type="file"
          accept="image/png, image/jpeg, image/webp"
          onChange={handleFileChange}
        />
        <button onClick={handleUpload} disabled={!selectedFile || isLoading}>
          {isLoading ? (
            <span className="mr-2 inline-block animate-spin rounded-full border-2 border-current border-t-transparent" />
          ) : null}
          Upload
        </button>
      </div>
      {error ? <p className="text-destructive">{error.message}</p> : null}
      {uploadedImageUrl ? (
        <img
          src={uploadedImageUrl}
          alt="Uploaded"
          className="w-full rounded-lg"
        />
      ) : null}
    </div>
  )
}

Error Handling

Uploads return undefined on failure:

const { uploadFile, error } = useFileUpload()

const handleUpload = async (file: File) => {
  const result = await uploadFile(file)

  if (!result) {
    return
  }

  console.log('Uploaded to:', result.url)
}

Render error from the hook in component JSX. React state updates on the next render, so the value in the same event-handler closure may still be from the previous render immediately after await uploadFile(...).

Best Practices

  • Upload one file at a time per hook instance. Use multiple hook instances for concurrent uploads.
  • Persist result.url with usePersistentItem when the file should survive reloads.
  • Stream files directly to storage instead of reading large files into memory.
  • Use the returned contentType when displaying or categorizing uploaded files.
  • Pass uploaded URLs to AI hooks as { url } attachments when you need AI processing.
  • Keep local File state separate from persisted URLs so users can clear or retry selections cleanly.