Detecting Emoji-Only Messages in PHP
Introduction
When building chatbots or messaging applications, you'll often need to detect when a user sends a message that contains only emojis. This is common in scenarios like:
- Instagram story reactions - Users often reply with just "❤️" or "😂"
- Chat reactions - Quick emoji responses like "👍" or "🔥"
- Message filtering - Treating emoji-only messages differently from text
In this article, we'll build a simple PHP function to detect emoji-only messages using regular expressions.
The Unicode Challenge
Emojis are more complex than they appear. What looks like a single emoji can actually be multiple Unicode characters combined:
- Basic emojis: Single code points like 😀 (U+1F600)
- Skin tone modifiers: 👋🏽 is 👋 + 🏽 (medium skin tone)
- ZWJ sequences: 👨👩👧 is actually 👨 + ZWJ + 👩 + ZWJ + 👧
- Flag emojis: 🇺🇸 is made of two regional indicator symbols
This complexity means we can't just check for a single character—we need a regex pattern that covers all emoji Unicode ranges.
The Implementation
Here's a practical implementation that handles most common emojis:
/**
* Checks if the text contains only emojis and whitespace.
* Useful for detecting reactions or emoji-only messages.
*/
function isOnlyEmojis(?string $text): bool
{
if (!$text || trim($text) === '') {
return false;
}
// Remove all whitespace
$textWithoutWhitespace = preg_replace('/\s+/u', '', $text);
if ($textWithoutWhitespace === '') {
return false;
}
// Unicode regex pattern for emojis
$emojiPattern = '/^[\x{1F600}-\x{1F64F}' . // Emoticons
'\x{1F300}-\x{1F5FF}' . // Misc Symbols and Pictographs
'\x{1F680}-\x{1F6FF}' . // Transport and Map
'\x{1F700}-\x{1F77F}' . // Alchemical Symbols
'\x{1F780}-\x{1F7FF}' . // Geometric Shapes Extended
'\x{1F800}-\x{1F8FF}' . // Supplemental Arrows-C
'\x{1F900}-\x{1F9FF}' . // Supplemental Symbols and Pictographs
'\x{1FA00}-\x{1FA6F}' . // Chess Symbols
'\x{1FA70}-\x{1FAFF}' . // Symbols and Pictographs Extended-A
'\x{1F1E6}-\x{1F1FF}' . // Flags (Regional Indicator Symbols)
'\x{2600}-\x{26FF}' . // Misc symbols (sun, moon, stars)
'\x{2700}-\x{27BF}' . // Dingbats
'\x{2300}-\x{23FF}' . // Misc Technical
'\x{2B50}\x{2B55}' . // Star and circle
'\x{231A}\x{231B}' . // Watch and hourglass
'\x{2328}' . // Keyboard
'\x{23CF}' . // Eject symbol
'\x{23E9}-\x{23F3}' . // Media control symbols
'\x{23F8}-\x{23FA}' . // Media control symbols
'\x{24C2}' . // Circled M
'\x{25AA}\x{25AB}' . // Small squares
'\x{25B6}\x{25C0}' . // Play buttons
'\x{25FB}-\x{25FE}' . // Squares
'\x{2934}\x{2935}' . // Arrows
'\x{2B05}-\x{2B07}' . // Arrows
'\x{3030}' . // Wavy dash
'\x{303D}' . // Part alternation mark
'\x{3297}' . // Circled Ideograph Congratulation
'\x{3299}' . // Circled Ideograph Secret
'\x{FE0F}' . // Variation Selector-16 (emoji presentation)
'\x{200D}' . // Zero Width Joiner (for combined emojis)
']+$/u';
return preg_match($emojiPattern, $textWithoutWhitespace) === 1;
}Usage Examples
// Basic emojis
isOnlyEmojis('😀'); // true
isOnlyEmojis('❤️🔥'); // true
isOnlyEmojis('👍👍👍'); // true
// With whitespace (still valid)
isOnlyEmojis('😀 😂 🎉'); // true
// Combined emojis (ZWJ sequences)
isOnlyEmojis('👨👩👧'); // true
isOnlyEmojis('👋🏽'); // true (skin tone modifier)
// Mixed content (not emoji-only)
isOnlyEmojis('Hello 👋'); // false
isOnlyEmojis('Nice! 🔥'); // false
isOnlyEmojis('123'); // false
// Edge cases
isOnlyEmojis(''); // false
isOnlyEmojis(null); // false
isOnlyEmojis(' '); // false (whitespace only)Understanding the Unicode Ranges
Let's break down the key Unicode ranges in our pattern:
| Range | Description | Examples |
|---|---|---|
1F600-1F64F | Emoticons | 😀 😂 😍 🙄 |
1F300-1F5FF | Misc Symbols & Pictographs | 🌟 🎉 🔥 💡 |
1F680-1F6FF | Transport & Map | 🚀 ✈️ 🏠 |
1F900-1F9FF | Supplemental Symbols | 🤖 🦄 🧠 |
1F1E6-1F1FF | Regional Indicators | 🇺🇸 🇪🇸 🇧🇷 |
2600-26FF | Misc Symbols | ☀️ ⭐ ♥️ |
FE0F | Variation Selector | Makes text symbols appear as emoji |
200D | Zero Width Joiner | Connects emojis (👨👩👧) |
Why include FE0F and 200D?
The Variation Selector-16 (U+FE0F) tells the system to render a character as an emoji. The Zero Width Joiner (U+200D) connects multiple emojis into one combined emoji, like family emojis or profession emojis.
Practical Use Case: Chatbot Reactions
Here's how you might use this in a chatbot context:
function handleIncomingMessage(string $message): void
{
if (isOnlyEmojis($message)) {
// It's a reaction! Handle differently
logReaction($message);
// Maybe don't trigger a full AI response for just "👍"
return;
}
// Process as a normal text message
processTextMessage($message);
}Limitations
This regex-based approach has some limitations:
- New emojis: Unicode adds new emojis regularly. The pattern may not cover the very latest additions.
- Some edge cases: Certain rarely-used emoji combinations might not be detected.
- Performance: For very long strings, regex can be slower than library-based approaches.
Alternative: Using a Library
For more comprehensive emoji detection, consider using a dedicated library like p3k/emoji-detector:
composer require p3k/emoji-detectoruse Emoji\Detector;
$detector = new Detector();
$result = $detector->detect('Hello 👋 World');
// Returns array of detected emojis with positionsThe library approach is more robust but adds a dependency. For simple "is this emoji-only?" checks, the regex approach is lightweight and sufficient.
Conclusion
Detecting emoji-only messages is useful for many messaging applications. The key points are:
- Emojis are complex - They can be single characters or combinations
- Use Unicode ranges - Cover the main emoji blocks in your regex
- Don't forget ZWJ and FE0F - These invisible characters are essential for combined emojis
- Consider your use case - Simple regex works for most cases; use a library for advanced needs
The regex approach shown here handles the vast majority of real-world emoji usage while keeping your code dependency-free.

