AI Workshop: learn to build apps with AI →
File APIs: Drag and drop file upload

Join the AI Workshop and learn to build real-world apps with AI. A hands-on, practical program to level up your skills.


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

Basic setup

Example drop zone with drag event handlers:

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

Handling drag events

Use dragover to add a class when a file is over the zone, and dragleave to remove it:

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 drop event fires when files are dropped. Access them via event.dataTransfer.items:

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

  const endpoint = '/api/upload'

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

    for (const item of event.dataTransfer.items) {
      if (item.kind === 'file') {
        const file = item.getAsFile()
        if (file) {
          if (!file.type.match('image.*')) {
            alert('Only images are 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.

Drop handler:

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 are 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