Skip to main content

File Upload Form

Upload, validate, attach, preview, and delete a private file.

Result

An authenticated user uploads a bounded image, attaches its storage ID to a document, sees progress and preview, and can delete it through the owning document.

Component flow

ts
const uploadState = useConvexFileUpload(api.files.generateUploadUrl, {
  maxSize: 5 * 1024 * 1024,
  allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
})
const attachFile = useConvexMutation(api.documents.attachFile)

async function submit(file: File) {
  const storageId = await uploadState.upload(file)
  const attached = await attachFile.safe({ documentId, storageId })

  if (!attached.ok) {
    await useConvex().mutation(api.files.removeUnattached, { storageId })
    throw attached.error
  }
}

const previewUrl = useConvexStorageUrl(api.files.getUrl, uploadState.data, {
  auth: 'required',
})

Backend invariants

  • generateUploadUrl requires identity.
  • attachFile loads the document and verifies edit access.
  • The document stores the storage ID.
  • getUrl checks read access before resolving a private URL.
  • Deletion accepts a document ID, checks ownership, deletes storage, then clears metadata.
  • Cleanup for unattached storage IDs is restricted to the user/workflow that created them.

Presentation

Render:

  • selected file name and client preflight errors;
  • upload progress and cancel action;
  • metadata-save error separately from byte-upload error;
  • preview only after authorized URL resolution;
  • deletion pending and failure states.

Verify

  • Oversized and disallowed browser files fail before upload.
  • Unauthorized metadata attachment fails in Convex.
  • Failed attachment removes or schedules cleanup for the orphan.
  • Another user cannot resolve or delete the file.
  • Component unmount aborts an active upload.