# useDownload (https://s3.dimah.dev/docs/react/hooks/download)



`useDownload` requests signed GET URLs from your server and handles browser-native downloads or client-side fetch streaming with live progress tracking.

```tsx title="components/download-avatar.tsx"
"use client";

import { useDownload } from "@dimah-s3/react";

export function DownloadAvatar({ avatarKey }: { avatarKey: string }) {
  const { download, isPending } = useDownload({
    route: "avatar",
  });

  return (
    <button
      type="button"
      onClick={() => void download(avatarKey)}
      disabled={isPending}
      className="btn"
    >
      {isPending ? "Generating link…" : "Download Avatar"}
    </button>
  );
}
```

***

## Download modes [#download-modes]

<div className="fd-steps">
  <div className="fd-step">
    ### Browser navigation (`mode: "navigate"`, default) [#1-browser-navigation-mode-navigate-default]

    Requests a presigned GET URL and navigates the browser directly to S3. S3 immediately responds with the binary file stream and triggers the browser's native file save dialog.

    ```tsx
    const { download, presign, phase, isPending } = useDownload({
      route: "avatar",
      onInitiated: (key) => {
        console.log("Browser handed download URL for:", key);
      },
    });

    // Trigger download
    await download(avatarKey);

    // Or get the presigned URL directly without triggering browser navigation
    const { url, expiresIn } = await presign(avatarKey);
    ```
  </div>

  <div className="fd-step">
    ### Fetch streaming (`mode: "fetch"`) [#2-fetch-streaming-mode-fetch]

    Fetches the object bytes through a client-side `fetch` stream. Provides fine-grained byte progress, speed calculation, and abortable cancellation.

    ```tsx
    const { download, cancel, progress, isDownloading, isPending } = useDownload({
      route: "document",
      mode: "fetch",
      onProgress: (key, progress) => {
        console.log(
          `Downloaded ${progress.percent}% (${progress.loaded}/${progress.total})`,
        );
      },
      onSuccess: (key, fileName) => {
        console.log(`Saved ${fileName} to disk`);
      },
    });

    return (
      <div>
        <button onClick={() => void download(documentKey)} disabled={isPending}>
          {isDownloading ? `Downloading: ${progress.percent}%` : "Download File"}
        </button>
        {isDownloading && (
          <button type="button" onClick={cancel}>
            Cancel
          </button>
        )}
      </div>
    );
    ```

    ***
  </div>
</div>

## Inline preview with `useObjectUrl` [#inline-preview-with-useobjecturl]

For displaying private S3 files inline (e.g. `<img>`, `<video>`, `<audio>`, or `<iframe>`), use `useObjectUrl`. It requests a signed URL with `disposition: "inline"` and automatically caches the signed URL in memory until shortly before its expiration window.

```tsx title="components/avatar-image.tsx"
"use client";

import { useObjectUrl } from "@dimah-s3/react";

export function AvatarImage({ avatarKey }: { avatarKey: string }) {
  const { url, isLoading, refresh } = useObjectUrl({
    route: "avatar",
    objectKey: avatarKey,
    disposition: "inline",
  });

  if (isLoading)
    return <div className="size-16 rounded-full bg-muted animate-pulse" />;
  if (!url) return null;

  return (
    <img
      src={url}
      alt="User Avatar"
      className="size-16 rounded-full object-cover"
    />
  );
}
```

***

## Type reference [#type-reference]

```ts
import type {
  UseNavigateDownloadOptions,
  UseNavigateDownloadReturn,
  UseFetchDownloadOptions,
  UseFetchDownloadReturn,
  UseObjectUrlOptions,
  UseObjectUrlReturn,
} from "@dimah-s3/react";
```

### UseNavigateDownloadOptions [#usenavigatedownloadoptions]

<AutoTypeTable path="packages/react/src/hooks/use-download.ts" name="UseNavigateDownloadOptions" />

***

### UseFetchDownloadOptions [#usefetchdownloadoptions]

<AutoTypeTable path="packages/react/src/hooks/use-download.ts" name="UseFetchDownloadOptions" />

***

### UseNavigateDownloadReturn [#usenavigatedownloadreturn]

<AutoTypeTable path="packages/react/src/hooks/use-download.ts" name="UseNavigateDownloadReturn" />

***

### UseFetchDownloadReturn [#usefetchdownloadreturn]

<AutoTypeTable path="packages/react/src/hooks/use-download.ts" name="UseFetchDownloadReturn" />

***

### UseObjectUrlOptions [#useobjecturloptions]

<AutoTypeTable path="packages/react/src/hooks/use-object-url.ts" name="UseObjectUrlOptions" />

***

### UseObjectUrlReturn [#useobjecturlreturn]

<AutoTypeTable path="packages/react/src/hooks/use-object-url.ts" name="UseObjectUrlReturn" />

## Frequently asked questions [#frequently-asked-questions]

<Accordions>
  <Accordion title="How do I specify a custom download filename from the client?">
    Pass the filename as the second argument to `download` or `presign`:

    ```tsx
    await download(avatarKey, "custom-avatar-name.png");
    ```
  </Accordion>

  <Accordion title="Can multiple buttons share a single useDownload instance?">
    Yes. `useDownload` tracks `objectKey` internally so loading states, errors, and progress indicators stay scoped to the button matching the active key.
  </Accordion>

  <Accordion title="How does useObjectUrl cache presigned URLs?">
    `useObjectUrl` caches signed URLs in memory indexed by route, key, disposition, and filename. The cached URL is returned instantly on subsequent renders until 15 seconds before its `expiresIn` TTL. Calling `refresh()` invalidates the cache and requests a fresh URL.
  </Accordion>
</Accordions>
