# How to implement file upload with drag and drop with Alpine

> Learn how to build file upload with drag and drop in Alpine.js, using the @dragover, @dragleave, and @drop events to collect files and POST them with fetch.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2023-12-21 | Topics: [JavaScript](https://flaviocopes.com/tags/js/), [Alpine.js](https://flaviocopes.com/tags/alpine/) | Canonical: https://flaviocopes.com/how-to-implement-file-upload-with-drag-and-drop-with-alpine/

I wrote about how to [implement file upload with drag and drop in vanilla JS](https://flaviocopes.com/how-to-implement-file-upload-with-drag-and-drop-in-vanilla-js/).

Now let me do the same with Alpine, which makes our code simpler.

Identify an HTML element you want people to drag their files to, and use the `@dragover` `@dragleave` to set a local `dragover` property to true when we’re hovering the element dragging some file, and to false when moving away.

This lets us add some style (in this example I use Tailwind CSS to change the cursor when hovering the element and its children):

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

`:class` is an Alpine.js thing, we can add the class only when `dragover` is true.

If directives like `:class` and `@dragover` are new to you, my free [Alpine.js cheatsheet](https://flaviocopes.com/tools/alpine-cheatsheet/) shows them all with live mini-demos.

`.prevent` on those events automatically calls `preventDefault()` on them, to avoid the browser from opening the file at full screen instead of triggering our drag and drop events.

Now you can add `@drop` event that is fired when we file is dropped on the element.

```typescript
<div
  ...
  @drop.prevent="drop"
>
...
</div>
```

You define the drop function in [JavaScript](https://flaviocopes.com/javascript/)

That’s where the action happens.

In this example I gather the files dropped, check they’re images (I only want images in this example) then I append them all to a `formData` object:

```typescript
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)
      }
    }
  }

  //...
}
```

Once I have the formData object I can do a `fetch()` request:

```typescript
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)
}
```

Alternatively you can trigger a form submit programmatically using htmx via `htmx.ajax()`, but there is some trickery to upload files with this method, I’ll explain that in another post.

How to handle that server-side depends on your server.

With [Astro](https://flaviocopes.com/astro-introduction/) I got the data using:

```javascript
const formData = await Astro.request.formData()

console.log(formData.getAll('files'))
```
