Emoji para desarrolladores
Casi todos los bugs con emoji vienen de un mismo desajuste: tu código cuenta algo distinto de lo que ve el lector. Un emoji de familia es una sola imagen, siete code points, 11 caracteres de JavaScript y 25 bytes. Pasamos las regex, los segmentadores y las comprobaciones de longitud habituales por los 3,963 emoji de la lista oficial de Unicode, y aquí publicamos lo que realmente detectan, para que elijas el método adecuado antes de que un informe de error te diga cuál elegiste.
Un emoji, una longitud distinta en cada lenguaje
Cada celda es lo que devuelve esa expresión para el emoji que encabeza la columna. Se reparten en cuatro grupos: unidades UTF-16, code points, bytes UTF-8 y grafemas (lo que cuenta una persona). La columna de grafemas la calcula Intl.Segmenter en este servidor, así que un runtime con datos de Unicode más antiguos puede devolver más para los emoji más recientes.
| Expresión | Cuenta | 😀 | ❤️ | 👍🏽 | 🇯🇵 | 👩💻 | 👨👩👧👦 | 🧑🏻❤️💋🧑🏼 |
|---|---|---|---|---|---|---|---|---|
JavaScripts.length | unidades UTF-16 | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
JavaScript[...s].length | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
JavaScriptIntl.Segmenter | grafemas | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Pythonlen(s) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Pythonlen(s.encode('utf-8')) | bytes UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
Javas.length() | unidades UTF-16 | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
Javas.codePointCount(0, s.length()) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
C#s.Length | unidades UTF-16 | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
C# (.NET 5+)new StringInfo(s).LengthInTextElements | grafemas | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Golen(s) | bytes UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
Goutf8.RuneCountInString(s) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Rusts.len() | bytes UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
Rusts.chars().count() | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Swifts.count | grafemas | 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 | grafemas | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
PHPstrlen($s) | bytes UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
PHPmb_strlen($s) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
PHPgrapheme_strlen($s) | grafemas | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
MySQLCHAR_LENGTH(s) | code points | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
MySQLLENGTH(s) | bytes UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
SQL ServerLEN(s) on nvarchar | unidades UTF-16 | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
Lo que un corte ingenuo le hace a un emoji
Aquí está s.slice(0, n) aplicado a la familia 👨👩👧👦 para cada n. La mayoría de los cortes no fallan de forma visible. Devuelven otra familia, más pequeña, y así es como este tipo de bug llega a producción sin que nadie lo note.
| Corte en n | Lo que queda | Code points restantes |
|---|---|---|
| s.slice(0, 1) | �medio par sustituto (se ve como �) | 0 |
| s.slice(0, 2) | 👨 | 1 |
| s.slice(0, 3) | 👨 | 2 |
| s.slice(0, 4) | 👨�medio par sustituto (se ve como �) | 2 |
| s.slice(0, 5) | 👨👩 | 3 |
| s.slice(0, 6) | 👨👩 | 4 |
| s.slice(0, 7) | 👨👩�medio par sustituto (se ve como �) | 4 |
| s.slice(0, 8) | 👨👩👧 | 5 |
| s.slice(0, 9) | 👨👩👧 | 6 |
| s.slice(0, 10) | 👨👩👧�medio par sustituto (se ve como �) | 6 |
| s.slice(0, 11) | 👨👩👧👦 | 7 |
La bandera que cambia de país
Las letras de indicador regional se emparejan desde el principio de la cadena. Quita un code point del principio de 🇺🇸🇯🇵 y las letras restantes se emparejan de otra forma: 🇸🇯🇵. Sin errores, solo otro país.
🇺🇸🇯🇵 US + JP → 🇸🇯 SJ (Svalbard & Jan Mayen) + 🇵
Qué detecta cada regex de emoji
Probado el 2026-10-20 contra los 3,963 emoji fully-qualified, en Node 25.2.1, que incluye datos de Unicode 17.0. Un patrón solo cuenta como coincidencia cuando abarca el emoji completo.
| Patrón | Coincidencias | Lo que se le escapa |
|---|---|---|
| /^\p{RGI_Emoji}$/v | 3,944 / 3,963 | Todos los emoji más nuevos que los datos de Unicode del motor: Emoji 18.0 (19) |
| Intl.Segmenter | 3,963 / 3,963 | Nada en este runtime |
| /\p{Extended_Pictographic}/u | 3,692 / 3,963 | 259 banderas y 12 keycaps, que no contienen ningún code point pictográfico |
| /^\p{Extended_Pictographic}$/u | 1,193 / 3,963 | Todo lo que tenga más de un code point: tonos de piel, banderas, keycaps, secuencias ZWJ y emoji con selector de estilo |
Y el que hay que evitar: \p{Emoji} también coincide con # * 0 1 2 3 4 5 6 7 8 9 © ®. Úsalo para eliminar emoji y borrarás todos los dígitos del texto.
Eliminar emoji: lo que deja cada método
Las dos líneas de abajo partían del mismo texto. Los restos son invisibles en la mayoría de las fuentes, y precisamente por eso dan problemas más tarde: en búsquedas, en deduplicación y en límites de longitud.
Great 👍🏽 job ❤️ 👩💻 🇯🇵 #️⃣1
| Método | Resultado | Code points sobrantes |
|---|---|---|
| Eliminar \p{Extended_Pictographic} | Great 🏽 job ️ 🇯🇵 #️⃣1 | U+1F3FD U+FE0F U+200D U+1F1EF U+1F1F5 U+FE0F U+20E3 |
| Eliminar \p{RGI_Emoji} | Great job 1 | ninguno |
Cuánto espacio ocupan los emoji en una base de datos
Bytes UTF-8 por emoji en los 3,963. 30% caben en cuatro bytes o menos; el más largo necesita 35. Si una columna está limitada en bytes y no en caracteres, dimensiónala según esta tabla, no según el aspecto del texto.
| Bytes UTF-8 | Emoji que caben | Proporción |
|---|---|---|
| ≤ 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
Elige la cuenta que corresponde al límite que aplicas
No existe una única longitud correcta, solo la que corresponde a la regla que estás aplicando. Una columna de base de datos limitada en bytes necesita bytes UTF-8. Un campo de texto que promete 50 caracteres a una persona necesita grafemas, o a quien escriba tres banderas le dirás que ha usado 12. SQL Server es la excepción: nvarchar(n) cuenta unidades UTF-16, así que 😀 ocupa dos caracteres de un nvarchar pero uno de un varchar de MySQL o PostgreSQL, que cuentan code points. Decide cuál de las cuatro quieres decir, ponlo en el nombre de la variable y lo demás viene solo.
Trunca en límites de grafema, no por índice
Cualquier corte por índice puede caer dentro de un emoji. Con UTF-16, el peor caso es medio par sustituto, que la mayoría de los renderizadores muestran como una caja con un signo de interrogación y que algunos codificadores JSON rechazan directamente. Con code points no se rompe nada a la vista, lo cual es peor: la familia pierde un hijo, un tono de piel se suelta y aparece como un cuadrado marrón, o una bandera se convierte en dos letras. La solución es segmentar primero y cortar un número entero de grafemas. Intl.Segmenter está en todos los navegadores principales desde que Firefox 125 lo incorporó en abril de 2024, y en Node desde antes, así que ya no hay excusa para escribir un divisor a mano.
Fragmentos listos para copiar
Pequeños y sin dependencias donde el lenguaje lo permite. Cada uno resuelve un caso de las tablas anteriores.
Contar lo que ve el lector (JavaScript)
const seg = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
const visibleLength = (s) => [...seg.segment(s)].length;
visibleLength('👨👩👧👦 ok'); // 4, while '👨👩👧👦 ok'.length is 14Truncar sin partir un 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;
}Encontrar todos los emoji, banderas y keycaps incluidos (JavaScript)
// ES2024 'v' flag: Chrome 112, Safari 17, Firefox 116, Node 20
const emoji = /\p{RGI_Emoji}/gv;
'Go 🇯🇵 #️⃣1 👍🏽'.match(emoji); // ['🇯🇵', '#️⃣', '👍🏽']Lo mismo en Python, con el módulo regex
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 10Columnas que aceptan cualquier 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 unitsPor qué ninguna regex está terminada
Cada propiedad de emoji de un motor de regex sale de datos de Unicode compilados en ese motor, así que un patrón solo está tan actualizado como el runtime que lo ejecuta. Nuestra propia prueba lo demuestra: \p{RGI_Emoji} detectó 3,944 de 3,963 porque las 19 secuencias más recientes aún no están en las tablas de Unicode 17.0 de Node 25.2.1. El mismo código las detectará tras actualizar el runtime, y un usuario con un navegador antiguo seguirá sin detectarlas. Si una validación debe aceptar emoji nuevos el mismo día que aparecen, compara por estructura (Extended_Pictographic más modificadores, joiners y selectores) o por grafema, y usa la propiedad solo donde un falso negativo no haga daño.
El mismo emoji se puede guardar de tres formas
Unicode recoge 3,963 emoji fully-qualified, más 1,029 formas minimally-qualified y 243 unqualified de las mismas imágenes, sobre todo las versiones sin el selector de estilo U+FE0F. ❤ y ❤️ se ven idénticos en muchas apps pero son cadenas distintas, así que una comprobación de unicidad, un contador de reacciones o un índice de búsqueda los trata como dos cosas. La normalización Unicode no ayuda: NFC y NFKC no tocan los selectores de variación. Si comparas o deduplicas emoji, elige una forma (fully-qualified es la opción segura) y convierte en la entrada usando la lista de cualificación, en lugar de quitar todos los U+FE0F, lo que además rompería keycaps como 1️⃣.
Conversor de emoji a UnicodeTodas las secuencias ZWJ, con sus tamañosContador de caracteres que cuenta bien los emojiGlosario de emoji
Preguntas que hacen los desarrolladores sobre emoji
¿Por qué la longitud de un emoji es 2 en JavaScript?
Las cadenas de JavaScript son UTF-16, y cualquier carácter por encima de U+FFFF ocupa dos unidades de 16 bits, un par sustituto. Casi todos los emoji están por encima de U+FFFF, así que incluso un simple 😀 tiene .length 2. Usa [...str].length para code points o Intl.Segmenter para lo que ve el lector.
¿Cuál es la mejor regex para detectar emoji?
En JavaScript moderno, /\p{RGI_Emoji}/v detecta secuencias de emoji completas, banderas y keycaps incluidos, pero solo hasta la versión de Unicode que conoce el motor. \p{Extended_Pictographic} se salta banderas y keycaps, y \p{Emoji} detecta por error dígitos, # y *.
¿Cómo cuento un emoji como un solo carácter?
Cuenta clústeres de grafemas. En JavaScript usa Intl.Segmenter con granularity grapheme; en Swift, String.count ya lo hace; en Python usa \X del módulo de terceros regex; en PHP, grapheme_strlen de la extensión intl.
¿Cuántos bytes necesito para guardar un emoji?
En UTF-8, entre 3 y 35 bytes. Un emoji de un solo code point por encima de U+FFFF ocupa 4. Los tonos de piel, las banderas y las secuencias unidas suman: en nuestra prueba, 59% de todos los emoji caben en 8 bytes, y el más largo, un beso con dos tonos de piel, ocupa 35.
¿Por qué mi emoji se convierte en ???? en la base de datos?
La columna o la conexión usa un juego de caracteres que no admite caracteres de cuatro bytes, normalmente el utf8 heredado de MySQL. Cambia tanto la tabla como la conexión a utf8mb4. Nuestra página del conversor Unicode explica la sentencia ALTER TABLE exacta.
