---
title: "useUploadWithProgress"
description: "File upload composable with real-time progress monitoring, supporting business validation, timeout and abort."
canonical_url: "https://nuxt.mhaibaraai.cn/en/docs/composables/use-upload-with-progress"
---
# useUploadWithProgress

> File upload composable with real-time progress monitoring, supporting business validation, timeout and abort.

## Usage

`useUploadWithProgress` is built on the native `XMLHttpRequest` and reuses `@movk/nuxt`'s endpoint, auth, business validation, Toast and `movk:api:*` hook system. The returned `data` is the business data unwrapped by `dataKey`, consistent with `useApiFetch<T>`.

```vue
<script setup lang="ts">
const selected = ref<File[] | null>(null)
const { progress, status, data, error, upload, abort } = useUploadWithProgress<{
  files: Array<{ name: string, size: number }>
}>()

async function handleUpload() {
  const { data, error: err, aborted } = await upload('/api/upload', selected.value!, {
    fieldName: 'files',
    onSuccess: d => console.log('Upload succeeded:', d.files)
  })

  if (aborted) return
  if (err) console.error(err)
  else console.log(data?.files)
}
</script>

<template>
  <UFileUpload v-model="selected" multiple />
  <UButton :disabled="!selected?.length" @click="handleUpload">Upload</UButton>
  <UProgress v-if="status === 'pending'" :model-value="progress ?? undefined" :max="100" />
  <UButton v-if="status === 'pending'" @click="abort">Cancel</UButton>
</template>
```

> [!NOTE]
> 
> Use this for scenarios that require displaying upload progress. For small files where progress is not needed, you can use 
> 
> $api
> 
>  directly.

## Basic Multi-File Upload

`fieldName` determines the FormData field name (defaults to `'file'`); `upload()` returns `{ data, error, aborted }`:

```vue [ComposablesUseUploadProgressBasicExample.vue]
<script setup lang="ts">
const STATUS = {
  idle: { color: 'neutral', label: '空闲' },
  pending: { color: 'info', label: '上传中' },
  success: { color: 'success', label: '完成' },
  error: { color: 'error', label: '失败' },
  aborted: { color: 'warning', label: '已取消' }
} as const

const selected = ref<File[] | null>(null)
const basic = useUploadWithProgress<{ files: Array<{ name: string, size: number }> }>()
</script>

<template>
  <div class="flex flex-col gap-3">
    <UFileUpload v-model="selected" multiple label="拖拽或点击选择文件" />
    <div class="flex items-center gap-2">
      <UButton
        :loading="basic.status.value === 'pending'"
        :disabled="!selected?.length"
        icon="i-lucide-upload"
        @click="basic.upload('/upload', selected!, { fieldName: 'files' })"
      >
        开始上传
      </UButton>
      <UButton
        v-if="basic.status.value === 'pending'"
        color="error"
        variant="soft"
        icon="i-lucide-x"
        @click="basic.abort"
      >
        中止
      </UButton>
      <UBadge :color="STATUS[basic.status.value].color" variant="subtle">
        {{ basic.progress.value ?? 0 }}% · {{ STATUS[basic.status.value].label }}
      </UBadge>
    </div>
    <UProgress :model-value="basic.progress.value ?? undefined" :max="100" />
    <UAlert
      v-if="basic.error.value"
      color="error"
      variant="subtle"
      :description="basic.error.value.message"
    />
    <UCard v-if="basic.data.value">
      <pre class="text-xs overflow-auto">{{ basic.data.value }}</pre>
    </UCard>
  </div>
</template>
```

## Single File, Multiple Files and Extra Fields

Pass a `File` for single-file upload or a `File[]` for multiple files (appended under the same `fieldName`); `fields` injects extra form fields submitted alongside the file:

```ts
await upload('/api/upload', file, { fieldName: 'file' })
await upload('/api/upload', [f1, f2, f3], { fieldName: 'files' })

await upload('/api/upload', file, {
  fieldName: 'avatar',
  fields: { userId: '123', visibility: 'private' }
})
```

```vue [ComposablesUseUploadProgressSingleExample.vue]
<script setup lang="ts">
const STATUS = {
  idle: { color: 'neutral', label: '空闲' },
  pending: { color: 'info', label: '上传中' },
  success: { color: 'success', label: '完成' },
  error: { color: 'error', label: '失败' },
  aborted: { color: 'warning', label: '已取消' }
} as const

const selected = ref<File | null>(null)
const single = useUploadWithProgress()
</script>

<template>
  <div class="flex flex-col gap-3">
    <UFileUpload v-model="selected" label="选择单个文件" />
    <div class="flex items-center gap-2">
      <UButton
        :loading="single.status.value === 'pending'"
        :disabled="!selected"
        icon="i-lucide-upload-cloud"
        @click="single.upload('/upload', selected!, {
          fields: { folder: 'avatars', visibility: 'private' }
        })"
      >
        上传文件（携带 fields）
      </UButton>
      <UBadge :color="STATUS[single.status.value].color" variant="subtle">
        {{ single.progress.value ?? 0 }}% · {{ STATUS[single.status.value].label }}
      </UBadge>
    </div>
    <UAlert
      v-if="single.error.value"
      color="error"
      variant="subtle"
      :description="single.error.value.message"
    />
    <UCard v-if="single.data.value">
      <pre class="text-xs overflow-auto">{{ single.data.value }}</pre>
    </UCard>
  </div>
</template>
```

## Timeout

`timeoutMs` sets the upload timeout in milliseconds (0 or omitted means no timeout). A timeout triggers the failure branch and `error.message` contains the timeout information:

```vue [ComposablesUseUploadProgressTimeoutExample.vue]
<script setup lang="ts">
const STATUS = {
  idle: { color: 'neutral', label: '空闲' },
  pending: { color: 'info', label: '上传中' },
  success: { color: 'success', label: '完成' },
  error: { color: 'error', label: '失败' },
  aborted: { color: 'warning', label: '已取消' }
} as const

const selected = ref<File[] | null>(null)
const timeout = useUploadWithProgress()
</script>

<template>
  <div class="flex flex-col gap-3">
    <UFileUpload v-model="selected" multiple label="选择文件后模拟超时" />
    <div class="flex items-center gap-2">
      <UButton
        color="neutral"
        :loading="timeout.status.value === 'pending'"
        :disabled="!selected?.length"
        icon="i-lucide-timer"
        @click="timeout.upload('/upload?slow=2000', selected!, { timeoutMs: 500 })"
      >
        模拟超时
      </UButton>
      <UBadge :color="STATUS[timeout.status.value].color" variant="subtle">
        {{ STATUS[timeout.status.value].label }}
      </UBadge>
    </div>
    <UAlert
      v-if="timeout.error.value"
      color="error"
      variant="subtle"
      :description="timeout.error.value.message"
    />
  </div>
</template>
```

## Cross-Origin Credentials

```ts
await upload('https://other.example/upload', file, { withCredentials: true })
```

## Abort Upload

```ts
const { status, upload, abort } = useUploadWithProgress()

upload('/api/upload', largeFile)

function handleCancel() {
  if (status.value === 'pending') abort()
}
```

After aborting:

- `status` changes to `'aborted'`; `progress` resets to `0`
- `error` remains `null` (not treated as failure)
- `onSuccess` / `onError` are not called; no Toast is shown
- `upload()` returns `{ data: null, error: null, aborted: true }`

## Authentication

The token is automatically read from the session and injected into the `Authorization` header. You can override it with the same header name in `headers` (user value takes priority).

```ts
await upload('/api/upload', file, {
  headers: { Authorization: 'Bearer override' }
})
```

## API

### useUploadWithProgress<T>()

`useUploadWithProgress<T>(): { progress, status, data, error, upload, abort }`

#### Returns

**progress** (`Ref<number | null>`): Upload progress, 0–100. Aligned with the download-side type, null is retained as a possible value but does not occur in practice for uploads.

**status** (`Ref<TransferStatus>`): Transfer status: 'idle' | 'pending' | 'success' | 'error' | 'aborted'.

**data** (`Ref<T | null>`): Business data unwrapped by dataKey.

**error** (`Ref<ApiError | Error | null>`): Error object. Business errors are ApiError; HTTP/network errors are Error. null on abort.

**upload()** (`(url: string, files: File | File[], options?: UploadWithProgressOptions<T>) => Promise<TransferResult<T>>`): Execute the upload.ParametersDefaults to 'file'.User value takes priority over the auto-injected auth header.Timeout in milliseconds. 0 or omitted means no timeout.Receives the unwrapped business data.ReturnsPromise<TransferResult<T>>: { data: T | null, error: ApiError | Error | null, aborted: boolean }.

**abort()** (`() => void`): Abort the current upload; status switches to 'aborted'.

## Changelog

See commit history for [src/runtime/composables/useUploadWithProgress.ts](https://github.com/mhaibaraai/movk-nuxt/commits/main/src/runtime/composables/useUploadWithProgress.ts).


## Sitemap

See the full [sitemap](https://nuxt.mhaibaraai.cn/sitemap.md) for all pages.
