# Upload Hooks (https://s3.dimah.dev/docs/server/hooks/upload)



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

```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({
      upload: {
        fileTypes: ["image/*"],
        maxFileSize: 2 * 1024 * 1024,
        guard: async ({ request }) => {
          const session = await getSession(request);
          if (!session) throw errors.unauthorized();
        },
        onConfirmed: async ({ key, contentLength, request }) => {
          const session = await getSession(request);
          await db.user.update({
            where: { id: session.userId },
            data: { avatarKey: key, avatarSize: contentLength },
          });
        },
      },
    }),
  },
});
```

***

## Upload lifecycle flow [#upload-lifecycle-flow]

<Flow
  label="Upload lifecycle"
  steps="[
  { name: &#x22;route.guard&#x22;, kind: &#x22;hook&#x22; },
  { name: &#x22;object&#x22;, kind: &#x22;hook&#x22; },
  { name: &#x22;upload.guard&#x22;, kind: &#x22;hook&#x22; },
  { name: &#x22;onPresigned&#x22;, kind: &#x22;hook&#x22; },
  { name: &#x22;Upload to S3&#x22;, kind: &#x22;s3&#x22; },
  { name: &#x22;confirmGuard&#x22;, kind: &#x22;hook&#x22; },
  { name: &#x22;HeadObject&#x22;, kind: &#x22;server&#x22; },
  { name: &#x22;onConfirmed&#x22;, kind: &#x22;hook&#x22; },
]"
/>

1. **`guard`**: Evaluates session and permissions before key generation.
2. **`object`**: Computes destination folder, key, metadata, ACL, storage class, cache control, or tags.
3. **`onPresigned`**: Fires after the presigned URL is created.
4. **`confirmGuard`**: Authorizes the confirmation request after bytes land in S3.
5. **`onConfirmed`**: Reads verified metadata from `HeadObject` and persists the record.

***

## Type reference [#type-reference]

```ts
import type {
  UploadGuardContext,
  UploadOnConfirmedContext,
  UploadConfirmGuardContext,
  UploadObjectContext,
  UploadObjectInfo,
  UploadOnPresignedContext,
} from "@dimah-s3/server";
```

### UploadGuardContext [#uploadguardcontext]

Runs after `upload.object` assigns the key (and after global / route guards).

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

***

### UploadOnConfirmedContext [#uploadonconfirmedcontext]

Runs after `HeadObject` validates the uploaded file size and content type.

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

***

### UploadConfirmGuardContext [#uploadconfirmguardcontext]

Runs before `HeadObject` when confirming single-shot uploads or completing multipart uploads.

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

***

### UploadObjectContext [#uploadobjectcontext]

Input passed to `upload.object` to generate custom destination keys and metadata.

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

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

<Accordions>
  <Accordion title="Why should I record file size in onConfirmed rather than presign?">
    Presign payloads represent client-declared values, which can be spoofed. In `onConfirmed`, the server queries S3 with `HeadObject` to obtain the actual byte length and content type.
  </Accordion>

  <Accordion title="What happens if onConfirmed throws an error?">
    If `onConfirmed` throws an error, dimah-s3 automatically performs a best-effort `DeleteObject` to prevent orphaned files in your S3 bucket.
  </Accordion>

  <Accordion title="How do I attach custom S3 user metadata?">
    Return `metadata` from `upload.object`:

    ```ts
    avatar: route({
      upload: {
        object: async ({ request, file }) => ({
          folder: "avatars",
          metadata: {
            originalName: file.name,
          },
        }),
      },
    });
    ```
  </Accordion>
</Accordions>
