Overview
The Utilities API provides essential system functions including secure file upload URL generation and other utility operations needed across the platform.
Location: convex/shared/utils.ts
Generate Upload URL
Generates a secure upload URL for file uploads to Convex storage.
TypeScript
JavaScript
Python
cURL
const uploadUrl = await convex . mutation ( api . shared . utils . generateUploadUrl );
// Use the URL to upload a file
const file = new File ([ 'content' ], 'example.jpg' , { type: 'image/jpeg' });
const response = await fetch ( uploadUrl , {
method: 'POST' ,
body: file
});
const { storageId } = await response . json ();
console . log ( 'File uploaded with ID:' , storageId );
"https://upload.convex.dev/upload/abc123def456?token=xyz789"
File Upload Process
The file upload process follows these steps:
Generate Upload URL
Call generateUploadUrl to get a secure, temporary upload URL
Upload File
Use the returned URL to upload your file via HTTP POST
Get Storage ID
The upload response contains a storageId that you can use to reference the file
Use Storage ID
Use the storageId in other API calls (e.g., creating products with images)
Supported File Types
Images Formats : JPEG, PNG, WebP, GIF, SVG
Max Size : 10MB
Recommended : WebP for web, PNG for transparency
Documents Formats : PDF, DOC, DOCX, TXT
Max Size : 25MB
Use Case : Trade licenses, contracts, receipts
Videos Formats : MP4, WebM, MOV
Max Size : 100MB
Use Case : Product demos, store tours
Audio Formats : MP3, WAV, OGG
Max Size : 25MB
Use Case : Voice notes, audio descriptions
Complete Upload Example
Here’s a complete example showing how to upload a file and use it:
React Component
Vanilla JS
import { useState } from 'react' ;
import { useMutation } from 'convex/react' ;
import { api } from './convex/_generated/api' ;
function FileUploadComponent () {
const [ uploading , setUploading ] = useState ( false );
const [ storageId , setStorageId ] = useState < string | null >( null );
const generateUploadUrl = useMutation ( api . shared . utils . generateUploadUrl );
const createProduct = useMutation ( api . shared . products . createProduct );
const handleFileUpload = async ( file : File ) => {
try {
setUploading ( true );
// Step 1: Generate upload URL
const uploadUrl = await generateUploadUrl ();
// Step 2: Upload file
const response = await fetch ( uploadUrl , {
method: 'POST' ,
body: file
});
const { storageId } = await response . json ();
setStorageId ( storageId );
// Step 3: Use storage ID in product creation
await createProduct ({
name: "New Product" ,
category: "k123456789" ,
price: 19.99 ,
isActive: true ,
primaryImage: storageId // Use the uploaded file
});
console . log ( 'Product created with uploaded image!' );
} catch ( error ) {
console . error ( 'Upload failed:' , error );
} finally {
setUploading ( false );
}
};
return (
< div >
< input
type = "file"
accept = "image/*"
onChange = {(e) => {
const file = e . target . files ?.[ 0 ];
if ( file ) handleFileUpload ( file );
}}
disabled = { uploading }
/>
{ uploading && < p > Uploading ...</ p >}
{ storageId && < p > Upload successful ! ID : { storageId }</ p >}
</ div >
);
}
Security & Best Practices
Always validate file types and sizes on the client side before uploading: function validateFile ( file : File ) : boolean {
// Check file size (10MB limit for images)
if ( file . size > 10 * 1024 * 1024 ) {
throw new Error ( 'File too large. Maximum size is 10MB.' );
}
// Check file type
const allowedTypes = [ 'image/jpeg' , 'image/png' , 'image/webp' ];
if ( ! allowedTypes . includes ( file . type )) {
throw new Error ( 'Invalid file type. Only JPEG, PNG, and WebP are allowed.' );
}
return true ;
}
Upload URLs expire after 1 hour for security. Generate a new URL if upload fails: async function uploadWithRetry ( file : File , maxRetries = 3 ) {
for ( let i = 0 ; i < maxRetries ; i ++ ) {
try {
const uploadUrl = await generateUploadUrl ();
const response = await fetch ( uploadUrl , {
method: 'POST' ,
body: file
});
if ( response . ok ) {
return await response . json ();
}
} catch ( error ) {
if ( i === maxRetries - 1 ) throw error ;
console . warn ( `Upload attempt ${ i + 1 } failed, retrying...` );
}
}
}
Track upload progress for better user experience: async function uploadWithProgress ( file : File , onProgress : ( progress : number ) => void ) {
const uploadUrl = await generateUploadUrl ();
return new Promise (( resolve , reject ) => {
const xhr = new XMLHttpRequest ();
xhr . upload . addEventListener ( 'progress' , ( e ) => {
if ( e . lengthComputable ) {
const progress = ( e . loaded / e . total ) * 100 ;
onProgress ( progress );
}
});
xhr . addEventListener ( 'load' , () => {
if ( xhr . status === 200 ) {
resolve ( JSON . parse ( xhr . responseText ));
} else {
reject ( new Error ( 'Upload failed' ));
}
});
xhr . addEventListener ( 'error' , () => reject ( new Error ( 'Upload failed' )));
xhr . open ( 'POST' , uploadUrl );
xhr . send ( file );
});
}
Error Handling
Upload URL generation failed
Status Code : 500{
"error" : "Failed to generate upload URL. Please try again."
}
Status Code : 400{
"error" : "File upload failed" ,
"details" : "File size exceeds maximum limit"
}
Status Code : 400{
"error" : "Invalid file type" ,
"allowedTypes" : [ "image/jpeg" , "image/png" , "image/webp" ]
}
Upload URLs are temporary and expire after 1 hour. Always generate a fresh URL for each upload operation.
For better performance, compress images before uploading and use WebP format when possible for web applications.