# Download Hooks (https://s3.dimah.dev/docs/server/hooks/download)



Enable download operations with `download: true` or a `DownloadConfig` object on a [named route](https://s3.dimah.dev/docs/server/routes).

```ts title="lib/s3.ts"
import { S3Client } from "@aws-sdk/client-s3";
import { dimahS3, errors, route } from "@dimah-s3/server";

export const awsS3 = new S3Client({/* env */});

export const s3 = dimahS3({
  client: awsS3,
  bucket: process.env.S3_BUCKET!,
  routes: {
    avatar: route({
      download: {
        disposition: "inline",
        guard: async ({ key, request }) => {
          const session = await getSession(request);
          if (!session) throw errors.unauthorized();
        },
      },
    }),
  },
});
```

***

## Download lifecycle flow [#download-lifecycle-flow]

<Flow
  label="Download flow"
  steps="[
  { name: &#x22;download.resolve&#x22;, kind: &#x22;hook&#x22; },
  { name: &#x22;download.guard&#x22;, kind: &#x22;hook&#x22; },
  { name: &#x22;onPresigned&#x22;, kind: &#x22;hook&#x22; },
  { name: &#x22;Fetch from S3&#x22;, kind: &#x22;s3&#x22; },
]"
/>

1. **`resolve`**: Optionally rewrites download `fileName`, `disposition` (`inline` vs `attachment`), or `expiresIn`.
2. **`guard`**: Validates request permissions for the specified `key`.
3. **`onPresigned`**: Receives the signed URL and expiration details.

***

## Type reference [#type-reference]

```ts
import type {
  DownloadGuardContext,
  DownloadOnPresignedContext,
  DownloadResolveInfo,
} from "@dimah-s3/server";
```

### DownloadGuardContext [#downloadguardcontext]

<AutoTypeTable path="packages/server/src/types/hook-contexts.ts" name="DownloadGuardContext" />

***

### DownloadOnPresignedContext [#downloadonpresignedcontext]

<AutoTypeTable path="packages/server/src/types/hook-contexts.ts" name="DownloadOnPresignedContext" />

***

### DownloadResolveInfo [#downloadresolveinfo]

<AutoTypeTable path="packages/server/src/types/hook-contexts.ts" name="DownloadResolveInfo" />

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

<Accordions>
  <Accordion title="How do I change the filename on download dynamically?">
    Use `download.resolve` to set the downloaded file name per request:

    ```ts
    avatar: route({
      download: {
        resolve: async ({ key, request }) => {
          const user = await getUserByKey(key);
          return {
            fileName: `${user.username}-avatar.png`,
            disposition: "attachment",
          };
        },
      },
    });
    ```
  </Accordion>

  <Accordion title="How do I stream files via same-origin proxy without direct bucket access?">
    Set `mode: "proxy"`. The client receives a same-origin URL (`/api/s3/file?key=...`) and your server streams the response:

    ```ts
    avatar: route({
      download: {
        mode: "proxy",
      },
    });
    ```
  </Accordion>
</Accordions>
