Skip to content

Axios Upload Progress — Complete Guide with Examples

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you'll learn about Axios upload progress tracking. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Axios onUploadProgress callback lets you track file upload progress in real time, displaying percentage, speed, and estimated time remaining for large file uploads.

What You'll Learn

By the end of this tutorial, you'll track single and multiple file uploads, calculate upload speed and ETA, build a progress UI, handle upload cancellation, and display per-file progress for batch uploads.

Why It Matters

File uploads can take seconds to minutes. Without progress feedback, users think the page is frozen or the upload failed. A progress indicator builds confidence, and upload speed helps users choose between Wi-Fi and cellular connections.

Real-World Use

Durga Antivirus Pro's file submission portal uses onUploadProgress for users uploading suspicious files for analysis. The dashboard shows per-file progress, overall batch progress, and estimated analysis time after upload completes.

Where This Fits in Your Learning Path

flowchart LR
    A["Download Progress"] --> B["**Upload Progress**"]
    B --> C["Cookies & Auth"]
    C --> D["HTTP/2 Adapters"]
    D --> E["Axios Advanced"]
    style B fill:#f97316,stroke:#c2410c,color:#fff
    style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
    style E fill:#22c55e,stroke:#16a34a,color:#fff

Single File Upload with Progress

Track upload progress for a single file using FormData.

const fileInput = document.querySelector('#file-input')
const file = fileInput.files[0]

const formData = new FormData()
formData.append('file', file)

const response = await axios.post('/api/upload', formData, {
  headers: { 'Content-Type': 'multipart/form-data' },
  onUploadProgress: function(progressEvent) {
    const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
    console.log(`Uploading: ${percent}%`)
  }
})

console.log('Upload complete:', response.data)

Expected output: Console logs progress from 0% to 100% as the file uploads. On completion, the server response is logged.

Multiple File Upload with Per-File Progress

Track each file individually while uploading a batch.

const files = document.querySelector('#file-input').files
const uploads = Array.from(files).map((file, index) => {
  const formData = new FormData()
  formData.append('file', file)

  return axios.post('/api/upload', formData, {
    onUploadProgress: function(e) {
      const percent = Math.round((e.loaded * 100) / e.total)
      console.log(`File ${index + 1}/${files.length} (${file.name}): ${percent}%`)
    }
  })
})

const results = await Promise.all(uploads)
console.log('All uploads complete:', results.length)

Expected output: Each file uploads independently with its own progress log. The overall batch completes when all individual uploads finish.

Upload Dashboard with Speed and ETA

Build a full upload dashboard with metrics.

async function uploadFile(file) {
  const formData = new FormData()
  formData.append('file', file)

  const startTime = Date.now()

  const { data } = await axios.post('/api/upload', formData, {
    onUploadProgress: function(e) {
      const now = Date.now()
      const elapsed = (now - startTime) / 1000
      const percent = Math.round((e.loaded * 100) / e.total)
      const speed = e.loaded / elapsed
      const eta = (e.total - e.loaded) / speed

      updateUI({
        fileName: file.name,
        percent,
        loaded: e.loaded,
        total: e.total,
        speed,
        eta
      })
    }
  })

  return data
}

function updateUI(info) {
  document.querySelector('#file-name').textContent = info.fileName
  document.querySelector('#progress-fill').style.width = info.percent + '%'
  document.querySelector('#progress-text').textContent = info.percent + '%'
  document.querySelector('#speed').textContent = formatSpeed(info.speed)
  document.querySelector('#eta').textContent = formatTime(info.eta)
}

Expected output: The dashboard updates with file name, progress bar, percentage, upload speed, and ETA.

Upload with Cancellation

Combine onUploadProgress with AbortController for cancellable uploads.

let controller = null

async function startUpload(file) {
  controller = new AbortController()
  const formData = new FormData()
  formData.append('file', file)

  try {
    const { data } = await axios.post('/api/upload', formData, {
      signal: controller.signal,
      onUploadProgress: function(e) {
        const percent = Math.round((e.loaded * 100) / e.total)
        console.log(`Upload: ${percent}%`)
        document.querySelector('#cancel-btn').disabled = false
      }
    })
    console.log('Upload complete:', data)
  } catch (error) {
    if (axios.isCancel(error)) {
      console.log('Upload was canceled by user')
    } else {
      console.error('Upload failed:', error)
    }
  }
}

function cancelUpload() {
  if (controller) {
    controller.abort('User canceled upload')
    document.querySelector('#cancel-btn').disabled = true
  }
}

Expected output: Upload starts with progress. Clicking the cancel button aborts the request and shows a cancellation message.

Common Mistakes

1. Forgetting to set multipart/form-data header

Axios sets Content-Type automatically for FormData, including the boundary. Setting it manually may break the request.

2. Using onUploadProgress for download tracking

onUploadProgress tracks data sent to the server. Use onDownloadProgress for tracking received data.

3. Appending files incorrectly to FormData

Use formData.append('fieldName', file, filename) for proper multipart encoding. Omitting the filename may cause server Parsing issues.

4. Not handling file size validation before upload

Check file size on the client before uploading. A 2GB file will fail after minutes of uploading if the server rejects it.

5. Creating a new FormData for each progress update

FormData is created once before the request. Creating it inside onUploadProgress has no effect on the existing request.

Practice Questions

  1. Which config option tracks upload progress? onUploadProgress callback in the request configuration.

  2. What is the difference between onUploadProgress and onDownloadProgress? onUploadProgress tracks data sent to the server. onDownloadProgress tracks data received from the server.

  3. How do you upload multiple files with individual progress? Create separate requests for each file, each with its own onUploadProgress callback.

  4. Can you cancel an upload in progress? Yes. Pass an AbortController signal and call controller.abort() to cancel.

  5. How does Axios determine upload total? From the Content-Length header of the request body. For FormData, Axios calculates it from the file sizes.

Challenge

Build a drag-and-drop file upload zone that accepts multiple files. Show a progress list with per-file progress bars, a total overall progress bar, and a cancel button for each file.

FAQ

Does onUploadProgress work with all browsers?

Yes. It works in all modern browsers that support XMLHttpRequest Level 2.

Can I track upload progress for non-file data?

Yes. Any POST/PUT/PATCH request body can be tracked with onUploadProgress.

Why is my upload progress stuck at 100%?

The 100% event fires when the browser finishes sending data. The server may still be processing. Use onDownloadProgress to track the response.

Does upload progress work with compressed requests?

Yes, but the loaded/total reflect the compressed size, not the original file size.

Can I use onUploadProgress in Node.js?

In older Node.js versions, upload progress is limited. In Node.js 18+, it works similarly to browsers.


Mini Project

Build a complete file uploader with drag-and-drop, multiple file support, per-file progress bars, overall progress, cancel per file, and upload speed display.

<div id="drop-zone" style="border:2px dashed #4ecdc4;padding:40px;text-align:center;border-radius:8px;cursor:pointer">
  <p>Drop files here or click to select</p>
</div>
<div id="upload-list"></div>
<div id="overall-progress" style="display:none">
  <div style="width:100%;background:#e0e0e0;border-radius:4px;overflow:hidden;margin-top:16px">
    <div id="overall-fill" style="width:0%;height:20px;background:#4ecdc4;transition:width 0.3s"></div>
  </div>
  <p id="overall-text" style="font-size:12px;color:#666;margin-top:4px"></p>
</div>

<script>
  const dropZone = document.querySelector('#drop-zone')

  dropZone.addEventListener('click', () => {
    const input = document.createElement('input')
    input.type = 'file'
    input.multiple = true
    input.onchange = () => uploadFiles(input.files)
    input.click()
  })

  dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.style.borderColor = '#ff6b6b' })
  dropZone.addEventListener('dragleave', () => { dropZone.style.borderColor = '#4ecdc4' })
  dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.style.borderColor = '#4ecdc4'; uploadFiles(e.dataTransfer.files) })

  async function uploadFiles(files) {
    const overallEl = document.querySelector('#overall-progress')
    const overallFill = document.querySelector('#overall-fill')
    const overallText = document.querySelector('#overall-text')
    const listEl = document.querySelector('#upload-list')
    overallEl.style.display = 'block'

    const totalFiles = files.length
    let completedFiles = 0

    for (const file of files) {
      const itemEl = document.createElement('div')
      itemEl.className = 'upload-item'
      itemEl.innerHTML = `<span>${file.name}</span><div style="width:100%;background:#eee;border-radius:4px;overflow:hidden"><div class="file-progress" style="width:0%;height:16px;background:#4ecdc4;transition:width 0.3s"></div></div><span class="file-status">0%</span>`
      listEl.appendChild(itemEl)

      const formData = new FormData()
      formData.append('file', file)
      const progressBar = itemEl.querySelector('.file-progress')
      const statusEl = itemEl.querySelector('.file-status')

      await axios.post('/api/upload', formData, {
        onUploadProgress: function(e) {
          const pct = Math.round((e.loaded * 100) / e.total)
          progressBar.style.width = pct + '%'
          statusEl.textContent = pct + '%'
        }
      })

      completedFiles++
      const overallPct = Math.round((completedFiles / totalFiles) * 100)
      overallFill.style.width = overallPct + '%'
      overallText.textContent = `${completedFiles} of ${totalFiles} files uploaded (${overallPct}%)`
      statusEl.textContent = 'Done'
      progressBar.style.background = '#2ecc71'
    }
  }
</script>

What's Next

Continue with authentication patterns:

Tutorial What You'll Learn
Cookies and Auth Manage cookies, HTTP-only cookies, and auth tokens
HTTP/2 Adapters Use HTTP/2 adapters for improved performance

Related topics: FormData and file handling, multipart/form-data encoding.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro