The WordPress Heartbeat API keeps the browser and the server in touch: every 15 to 60 seconds an open admin tab sends a request to admin-ajax.php. It powers post locking, the “session expired” login popup and live notices from plugins. On shared hosting, a few editors with many open tabs add up to a lot of PHP requests, and some hosts throttle or suspend accounts for it.
This snippet modifies the Heartbeat API without a plugin: it slows it down everywhere, switches it off on the front end, and can also switch it off in wp-admin while keeping it on the editor screens where it matters.
The snippet
<?php
/**
* Slow down the WordPress Heartbeat API and switch it off where it is not needed.
*/
// ===== Config =====
function dpc_heartbeat_config(): array {
$config = array(
// Seconds between Heartbeat requests, 15-120. WordPress default: 60.
'interval' => 120,
// Stop Heartbeat on the front end (only if no other script there depends on it).
'disable_front' => true,
// Stop Heartbeat in wp-admin, except on the post editor screens.
// This also turns off the "Your session has expired" login popup on those screens.
'disable_admin' => false,
);
// ===== End of config =====
return (array) apply_filters( 'dpc_heartbeat_config', $config );
}
add_filter( 'heartbeat_settings', 'dpc_heartbeat_interval' );
function dpc_heartbeat_interval( $settings ) {
$config = dpc_heartbeat_config();
if ( ! empty( $config['interval'] ) ) {
$settings['interval'] = max( 15, min( 120, (int) $config['interval'] ) );
}
return $settings;
}
add_action( 'wp_enqueue_scripts', 'dpc_heartbeat_front', PHP_INT_MAX );
function dpc_heartbeat_front(): void {
$config = dpc_heartbeat_config();
if ( ! empty( $config['disable_front'] ) ) {
// Dequeue, not deregister: scripts that depend on Heartbeat still get it.
wp_dequeue_script( 'heartbeat' );
}
}
// wp-admin loads Heartbeat through the session-expired check (wp-auth-check) on every screen.
add_filter( 'wp_auth_check_load', 'dpc_heartbeat_admin', 10, 2 );
function dpc_heartbeat_admin( $show, $screen = null ) {
$config = dpc_heartbeat_config();
if ( empty( $config['disable_admin'] ) || ! $screen instanceof WP_Screen ) {
return $show;
}
// Keep it on the editor: post locks and the block editor need it.
return 'post' === $screen->base ? $show : false;
}
add_action( 'admin_enqueue_scripts', 'dpc_heartbeat_admin_dequeue', PHP_INT_MAX );
function dpc_heartbeat_admin_dequeue(): void {
$config = dpc_heartbeat_config();
$screen = get_current_screen();
if ( ! empty( $config['disable_admin'] ) && $screen && 'post' !== $screen->base ) {
wp_dequeue_script( 'heartbeat' );
}
}
How it works
heartbeat_settingssets the options WordPress prints forheartbeat.js. The snippet setsintervalto your value, limited to 15–120 seconds. Post locks expire after 150 seconds, so a longer interval would let two people edit the same post.- On the front end the snippet dequeues
heartbeatat the very end ofwp_enqueue_scripts. It does not deregister it: a plugin script that lists Heartbeat as a dependency still loads it and keeps working. - In wp-admin, Heartbeat is loaded on every screen by the session-expired check,
wp-auth-check. Withdisable_adminon, thewp_auth_check_loadfilter turns that check off on every screen except the post editor, and Heartbeat is dequeued there too. - The older version of this post deregistered
heartbeatoniniteverywhere. WordPress still registerswp-auth-checkandautosavewith Heartbeat as a dependency, so every admin page logged a “called incorrectly” notice for missing dependencies, and post locks stopped working in the editor.
Add it with Scripts Organizer
In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor, edit the config and publish. Leave Action/Hook empty.
The settings are needed on the front end and in wp-admin, so Everywhere is the right trigger; Conditions would only cover the front end.
The same code also works unchanged in a small plugin or an mu-plugin.
Customise it
- Only slow it down: set
'disable_front' => falseand keepdisable_adminoff. Then only the interval changes. - Fewest requests: set
'interval' => 120and'disable_admin' => true. The editor screens keep Heartbeat at two minutes. - Override the config from another snippet with the
dpc_heartbeat_configfilter, for example to switch it off in wp-admin only for some roles.
Gotchas
- With
disable_adminon, users whose login expires on a screen without Heartbeat find out only when they save. The editor screens still show the login popup. - WooCommerce, page builders, membership and chat plugins sometimes use Heartbeat for live updates. If something stops refreshing, set the interval back to 60 first.
- The browser itself already slows Heartbeat down to 120 seconds in background tabs, so a lower interval mostly matters for the tab people are looking at.
- To see what still loads, open the Network tab and filter on
admin-ajax.php, or list everything hooked toheartbeat_received.
In the Scripts Organizer library Import it with one click from Scripts Organizer.