Since UGC Emotes doesnt really have much of a moderation system yet (example text) (and I doubt there will be much in the future) here is an Emote remover which uses a whitelist system based on CreatorTargetId so you can whitelist certain creators
Services that are referenced (for weakref anticheats) : AvatarEditorService & Players
Some notes
This is handled on the server to minimize stress upon roblox apis
This currently only (correctly) supports User made content (not groups) but if someone REALLY needs it to have group made content I'll add support for it
This isnt super microoptimized partly because I want it to be configurable / forkable for people and partly because I am not about to overengineer this at 1am in the morning
There is a much better way to do this
get the remotes and check if the client cached blacklisted ids (from the server)
a Remote system where the client invokes an array of the equipped ids (max being 8) (only if client hasnt been cached)
server then caches that the character has been checked then the server verifies the ids (set an attribute on the HumanoidDescription or cache the instance or smthing because no GetDebugId / UniqueId :C )
the server sends the client the blacklisted ids (only the ids the client sent if any)
client then removes emotes with blacklisted ids
--!optimize 2
--!native
type Emote = {
Name : string,
IsAllowed : boolean?
}
type Emotes = {
[number] : Emote
}
local AllowedCreators = table.freeze({ -- really shouldnt be edited after start
1
})
local function IsntEmpty(tbl : {any}) -- sake of readablity
return next(tbl) ~= nil
end
local function HandleEmotes(HumanoidDescription : HumanoidDescription, Emotes : Emotes)
for Id : number, Emote : Emote in Emotes do
if not Emote.IsAllowed then
HumanoidDescription:RemoveEmote(Emote.Name)
--warn("Removed Emote:", Emote.Name, `({Id})`)
end
end
end
-- ^^^ configs ^^^
local AvatarEditorService = game:GetService("AvatarEditorService")
local Players = game:GetService("Players")
local AllowedCache = {}
local function IsCreatorAllowed(CreatorId : number)
return table.find(AllowedCreators, CreatorId) ~= nil
end
local function GetBatchItemDetails(AssetIds : {number})
return pcall(AvatarEditorService.GetBatchItemDetails, AvatarEditorService, AssetIds, Enum.AvatarItemType.Asset) -- as of 8/7/2025 it does not cache
end
local function FormatEmotes(Emotes : {[string] : {number}}) -- makes it assetid : emotename
local FormattedEmotes = {}
for i,v in Emotes do
local Id = v[1]
FormattedEmotes[Id] = {
Name = i,
IsAllowed = AllowedCache[Id]
}
end
return FormattedEmotes
end
local function CacheUnknownAssets(Emotes : Emotes) -- not handling it in here for the sake of others that want to fork
local Assets = {}
for i,v in Emotes do
if v.IsAllowed == nil then
table.insert(Assets, i)
end
end
if IsntEmpty(Assets) then
local suc, err = GetBatchItemDetails(Assets)
if suc then
for i,v in err do
local AssetId = v.Id
local IsAllowed = IsCreatorAllowed(v.CreatorTargetId)
AllowedCache[AssetId] = IsAllowed
Emotes[AssetId].IsAllowed = IsAllowed
end
else
for i, v in Assets do
Emotes[v].IsAllowed = true -- if the api request fails might as well just let them use the emote
end
end
end
end
local function HandleHumanoidDescription(HumanoidDescription : HumanoidDescription)
local Emotes = HumanoidDescription:GetEmotes()
local FormattedEmotes = if IsntEmpty(Emotes) then FormatEmotes(Emotes) else nil
if FormattedEmotes then
CacheUnknownAssets(FormattedEmotes)
HandleEmotes(HumanoidDescription, FormattedEmotes)
end
end
local function RegisterCharacter(Character : Model)
local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")
local HumanoidDescription = if Humanoid then Humanoid:FindFirstChildWhichIsA("HumanoidDescription") else nil
if HumanoidDescription then
HandleHumanoidDescription(HumanoidDescription)
end
end
local function RegisterPlayer(plr : Player)
plr.CharacterAdded:Connect(RegisterCharacter)
if plr.Character then
RegisterCharacter(plr.Character)
end
end
Players.PlayerAdded:Connect(RegisterPlayer)
for i,v in Players:GetPlayers() do
RegisterPlayer(v)
end
hope this helps