Skip to main content
Best Emojis

Emoji for Developers

Most emoji bugs come from one mismatch: your code counts something different from what the reader sees. A family emoji is one picture, seven code points, 11 JavaScript characters and 25 bytes. We ran the usual regexes, segmenters and length checks against all 3,963 emoji in Unicode's own list, and this page publishes what they actually match, so you can pick the method that fits your case before a bug report tells you which one you chose.

3,963
emoji in Unicode 18.0, all tested
2,770
are more than one code point
271
missed by an Extended_Pictographic filter
35
bytes in the longest emoji

One emoji, a different length in every language

Each cell is what that expression returns for the emoji at the top of its column. They fall into four groups: UTF-16 units, code points, UTF-8 bytes, and graphemes (what a person counts). The grapheme column is worked out by Intl.Segmenter on this server, so a runtime with older Unicode data can return more for the newest emoji.

ExpressionCounts๐Ÿ˜€โค๏ธ๐Ÿ‘๐Ÿฝ๐Ÿ‡ฏ๐Ÿ‡ต๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ
JavaScripts.lengthUTF-16 units224451115
JavaScript[...s].lengthcode points12223710
JavaScriptIntl.Segmentergraphemes1111111
Pythonlen(s)code points12223710
Pythonlen(s.encode('utf-8'))UTF-8 bytes4688112535
Javas.length()UTF-16 units224451115
Javas.codePointCount(0, s.length())code points12223710
C#s.LengthUTF-16 units224451115
C# (.NET 5+)new StringInfo(s).LengthInTextElementsgraphemes1111111
Golen(s)UTF-8 bytes4688112535
Goutf8.RuneCountInString(s)code points12223710
Rusts.len()UTF-8 bytes4688112535
Rusts.chars().count()code points12223710
Swifts.countgraphemes1111111
Swifts.unicodeScalars.countcode points12223710
Rubys.lengthcode points12223710
Rubys.grapheme_clusters.lengthgraphemes1111111
PHPstrlen($s)UTF-8 bytes4688112535
PHPmb_strlen($s)code points12223710
PHPgrapheme_strlen($s)graphemes1111111
MySQLCHAR_LENGTH(s)code points12223710
MySQLLENGTH(s)UTF-8 bytes4688112535
SQL ServerLEN(s) on nvarcharUTF-16 units224451115

What a naive cut does to an emoji

Here is s.slice(0, n) applied to the family ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ for every n. Most cuts do not fail loudly. They return a different, smaller family, which is how this kind of bug ships unnoticed.

Cut at nWhat is leftCode points left
s.slice(0, 1)๏ฟฝhalf a surrogate pair (shows as ๏ฟฝ)0
s.slice(0, 2)๐Ÿ‘จ1
s.slice(0, 3)๐Ÿ‘จโ€2
s.slice(0, 4)๐Ÿ‘จโ€๏ฟฝhalf a surrogate pair (shows as ๏ฟฝ)2
s.slice(0, 5)๐Ÿ‘จโ€๐Ÿ‘ฉ3
s.slice(0, 6)๐Ÿ‘จโ€๐Ÿ‘ฉโ€4
s.slice(0, 7)๐Ÿ‘จโ€๐Ÿ‘ฉโ€๏ฟฝhalf a surrogate pair (shows as ๏ฟฝ)4
s.slice(0, 8)๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง5
s.slice(0, 9)๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€6
s.slice(0, 10)๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๏ฟฝhalf a surrogate pair (shows as ๏ฟฝ)6
s.slice(0, 11)๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ7

The flag that changes country

Regional indicator letters pair up from the start of the string. Remove one code point from the front of ๐Ÿ‡บ๐Ÿ‡ธ๐Ÿ‡ฏ๐Ÿ‡ต and the remaining letters pair up differently: ๐Ÿ‡ธ๐Ÿ‡ฏ๐Ÿ‡ต. No error, just a different country.

๐Ÿ‡บ๐Ÿ‡ธ๐Ÿ‡ฏ๐Ÿ‡ต US + JP โ†’ ๐Ÿ‡ธ๐Ÿ‡ฏ SJ (Svalbard & Jan Mayen) + ๐Ÿ‡ต

Which emoji regex matches what

Tested on 2026-10-20 against all 3,963 fully-qualified emoji, on Node 25.2.1, which ships Unicode 17.0 data. A pattern counts as a match only when it matches the whole emoji.

PatternMatchesWhat it misses
/^\p{RGI_Emoji}$/v3,944 / 3,963Every emoji newer than the engine's Unicode data: Emoji 18.0 (19)
Intl.Segmenter3,963 / 3,963Nothing on this runtime
/\p{Extended_Pictographic}/u3,692 / 3,963259 flags and 12 keycaps, which contain no pictographic code point at all
/^\p{Extended_Pictographic}$/u1,193 / 3,963Everything made of more than one code point: skin tones, flags, keycaps, ZWJ sequences and emoji with a style selector

And the one to avoid: \p{Emoji} also matches # * 0 1 2 3 4 5 6 7 8 9 ยฉ ยฎ. Use it to strip emoji and you delete every digit in the text.

Stripping emoji: what each approach leaves behind

Both lines below started as the same text. The leftovers are invisible in most fonts, which is exactly why they cause trouble later: in search, in deduplication, and in length limits.

Great ๐Ÿ‘๐Ÿฝ job โค๏ธ ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿ‡ฏ๐Ÿ‡ต #๏ธโƒฃ1

ApproachResultLeftover code points
Remove \p{Extended_Pictographic}Great ๐Ÿฝ job ๏ธ โ€ ๐Ÿ‡ฏ๐Ÿ‡ต #๏ธโƒฃ1U+1F3FD U+FE0F U+200D U+1F1EF U+1F1F5 U+FE0F U+20E3
Remove \p{RGI_Emoji}Great job 1none

How much space emoji take in a database

UTF-8 bytes per emoji across all 3,963. 30% fit in four bytes or fewer; the longest needs 35. If a column is limited in bytes rather than characters, size it from this table, not from what the text looks like.

UTF-8 bytesEmoji that fitShare
โ‰ค 41,19330%
โ‰ค 82,34659%
โ‰ค 112,42461%
โ‰ค 173,42486%
โ‰ค 283,86898%
โ‰ค 353,963100%

๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ U+1F9D1 U+1F3FB U+200D U+2764 U+FE0F U+200D U+1F48B U+200D U+1F9D1 U+1F3FC

Pick the count that matches the limit you enforce

There is no single right length, only the one that matches the rule you are enforcing. A database column limited in bytes needs UTF-8 bytes. A text field that promises 50 characters to a person needs graphemes, or a user typing three flags will be told they used 12. SQL Server is the odd one out: nvarchar(n) counts UTF-16 units, so ๐Ÿ˜€ uses two of an nvarchar's characters but one of a MySQL or PostgreSQL varchar's, which count code points. Decide which of the four you mean, write it in the variable name, and the rest follows.

Truncate on grapheme boundaries, not indexes

Any cut by index can land inside an emoji. With UTF-16 the worst case is half a surrogate pair, which most renderers show as a question-mark box and some JSON encoders reject outright. With code points nothing breaks visibly, which is worse: the family loses a child, a skin tone falls off and appears as a brown square, or a flag turns into two letters. The fix is to segment first and cut a whole number of graphemes. Intl.Segmenter has been in every major browser since Firefox 125 shipped it in April 2024, and in Node for longer, so the old excuse for a hand-written splitter is gone.

Copy-ready snippets

Small, dependency-free where the language allows it. Each one solves a case from the tables above.

Count what the reader sees (JavaScript)

const seg = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
const visibleLength = (s) => [...seg.segment(s)].length;

visibleLength('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ok'); // 4, while '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ok'.length is 14

Truncate without splitting an emoji (JavaScript)

function truncate(s, max, ellipsis = 'โ€ฆ') {
  const parts = [...seg.segment(s)].map((x) => x.segment);
  return parts.length <= max ? s : parts.slice(0, max - 1).join('') + ellipsis;
}

Find every emoji, flags and keycaps included (JavaScript)

// ES2024 'v' flag: Chrome 112, Safari 17, Firefox 116, Node 20
const emoji = /\p{RGI_Emoji}/gv;
'Go ๐Ÿ‡ฏ๐Ÿ‡ต #๏ธโƒฃ1 ๐Ÿ‘๐Ÿฝ'.match(emoji); // ['๐Ÿ‡ฏ๐Ÿ‡ต', '#๏ธโƒฃ', '๐Ÿ‘๐Ÿฝ']

The same in Python, with the regex module

import regex  # pip install regex; the built-in re has no \X

graphemes = regex.findall(r'\X', '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ok')
len(graphemes)  # 4, while len('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ok') is 10

Columns that accept every emoji

-- MySQL: utf8mb4 on the column AND the connection
CREATE TABLE posts (body VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci);
SET NAMES utf8mb4;

-- PostgreSQL: any UTF8 database already stores every emoji
-- SQL Server: use nvarchar, and remember LEN() counts UTF-16 units

Why no regex is finished

Every emoji property in a regex engine comes from Unicode data compiled into that engine, so a pattern is only as current as the runtime that runs it. Our own test shows it: \p{RGI_Emoji} matched 3,944 of 3,963 because the 19 newest sequences are not in Node 25.2.1's Unicode 17.0 tables yet. The same code will match them after a runtime upgrade, and a user on an older browser will keep missing them. If a check must accept new emoji the day they appear, match by structure (Extended_Pictographic plus modifiers, joiners and selectors) or by grapheme, and use the property only where a false negative is harmless.

The same emoji can be stored three ways

Unicode lists 3,963 fully-qualified emoji, plus 1,029 minimally-qualified and 243 unqualified forms of the same pictures, mostly the versions without the U+FE0F style selector. โค and โค๏ธ look identical in many apps but are different strings, so a uniqueness check, a reaction count or a search index treats them as two things. Unicode normalization does not help here: NFC and NFKC leave variation selectors alone. If you compare or deduplicate emoji, decide on one form (fully-qualified is the safe choice) and convert on input, using the qualified list rather than stripping every U+FE0F, which would also break keycaps such as 1๏ธโƒฃ.

Emoji Unicode converterAll ZWJ sequences, with their sizesCharacter counter that counts emoji correctlyEmoji glossary

Questions developers ask about emoji

Why is the length of an emoji 2 in JavaScript?

JavaScript strings are UTF-16, and any character above U+FFFF takes two 16-bit units, a surrogate pair. Almost every emoji lives above U+FFFF, so even a simple ๐Ÿ˜€ has a .length of 2. Use [...str].length for code points or Intl.Segmenter for what the reader sees.

What is the best regex to match emoji?

In modern JavaScript, /\p{RGI_Emoji}/v matches complete emoji sequences, including flags and keycaps, but only up to the Unicode version the engine knows. \p{Extended_Pictographic} misses flags and keycaps, and \p{Emoji} wrongly matches digits, # and *.

How do I count emoji as one character?

Count grapheme clusters. In JavaScript use Intl.Segmenter with granularity grapheme; in Swift, String.count already does; in Python use the third-party regex module's \X; in PHP, grapheme_strlen from the intl extension.

How many bytes do I need to store one emoji?

In UTF-8, between 3 and 35 bytes. A single-code-point emoji above U+FFFF takes 4. Skin tones, flags and joined sequences add up: in our test 59% of all emoji fit in 8 bytes, and the longest, a kiss with two skin tones, takes 35.

Why does my emoji turn into ???? in the database?

The column or the connection uses a character set that cannot hold four-byte characters, usually MySQL's legacy utf8. Switch both the table and the connection to utf8mb4. Our Unicode converter page walks through the exact ALTER TABLE statement.