To clean up WordPress's head output, remove only the discovery tags and compatibility features your site does not use. Keep the canonical tag, required block styles, and asset versions. Removing a tag is different from disabling the feature behind it.
I use a small must-use plugin so these decisions survive a theme switch. The configurable example below covers emojis, shortlinks, feed discovery, oEmbed discovery, and generator tags. It leaves feed URLs and embedded content working unless I explicitly choose otherwise.
Choose only the features your site does not use
| Feature | What removal changes | Check before removing |
|---|---|---|
| Emoji support | Detection, optional polyfill, styling and email/feed substitution | Emoji display on the browsers and operating systems you support |
| Shortlink discovery | HTML tag and HTTP Link header | Clients that consume shortlinks; ?p=123 still works |
| Feed discovery | RSS/Atom <link> tags | Subscribers and integrations; feed endpoints remain live |
| oEmbed discovery | Other sites' discovery of your post embeds | Whether other publishers embed your posts |
| Generator output | Version metadata | This does not secure an outdated installation |
The savings depend on what a page actually loads. A removed tag saves HTML bytes; a conditional script can also save a request when it would otherwise load. Measure the Network panel and page timing before claiming a speed improvement. Feeds and embeds are useful features, and their existence alone is not evidence of an SEO problem.
One configurable must-use plugin
Save this as wp-content/mu-plugins/te-head-cleanup.php. Create the directory if necessary. Change the booleans to match the site's requirements; the conservative default removes shortlink and generator discovery only.
<?php
/**
* Plugin Name: TE Head Cleanup
* Plugin URI: https://techearl.com/clean-up-wordpress-wp-head
* Description: Selective cleanup of WordPress discovery output.
* Version: 2.0.0
* Author: Ishan Karunaratne
* Author URI: https://techearl.com
* License: GPL-2.0-or-later
*/
defined( 'ABSPATH' ) || exit;
add_action( 'init', function () {
$features = array(
'emojis' => false,
'shortlinks' => true,
'feed_links' => false,
'oembed_discovery' => false,
'generator' => true,
'rsd' => false,
);
if ( $features['emojis'] ) {
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_enqueue_scripts', 'wp_enqueue_emoji_styles' );
remove_action( 'admin_enqueue_scripts', 'wp_enqueue_emoji_styles' );
// Compatibility with installations using the pre-6.4 callbacks.
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' );
add_filter( 'emoji_svg_url', '__return_false' );
add_filter( 'tiny_mce_plugins', function ( $plugins ) {
return is_array( $plugins )
? array_values( array_diff( $plugins, array( 'wpemoji' ) ) )
: $plugins;
} );
}
if ( $features['shortlinks'] ) {
remove_action( 'wp_head', 'wp_shortlink_wp_head', 10 );
remove_action( 'template_redirect', 'wp_shortlink_header', 11 );
}
if ( $features['feed_links'] ) {
remove_action( 'wp_head', 'feed_links', 2 );
remove_action( 'wp_head', 'feed_links_extra', 3 );
}
if ( $features['oembed_discovery'] ) {
add_filter( 'oembed_discovery_links', '__return_empty_string' );
}
if ( $features['generator'] ) {
remove_action( 'wp_head', 'wp_generator' );
add_filter( 'the_generator', '__return_empty_string' );
}
if ( $features['rsd'] ) {
remove_action( 'wp_head', 'rsd_link' );
}
}, 100 );The priority passed to remove_action() must match the original registration. For example, emoji detection uses priority 7, feed links use 2 and 3, and the shortlink header uses 11. Register the removal after core has registered the callback and before the callback runs.
Emoji scripts and styles
WordPress tests emoji support and may load a compatibility script when needed. Removing it does not remove Unicode characters from your posts, but it gives rendering responsibility to the visitor's browser and fonts. “Modern browser” does not guarantee support for every newer emoji sequence.
The plugin removes both the current enqueue callbacks and the older style-printing callbacks. print_emoji_styles() was deprecated in WordPress 6.4. The TinyMCE filter applies to the classic editor; it is not a block-editor setting. The feed and email filters control conversion to image-based emoji in those outputs, so include them only if that is the intended behavior.
Shortlink tag and Link header
These are separate outputs:
remove_action( 'wp_head', 'wp_shortlink_wp_head', 10 );
remove_action( 'template_redirect', 'wp_shortlink_header', 11 );Removing discovery does not disable https://example.com/?p=123. WordPress can still resolve that query to the post. Do not remove that access path merely to hide the tag.
curl -sS https://example.com/sample-post/ | grep -i shortlink
curl -sSI https://example.com/sample-post/ | grep -i shortlink
curl -sSI 'https://example.com/?p=123'Expect no shortlink output from the first two when those options are enabled; inspect the third separately for the site's normal redirect or response.
Feed tags, comments feeds and feed URL behavior
Removing feed_links and feed_links_extra removes discovery, not /feed/, /comments/feed/, or ?feed=rss2. To keep the main feeds advertised but remove contextual links, remove only feed_links_extra at priority 3. To suppress the global comments-feed tag while keeping the posts-feed tag:
add_filter( 'feed_links_show_comments_feed', '__return_false' );That filter also changes discovery only. Existing subscribers can still request the feed.
If the site has deliberately retired all feeds and has no equivalent replacement, a separate optional handler can return 410 Gone. Keep the rewrite rules so WordPress recognizes both pretty and query-string feed requests:
add_action( 'template_redirect', function () {
if ( ! is_feed() ) {
return;
}
nocache_headers();
wp_die( 'This feed has been retired.', 'Feed unavailable', array( 'response' => 410 ) );
}, 1 );For a moved feed, use a permanent redirect to the exact replacement feed instead. A homepage is not a replacement for every feed. Do not remove feed rewrite rules and then expect is_feed() to identify the now-unmatched URLs. Do not derive a redirect by merely stripping /feed/; query-string feeds need explicit handling and can otherwise redirect to themselves.
oEmbed provider cleanup without breaking pasted embeds
WordPress can both provide embeds of its posts and consume embeds from elsewhere. Hiding its provider discovery links is straightforward:
add_filter( 'oembed_discovery_links', '__return_empty_string' );This filter has existed since WordPress 4.4. It avoids relying on a particular head-hook priority.
For an intentional provider shutdown, the following optional code removes only the provider REST route and redirects recognized post-embed documents to their actual published post. It keeps the consumer proxy and host script available:
add_filter( 'rest_endpoints', function ( $routes ) {
unset( $routes['/oembed/1.0/embed'] );
return $routes;
} );
add_action( 'template_redirect', function () {
if ( ! is_embed() ) {
return;
}
$post = get_queried_object();
if ( $post instanceof WP_Post && is_post_publicly_viewable( $post ) ) {
wp_safe_redirect( get_permalink( $post ), 301 );
exit;
}
}, 1 );Removing a route from rest_endpoints makes it unavailable; it does not merely hide it from the API index. This is intentional for the provider route above.
Leave embed_oembed_discover unchanged if pasted embeds from arbitrary providers must continue working. Leave wp_oembed_add_host_js registered: WordPress uses its presence when deciding whether to enqueue wp-embed for consumed WordPress post embeds. Removing all wp_oembed_register_route registration would also remove the consumer proxy route.
Existing consumed embeds have per-post caches. Provider discovery cleanup does not require deleting those caches. If you change consumer behavior, use a scoped cache-maintenance procedure for affected posts and verify the available commands with wp help embed; never use wp post meta delete --all as an embed-cache shortcut, because that option concerns all metadata for a specified object.
Generator tags and asset versions
remove_action( 'wp_head', 'wp_generator' ) removes the HTML generator. The the_generator filter also suppresses generator strings in other supported output formats, including feeds. Neither patches a vulnerability or reliably hides a WordPress version. Keep core, plugins and themes updated.
Leave asset ?ver= parameters alone. They help invalidate browser/CDN caches when an asset changes. For a stylesheet you own, supply its real version when enqueueing it:
add_action( 'wp_enqueue_scripts', function () {
$file = get_stylesheet_directory() . '/assets/site.css';
if ( is_file( $file ) ) {
wp_enqueue_style(
'te-site',
get_stylesheet_directory_uri() . '/assets/site.css',
array(),
(string) filemtime( $file )
);
}
} );Do not replace versions on arbitrary plugin assets or use the current time as the version on every request.
Flush rewrite rules once and test removed and retained behavior
The examples here keep rewrite rules in place, so they need no rewrite flush. If you adapt the code to add or remove rules, rebuild the stored rule map once after deployment with wp rewrite flush or Settings → Permalinks. Repeat once after removing that rule-changing code. Do not flush on every request; must-use plugins have no activation hook.
After purging page caches, check a logged-out post, the homepage, a post containing a video and a WordPress-post embed, the editor, and any feed consumers. Match expectations to the enabled switches:
curl -sS https://example.com/sample-post/ | grep -iE 'emoji|shortlink|oembed|generator|application/rss'
curl -sSI https://example.com/sample-post/
curl -sSI https://example.com/feed/
curl -sSI 'https://example.com/?feed=rss2'
curl -sSI https://example.com/sample-post/embed/An empty grep result is not a universal pass condition: retained features should still appear, and a word such as “emoji” can occur in ordinary article text. Inspect the actual tags, HTTP status and browser behavior. Keep canonical tags intact.
For larger performance work, continue with ACF profiling, WooCommerce optimization, or the separately tested Dashicons, jQuery Migrate and block CSS decisions.
Sources
Authoritative references this article was fact-checked against.
- WordPress remove_actiondeveloper.wordpress.org
- Current emoji style callbackdeveloper.wordpress.org
- oEmbed discovery filterdeveloper.wordpress.org
- WordPress post-embed host scriptdeveloper.wordpress.org
- Consumer discovery filterdeveloper.wordpress.org
- WP-CLI metadata deletion semanticsdeveloper.wordpress.org
- WordPress feed detectiondeveloper.wordpress.org
- Rewrite flushingdeveloper.wordpress.org





