---
title: "Image upload with preview"
description: "Pick an image, preview it locally, upload it to Convex storage, and show the stored file."
canonical_url: "https://nuxt-convex-module.dev/recipes/image-upload"
---
# Image upload with preview

> Pick an image, preview it locally, upload it to Convex storage, and show the stored file.

`useUpload` handles the upload URL, the `POST` and the progress; `useStorageUrl` turns the returned storage id into a served URL. A local object URL gives the preview while the upload runs. Try it:

<playground-files>



</playground-files>

```vue [components/AvatarUpload.vue]
<script setup lang="ts">
import { api } from '#convex/api'

const { upload, progress, isUploading, error, storageId } = useUpload(api.files.generateUploadUrl, {
  onSuccess: storageId => saveAvatar({ storageId }),
})
const saveAvatar = useMutation(api.files.save)
const storedUrl = useStorageUrl(api.files.url, storageId)

const preview = ref<string>()

function onChange(event: Event) {
  const file = (event.target as HTMLInputElement).files?.[0]
  if (!file) return
  preview.value = URL.createObjectURL(file)
  upload(file)
}
</script>

<template>
  <input type="file" accept="image/*" @change="onChange">
  <img v-if="storedUrl" :src="storedUrl" alt="">
  <img v-else-if="preview" :src="preview" alt="" :style="{ opacity: 0.5 + progress / 2 }">
  <progress v-if="isUploading" :value="progress" max="1" />
  <p v-if="error">{{ error.message }}</p>
</template>
```

- `upload()` never throws: it resolves to the storage id, or `null` with `error` set.
- `storedUrl` is `undefined` until the id exists and the query resolves, so the preview stays up until the real file is ready — no flash.
- Limit size and type in your Convex `save` mutation; the upload goes straight to Convex, so no Nitro limit applies — [File storage security](https://nuxt-convex-module.dev/getting-started/security#file-storage).

Batches with bounded concurrency (`useUploadQueue`) and the plain `uploadFile()`: [File storage](https://nuxt-convex-module.dev/guide/file-storage).


## Sitemap

See the full [sitemap](https://nuxt-convex-module.dev/sitemap.md) for all pages.
