Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Commonmark Emoji Laravel Package

elgigi/commonmark-emoji

An extension for the League CommonMark PHP parser that adds emoji support, converting :shortcode: names into Unicode emoji as Markdown is rendered. Drop it into an existing parser configuration to add emoji to posts and comments without touching your markup.

View on GitHub
Deep Wiki
Context7

Getting Started

elgigi/commonmark-emoji is a small inline-parser extension for league/commonmark that converts GitHub-style shortcodes (:wave:, :rocket:) into UTF-8 emoji during Markdown β†’ HTML conversion. It is not a standalone parser and not a UI picker β€” it is one ExtensionInterface you register on a CommonMark environment.

Install it alongside CommonMark itself:

composer require elgigi/commonmark-emoji league/commonmark

Then wire it into an environment:

use ElGigi\CommonMarkEmoji\EmojiExtension;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\MarkdownConverter;

$environment = new Environment([
    'html_input' => 'escape',
    'allow_unsafe_links' => false,
]);
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new EmojiExtension());

$converter = new MarkdownConverter($environment);

echo $converter->convert('Ship it :rocket:');
// <p>Ship it πŸš€</p>

If you already use the convenience CommonMarkConverter, register the extension on its environment instead:

$converter = new \League\CommonMark\CommonMarkConverter();
$converter->getEnvironment()->addExtension(new EmojiExtension());

Where to look first. Read the README for the exact registration snippet, then open src/EmojiExtension.php (it should register one inline parser and one node renderer). The shortcode map is the largest file in src/ β€” that is your source of truth for which tags are supported. Check the tests to see the exact shortcode grammar the parser’s match definition accepts. Finally, run composer show elgigi/commonmark-emoji and look at the requires block: the last release is from August 2021, so confirm whether it targets league/commonmark ^1.5, ^2.0, or both before committing to it.

First use case. The lowest-friction win is comment bodies or issue descriptions: user types :+1: nice work :tada:, the converter emits <p>πŸ‘ nice work πŸŽ‰</p>. No frontend JS, no emoji picker, no data attributes β€” just text replacement during the existing Markdown pass.

Implementation Patterns

Build the converter once, not per request. In a Laravel service provider, bind a configured converter as a singleton. Rebuilding the environment (and re-registering parsers) on every render is wasted work, and it gives you one place to change the emoji map later:

$this->app->singleton(MarkdownConverter::class, function () {
    $environment = new Environment([
        'html_input' => 'escape',
        'allow_unsafe_links' => false,
    ]);
    $environment->addExtension(new CommonMarkCoreExtension());
    $environment->addExtension(new EmojiExtension());

    return new MarkdownConverter($environment);
});

Then expose a Blade directive or helper (@markdown($comment->body)) that resolves the singleton. That is the integration point most teams actually want β€” the extension itself is a one-liner.

Keep it in the same environment as the rest of your syntax. If you use GithubFlavoredMarkdownConverter or a custom environment with tables, strikethrough, or autolinks, add EmojiExtension to that environment. Do not create a second converter just for emoji; you will get inconsistent output between fields.

Decide on output markup early. The package’s default behavior is raw UTF-8 text, which is correct and accessible in most contexts. If your design system requires <span class="emoji" role="img" aria-label="rocket">πŸš€</span>, subclass or replace the bundled renderer and register it with $environment->addRenderer(Emoji::class, new YourRenderer()). This is the cleanest extension point: the node type stays the same, only the HTML representation changes.

Custom shortcodes. The extension ships a fixed shortcode map; there is no general-purpose config array for adding :company_logo:. If you need private emoji, write your own inline parser that matches :company_\w+: and register it on the same environment (alongside, not instead of, EmojiExtension). Keep the match definitions mutually exclusive so the two parsers do not race for the same cursor position.

Cache the rendered HTML, not the Markdown. Conversion is cheap, but if comments are read far more often than written, store the rendered HTML on save. Invalidate it whenever you change the emoji map, the renderer, or your sanitization rules.

Testing pattern. Assert on the converted output, including the negative cases:

$html = $converter->convert('Hi :wave: and :not_a_real_emoji:');
$this->assertStringContainsString('πŸ‘‹', $html);
$this->assertStringContainsString(':not_a_real_emoji:', $html);

Also assert that shortcodes inside backticks and fenced code blocks are left untouched β€” that is the behavior you want, and it is the easiest regression to introduce with a custom parser.

Gotchas and Tips

  • Version compatibility is the first risk. A 2021 release sitting at 29 stars and zero dependents may predate or only partially support current league/commonmark 2.x APIs (EnvironmentBuilderInterface, InlineParserMatch, etc.). Run composer why-not league/commonmark 2.7 and check the constraint before adopting. If it only supports 1.x, you are adopting an EOL parser stack to get emoji.
  • MySQL needs utf8mb4. Emoji are four-byte characters. utf8 columns will truncate or error. This is a database schema issue, not a package issue, but it is the most common production failure after adding emoji support.
  • Unknown shortcodes are left literal. :foo_bar: with no mapping should pass through as text. Verify this in a test β€” you do not want the parser swallowing punctuation around unknown tags.
  • Watch the grammar for + and -. Names like :+1: and :-1: are legitimate GitHub tags. If the match definition omits those characters, they will render literally. Skin-tone modifiers (:+1::skin-tone-2:) and ZWJ sequences are a likely gap β€” check the map before promising them to users.
  • Colon collisions. Text like 12:30:45 or ratio 3:2: should not be eaten. Because the parser only matches when the closed name exists in the map, most of these survive, but add a test with times and ratios if you accept arbitrary user input.
  • Code spans are safe, preprocessing is not. Inline parsers are not invoked inside code spans or fenced blocks, so :wave: in a snippet stays literal. If you ever pre-replace shortcodes with str_replace before Markdown conversion, you break that guarantee β€” don’t.
  • Sanitize after conversion, not before. If you allow raw HTML in Markdown, run the converted output through your sanitizer (e.g., HTMLPurifier) afterward. Emoji themselves add no XSS surface, but the surrounding HTML pipeline still needs the same treatment you already apply.
  • Performance is a non-issue at normal volume. The parser runs per text node and does a map lookup; the cost is dominated by Markdown parsing itself. The one thing to avoid is including the full emoji map on every request if you only need a dozen tags β€” but with Composer autoloading this is a micro-optimization, not a real problem.
  • Maintenance risk is real. With one old release and no downstream dependents, treat this as a small, replaceable dependency: pin the version, add your own contract test for the shortcodes you rely on, and keep a fallback in mind. A hand-rolled inline parser over a shortcode array is roughly 50 lines and gives you full control over the map, the match grammar, and the rendered markup. If emoji support is core to your product, that may be the better long-term trade.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console