DEV Community

Cover image for How to hide a secret message inside a single emoji (invisible Unicode, explained)
Emoji Hide
Emoji Hide

Posted on

How to hide a secret message inside a single emoji (invisible Unicode, explained)

How to hide a secret message inside a single emoji (invisible Unicode, explained)

Copy this emoji: 🦊

Looks empty, right? There might be an entire sentence hiding inside it β€” and your browser, your chat app, and your clipboard would never tell you. Here's how the trick works, and how I built a tool around it: Emoji Hide.

Unicode has characters you can't see

Most people think of Unicode as "the thing that gives us emoji." But scattered through the standard are code points with no visible glyph at all:

  • Zero-width characters β€” U+200B (zero width space), U+200C, U+200D (zero width joiner β€” the one that glues πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ together), U+FEFF.
  • Variation selectors β€” U+FE00–U+FE0F, sixteen invisible code points. You've already used one today: U+FE0F is what turns ❀ into ❀️.
  • Tag characters β€” U+E0000–U+E007F, a legacy block originally meant for invisible language tags.

They're invisible, but they're data. They survive copy-paste. They count toward string length. And crucially, most renderers just… skip over them.

The encoding trick

Sixteen variation selectors is a suspiciously convenient number β€” and Unicode has far more than sixteen to work with. Here's the actual scheme running on emojihide.com:

  1. Your message (encrypted first, if you set a password) is wrapped in a small versioned JSON envelope, then turned into UTF-8 bytes.
  2. Each byte maps to exactly one invisible variation selector. Byte values 0–15 use U+FE00–U+FE0F (the classic sixteen); 16–255 use U+E0100–U+E01EF from the Variation Selectors Supplement block. 256 byte values β†’ 256 selectors β€” one byte costs one invisible character.
  3. The invisible run is bracketed by two more invisible characters: U+2063 marks where the hidden data starts and U+2064 marks the end, so the decoder knows exactly where the payload lives. Then it's attached to your carrier emoji.
// the actual scheme: 1 byte -> 1 invisible code point, bracketed by sentinels
function byteToInvisible(byte) {
  return String.fromCodePoint(byte < 16 ? 0xFE00 + byte : 0xE0100 + byte - 16);
}

const START = String.fromCodePoint(0x2063);
const END = String.fromCodePoint(0x2064);

function encode(bytes) {
  return START + [...bytes].map(byteToInvisible).join('') + END;
}
Enter fullscreen mode Exit fullscreen mode

Decoding is the mirror: strip the visible emoji, find the sentinels, read the selectors back into bytes, unwrap the JSON envelope. That's the whole magic trick β€” everything else is UX.

Hiding is not encrypting

This is the part most "secret message" toys get wrong. Invisible text is obfuscation: anyone who knows the scheme can decode it. Real privacy needs a second, independent layer.

So the pipeline in Emoji Hide is: message β†’ encrypt with password (authenticated encryption, in the browser) β†’ encode ciphertext as invisible code points β†’ attach to emoji. An attacker who extracts the invisible run gets ciphertext, not your message. And because everything runs client-side, there's no server that ever sees the plaintext β€” nothing to breach, subpoena, or leak.

Why hidden messages sometimes vanish

The honest caveat, and the most interesting engineering detail: invisible characters are at the mercy of every platform your emoji passes through. Some chat apps and social networks normalize, strip, or mangle zero-width and special code points in transit. A message that decodes perfectly from a clipboard paste can arrive broken after a trip through certain apps.

That's not a bug in the idea β€” it's the terrain. If you're building with invisible Unicode, test your target platforms and treat "survives the pipeline" as a feature to verify, not assume.

Try it

Write a message at emojihide.com, lock it with a password, and you get one ordinary-looking emoji. Send it anywhere. Whoever has the password can pull your message back out; everyone else just sees 🦊.

One emoji. One hidden message. Zero servers involved.

Top comments (0)