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

Hook Options

  • onError ((error: Error) => void): Optional custom error handler. Providing it suppresses the default error toast.

Returns

  • download (function): Trigger a file download.
  • 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 data = JSON.stringify(tasks, null, 2)
download(data, 'tasks-export.json')

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

const { download } = useDownload({
  onError: (err) => {
    console.error('Download failed:', err.message)
  },
})

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.