File Download Reference

Use useDownload to save generated data, uploaded files, AI outputs, and client-side media processing results to the user's device.

useDownload

Returns

  • download (function): Start a browser download. Resolves true when preparation finishes and the download starts, or undefined on failure. true does not mean the user saved the file.
  • isDownloading (boolean): Whether a download is in progress.
  • error (Error | null): Current download error.
  • clearError (() => void): Clear the current error.

download Arguments

  • data (Blob, string, ArrayBuffer, or { url: string }, required): Data or URL to download.
  • filename (string): Optional filename. When omitted for a File, the file's own name is used.
  • mediaType (string): Optional media type override. Otherwise it is inferred from the filename.

Supported Data Types

  • Blob: Binary images, audio, video, or generated files.
  • string: Text content or a data URL.
  • ArrayBuffer: Raw binary data.
  • { url: string }: Hosted file URL.

Examples

Export JSON:

const { download } = useDownload()

const handleExport = async () => {
  const data = JSON.stringify(tasks, null, 2)
  const started = await download(data, 'tasks-export.json')
  if (!started) return
}

Download a hosted image:

download({ url: imageUrl }, 'generated-image.png')

Download a File returned by useVideoStitch or useVideoAudioOverlay:

download(result)

Omitting the filename uses the File object's existing name and .mp4 extension.

Batch Downloads

For multiple files in one archive, combine JSZip with useDownload:

import JSZip from 'jszip'
import { useDownload } from '@/hooks/use-download'

type Panel = { id: string; imageUrl: string; name: string }

export default function App({ panels }: { panels: Panel[] }) {
  const { download } = useDownload()
  const [isZipping, setIsZipping] = React.useState(false)

  const handleDownloadAll = async () => {
    setIsZipping(true)
    try {
      const zip = new JSZip()

      for (let index = 0; index < panels.length; index++) {
        const panel = panels[index]
        const response = await fetch(panel.imageUrl)
        if (!response.ok) {
          throw new Error(`Failed to fetch ${panel.imageUrl}`)
        }
        const blob = await response.blob()
        const filename = `${String(index + 1).padStart(2, '0')}-${panel.name}.png`
        zip.file(filename, blob)
      }

      const zipBlob = await zip.generateAsync({ type: 'blob' })
      download(zipBlob, 'my-export.zip')
    } catch (error) {
      console.error('Failed to create ZIP archive:', error)
    } finally {
      setIsZipping(false)
    }
  }

  return (
    <button
      onClick={handleDownloadAll}
      disabled={isZipping || panels.length === 0}
    >
      {isZipping ? (
        <span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
      ) : null}
      Download all
    </button>
  )
}

Useful JSZip operations:

const zip = new JSZip()
zip.file('readme.txt', 'Hello World')
zip.file('data.json', JSON.stringify(data, null, 2))
zip.folder('images')?.file('photo.png', imageBlob)
const blob = await zip.generateAsync({ type: 'blob' })

Error Handling

download resolves true when the browser download starts and undefined when preparation fails. Failure also sets error and shows the platform toast. true does not mean the user saved the file.

const started = await download(data, 'tasks-export.json')
if (!started) return

Best Practices

  • Let the hook infer media types from filenames when possible.
  • Omit the filename for File results that already include the correct extension.
  • Use { url } for hosted files and generated media URLs.
  • Show a loading state when building ZIP files or fetching many remote files.
  • Use zero-padded filenames in ZIP exports so files sort correctly.
  • Organize ZIP contents with folders and include a manifest when exports need context.
  • Handle remote fetch failures when batching files from URLs.