# How to Handle File Uploads with Progress Tracking and Custom Upload Behavior in Element UI

> Master Element UI file uploads with progress tracking and custom behavior. Learn to leverage XHR requests, hooks, and the httpRequest prop for complete control.

- Repository: [饿了么前端/element](https://github.com/ElemeFE/element)
- Tags: how-to-guide
- Published: 2026-03-07

---

**The Element UI Upload component provides a complete file upload solution with built-in progress tracking through XHR-based requests, lifecycle hooks, and customizable HTTP implementations via the `httpRequest` prop.**

Handling file uploads with progress tracking and custom upload behavior in the ElemeFE/element repository relies on a modular architecture that separates UI concerns from network logic. The component found in [`packages/upload/src/upload.vue`](https://github.com/ElemeFE/element/blob/main/packages/upload/src/upload.vue) orchestrates file selection, validation, and request management, while delegating actual HTTP transmission to a pluggable request handler. Understanding this separation allows developers to intercept uploads for preprocessing, replace the default XHR implementation, or create completely custom user interfaces while retaining progress reporting capabilities.

## Understanding the Upload Component Architecture

The Upload component consists of three coordinated modules that handle distinct responsibilities:

- **[`upload.vue`](https://github.com/ElemeFE/element/blob/main/upload.vue)** – The core orchestrator located at [`packages/upload/src/upload.vue`](https://github.com/ElemeFE/element/blob/main/packages/upload/src/upload.vue) manages file selection, enforces limits, executes lifecycle hooks, and coordinates between the UI and network layers.
- **[`ajax.js`](https://github.com/ElemeFE/element/blob/main/ajax.js)** – The default XHR implementation at [`packages/upload/src/ajax.js`](https://github.com/ElemeFE/element/blob/main/packages/upload/src/ajax.js) handles `FormData` construction, progress events, and response parsing.
- **[`upload-list.vue`](https://github.com/ElemeFE/element/blob/main/upload-list.vue)** – The presentation layer at [`packages/upload/src/upload-list.vue`](https://github.com/ElemeFE/element/blob/main/packages/upload/src/upload-list.vue) renders file lists and displays `<el-progress>` bars during active uploads.

This architecture allows you to replace any individual piece—such as substituting the default AJAX transport with a `fetch`-based implementation—without modifying the component's core logic.

## The File Upload Lifecycle

### File Selection and Queueing

When users select files through the native `<input type="file">` element, the `handleChange` method in [`upload.vue`](https://github.com/ElemeFE/element/blob/main/upload.vue) captures the `FileList` and initiates processing:

```javascript
handleChange(ev) {
  const files = ev.target.files;
  if (!files) return;
  this.uploadFiles(files);
}

```

*(see lines 62-66 of upload.vue)*

The `uploadFiles` method enforces the `limit` and `multiple` constraints, then invokes `onStart` for each file. If `autoUpload` is enabled (the default), it immediately triggers the upload process; otherwise, files queue until manually started.

### Before-Upload Validation

Before any network activity begins, the `upload` method checks the `beforeUpload` prop (lines 87-118). This hook supports three return types:

- **`false`** – Cancels the upload immediately
- **`File` or `Blob`** – Proceeds with the returned file (synchronous transformation)
- **`Promise<File|Blob>`** – Waits for async operations like image compression or server-side validation before proceeding

If the promise resolves, the component preserves raw-file metadata and proceeds to `post`; if rejected or returned as `false`, the upload aborts.

### HTTP Request Execution

The `post` method constructs an options object containing callbacks for progress, success, and error, then delegates to the `httpRequest` prop (lines 135-161). By default, this references the `ajax` function, but you can inject any transport mechanism that conforms to the expected interface:

```javascript
post(rawFile) {
  const { uid } = rawFile;
  const options = {
    headers: this.headers,
    withCredentials: this.withCredentials,
    file: rawFile,
    data: this.data,
    filename: this.name,
    action: this.action,
    onProgress: e => {
      this.onProgress(e, rawFile);
    },
    onSuccess: res => {
      this.onSuccess(res, rawFile);
      delete this.reqs[uid];
    },
    onError: err => {
      this.onError(err, rawFile);
      delete this.reqs[uid];
    }
  };
  const req = this.httpRequest(options);
  this.reqs[uid] = req;
}

```

The request object is stored in `this.reqs` keyed by the file's `uid`, enabling individual or bulk abortion via the component's `abort(file?)` method.

### Progress Tracking and UI Updates

In [`packages/upload/src/ajax.js`](https://github.com/ElemeFE/element/blob/main/packages/upload/src/ajax.js), the default transport attaches an `onprogress` listener to the XHR upload object (lines 39-46):

```javascript
xhr.upload.onprogress = function progress(e) {
  if (e.total > 0) {
    e.percent = e.loaded / e.total * 100;
  }
  option.onProgress(e);
};

```

This calculates a percentage and invokes the `onProgress` callback supplied by [`upload.vue`](https://github.com/ElemeFE/element/blob/main/upload.vue), which updates the file's `percentage` property. Meanwhile, [`upload-list.vue`](https://github.com/ElemeFE/element/blob/main/upload-list.vue) monitors the file status and renders an `<el-progress>` element whenever `status === 'uploading'` (lines 40-44).

## Implementing Custom Upload Behavior

### Custom HTTP Requests with httpRequest

Override the default XHR behavior by providing a function to the `httpRequest` prop. Your function receives an options object containing `headers`, `withCredentials`, `file`, `data`, `filename`, `action`, `onProgress`, `onSuccess`, and `onError`. This enables integration with alternative transports like `fetch`, Axios, or cloud storage SDKs.

### Async Validation and Transformation

Use the `beforeUpload` hook to perform client-side validation or file manipulation. Return a Promise to handle async operations such as:
- Image compression or format conversion
- Virus scanning via client-side wasm
- Duplicate checking against existing uploads

The resolved value replaces the original file in the upload pipeline.

### Manual Upload Control

Set `:auto-upload="false"` to queue files without immediate transmission. Trigger uploads manually by calling `uploadFiles` on the component reference, allowing batch operations or user-initiated submission flows.

## Practical Implementation Example

The following implementation demonstrates disabled auto-upload, custom request handling with `fetch`, file size validation, and progress logging:

```vue
<template>
  <el-upload
    ref="upload"
    class="custom-uploader"
    action="https://api.example.com/upload"
    :auto-upload="false"
    :multiple="true"
    :limit="5"
    :on-start="handleStart"
    :on-progress="handleProgress"
    :on-success="handleSuccess"
    :on-error="handleError"
    :before-upload="validateFile"
    :http-request="customRequest"
  >
    <el-button slot="trigger" size="small" type="primary">Select Files</el-button>
    <el-button 
      style="margin-left: 10px;" 
      size="small" 
      type="success"
      @click="submitUpload"
    >
      Upload to Server
    </el-button>
  </el-upload>
</template>

<script>
export default {
  methods: {
    handleStart(file) {
      console.log('Upload starting:', file.name);
    },
    
    handleProgress(event, file) {
      console.log(`Upload progress for ${file.name}: ${event.percent.toFixed(1)}%`);
    },
    
    handleSuccess(response, file) {
      console.log('Upload completed:', file.name, response);
    },
    
    handleError(error, file) {
      console.error('Upload failed:', file.name, error);
    },
    
    validateFile(file) {
      const maxSize = 2 * 1024 * 1024; // 2MB
      if (file.size > maxSize) {
        this.$message.error('File size exceeds 2MB limit');
        return false;
      }
      
      // Async transformation example
      return new Promise((resolve) => {
        this.compressImage(file, (compressedBlob) => {
          const transformedFile = new File(
            [compressedBlob], 
            file.name, 
            { type: file.type }
          );
          resolve(transformedFile);
        });
      });
    },
    
    customRequest(options) {
      const formData = new FormData();
      
      if (options.data) {
        Object.entries(options.data).forEach(([key, value]) => {
          formData.append(key, value);
        });
      }
      
      formData.append(options.filename, options.file);
      
      return fetch(options.action, {
        method: 'POST',
        body: formData,
        credentials: options.withCredentials ? 'include' : 'same-origin',
        headers: options.headers
      })
      .then(response => {
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        return response.json();
      })
      .then(options.onSuccess)
      .catch(options.onError);
    },
    
    submitUpload() {
      this.$refs.upload.submit();
    },
    
    compressImage(file, callback) {
      // Image compression logic here
      callback(file);
    }
  }
};
</script>

```

This example integrates all major customization points: synchronous validation, asynchronous file transformation, a `fetch`-based transport replacing the default XHR, and manual upload triggering.

## Summary

- **Modular Architecture** – The Upload component separates concerns between [`upload.vue`](https://github.com/ElemeFE/element/blob/main/upload.vue) (orchestration), [`ajax.js`](https://github.com/ElemeFE/element/blob/main/ajax.js) (transport), and [`upload-list.vue`](https://github.com/ElemeFE/element/blob/main/upload-list.vue) (UI), enabling targeted customizations.
- **Progress Tracking** – Built-in progress reporting relies on `xhr.upload.onprogress` in the default implementation, passing percentage data through the `onProgress` callback to update UI elements.
- **Custom Transport** – Replace the default XHR behavior by providing a custom `http-request` function that conforms to the options interface, supporting fetch, Axios, or SDK-based uploads.
- **Lifecycle Hooks** – Use `before-upload` for validation and transformation (supports async/Promise), `on-start` for initiation side effects, and `on-success`/`on-error` for result handling.
- **Manual Control** – Disable `auto-upload` to queue files indefinitely, then trigger uploads individually or in batches via component methods.

## Frequently Asked Questions

### How does Element UI calculate and display upload progress?

Element UI calculates upload progress in [`packages/upload/src/ajax.js`](https://github.com/ElemeFE/element/blob/main/packages/upload/src/ajax.js) by attaching an `onprogress` event listener to the XMLHttpRequest's upload property. When the browser fires progress events, the code computes `e.percent = e.loaded / e.total * 100` and invokes the `onProgress` callback. This updates the file object's `percentage` property, which [`upload-list.vue`](https://github.com/ElemeFE/element/blob/main/upload-list.vue) binds to an `<el-progress>` component for visual feedback.

### Can I use the Fetch API instead of XMLHttpRequest for file uploads?

Yes. Pass a custom function to the `http-request` prop that accepts the standard options object and returns a Promise or request handle. Your implementation should call `options.onProgress` with percentage data if needed, then resolve with `options.onSuccess` or reject with `options.onError`. This completely replaces the default XHR-based [`ajax.js`](https://github.com/ElemeFE/element/blob/main/ajax.js) implementation.

### How do I abort an ongoing file upload?

Call the `abort(file)` method on the Upload component instance. If you pass a specific file object (or its `uid`), it aborts that individual request by looking up the stored request handle in `this.reqs[uid]`. Calling `abort()` without arguments iterates through all pending requests in `this.reqs` and aborts each one, effectively canceling all active uploads.

### What is the difference between the `before-upload` and `on-start` hooks?

The `before-upload` hook executes before any network request begins and can block or transform the upload—return `false` to cancel, a `File`/`Blob` to replace the file, or a Promise for async validation. The `on-start` hook fires immediately after `before-upload` resolves successfully and purely signals that the upload is beginning; it cannot prevent the upload and is used for side effects like logging or UI state changes.