Wei-Shaw/sub2api · warning
profile.avatar.invalidType
Error message
profile.avatar.invalidType
What it means
In frontend/src/components/user/profile/ProfileAvatarCard.vue:193, prepareAvatarUpload() throws the localized 'profile.avatar.invalidType' when file.type does not start with 'image/'. It is the first validation gate: only image MIME types are accepted for avatars. file.type comes from the OS/browser MIME sniffing of the file picker selection.
Source
Thrown at frontend/src/components/user/profile/ProfileAvatarCard.vue:193
canvas.height = height
ctx.clearRect(0, 0, width, height)
ctx.drawImage(image, 0, 0, width, height)
for (const quality of avatarQualitySteps) {
const blob = await canvasToBlob(canvas, 'image/webp', quality)
if (blob.size <= targetAvatarUploadBytes) {
const fileName = file.name.replace(/\.[^.]+$/, '') || 'avatar'
return new File([blob], `${fileName}.webp`, { type: 'image/webp' })
}
}
}
throw new Error(t('profile.avatar.compressTooLarge'))
}
async function prepareAvatarUpload(file: File): Promise<File> {
if (!file.type.startsWith('image/')) {
throw new Error(t('profile.avatar.invalidType'))
}
if (file.type === 'image/gif') {
if (file.size > targetAvatarUploadBytes) {
throw new Error(t('profile.avatar.gifTooLarge'))
}
return file
}
if (file.size <= targetAvatarUploadBytes) {
return file
}
return compressAvatarFile(file)
}
async function handleAvatarFileChange(event: Event) {
const input = event.target as HTMLInputElement | null
const file = input?.files?.[0]
if (input) {
input.value = ''View on GitHub (pinned to 073e92d171)
Solutions
- Add accept="image/*" to the file input so the picker filters non-images upfront.
- Also treat an empty file.type as invalid (or sniff magic bytes) since empty types slip past startsWith.
- Show the localized invalidType message next to the picker so the user immediately understands.
- Keep server-side MIME/extension validation as the authoritative check.
Example fix
// before
if (!file.type.startsWith('image/')) {
throw new Error(t('profile.avatar.invalidType'))
}
// after
if (!file.type.startsWith('image/')) {
throw new Error(t('profile.avatar.invalidType'))
}
// template: <input type="file" accept="image/gif,image/jpeg,image/png,image/webp" @change="handleAvatarFileChange"> Defensive patterns
Strategy: validation
Validate before calling
export function isUploadableImage(file: File): boolean {
return file.type.startsWith('image/') && file.type !== '';
}
// in the change handler:
if (!isUploadableImage(file)) { showInvalidTypeError(); return; } Type guard
function isImageFile(file: File): file is File & { type: `image/${string}` } {
return file.type.startsWith('image/');
} Prevention
- Set accept="image/*" (or an explicit list) on the file input
- Treat empty MIME type as invalid, since sniffing can return blank strings
- Keep server-side MIME validation as the authoritative check
When it happens
Trigger: User selects a non-image file (PDF, zip, video) through the avatar file input; a file with an empty or application/* MIME type (common for extensionless files or files from cloud drives); drag-drop of a file whose type the browser reports as blank.
Common situations: File inputs with accept filters not applied (or accept attribute missing so any file is selectable); Android content-provider URIs sometimes report empty MIME types; users renaming files to .jpg without real image content still pass this check but fail server-side — pair with server validation.
Related errors
- profile.avatar.gifTooLarge
- profile.avatar.compressFailed
- profile.avatar.compressTooLarge
- Passkeys are not supported by this browser
- Passkey sign-in was cancelled
AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15).
Data as JSON: /api/errors/295ebe58948456f6.
Report an issue: GitHub.