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
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
generateUploadUrlrequires identity.attachFileloads the document and verifies edit access.- The document stores the storage ID.
getUrlchecks 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.