File APIs: Drag and drop file upload

Join the AI Workshop to learn more about AI and how it can be applied to web development. Next cohort February 1st, 2026

The AI-first Web Development BOOTCAMP cohort starts February 24th, 2026. 10 weeks of intensive training and hands-on projects.


The Drag and Drop API allows users to drag files directly onto an element to upload them.

Basic setup

Create a drop zone element with drag event handlers:

<div
  id="dropzone"
  ondragover="dragOverHandler(event)"
  ondragleave="dragLeaveHandler(event)"
  ondrop="dropHandler(event)"
>
  Drop files here
</div>

Handling drag events

Use ondragover to style the element when a file is being dragged over it, and ondragleave to reset:

function dragOverHandler(event) {
  event.preventDefault()
  const dropzone = document.querySelector('#dropzone')
  dropzone.classList.add('dragging_over')
}

function dragLeaveHandler(event) {
  event.preventDefault()
  const dropzone = document.querySelector('#dropzone')
  dropzone.classList.remove('dragging_over')
}
#dropzone.dragging_over {
  border: 2px dashed #fff;
  background-color: #666;
}

Handling the drop

The ondrop event fires when files are dropped. Access the files through event.dataTransfer.items:

async function dropHandler(event) {
  event.preventDefault()

  const endpoint = '/api/upload'

  if (event.dataTransfer.items) {
    const formData = new FormData()

    for (let item of event.dataTransfer.items) {
      if (item.kind === 'file') {
        const file = item.getAsFile()
        if (file) {
          if (!file.type.match('image.*')) {
            alert('only images supported')
            return
          }
          formData.append('files', file)
        }
      }
    }

    try {
      const response = await fetch(endpoint, {
        method: 'POST',
        body: formData,
      })

      if (response.ok) {
        console.log('File upload successful')
      } else {
        console.error('File upload failed', response)
      }
    } catch (error) {
      console.error('Error uploading file', error)
    }
  }
}

Using Alpine.js

Alpine.js simplifies the drag and drop implementation:

<div
  x-data="{ dragover: false }"
  :class="{ '*:cursor-alias': dragover }"
  @dragover.prevent="dragover = true"
  @dragleave.prevent="dragover = false"
  @drop.prevent="drop"
>
  Drop files here
</div>

The .prevent modifier automatically calls preventDefault() to stop the browser from opening the file.

Define the drop function:

async function drop(event) {
  if (!event.dataTransfer.items) return

  const formData = new FormData()

  for (let item of event.dataTransfer.items) {
    if (item.kind === 'file') {
      const file = item.getAsFile()
      if (file) {
        if (!file.type.match('image.*')) {
          alert('only images supported')
          return
        }
        formData.append('files', file)
      }
    }
  }

  const response = await fetch('/api/upload', {
    method: 'POST',
    body: formData,
  })

  if (response.ok) {
    console.log('File upload successful')
  }
}

Lessons in this unit:

0: Introduction
1: The File object
2: FileList
3: FileReader
4: Blob
5: FormData
6: Accept only images in file input
7: Check if checkbox is checked
8: Reset a form
9: File upload with server handling
10: ▶︎ Drag and drop file upload
11: Validating file size before upload