Skip to content
18 changes: 12 additions & 6 deletions apps/sim/app/api/files/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
SUPPORTED_AUDIO_EXTENSIONS,
SUPPORTED_CODE_EXTENSIONS,
SUPPORTED_DOCUMENT_EXTENSIONS,
SUPPORTED_IMAGE_EXTENSIONS,
SUPPORTED_VIDEO_EXTENSIONS,
validateFileType,
} from '@/lib/uploads/utils/validation'
Expand All @@ -28,12 +29,10 @@ import {
InvalidRequestError,
} from '@/app/api/files/utils'

const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'] as const

const ALLOWED_EXTENSIONS = new Set<string>([
...SUPPORTED_DOCUMENT_EXTENSIONS,
...SUPPORTED_CODE_EXTENSIONS,
...IMAGE_EXTENSIONS,
...SUPPORTED_IMAGE_EXTENSIONS,
Comment thread
waleedlatif1 marked this conversation as resolved.
...SUPPORTED_AUDIO_EXTENSIONS,
...SUPPORTED_VIDEO_EXTENSIONS,
])
Expand Down Expand Up @@ -305,10 +304,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
context === 'profile-pictures' ||
context === 'workspace-logos'
) {
if (context !== 'copilot' && !isImageFileType(file.type)) {
throw new InvalidRequestError(
`Only image files (JPEG, PNG, GIF, WebP, SVG) are allowed for ${context} uploads`
if (context !== 'copilot') {
const mimeType = file.type
const isGenericMime = !mimeType || mimeType === 'application/octet-stream'
const extension = originalName.split('.').pop()?.toLowerCase() ?? ''
const extensionIsImage = (SUPPORTED_IMAGE_EXTENSIONS as readonly string[]).includes(
extension
)
const isImage = isGenericMime ? extensionIsImage : isImageFileType(mimeType)
if (!isImage) {
throw new InvalidRequestError(`Only image files are allowed for ${context} uploads`)
}
Comment thread
waleedlatif1 marked this conversation as resolved.
}

if (context === 'workspace-logos') {
Expand Down
25 changes: 25 additions & 0 deletions apps/sim/lib/uploads/utils/file-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export const MIME_TYPE_MAPPING: Record<string, 'image' | 'document' | 'audio' |
'image/gif': 'image',
'image/webp': 'image',
'image/svg+xml': 'image', // SVG upload is allowed; createFileContent handles it separately for Claude API
'image/bmp': 'image',
'image/tiff': 'image',
'image/heic': 'image',
'image/heif': 'image',
'image/avif': 'image',
'image/x-icon': 'image',
'image/vnd.microsoft.icon': 'image',
Comment thread
waleedlatif1 marked this conversation as resolved.
Comment thread
waleedlatif1 marked this conversation as resolved.

// Documents
'application/pdf': 'document',
Expand Down Expand Up @@ -158,6 +165,10 @@ export function createFileContent(fileBuffer: Buffer, mimeType: string): Message
return null
}

if (contentType === 'image' && !CLAUDE_SUPPORTED_IMAGE_MIME_TYPES.has(mimeType.toLowerCase())) {
return null
}

return {
type: contentType,
source: {
Expand All @@ -168,6 +179,13 @@ export function createFileContent(fileBuffer: Buffer, mimeType: string): Message
}
}

const CLAUDE_SUPPORTED_IMAGE_MIME_TYPES = new Set([
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
])
Comment thread
waleedlatif1 marked this conversation as resolved.

/**
* Extract file extension from filename
*/
Expand All @@ -184,6 +202,13 @@ const EXTENSION_TO_MIME: Record<string, string> = {
gif: 'image/gif',
webp: 'image/webp',
svg: 'image/svg+xml',
bmp: 'image/bmp',
tif: 'image/tiff',
tiff: 'image/tiff',
heic: 'image/heic',
heif: 'image/heif',
avif: 'image/avif',
ico: 'image/x-icon',
Comment thread
waleedlatif1 marked this conversation as resolved.

// Documents
pdf: 'application/pdf',
Expand Down
Loading