{"record":{"id":"d0c88ef6ea944e40","repo":"RocketChat/Rocket.Chat","slug":"emoji-is-not-image","errorCode":"emoji-is-not-image","errorMessage":"Emoji file provided cannot be uploaded since it's not an image","messagePattern":"Emoji file provided cannot be uploaded since it's not an image","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/api/v1/emoji-custom.ts","lineNumber":202,"sourceCode":"\t\t\t\t401: validateUnauthorizedErrorResponse,\n\t\t\t},\n\t\t},\n\t\tasync function action() {\n\t\t\tconst emoji = await getUploadFormData(\n\t\t\t\t{\n\t\t\t\t\trequest: this.request,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfield: 'emoji',\n\t\t\t\t\tsizeLimit: settings.get('FileUpload_MaxFileSize'),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst { fields, fileBuffer, mimetype } = emoji;\n\n\t\t\tconst isUploadable = await Media.isImage(fileBuffer);\n\t\t\tif (!isUploadable) {\n\t\t\t\tthrow new Meteor.Error('emoji-is-not-image', \"Emoji file provided cannot be uploaded since it's not an image\");\n\t\t\t}\n\n\t\t\tconst [, extension] = mimetype.split('/');\n\t\t\tfields.extension = extension;\n\n\t\t\tconst emojiData = await insertOrUpdateEmoji(this.userId, {\n\t\t\t\t...fields,\n\t\t\t\tnewFile: true,\n\t\t\t\taliases: fields.aliases || '',\n\t\t\t\tname: fields.name,\n\t\t\t\textension: fields.extension,\n\t\t\t});\n\n\t\t\tawait uploadEmojiCustomWithBuffer(this.userId, fileBuffer, mimetype, emojiData);\n\n\t\t\treturn API.v1.success();\n\t\t},\n\t)","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/emoji-custom.ts#L184-L220","documentation":"Thrown by POST emoji-custom.create when the uploaded file's content is not recognized as an image. Rocket.Chat runs Media.isImage(fileBuffer), which sniffs the buffer's magic bytes (file-type style detection) rather than trusting the declared mimetype or file extension, so renaming a non-image to .png will not pass. This is a content-type safety check: custom emojis are served to all clients as images.","triggerScenarios":"POST /api/v1/emoji-custom.create with multipart field 'emoji' containing a PDF, ZIP, text file, or SVG (SVG is typically not detected as a binary image type by magic-byte sniffing and gets rejected). Also a truncated/corrupted image whose header bytes are damaged, or an empty buffer with a fake image mimetype.","commonSituations":"Design teams exporting emoji assets as SVG and uploading directly; automated import scripts feeding whatever file sits in a directory; files that lost bytes in transit (wrong multipart sizeLimit handling); users renaming files to bypass extension checks.","solutions":["Upload a real raster image (PNG, JPG/JPEG, GIF) as the 'emoji' multipart field - convert SVG to PNG first","Verify the file locally before uploading by sniffing its content, not its name (e.g. `file emoji.png` or the file-type npm package)","If the file should be valid, re-export it from the source tool - a truncated download or 0-byte file also fails the sniff","Check you are reading the file from disk correctly in the client (fs.readFileSync vs accidentally passing a path string or JSON body)"],"exampleFix":"// before\nconst form = new FormData();\nform.append('emoji', fs.createReadStream('logo.svg'), 'logo.svg'); // SVG rejected by content sniffing\n\n// after\nconst form = new FormData();\nform.append('emoji', fs.createReadStream('logo.png'), 'logo.png');","handlingStrategy":"validation","validationCode":"import { fromBuffer } from 'file-type';\nasync function assertUploadableImage(buffer) {\n  const type = await fromBuffer(buffer);\n  if (!type || !/^image\\/(png|jpeg|gif)$/.test(type.mime)) {\n    throw new Error(`Not an uploadable image (detected: ${type?.mime ?? 'unknown'})`);\n  }\n}","typeGuard":"const isImageBuffer = async (buf: Buffer): Promise<boolean> => {\n  const t = await fromBuffer(buf);\n  return !!t && ['image/png', 'image/jpeg', 'image/gif'].includes(t.mime);\n};","tryCatchPattern":null,"preventionTips":["Sniff magic bytes locally with the file-type package before uploading - never trust extensions","Convert design assets (SVG) to PNG in your build step, not at upload time","In bulk importers, pre-filter the directory and log skipped non-image files instead of failing the whole run"],"tags":["rest-api","emoji-custom","file-upload","image-validation"],"backgroundTag":"invalid-file-type","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}