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
- List current emojis (GET /v1/emoji-custom.list) and copy the exact _id
- Refresh the emoji picker/admin list and retry with a live id
- 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
- Delete emojis using ids from a fresh emoji-custom.list call
- Guard double-submits in the UI to avoid racing deletes
- Treat repeat hits as already-deleted and reconcile state
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
- error-application-not-found
- error-operation-not-found
- error-operation-not-found
- error-invalid-integration
- invalid_integration
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/99f1e75f805e207b.
Report an issue: GitHub.