File Uploads

input type=file + FormData + fetch

File Uploads

Handle file selection, preview, and multipart upload using built-in browser APIs. No library required for the basics.

4 min read Level 2/5 #vue#forms#files
What you'll learn
  • Read selected files from the change event
  • Preview an image with createObjectURL
  • Send the file via FormData and fetch

File inputs work a little differently from other inputs — you cannot use v-model on them. Read files from the change event, then upload with FormData.

Reading the Selected File

<script setup lang="ts">
import { ref } from 'vue'

const file = ref<File | null>(null)

function onPick(e: Event) {
  const input = e.target as HTMLInputElement
  file.value = input.files?.[0] ?? null
}
</script>

<template>
  <input type="file" @change="onPick" />
  <p v-if="file">Picked: {{ file.name }} ({{ file.size }} bytes)</p>
</template>

Image Preview

For images, build a local preview URL with URL.createObjectURL. Revoke it when the component unmounts to free memory.

<script setup lang="ts">
import { ref, computed, onBeforeUnmount } from 'vue'

const file = ref<File | null>(null)
const previewUrl = computed(() =>
  file.value ? URL.createObjectURL(file.value) : ''
)

onBeforeUnmount(() => {
  if (previewUrl.value) URL.revokeObjectURL(previewUrl.value)
})
</script>

<template>
  <input type="file" accept="image/*" @change="e => file = (e.target as HTMLInputElement).files?.[0] ?? null" />
  <img v-if="previewUrl" :src="previewUrl" alt="preview" />
</template>

Uploading

Bundle the file into FormData and post it. Do not set the Content-Type header yourself — the browser fills in the multipart boundary.

async function upload(file: File) {
  const fd = new FormData()
  fd.append('file', file)
  fd.append('caption', 'My photo')

  const res = await fetch('/api/upload', { method: 'POST', body: fd })
  if (!res.ok) throw new Error(await res.text())
  return res.json()
}

Multiple Files

Add the multiple attribute and iterate input.files:

<input type="file" multiple @change="onPickMany" />
function onPickMany(e: Event) {
  const list = (e.target as HTMLInputElement).files
  if (!list) return
  for (const f of Array.from(list)) {
    upload(f)
  }
}
Form State Management →