Library / SDK
markdown-it/markdown-it-emoji avatar
markdown-it/markdown-it-emoji

markdown-it-emoji turns shortcodes into unicode or a custom renderer

Emoji syntax plugin for markdown-it markdown parser

772 stars171 forksJavaScriptMIT

At a glance

What is it?
markdown-it-emoji is the emoji plugin for the markdown-it parser, and its job is small: recognize :shortcode: syntax and emoticon shortcuts, then either emit the unicode character or hand the token to a renderer you write. Version 3 changed the exports, which is the one migration note the project carries, and the package ships ESM, CommonJS and UMD builds for that reason.
Who is it for?
Adopt markdown-it-emoji if you already render markdown with markdown-it and want shortcode emoji in the output, since it is a small, maintained plugin with a documented option set and a custom renderer hook. Skip it if you need a full CommonJS-only toolchain without a bundler, because the package is ESM first, or if you want emoji already resolved before the parser sees it, which is a job for a preprocessor.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The whole plugin is one use() call

markdown-it-emoji is a plugin for the markdown-it markdown parser, adding emoji and emoticon syntax support, and it also supports emoticon shortcuts like `:)` and `:-(`.

Installation is the usual npm line:

bash
npm install markdown-it-emoji

And the use is the usual markdown-it plugin shape:

js
//
// { full, light, bare } configs available.
//
//  full:  includes all available emojis support
//  light: includes small subset of most useable emojis
//  bare:  no defaults
//
// Also CJS & UMD builds available in `dist/` folder of published package,
// if your env not supports ESM modules use.
//
import { full as emoji } from 'markdown-it-emoji'
import markdownit from 'markdown-it'

const md = markdownit().use(emoji/* , options */);

That is the entire integration. The plugin registers itself with markdown-it and you get shortcode parsing for free.

The three named exports are the first decision you make. `full` includes all available emoji support. `light` includes a small subset of the most usable emojis. `bare` includes no defaults at all, which is the one to reach for when you are going to supply your own definitions.

The comment about CJS and UMD builds in the `dist/` folder of the published package is there for a reason. The package is ESM first, and the note tells you what to do if your environment does not support ESM modules.

The project is JavaScript, MIT licensed, version 3.1.0, with the last push on 2026-10-12 and no GitHub releases, so the npm version is the number to track.

Three options cover definitions, whitelist and shortcuts

The options are all optional, and the README lists exactly three.

`defs` is an object that rewrites the available emoji definitions, with an example of `{ name1: char1, name2: char2, ... }`. This is how you add your own shortcodes, or replace what a built-in one maps to.

`enabled` is an array that disables all emojis except the whitelisted ones. This is the option that matters most for anyone with a content policy, because it lets you ship a renderer where the emoji set is closed rather than open. A plugin that turns any `:shortcode:` into a character is a small surface for abuse, and a whitelist closes it.

`shortcuts` is an object that rewrites the default shortcuts, with the example `{ "smile": [ ":)", ":-)" ], "laughing": ":D" }`. So the emoticon mapping is editable in the same way the definitions are, and you can point an existing name at a different set of textual forms.

Those three together are enough to build a controlled emoji vocabulary without touching the plugin source.

One browser difference is worth knowing about. If you load the script directly into the page without using a package system, the module will add itself globally with the name `markdownitEmoji`. That is the only behavioural difference between the bundled and the module-resolved usage, and it matters if you are loading the plugin from a CDN rather than from npm.

The renderer rule is where you decide what an emoji looks like

By default, emojis are rendered as the appropriate unicode characters. The README then shows how to change the renderer function, and this is the mechanism that makes the plugin adaptable rather than prescriptive.

The rule is a property on markdown-it's renderer object, and the first example renders as span blocks, for example to use a custom iconic font:

js
// ...
// initialize

md.renderer.rules.emoji = function(token, idx) {
  return '<span class="emoji emoji_' + token[idx].markup + '"></span>';
};

The second example uses twemoji:

js
// ...
// initialize

import twemoji from 'twemoji'

md.renderer.rules.emoji = function(token, idx) {
  return twemoji.parse(token[idx].content);
};

Note which token field each one uses. The span version uses `token[idx].markup`, which is the source shortcode as written, so the class name is derived from the shortcode itself and the emoji comes from your font. The twemoji version uses `token[idx].content`, which is the resolved character, and hands it to twemoji's parser to turn into an image tag.

Choosing between them is a decision about where the emoji lives. The span approach keeps everything as text and delegates the visual to a font, which means the emoji inherits the surrounding text metrics and needs no network fetch. The twemoji approach produces images, which look identical everywhere but cost a request and a file.

The documentation points at the twemoji docs for the string parsing behaviour, with the note that you may need more options to change image size and type, and gives the CSS for making the image height match the line height:

css
.emoji {
  height: 1.2em;
}

What actually changes when you write :satellite:

The end-to-end behaviour is easy to state, and it is worth a concrete example because the plugin's output is the only thing most users ever see.

In your markdown file:

md
Hello from mars :satellite:

becomes:

code
Hello from mars 📡

That is the default path. The shortcode is recognised during parsing, a token is produced, and the default renderer emits the unicode character in place of the text.

The subtlety is that the shortcode is replaced during parsing rather than being left in the output for a later pass. That is what makes the renderer rule approach work, because by the time rendering happens the token carries both the original markup and the resolved content, and a custom rule can use either.

For a documentation site or a blog, this means the markdown source stays portable and readable, and the emoji renders as a character in the browser. For a system that needs images for older clients, or that wants a consistent look across platforms, the renderer override is the switch.

One thing the plugin does not do is handle skin tone modifiers, image sizing or any of the presentation detail of emoji rendering. The README's note about reading the twemoji docs is where that responsibility goes, and it is a sign that the plugin deliberately stays out of presentation.

The v3 export change is the migration anyone hits

There is one prominent warning near the top of the documentation, and it is a migration note: v3 changed exports.

In practice this means the plugin no longer offers a single default export, and the shape of the import is what the usage example shows, with `full as emoji` pulled from the module and then handed to `use()`. If you are upgrading from version 2 and your code assumed a default export, that line has to change.

The reason for the ESM-first packaging is visible in the package metadata. The `main` field points at `dist/index.cjs.js`, the `module` field points at `index.mjs`, and the `exports` map has a conditional root entry with `require` pointing at the CommonJS build and `import` pointing at the ESM entry, plus a wildcard entry that resolves everything else through the same two paths.

So the package ships both worlds, and the conditional exports are what let Node pick correctly. The `files` list shows what actually ships: index.mjs, lib/ and dist/.

The build is Rollup, with rollup.config.mjs at the root and the terser plugin for minification. The test script is unusual and worth noting: `npm run test` runs the lint, then the build, then mocha under c8 with text, html and lcov reporters, excluding dist and test from the coverage measurement. The prepublishOnly hook runs lint and build, so a published package has been through both.

The repository also has a support/ directory and a test/ directory, and the CI badge points at a GitHub Actions workflow.

Where this sits against a preprocessor or a different markdown parser

The comparison that matters is with the other two ways to get emoji into rendered markdown.

The first is a preprocessor. You rewrite `:shortcode:` into unicode or into an image tag before markdown-it ever sees the text, with a script or a build step. That works with any renderer, but it means the substitution happens outside the parser, it has to respect the markdown context itself, and a shortcode inside a code block or a link title is your problem rather than the parser's. Doing it inside the parser means the plugin's own tokeniser handles those cases.

The second is a different markdown parser or an emoji extension for a different one. markdown-it has a plugin API, and this plugin uses it properly: it registers a rule and exposes a renderer override. A parser with emoji built in would save the dependency but tie you to that parser's release cycle, and the ecosystem of markdown-it plugins is the reason many projects are on markdown-it in the first place.

The third comparison is with a full unicode approach, where you do not use shortcodes at all and just write the emoji character. That is simpler and it is the right answer for a project where you control the authors. Shortcodes earn their place when content comes from people who find typing emoji characters awkward, or from systems that need to filter or transform them, and the `enabled` whitelist option is what makes that case safe.

For the narrow case, this is a well-behaved small plugin: MIT licensed, version 3.1.0, actively pushed, with an option set that is three entries long.

Editorial conclusion

Adopt markdown-it-emoji if you already render markdown with markdown-it and want shortcode emoji in the output, since it is a small, maintained plugin with a documented option set and a custom renderer hook. Skip it if you need a full CommonJS-only toolchain without a bundler, because the package is ESM first, or if you want emoji already resolved before the parser sees it, which is a job for a preprocessor. Verify first that you are on version 3 and import the named config rather than a default export, that your chosen picker of emoji definitions matches your content policy, and that the renderer rule you override returns the markup you expect for both the markup and content fields of the token.

Frequently asked questions

How do I install and use markdown-it-emoji?

Run `npm install markdown-it-emoji`, then import the config you want and pass it to markdown-it, for example `import { full as emoji } from 'markdown-it-emoji'` followed by `const md = markdownit().use(emoji)`. CommonJS and UMD builds are in the `dist/` folder of the published package if your environment does not support ESM modules.

How do I render emoji as images with twemoji in markdown-it-emoji?

Override the emoji renderer rule. Import twemoji, then set `md.renderer.rules.emoji` to a function that returns `twemoji.parse(token[idx].content)`. The plugin's own span example instead uses `token[idx].markup`, the source shortcode, which suits a custom icon font.

Can I restrict which emoji markdown-it-emoji allows?

Yes. The `enabled` option is an array that disables all emojis except the whitelisted ones, and the `defs` option rewrites the available definitions if you need to add or replace shortcodes.

Official sources

  1. Issues
  2. License: MIT
  3. markdown-it/markdown-it-emoji on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/markdown-it-markdown-it-emoji.svg)](https://hysenlabs.com/projects/markdown-it-markdown-it-emoji)

Was this useful?