Skip to main content

Upload Files

Upload one file to Convex storage with progress, cancellation, and reactive state.

File upload has two network steps: request an upload URL through a mutation, then upload the bytes directly to Convex storage.

Backend

convex/files.ts
import { mutation } from './_generated/server'

export const generateUploadUrl = mutation({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity()
    if (!identity) throw new Error('Unauthenticated')
    return await ctx.storage.generateUploadUrl()
  },
})

Component

vue
<script setup lang="ts">
import { api } from '#convex/api'

const {
  upload,
  status,
  pending,
  progress,
  error,
  data: storageId,
  cancel,
} = useConvexFileUpload(api.files.generateUploadUrl, {
  maxSize: 5 * 1024 * 1024,
  allowedTypes: ['image/*', 'application/pdf'],
})

async function chooseFile(event: Event) {
  const input = event.target as HTMLInputElement
  const file = input.files?.[0]
  if (file) await upload(file)
}
</script>

<template>
  <input type="file" :disabled="pending" @change="chooseFile" />
  <progress v-if="pending" :value="progress" max="100" />
  <button v-if="pending" @click="cancel">Cancel</button>
  <p v-if="error">{{ error.message }}</p>
  <p v-if="storageId">Uploaded as {{ storageId }}</p>
</template>

State

status is idle, pending, success, or error. data contains the last storage ID. cancel() aborts an active XHR and resets local state.

One composable instance accepts one active upload. A second call while pending rejects instead of interleaving shared progress state.

Validation boundary

maxSize and allowedTypes provide immediate browser feedback. File name and MIME type are client-controlled. Validate product policy again before publishing or processing the file.

Save metadata

The upload only creates a storage object. Store its ID in a product document through a mutation:

ts
const storageId = await upload(file)
await createDocument({ title, storageId })

Plan cleanup if upload succeeds and metadata creation fails.