25 Useful WordPress functions.php Snippets for Agencies
Copy-paste functions.php snippets I use across every client site — from disabling Gutenberg to cleaning up the admin, with real-world context.
After managing 30+ WordPress client sites, I have a growing library of functions.php snippets that solve recurring problems. These are not clever hacks — they are battle-tested pieces of code that go into almost every project I touch. Some clean up the WordPress admin, some improve performance, some fix annoyances. All of them are safe to use in a child theme or a site-specific plugin.
A quick note before you start: never paste code directly into your active theme's functions.php via the WordPress editor. One syntax error and you get locked out. Always edit via FTP, SSH, or a local dev environment first.
1. Disable Gutenberg for specific post types
Gutenberg is fine for posts and pages, but some post types — testimonials, team members, portfolio items — are better off with the Classic Editor or a meta-box-based interface.
add_filter( 'use_block_editor_for_post_type', function( $use, $post_type ) {
$disabled = [ 'testimonial', 'portfolio', 'team_member' ];
if ( in_array( $post_type, $disabled, true ) ) {
return false;
}
return $use;
}, 10, 2 );
2. Remove WordPress version from head
Exposing the WordPress version in the page source is a minor security risk — it tells attackers exactly which vulnerabilities to target.
remove_action( 'wp_head', 'wp_generator' );
Add this alongside removing it from the RSS feed:
add_filter( 'the_generator', '__return_empty_string' );
3. Limit post revisions
By default WordPress saves unlimited post revisions. On a busy blog or with Gutenberg's auto-save interval, your wp_posts table balloons. Limit it:
add_filter( 'wp_revisions_to_keep', function( $num, $post ) {
return 5; // keep last 5 revisions
}, 10, 2 );
Or set it in wp-config.php instead: define( 'WP_POST_REVISIONS', 5 );
4. Add custom image sizes
WordPress registers a handful of default image sizes. For most themes you need custom ones. Register them properly so the media library uses them:
add_action( 'after_setup_theme', function() {
add_image_size( 'card-thumbnail', 600, 400, true ); // hard crop
add_image_size( 'hero-wide', 1600, 600, true );
add_image_size( 'avatar', 80, 80, true );
} );
After adding new sizes, run the Regenerate Thumbnails plugin once to process existing images.
5. Defer non-critical JavaScript
Loading all scripts synchronously blocks rendering. This snippet defers any script not explicitly excluded:
add_filter( 'script_loader_tag', function( $tag, $handle ) {
$no_defer = [ 'jquery', 'jquery-core', 'wc-cart-fragments' ];
if ( in_array( $handle, $no_defer, true ) ) {
return $tag;
}
return str_replace( ' src', ' defer="defer" src', $tag );
}, 10, 2 );
Test thoroughly — some plugins break when their scripts are deferred.
6. Remove emoji scripts
WordPress loads an emoji script and stylesheet on every page even if you never use emoji. This is ~10 KB of wasted requests:
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' );
} );
7. Disable XML-RPC
XML-RPC is a legacy remote access interface. Unless you use the official WordPress mobile app or Jetpack, disable it entirely. It is a common attack vector for brute-force and DDoS amplification:
add_filter( 'xmlrpc_enabled', '__return_false' );
To also block the HTTP request before PHP executes, add this to your Nginx config (covered in a separate article).
8. Redirect attachment pages to the parent post
WordPress creates individual pages for every media attachment. These thin pages hurt your crawl budget and can rank for irrelevant queries. Redirect them:
add_action( 'template_redirect', function() {
if ( is_attachment() ) {
$parent = wp_get_post_parent_id( get_the_ID() );
wp_redirect( $parent ? get_permalink( $parent ) : home_url(), 301 );
exit;
}
} );
9. Change the default login URL
/wp-login.php is where 100% of brute-force bots knock first. A simple rename makes automated attacks miss entirely. WPS Hide Login does this cleanly, but here is the manual approach:
// This is a simplified version — for production use WPS Hide Login plugin
// which handles all edge cases including redirects and REST API.
add_action( 'init', function() {
if ( strpos( $_SERVER['REQUEST_URI'] ?? '', 'wp-login.php' ) !== false
&& ! is_user_logged_in() ) {
wp_redirect( home_url( '404' ), 302 );
exit;
}
} );
10. Auto-update plugins silently
For client sites where you want automatic plugin updates without emailing the admin on every update:
add_filter( 'auto_update_plugin', '__return_true' ); add_filter( 'auto_update_theme', '__return_true' ); // Disable the update emails to avoid inbox noise add_filter( 'auto_core_update_send_email', '__return_false' ); add_filter( 'send_plugin_theme_update_email', '__return_false' );
Use this alongside WP Umbrella or ManageWP to monitor that updates do not break anything.
11. Add custom columns to the posts list table
The default posts list shows title, author, categories, tags, date. For clients managing lots of content, adding a featured image column is incredibly useful:
add_filter( 'manage_posts_columns', function( $columns ) {
$new = [];
foreach ( $columns as $key => $val ) {
if ( $key === 'title' ) {
$new['thumbnail'] = __( 'Image' );
}
$new[ $key ] = $val;
}
return $new;
} );
add_action( 'manage_posts_custom_column', function( $column ) {
if ( $column === 'thumbnail' ) {
echo get_the_post_thumbnail( null, [ 60, 60 ] );
}
} );
12. Force SSL for admin and login
Even if your whole site is HTTPS, explicitly forcing SSL for admin reduces the chance of a misconfigured cache serving the login page over HTTP:
// In wp-config.php is cleaner, but can also go in functions.php
if ( ! defined( 'FORCE_SSL_ADMIN' ) ) {
define( 'FORCE_SSL_ADMIN', true );
}
13. Increase WP Cron reliability
WP Cron only fires when someone visits the site. On low-traffic sites this means scheduled tasks run hours late. Disable the built-in cron and set up a real server cron instead:
// In wp-config.php: define( 'DISABLE_WP_CRON', true );
Then add a server cron job (every 5 minutes):
*/5 * * * * wget -q -O - https://yourdomain.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
Or with WP-CLI:
*/5 * * * * cd /var/www/html && wp cron event run --due-now --quiet
14. Log slow database queries
When a page is slow and you suspect a plugin, log queries that take longer than 0.05 seconds:
// In wp-config.php — remove after debugging define( 'SAVEQUERIES', true );
Then in functions.php, dump them at the bottom of the page (admin only):
add_action( 'shutdown', function() {
if ( ! current_user_can( 'manage_options' ) ) return;
global $wpdb;
echo '<pre style="font-size:11px;background:#111;color:#0f0;padding:1rem;margin:2rem">';
foreach ( (array) $wpdb->queries as $q ) {
if ( $q[1] > 0.05 ) {
echo esc_html( round( $q[1], 4 ) . 's ' . $q[0] ) . "\n";
}
}
echo '</pre>';
} );
15. Remove the admin bar for non-admins
The WordPress toolbar appears for all logged-in users including subscribers. Remove it for everyone except administrators:
add_action( 'after_setup_theme', function() {
if ( ! current_user_can( 'manage_options' ) ) {
show_admin_bar( false );
}
} );
16. Disable file editing from the admin
The built-in theme and plugin editor is a security risk. If an attacker gets admin access, they can inject code directly. Disable it:
// Better placed in wp-config.php define( 'DISALLOW_FILE_EDIT', true );
17. Change the login logo and URL
For white-label client sites, replace the WordPress logo on the login screen with the client's logo:
add_action( 'login_enqueue_scripts', function() {
echo '<style>
.login h1 a {
background-image: url(' . get_stylesheet_directory_uri() . '/img/client-logo.png) !important;
background-size: contain !important;
width: 200px !important;
height: 60px !important;
}
</style>';
} );
add_filter( 'login_headerurl', function() {
return home_url();
} );
18. Preconnect to external origins
If your site loads Google Fonts, Gravatar, or a CDN, add preconnect hints to speed up DNS resolution:
add_action( 'wp_head', function() {
echo '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>';
echo '<link rel="dns-prefetch" href="//fonts.gstatic.com">';
}, 1 );
19. Clean up the dashboard widgets
Default WordPress dashboard is cluttered with Welcome panels, news feeds, and plugin ads. For client handoffs, clean it up:
add_action( 'wp_dashboard_setup', function() {
remove_meta_box( 'dashboard_welcome', 'dashboard', 'normal' );
remove_meta_box( 'dashboard_primary', 'dashboard', 'side' );
remove_meta_box( 'dashboard_quick_press', 'dashboard', 'side' );
remove_meta_box( 'dashboard_activity', 'dashboard', 'normal' );
} );
20. Add a maintenance mode
A simple maintenance mode that shows a 503 to everyone except admins:
add_action( 'template_redirect', function() {
if ( is_user_logged_in() && current_user_can( 'manage_options' ) ) return;
if ( defined( 'MAINTENANCE_MODE' ) && MAINTENANCE_MODE ) {
status_header( 503 );
nocache_headers();
echo '<!DOCTYPE html><html><body style="font-family:sans-serif;text-align:center;padding:5rem"><h1>Site under maintenance</h1><p>Back shortly.</p></body></html>';
exit;
}
} );
Toggle by adding define( 'MAINTENANCE_MODE', true ); to wp-config.php.
21. Register a custom post type cleanly
Most tutorials show bloated CPT registration. Here is a clean, minimal version:
add_action( 'init', function() {
register_post_type( 'project', [
'labels' => [ 'name' => 'Projects', 'singular_name' => 'Project' ],
'public' => true,
'has_archive' => true,
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt' ],
'menu_icon' => 'dashicons-portfolio',
'rewrite' => [ 'slug' => 'projects' ],
'show_in_rest'=> true, // required for Gutenberg support
] );
} );
After registering a new CPT, go to Settings > Permalinks and just click Save — WordPress needs to flush rewrite rules.
22. Disable REST API for unauthenticated users
If your site is not a headless app and does not need the REST API publicly accessible, restrict it:
add_filter( 'rest_authentication_errors', function( $result ) {
if ( ! empty( $result ) ) return $result;
if ( ! is_user_logged_in() ) {
return new WP_Error( 'rest_not_logged_in', 'REST API restricted.', [ 'status' => 401 ] );
}
return $result;
} );
Note: some plugins (Contact Form 7, WooCommerce) rely on the REST API. Test before deploying.
23. Auto-set featured image from first image in content
For blogs where authors forget to set a featured image, fall back to the first <img> in the post:
add_filter( 'post_thumbnail_html', function( $html, $post_id ) {
if ( $html ) return $html;
$post = get_post( $post_id );
$content = $post->post_content ?? '';
preg_match( '/<img[^>]+src=["\']([^"\']+)["\']/', $content, $m );
if ( ! empty( $m[1] ) ) {
return '<img src="' . esc_url( $m[1] ) . '" alt="">';
}
return $html;
}, 10, 2 );
24. Send transactional emails via SMTP
WordPress sends email through PHP mail by default, which gets flagged as spam by Gmail and Outlook. Use SMTP instead:
add_action( 'phpmailer_init', function( $phpmailer ) {
$phpmailer->isSMTP();
$phpmailer->Host = defined( 'SMTP_HOST' ) ? SMTP_HOST : '';
$phpmailer->SMTPAuth = true;
$phpmailer->Port = defined( 'SMTP_PORT' ) ? SMTP_PORT : 587;
$phpmailer->Username = defined( 'SMTP_USER' ) ? SMTP_USER : '';
$phpmailer->Password = defined( 'SMTP_PASS' ) ? SMTP_PASS : '';
$phpmailer->SMTPSecure = 'tls';
$phpmailer->From = defined( 'SMTP_FROM' ) ? SMTP_FROM : get_option( 'admin_email' );
$phpmailer->FromName = get_option( 'blogname' );
} );
Define SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM in wp-config.php. Never hardcode credentials in theme files.
25. Hide WordPress from the login error message
The default login error says either "The username is incorrect" or "The password you entered is incorrect" — revealing which half of the credentials is wrong. Neutralise it:
add_filter( 'login_errors', function() {
return 'Incorrect username or password.';
} );
Organising these snippets for client projects
Rather than dumping everything into functions.php, I recommend a site-specific plugin approach. Create /wp-content/plugins/site-tweaks/site-tweaks.php with a minimal plugin header, then require individual files per category:
<?php /** * Plugin Name: Site Tweaks * Description: Site-specific customisations. */ require_once __DIR__ . '/inc/performance.php'; require_once __DIR__ . '/inc/security.php'; require_once __DIR__ . '/inc/admin.php';
This survives theme switches and is easier to version-control and hand off to another developer.
Related reading
// new_articles
Get notified when new guides drop
Practical WordPress guides from a working agency owner. No filler. Unsubscribe any time.
Was this article helpful?
Thanks for the feedback!