# Error Handling

upup reports failures as a typed `UpupError` (or one of its subclasses). Every
`UpupError` carries:

- `message` — human-readable description
- `code` — a stable string from the `UpupErrorCode` enum
- `retryable` — whether the failure is transient and worth retrying
- `status` — the originating HTTP status, when the error came from a response

## Error types

Import the error classes and the code enum from `@useupup/core`:

```typescript
import {
    UpupError,
    UpupErrorCode,
    UpupAuthError,
    UpupNetworkError,
    UpupValidationError,
    UpupQuotaError,
    UpupStorageError,
    UpupConfigError,
} from '@useupup/core'
```

| Class                 | Raised when                                                 | Extra fields                   |
| --------------------- | ----------------------------------------------------------- | ------------------------------ |
| `UpupError`           | Base class for every upup error                             | `code`, `retryable`, `status?` |
| `UpupValidationError` | A file fails a size / type / count check                    | `reason`, `file`               |
| `UpupNetworkError`    | A fetch / XHR fails (marked `retryable`)                    | `status?`                      |
| `UpupStorageError`    | An S3 / storage operation fails                             | `provider`, `operation`        |
| `UpupAuthError`       | A cloud-drive OAuth / provider call fails                   | `provider`                     |
| `UpupQuotaError`      | A configured quota is exceeded                              | `limit`, `used`                |
| `UpupConfigError`     | Configuration is missing or invalid (e.g. no upload target) | —                              |

The `code` values come from the `UpupErrorCode` enum. See [Error
Codes](/docs/api-reference/error-codes/) for the complete list with the trigger
behind each one, or [Error Monitoring](/docs/guides/error-monitoring/) for the
codes you'll tag most often.

## Inspecting an error

Upload failures surface through the core `upload-error` event with the full
`Error` object and the file that failed. Narrow with `instanceof`, or switch on
`code`:

```typescript
import { UpupError, UpupErrorCode, UpupValidationError } from '@useupup/core'

// error is the Error object from an upload-error event (see Error Monitoring).
function describeUploadError(error: unknown): string {
    if (error instanceof UpupValidationError) {
        return `${error.file.name} rejected: ${error.reason}`
    }
    if (error instanceof UpupError) {
        if (error.code === UpupErrorCode.PRESIGN_FAILED) {
            return 'Your token endpoint returned an error.'
        }
        return error.retryable
            ? 'Transient failure — safe to retry.'
            : 'Permanent failure — needs attention.'
    }
    return 'Unknown error.'
}
```

Both surfaces — the headless core's `upload-error` event (full `Error`) and the
React `onError` prop (message string) — plus wiring them into an error tracker
are covered in [Error Monitoring](/docs/guides/error-monitoring/).

## Retry behavior

Configure automatic retries with the [`maxRetries`](/docs/api-reference/upupuploader/optional-props/#maxretries) prop. Each file upload is retried up to that many times before it counts as a failure.

```tsx
<UpupUploader
    provider="aws"
    uploadEndpoint="/api/upload-token"
    maxRetries={3}
/>
```

Once a file has exhausted its attempts and the run is marked failed, a manual **"Retry"** button appears in the UI. It appears whether or not `maxRetries` is set — that prop controls how many automatic attempts happen before the failure, not whether the manual button is offered.

### Resumable upload recovery

When [resumable multipart uploads](/docs/resumable-uploads/) are enabled (`resumable={{ protocol: 'multipart' }}`), the failure UI changes and so does what recovery costs:

- The button shown **after a failure** is labelled **"Resume"** instead of "Retry".
- It invokes the same retry command as a non-resumable upload, but the file continues from its last completed part rather than restarting. The failed attempt left its server-side parts in place on purpose, and the retry re-attaches to them through `POST /multipart/resume`.

Note that this is not the **"Resume"** button shown while a run is _paused_ —
that one is a separate control on the pause/resume path. Only the failure-state
button is described here.

Part-level continuation survives a page refresh too, provided the file comes
back: pair `resumable` with `crashRecovery` and the restored file re-attaches
mid-transfer. It is on by default (`persist: true`) and degrades to a fresh
upload — never to a failure — whenever the session cannot be trusted. What
that costs your bucket, and the `AbortIncompleteMultipartUpload` lifecycle rule
it obliges you to configure, are covered in [Cross-reload
resume](/docs/resumable-uploads/#cross-reload-resume).

In client mode (`uploadEndpoint`), where multipart cannot run, tus remains the
resumable option: it delegates fingerprinting and offset recovery to
`tus-js-client`. See [Resumable Uploads](/docs/resumable-uploads/).
