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.
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.
| Expression | Counts | ๐ | โค๏ธ | ๐๐ฝ | ๐ฏ๐ต | ๐ฉโ๐ป | ๐จโ๐ฉโ๐งโ๐ฆ | ๐ง๐ปโโค๏ธโ๐โ๐ง๐ผ |
|---|---|---|---|---|---|---|---|---|
JavaScripts.length | UTF-16 units | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
JavaScript[...s].length | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
JavaScriptIntl.Segmenter | graphemes | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Pythonlen(s) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Pythonlen(s.encode('utf-8')) | UTF-8 bytes | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
Javas.length() | UTF-16 units | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
Javas.codePointCount(0, s.length()) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
C#s.Length | UTF-16 units | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
C# (.NET 5+)new StringInfo(s).LengthInTextElements | graphemes | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Golen(s) | UTF-8 bytes | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
Goutf8.RuneCountInString(s) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Rusts.len() | UTF-8 bytes | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
Rusts.chars().count() | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Swifts.count | graphemes | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Swifts.unicodeScalars.count | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Rubys.length | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Rubys.grapheme_clusters.length | graphemes | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
PHPstrlen($s) | UTF-8 bytes | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
PHPmb_strlen($s) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
PHPgrapheme_strlen($s) | graphemes | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
MySQLCHAR_LENGTH(s) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
MySQLLENGTH(s) | UTF-8 bytes | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
SQL ServerLEN(s) on nvarchar | UTF-16 units | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
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 n | What is left | Code 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.
| Pattern | Matches | What it misses |
|---|---|---|
| /^\p{RGI_Emoji}$/v | 3,944 / 3,963 | Every emoji newer than the engine's Unicode data: Emoji 18.0 (19) |
| Intl.Segmenter | 3,963 / 3,963 | Nothing on this runtime |
| /\p{Extended_Pictographic}/u | 3,692 / 3,963 | 259 flags and 12 keycaps, which contain no pictographic code point at all |
| /^\p{Extended_Pictographic}$/u | 1,193 / 3,963 | Everything 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
| Approach | Result | Leftover code points |
|---|---|---|
| Remove \p{Extended_Pictographic} | Great ๐ฝ job ๏ธ โ ๐ฏ๐ต #๏ธโฃ1 | U+1F3FD U+FE0F U+200D U+1F1EF U+1F1F5 U+FE0F U+20E3 |
| Remove \p{RGI_Emoji} | Great job 1 | none |
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 bytes | Emoji that fit | Share |
|---|---|---|
| โค 4 | 1,193 | 30% |
| โค 8 | 2,346 | 59% |
| โค 11 | 2,424 | 61% |
| โค 17 | 3,424 | 86% |
| โค 28 | 3,868 | 98% |
| โค 35 | 3,963 | 100% |
๐ง๐ปโโค๏ธโ๐โ๐ง๐ผ 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 14Truncate 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 10Columns 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 unitsWhy 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.
