Custom Emoji Transport Spec
Custom emojis are a beloved feature of virtually every messaging platform. But they are all built bespoke per platform with no interoperability. Unicode is adding support for new emojis, but at a glacial pace, and the obscurity and uniqueness of custom emojis is a feature, not a bug. This spec outlines a cross-platform custom emoji protocol, and a plan for rolling it out.
Demo
This uses a javascript implementation of the protocol, and reflects how a vendor would update their text rendering to support these custom emojis:
Supported renderer:
�� this is cool! ��
Same text in an unsupported renderer:
�� this is cool! ��
Motivation
Language is the human-to-human information exchange protocol. Emojis are a significant advance in the compression of that protocol -- famously, a picture is worth a thousand words. The value here is self evident: everyone you know uses emojis, you use emojis, younger generations use far more emojis than yours, etc...
A more compressed language format begets an increase in bandwidth. It brings people closer together.
Spec Outline
Ok now the nerd stuff
You can play around with the protocol here
-
A custom emoji gets encoded as a pipe-delimited string:
vendor_id|emoji_id
For example:slackmojis|1643514525/5197/party_blob.gif
- For portability and fallback rendering in unsupported apps, we use nonprinting unicode characters to encode the string. Each character is converted to its Unicode codepoint, which we write in base-8 (there are 8ish zero-width unicode characters that reliably survive copy paste and do not render on any platform)
-
Each base-8 digit is then mapped to a nonprinting unicode character
using this map:
and delimited by U+206A - INHIBIT SYMMETRIC SWAPPING.Digit Unicode Name Rendered 0 U+200B ZERO WIDTH SPACE 1 U+200C ZERO WIDTH NON-JOINER 2 U+200D ZERO WIDTH JOINER 3 U+2060 WORD JOINER 4 U+2061 FUNCTION APPLICATION 5 U+2062 INVISIBLE TIMES 6 U+2063 INVISIBLE SEPARATOR 7 U+2064 INVISIBLE PLUS - Finally, we add a � at the beginning and end of the string, so noncompliant renderers still show an indication that something is supposed to be there.
-
Security considerations:
We use the pipe-delimited vendor_id|emoji_id format instead of a raw URL to enable app vendors to allowlist emoji hosting services explicitly. This prevents bad actors from using custom emojis as an attack vector -- app vendors will not need to blindly trust any URL, but rather just the URLs of known/trusted emoji hosting services. We could host some kind of DNS-like vendor lookup service, or app vendors could just map in code (perhaps via a public library).
Adoption Strategy
This protocol requires app vendors to implement it, which is a big lift. I think we would have to 1. build an app that allows users to upload and type these 2. implement the protocol ourselves in opensource apps 3. try to convince some apps with bigger userbases and eventually 4. get adopted as a W3C standard.
Initial targets are open-source platforms like Mattermost and Brave.
Platforms like Discord and Twitch already support custom emojis, but aren’t standardized. If we could get one or two adopters here, W3C might be persuadable.
Concerns
-
This whole steganography business inflates the length of the
string dramatically, it might take hundreds of characters to
represent a single emoji. Generally bandwidth and storage are not
really a problem in 2025 (right??) but platforms like twitter
(which impose low character limits) will not like this.
We do this to ensure noncompliant renderers won't render some ugly raw string, but perhaps it would be better for the fallback case to just show some inscrutable compressed string rather an invisible long one? - Will there ever actually be an ecosystem of custom emoji vendors? Or should I just build the one to rule them all? The vendor_id|emoji_id format could be simplified if we weren't planning for a future where multiple custom emoji vendors could coexist. We could also go the other way and just use the raw jpg/gif URL, and rely on platforms to impose any security restrictions themselves. This is obviously the most flexible but also feels like the most work for vendors, which I don't like.
This is a spec — not a finished thing
If you have thoughts, objections, better ideas, security concerns, or want to help push this toward something real, I’d love to hear it.