To disable WordPress emojis, drop this remove_action() block into your theme's functions.php (or, better, a small site-specific plugin). It stops the emoji script, the inline detection JS, and the emoji CSS from printing on every page:
add_action( 'init', function () {
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'admin_print_styles', 'print_emoji_styles' );
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
} );That is the whole fix. WordPress core registers those actions in wp-includes/default-filters.php, and removing them at init stops the emoji machinery before wp_head runs. No plugin required, nothing to configure.
What WordPress actually loads for emojis
Since WordPress 4.2 (April 2015), every front-end page gets an emoji polyfill so that older browsers and operating systems render 🎉 and friends consistently. On a clean install, view-source on the front page shows two things in the <head>:
- A block of inline JavaScript (the detection script) that checks whether the browser can render the current emoji set natively.
- A conditional load of
wp-emoji-release.min.jsfromwp-includes/js/wp-emoji-release.min.js, pulled in only when that detection decides the browser needs the polyfill.
Plus a small <style> block (img.wp-smiley) that controls how emoji images sit inline with text.
The inline script is small but it is render-blocking parse work in the <head>, and on modern browsers the polyfill it guards is almost never needed: every current version of Chrome, Firefox, Safari, and Edge renders Unicode emoji natively. So for the overwhelming majority of visitors you are shipping detection code to solve a problem they do not have.
How much speed does this actually save?
Honestly, not much. This is a micro-optimization, and I want to be straight about that before you spend an afternoon on it.
The inline detection script is a few hundred bytes of gzipped HTML. The wp-emoji-release.min.js file is only fetched when detection fails, which on a modern browser is basically never, so on most page loads you are not saving a network request at all. What you reliably save is:
- The inline script bytes in every HTML response (small, but it is on every page).
- A little main-thread parse and execution time in the
<head>before the browser moves on. - One line of noise in the Network tab and in Lighthouse "reduce unused JavaScript" nags.
If you are chasing a perfect PageSpeed score or auditing a site where every request matters, removing emojis is a clean, zero-risk item to tick off. If you are trying to make a genuinely slow site fast, your time is better spent on a caching layer, image sizing, and your slowest plugins. See my notes on tracking down ACF-driven query bloat for the kind of problem that actually moves the needle.
Put it in a plugin, not the theme
The snippet works in functions.php, but I do not keep it there. Theme code disappears the moment you switch themes, and "why did emojis come back?" is a genuinely annoying thing to debug six months later.
Drop this file into wp-content/mu-plugins/disable-emojis.php instead. Files in mu-plugins (must-use plugins) load automatically, before regular plugins, and survive theme switches:
<?php
/**
* Plugin Name: Disable Emojis
* Description: Stops WordPress printing the emoji detection script and styles.
*/
add_action( 'init', function () {
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'admin_print_styles', 'print_emoji_styles' );
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
// Drop the wpemoji TinyMCE plugin from the classic editor.
add_filter( 'tiny_mce_plugins', function ( $plugins ) {
return is_array( $plugins ) ? array_diff( $plugins, array( 'wpemoji' ) ) : array();
} );
// Stop the emoji JS from getting prefetched via the resource-hints DNS line.
add_filter( 'wp_resource_hints', function ( $urls, $relation_type ) {
if ( 'dns-prefetch' === $relation_type ) {
$emoji_svg = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' );
$urls = array_diff( $urls, array( $emoji_svg ) );
}
return $urls;
}, 10, 2 );
} );The tiny_mce_plugins filter drops the wpemoji plugin from the classic editor's TinyMCE instance, so the editor stops loading the emoji code too. It only matters if you (or your clients) still write in the classic editor; the block editor does not register that plugin, so the filter is a harmless no-op on a Gutenberg-only site.
The extra wp_resource_hints filter removes the s.w.org dns-prefetch line that WordPress adds for the emoji SVG host. It is the one easy-to-miss leftover: strip the script and styles but forget this, and view-source still shows a <link rel="dns-prefetch" href="//s.w.org"> you did not want.
The hook priority detail people get wrong
The one place this snippet goes wrong is the priority argument:
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );remove_action() only matches when the priority you pass is the same priority the action was added with. WordPress hooks print_emoji_detection_script onto wp_head at priority 7, so you have to pass 7 here. Leave it off (which defaults to 10) and the remove_action call silently does nothing: no error, no warning, the emoji script keeps printing, and you waste twenty minutes wondering why.
The same is true for any "remove a core action" snippet. Find the original add_action() in core, copy its priority exactly. This one is documented in wp-includes/default-filters.php.
Comparison: the three common approaches
| Approach | Survives theme switch | Code to maintain | When I use it |
|---|---|---|---|
functions.php snippet | No | One block | Quick test, or a site I fully control |
mu-plugins file | Yes | One file | My default for any real site |
| Disable-emojis plugin | Yes | Zero (someone else's) | Client sites where a non-dev edits things |
For a developer-controlled site, the mu-plugins file wins: it is the same seven lines, it survives theme changes, and there is no third-party plugin to keep updated or to trust. The standalone plugin is fine when the person maintaining the site will not touch PHP, but it is a lot of overhead for what is ultimately a remove_action() call.
Verify it worked
After adding the snippet, hard-refresh the front page and check the page source. Search the HTML for emoji. A clean result means:
- No inline
_wpemojiSettings/wpemojidetection block in the<head>. - No
wp-emoji-release.min.jsrequest in the browser Network tab. - No
img.wp-smileystyle block. - No
//s.w.orgdns-prefetch link (if you included the resource-hints filter).
If emoji still shows up, the usual cause is the priority bug above, a page cache serving the old HTML (purge it), or a plugin re-adding emoji support after your init callback runs.
One honest caveat: removing the polyfill does not stop emoji from working. On any current browser, a 🚀 you type into a post still renders fine, because the browser handles Unicode emoji natively now. You are only removing the compatibility shim for browsers almost nobody uses anymore.
See also
- Tracking Down ACF Performance Issues: where the real WordPress page-weight problems usually hide, once you have ticked off the cosmetic ones like emoji scripts
- How to Change a WordPress Password: four reliable ways to rotate a login, from the dashboard and WP-CLI to a direct database edit when you are locked out
Sources
Authoritative references this article was fact-checked against.
- remove_action(): WordPress Developer Referencedeveloper.wordpress.org
- init hook: WordPress Developer Referencedeveloper.wordpress.org





