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

Enable upload operations on a [named route](https://v1.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 }) => {
          // [!code ++]
          const session = await getSession(request); // [!code ++]
          await db.user.update({
            // [!code ++]
            where: { id: session.userId }, // [!code ++]
            data: { avatarKey: key, avatarSize: contentLength }, // [!code ++]
          }); // [!code ++]
        }, // [!code ++]
      },
    }),
  },
});
```

---

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

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

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

---

## Type reference [#type-reference]

```ts
import type {
  UploadGuardContext,
  UploadOnConfirmedContext,
  UploadConfirmGuardContext,
  UploadObjectContext,
} 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 after confirmation rather than presigning?">

Record it in `onConfirmed`, where `contentLength` comes from S3 `HeadObject`.
Presign payloads contain browser-declared values and can be spoofed.

</Accordion>

<Accordion title="What happens if onConfirmed throws?">

dimah-s3 attempts a best-effort `DeleteObject` so a failed callback does not
leave an orphaned file. The original callback error is still returned.

</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: {
        // [!code ++]
        originalName: file.name, // [!code ++]
      }, // [!code ++]
    }),
  },
});
```

</Accordion>

</Accordions>
