# useDownload (https://v1.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]

### 1. 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, presignMany, 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);

// Ask for a shorter link. The server clamps this to the route cap.
const hour = await presign(avatarKey, "avatar.png", 3600);
```

`presignMany` signs several files without changing `phase` or `objectKey`, so a row can copy a link without looking like a download in progress. Each entry may set `expiresIn`. The result includes the TTL the server signed.

```tsx
const { signed, failed } = await presignMany([
  { key: avatarKey, fileName: "avatar.png", expiresIn: 900 },
]);
```

### 2. 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", // [!code ++]
  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>
);
```

---

## 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 } = useObjectUrl({
    route: "avatar",
    objectKey: avatarKey,
    disposition: "inline", // [!code ++]
  });

  if (isLoading)
    return <div className="bg-muted size-16 animate-pulse rounded-full" />;
  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 set a custom download filename?">

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 one 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?">

It caches URLs in memory by route, key, disposition, and filename. Presigned
URLs are reused until 15 seconds before their `expiresIn` window; proxy URLs
with `expiresIn: 0` are cached for up to 24 hours. Calling `refresh()`
invalidates the entry and requests a fresh URL.

</Accordion>

</Accordions>
