RocketChat/Rocket.Chat · error · Meteor.Error

Custom_Emoji_Error_Invalid_Emoji

Custom_Emoji_Error_Invalid_Emoji

Error message

Invalid emoji

What it means

Thrown by deleteEmojiCustom when EmojiCustom.findOneById(emojiID) returns null — the id does not match any custom emoji. The permission check has already passed at this point, so this error is purely a bad-record-id problem.

Source

Thrown at apps/meteor/server/meteor-methods/media/deleteEmojiCustom.ts:25

import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
import { RocketChatFileEmojiCustomInstance } from '../../lib/media/emoji-custom/startup/emoji-custom';

declare module '@rocket.chat/ddp-client' {
	// eslint-disable-next-line @typescript-eslint/naming-convention
	interface ServerMethods {
		deleteEmojiCustom(emojiID: ICustomEmojiDescriptor['_id']): boolean;
	}
}

export const deleteEmojiCustom = async (userId: string, emojiID: ICustomEmojiDescriptor['_id']): Promise<boolean> => {
	if (!(await hasPermissionAsync(userId, 'manage-emoji'))) {
		throw new Meteor.Error('not_authorized');
	}

	const emoji = await EmojiCustom.findOneById(emojiID);
	if (emoji == null) {
		throw new Meteor.Error('Custom_Emoji_Error_Invalid_Emoji', 'Invalid emoji', {
			method: 'deleteEmojiCustom',
		});
	}

	await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emoji.name}.${emoji.extension}`));
	await EmojiCustom.removeById(emojiID);
	void api.broadcast('emoji.deleteCustom', emoji);

	return true;
};

Meteor.methods<ServerMethods>({
	async deleteEmojiCustom(emojiID) {
		methodDeprecationLogger.method('deleteEmojiCustom', '9.0.0', '/v1/emoji-custom.delete');
		if (!this.userId) {
			throw new Meteor.Error('not_authorized');
		}

View on GitHub (pinned to b2c16d5842)

Solutions

  1. List current emojis (GET /v1/emoji-custom.list) and copy the exact _id
  2. Refresh the emoji picker/admin list and retry with a live id
  3. Treat a repeat failure as 'already deleted' and reconcile your local state

Example fix

// before
await Meteor.callAsync('deleteEmojiCustom', 'wrong-or-stale-id');

// after
const res = await fetch('/api/v1/emoji-custom.list', { headers });
const { emojis } = await res.json();
const emoji = emojis.find((e) => e._id === emojiId);
if (!emoji) throw new Error('Unknown emoji id');
await Meteor.callAsync('deleteEmojiCustom', emoji._id);
Defensive patterns

Strategy: validation

Validate before calling

const res = await fetch('/api/v1/emoji-custom.list', { headers });
const { emojis } = await res.json();
if (!emojis.some((e) => e._id === emojiId)) {
  throw new Error('Unknown emoji id');
}

Type guard

const isKnownEmojiId = (emojis: { _id: string }[], id: string): boolean =>
  emojis.some((e) => e._id === id);

Try / catch

try {
  await Meteor.callAsync('deleteEmojiCustom', emojiId);
} catch (err) {
  if (err instanceof Meteor.Error && err.error === 'Custom_Emoji_Error_Invalid_Emoji') {
    // id is wrong or already deleted: refresh the emoji list, do not retry
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Deleting with a mistyped, truncated, or already-removed emoji id; UI holding a stale emoji list after another admin deleted the same emoji; ids copied from a different workspace.

Common situations: Double-submit of a delete form; concurrent emoji management; migration scripts carrying old emoji ids.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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