Emoji

Emoji

Last updated 2026-11-02

  • emoji
  • code point
  • bytes
  • utf8mb4
  • length
  • grapheme cluster
  • rendering
  • ZWJ
On this page

There is one thing to get right about emoji: they are characters, not images. So they are governed by encoding, length, charset and font rules — and those rules count "one emoji" differently.

A character, not a picture

😀 is a code point (U+1F600) stored in the text like any other character. The coloured face you see is drawn by a font.

Consequences, all of which matter:

  • the same text looks different on Apple, Google and Microsoft systems — three different emoji fonts, three designs;
  • a screenshot or a comp with an emoji in it will not reproduce elsewhere;
  • a font may carry no emoji glyphs at all, in which case the browser falls back to the system emoji font (see Shaping and fallback);
  • colour emoji are implemented with bitmap or vector tables (CBDT, sbix, COLR…), but to your code they are still one character.

Three kinds of "length"

The same string gives three different numbers:

Question How "😀" "👨‍👩‍👧"
UTF-16 code units JavaScript .length, HTML maxlength 2 8
code points [...str].length, Array.from(str).length 1 5
grapheme clusters (what looks like one character) Intl.Segmenter 1 1

The family emoji is man + zero-width joiner + woman + zero-width joiner + girl: five code points, two of which take no width themselves but decide whether the neighbours merge.

'😀'.length                                    // 2   ← code units
'😀'.codePointAt(0)                            // 128512
[...'😀'].length                               // 1   ← code points
[...new Intl.Segmenter().segment('👨‍👩‍👧')].length  // 1   ← grapheme clusters

Before building a character counter (remaining characters, nickname limits), decide which one you mean. Most products want grapheme clusters, while the implementation usually uses .length.

Bytes

In UTF-8 an emoji is usually 4 bytes (everything outside the BMP takes 4):

Text Code points UTF-16 units UTF-8 bytes
A 1 1 1
中 1 1 3
😀 1 2 4
👨‍👩‍👧 5 8 18
new TextEncoder().encode('😀').length   // 4
Buffer.byteLength('😀', 'utf8')         // 4 (Node)

"Counted in bytes" and "counted in characters" can differ by 4× — most database surprises come from exactly this.

Lengths in a database

Two kinds of problem look alike and have different causes.

One: the charset cannot hold the character. MySQL's utf8 is really utf8mb3 (max 3 bytes per character), so a 4-byte emoji fails outright:

Incorrect string value: '\xF0\x9F\x98\x80' for column 'name' at row 1

That is not "the column is too short" — it is a 3-byte charset. The fix: use utf8mb4 for the database, tables, columns and connection.

Two: the length is counted differently.

  • VARCHAR(n) counts characters in MySQL (not bytes); PostgreSQL also counts characters; SQLite barely limits;
  • but "character" there means code point, not grapheme cluster — a family emoji counts as 5;
  • LENGTH() returns bytes, CHAR_LENGTH() returns characters (in Postgres, OCTET_LENGTH() and LENGTH()):
    SELECT LENGTH('😀'), CHAR_LENGTH('😀');   -- 4, 1
    Using LENGTH() for business rules blows up immediately on Chinese and emoji;
  • index prefixes have byte limits: 767 bytes in the old (COMPACT) row format; with 4 bytes per utf8mb4 character that is where VARCHAR(191) came from. DYNAMIC allows 3072 bytes (about VARCHAR(768)).

Front end versus back end

The same sentence can be measured three ways:

Layer Counts "😀"
HTML maxlength / JS .length UTF-16 code units 2
Database VARCHAR(n) code points 1
Validation regex . without the u flag code units 2

Which produces "the form says one character left, the server says it is too long". The usual approach:

  • count with [...value].length or Intl.Segmenter on the client, not .length;
  • validate by characters (CHAR_LENGTH) on the server;
  • normalize('NFC') before storing (see Character properties);
  • never truncate by bytes — that cuts a surrogate pair in half and yields mojibake.

Rendering: still not an image

Because emoji are characters, how they look is decided by font plus system:

  • Apple Color Emoji, Segoe UI Emoji and Noto Color Emoji are three designs; the same code point is drawn differently;
  • some characters have two presentations (text and emoji), chosen by variation selectors: U+FE0F for emoji, U+FE0E for text;
  • when a system or font lags behind Unicode, newer emoji show as tofu.

So "the emoji in the comp" may become a different shape elsewhere. Not a bug — normal behaviour for a character.

What you can see here

  • the character drawer shows any character's code point and the escape spellings in each language (astral characters like \uD83D\uDE00 are easy to get wrong);
  • coverage usually shows nothing for emoji: ordinary Latin or Chinese fonts carry no emoji glyphs, and the emoji you see on the page come from the system emoji font;
  • measuring an emoji in the metrics panel tells you plainly when the font has no glyph for it.

Further reading

  • The Unicode Standard: the emoji chapters and emoji-data.txt
  • UTS #51 (Unicode Emoji)
  • MDN: Intl.Segmenter, String#codePointAt
  • This site: Code points, Character properties