RocketChat/Rocket.Chat · error · Meteor.Error

emoji-is-not-image

emoji-is-not-image

Error message

Emoji file provided cannot be uploaded since it's not an image

What it means

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.

Solutions

  1. Upload a real raster image (PNG, JPG/JPEG, GIF) as the 'emoji' multipart field - convert SVG to PNG first
  2. Verify the file locally before uploading by sniffing its content, not its name (e.g. `file emoji.png` or the file-type npm package)
  3. If the file should be valid, re-export it from the source tool - a truncated download or 0-byte file also fails the sniff
  4. Check you are reading the file from disk correctly in the client (fs.readFileSync vs accidentally passing a path string or JSON body)

Example fix

// before
const form = new FormData();
form.append('emoji', fs.createReadStream('logo.svg'), 'logo.svg'); // SVG rejected by content sniffing

// after
const form = new FormData();
form.append('emoji', fs.createReadStream('logo.png'), 'logo.png');
Defensive patterns

Strategy: validation

Validate before calling

import { fromBuffer } from 'file-type';
async function assertUploadableImage(buffer) {
  const type = await fromBuffer(buffer);
  if (!type || !/^image\/(png|jpeg|gif)$/.test(type.mime)) {
    throw new Error(`Not an uploadable image (detected: ${type?.mime ?? 'unknown'})`);
  }
}

Type guard

const isImageBuffer = async (buf: Buffer): Promise<boolean> => {
  const t = await fromBuffer(buf);
  return !!t && ['image/png', 'image/jpeg', 'image/gif'].includes(t.mime);
};

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/d0c88ef6ea944e40. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/server/api/v1/emoji-custom.ts:202

				401: validateUnauthorizedErrorResponse,
			},
		},
		async function action() {
			const emoji = await getUploadFormData(
				{
					request: this.request,
				},
				{
					field: 'emoji',
					sizeLimit: settings.get('FileUpload_MaxFileSize'),
				},
			);

			const { fields, fileBuffer, mimetype } = emoji;

			const isUploadable = await Media.isImage(fileBuffer);
			if (!isUploadable) {
				throw new Meteor.Error('emoji-is-not-image', "Emoji file provided cannot be uploaded since it's not an image");
			}

			const [, extension] = mimetype.split('/');
			fields.extension = extension;

			const emojiData = await insertOrUpdateEmoji(this.userId, {
				...fields,
				newFile: true,
				aliases: fields.aliases || '',
				name: fields.name,
				extension: fields.extension,
			});

			await uploadEmojiCustomWithBuffer(this.userId, fileBuffer, mimetype, emojiData);

			return API.v1.success();
		},
	)

View on GitHub (pinned to b2c16d5842)