uploadthing
semver
>=6.0.0postconditions10functions8last verified2026-04-17coverage score80%Postconditions — what we check
- UTApi.uploadFiles · upload-files-no-try-catcherrorWhenutapi.uploadFiles() is called without try/catch. Configuration failures (UPLOADTHING_TOKEN missing, invalid server config, concurrency out of range 1-25) throw UploadThingError before any upload begins. If called from a browser context, throws UploadThingError({ code: "INTERNAL_SERVER_ERROR" }).Throws
UploadThingError with code MISSING_ENV (UPLOADTHING_TOKEN not set), INVALID_SERVER_CONFIG (malformed token or config), BAD_REQUEST (concurrency outside 1-25 range), or INTERNAL_SERVER_ERROR (called from browser). Confirmed from source: guardServerOnly() throws on browser, concurrency check throws BAD_REQUEST, executeAsync throws on Cause.squash(exit.cause).Required handlingCaller MUST wrap utapi.uploadFiles() in try/catch. Additionally, when uploading multiple files, caller MUST check result.error on each response — individual file failures return { data: null, error: { code, message, data } } rather than throwing. Minimum handling: try { const responses = await utapi.uploadFiles(files); const uploaded = []; const failed = []; for (const response of Array.isArray(responses) ? responses : [responses]) { if (response.error) { failed.push(response.error); } else { uploaded.push(response.data); } } if (failed.length > 0) { console.error('Some files failed to upload:', failed); } } catch (error) { if (error instanceof UploadThingError) { console.error('Upload configuration error:', error.code, error.message); } throw error; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - UTApi.uploadFiles · upload-files-result-error-uncheckederrorWhenutapi.uploadFiles() is called with an array of files and the caller does not check result.error on each returned item. Individual file upload failures (UPLOAD_FAILED, TOO_LARGE, FILE_LIMIT_EXCEEDED) are returned as { data: null, error: { code, message, data } } rather than thrown. Callers that only do `const results = await utapi.uploadFiles(files)` and then access `results[i].data.key` will get TypeError on failed files.Throws
Does NOT throw for per-file failures. Returns { data: null, error: { code, message } } for each failed file. Codes include: UPLOAD_FAILED (S3 upload error), TOO_LARGE (file exceeds size limit — HTTP 413), FILE_LIMIT_EXCEEDED (storage quota exceeded), MISSING_ENV (config issue at file-upload time). Source: uploadthing/server/index.js Effect.match { onFailure: (error) => ({ data: null, error }) }Required handlingAfter awaiting uploadFiles(), iterate results and check result.error. Accessing result.data without checking result.error first causes TypeError when data is null. const results = await utapi.uploadFiles(files); for (const result of Array.isArray(results) ? results : [results]) { if (result.error !== null) { // Handle per-file failure — result.data is null here logger.error({ code: result.error.code, message: result.error.message }); continue; } // Safe to access result.data here await db.files.create({ key: result.data.key, url: result.data.ufsUrl }); }costmediumin prodsilent failureusers seelost datavisibilitysilent - UTApi.uploadFilesFromUrl · upload-from-url-no-try-catcherrorWhenutapi.uploadFilesFromUrl() is called without try/catch. Configuration errors (UPLOADTHING_TOKEN missing, INVALID_SERVER_CONFIG) throw before any download begins. If concurrency is outside 1-25, throws BAD_REQUEST.Throws
UploadThingError with code MISSING_ENV (token not set), INVALID_SERVER_CONFIG (malformed config), BAD_REQUEST (concurrency outside 1-25). Per-URL failures are returned in result.error rather than thrown. Confirmed from source: executeAsync throws on Cause.squash(exit.cause).Required handlingCaller MUST wrap utapi.uploadFilesFromUrl() in try/catch. Also MUST check result.error on each response (same pattern as uploadFiles). try { const result = await utapi.uploadFilesFromUrl(url); if (result.error) { console.error('Upload from URL failed:', result.error.code); return null; } return result.data; } catch (error) { console.error('Upload configuration error:', error); throw error; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - UTApi.uploadFilesFromUrl · upload-from-url-data-url-rejectedwarningWhenuploadFilesFromUrl() is called with a data: URL (e.g., "data:image/png;base64,..."). The source code explicitly checks for data: prefix and returns BAD_REQUEST immediately, before any network request. Callers that pass canvas.toDataURL() or file reader results directly will get a per-result error (not thrown).Throws
Does NOT throw — returns { data: null, error: { code: "BAD_REQUEST", message: "Please use uploadFiles() for data URLs. uploadFilesFromUrl() is intended for use with remote URLs only." } }. Confirmed from source: downloadFile() checks url.startsWith("data:") and returns Effect.fail({ code: "BAD_REQUEST", ... }).Required handlingCheck whether the URL is a data URL before calling uploadFilesFromUrl(). Use uploadFiles() instead for data URLs or Blob/File objects: if (typeof url === 'string' && url.startsWith('data:')) { // Convert data URL to File and use uploadFiles() instead const blob = await fetch(url).then(r => r.blob()); return await utapi.uploadFiles(new File([blob], 'file.png')); } return await utapi.uploadFilesFromUrl(url);costlowin prodsilent failureusers seeservice unavailablevisibilitysilent - UTApi.deleteFiles · delete-files-no-try-catcherrorWhenutapi.deleteFiles() is called without try/catch. Authentication failures (UPLOADTHING_TOKEN missing or invalid), network failures, and server errors throw UploadThingError.Throws
UploadThingError with code MISSING_ENV (token not set), INVALID_SERVER_CONFIG (malformed config), FORBIDDEN (invalid API key — HTTP 403), or INTERNAL_SERVER_ERROR (server failure — HTTP 500). Network failures propagate through executeAsync as UploadThingError. Source: requestUploadThing catches ConfigError → INVALID_SERVER_CONFIG.Required handlingCaller MUST wrap utapi.deleteFiles() in try/catch. A thrown error means no deletion occurred — files remain in storage. Retry with backoff on network failures. try { const { success, deletedCount } = await utapi.deleteFiles(fileKeys); if (!success || deletedCount === 0) { // Keys were not found — files may already be deleted or keys are wrong console.warn('No files deleted — keys may not exist'); } } catch (error) { if (error instanceof UploadThingError) { console.error('Delete failed:', error.code, error.message); } throw error; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - UTApi.generateSignedURL · generate-signed-url-no-try-catcherrorWhenutapi.generateSignedURL() is called without try/catch. Throws UploadThingError with BAD_REQUEST for invalid expiresIn parameter (non-time-string, NaN, or greater than 7 days). Throws INVALID_SERVER_CONFIG when UPLOADTHING_TOKEN is missing or malformed.Throws
UploadThingError with code BAD_REQUEST (invalid expiresIn: "expiresIn must be a valid time string, for example '1d', '2 days', or a number of seconds." OR "expiresIn must be less than 7 days (604800 seconds)."), INVALID_SERVER_CONFIG (UPLOADTHING_TOKEN missing or malformed — includes ConfigError cause), MISSING_ENV (token environment variable not set). Confirmed from source: explicit throw new UploadThingError$1 for both checks.Required handlingCaller MUST wrap utapi.generateSignedURL() in try/catch. Validate expiresIn before passing — invalid values throw immediately. try { const { ufsUrl } = await utapi.generateSignedURL(fileKey, { expiresIn: '1 hour' // valid: '1h', '30 minutes', 3600 (seconds) }); return ufsUrl; } catch (error) { if (error instanceof UploadThingError) { if (error.code === 'BAD_REQUEST') { // Invalid expiresIn — fix the parameter throw new Error(`Invalid signed URL config: ${error.message}`); } if (error.code === 'INVALID_SERVER_CONFIG' || error.code === 'MISSING_ENV') { // UPLOADTHING_TOKEN not configured throw new Error('UploadThing not configured'); } } throw error; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - UTApi.renameFiles · rename-files-no-try-catcherrorWhenutapi.renameFiles() is called without try/catch. Authentication failures (invalid/missing UPLOADTHING_TOKEN), server errors (5xx), and network failures throw UploadThingError.Throws
UploadThingError with code MISSING_ENV (token not set), INVALID_SERVER_CONFIG (malformed config), FORBIDDEN (invalid API key — HTTP 403), INTERNAL_SERVER_ERROR (UploadThing API failure — HTTP 500). Source: requestUploadThing catches ConfigError → INVALID_SERVER_CONFIG; executeAsync throws on HTTP non-2xx response.Required handlingCaller MUST wrap utapi.renameFiles() in try/catch. An uncaught error means the rename did not occur. File retains the original name in storage. try { const { success } = await utapi.renameFiles([ { fileKey: existingKey, newName: 'profile-photo.jpg' } ]); if (!success) { console.warn('Rename reported as unsuccessful'); } } catch (error) { if (error instanceof UploadThingError) { console.error('Rename failed:', error.code, error.message); } throw error; }costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - UTApi.updateACL · update-acl-no-try-catcherrorWhenutapi.updateACL() is called without try/catch. Authentication failures (invalid/missing UPLOADTHING_TOKEN), server errors, and network failures throw UploadThingError. Also throws if the app does not have per-file ACL enabled in UploadThing settings.Throws
UploadThingError with code MISSING_ENV (token not set), INVALID_SERVER_CONFIG (malformed config), FORBIDDEN (API key invalid OR per-file ACL not enabled for the app — HTTP 403), INTERNAL_SERVER_ERROR (HTTP 500). Source: requestUploadThing catches ConfigError; executeAsync throws on failure.Required handlingCaller MUST wrap utapi.updateACL() in try/catch. An uncaught error means the ACL change did not apply — files remain at their previous access level. If updating from public to private, uncaught failure leaves files publicly accessible when they should be private. try { await utapi.updateACL(fileKeys, 'private'); } catch (error) { if (error instanceof UploadThingError && error.code === 'FORBIDDEN') { // Per-file ACL may not be enabled in your UploadThing account throw new Error('Cannot update ACL: check UploadThing app settings'); } throw error; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - UTApi.listFiles · list-files-no-try-catchwarningWhenutapi.listFiles() is called without try/catch. Authentication failures, network failures, and server errors throw UploadThingError. Unlike uploadFiles, there is no per-item error structure — the entire call either succeeds or throws.Throws
UploadThingError with code MISSING_ENV (token not set), INVALID_SERVER_CONFIG (malformed config), FORBIDDEN (invalid API key — HTTP 403), INTERNAL_SERVER_ERROR (UploadThing API failure — HTTP 500). Source: requestUploadThing catches ConfigError; executeAsync throws on failure.Required handlingCaller MUST wrap utapi.listFiles() in try/catch. In admin panels or migration scripts, an uncaught error halts the process and may leave a partial list. try { const { files, hasMore } = await utapi.listFiles({ limit: 100 }); // Process files... if (hasMore) { // Fetch next page with offset } } catch (error) { if (error instanceof UploadThingError) { console.error('Failed to list files:', error.code, error.message); } throw error; }costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - UTApi.getUsageInfo · get-usage-info-no-try-catchwarningWhenutapi.getUsageInfo() is called without try/catch. Authentication failures, network failures, and server errors throw UploadThingError. A storage quota check that throws will prevent the caller from knowing whether uploads are allowed — may cause silent over-quota uploads.Throws
UploadThingError with code MISSING_ENV (token not set), INVALID_SERVER_CONFIG (malformed config), FORBIDDEN (invalid API key — HTTP 403), INTERNAL_SERVER_ERROR (UploadThing API failure — HTTP 500). Source: requestUploadThing catches ConfigError; executeAsync throws on failure.Required handlingCaller MUST wrap utapi.getUsageInfo() in try/catch. In quota-enforcement flows, an uncaught error prevents quota validation — uploads may proceed past quota limits silently. try { const { filesUploaded, limitBytes, appTotalBytes } = await utapi.getUsageInfo(); const usagePercent = (appTotalBytes / limitBytes) * 100; if (usagePercent > 90) { console.warn(`Storage at ${usagePercent.toFixed(1)}% capacity`); } } catch (error) { if (error instanceof UploadThingError) { // Don't block uploads on monitoring failure — log and continue console.error('Failed to get usage info:', error.message); return null; } throw error; }costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]docs.uploadthing.com/api-reference/ut-apihttps://docs.uploadthing.com/api-reference/ut-api#uploadfiles
- [2]docs.uploadthing.com/errorshttps://docs.uploadthing.com/errors
- [3]github.com/pingdotgg/uploadthinghttps://github.com/pingdotgg/uploadthing/blob/main/packages/uploadthing/src/sdk/index.ts
- [4]docs.uploadthing.com/api-reference/ut-apihttps://docs.uploadthing.com/api-reference/ut-api#uploadfilesfromurl
- [5]docs.uploadthing.com/api-reference/ut-apihttps://docs.uploadthing.com/api-reference/ut-api
- [6]docs.uploadthing.com/api-reference/ut-apihttps://docs.uploadthing.com/api-reference/ut-api#generatesignedurl
Need a different package?
Request a profile