You need to clear transients in WordPress when a plugin keeps serving stale API data, when a debug session leaves thousands of _transient_ rows in wp_options, or when a migration copied cached values from another site. WordPress already deletes *expired* transients once a day. It has no button for clearing all of them, so people end up running SQL in phpMyAdmin and forgetting the object cache.
This snippet adds a Clear transients item to the admin bar for administrators. One click deletes every transient row from the options table, removes the matching entries from the options cache, and flushes the transient groups when a persistent object cache (Redis, Memcached) supports that. Then it sends you back to the page you were on, with a notice showing how many rows it removed.
The snippet
Put it in a small plugin, an mu-plugin or your theme’s functions.php. It also works pasted into one Scripts Organizer PHP block set to run Everywhere.
<?php
/**
* Clear all WordPress transients from the admin bar.
*/
function dpc_clear_transients(): array {
global $wpdb;
$result = [ 'rows' => 0, 'cache_flushed' => false ];
// With a persistent object cache, transients live in cache groups, not the database.
if ( wp_using_ext_object_cache() && wp_cache_supports( 'flush_group' ) ) {
wp_cache_flush_group( 'transient' );
wp_cache_flush_group( 'site-transient' );
$result['cache_flushed'] = true;
}
// Transient keys that must survive, e.g. the cron lock.
$keep = (array) apply_filters( 'dpc_clear_transients_keep', [ 'doing_cron' ] );
$names = $wpdb->get_col(
$wpdb->prepare(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
$wpdb->esc_like( '_transient_' ) . '%',
$wpdb->esc_like( '_site_transient_' ) . '%'
)
);
$names = array_filter(
$names,
static function ( string $name ) use ( $keep ): bool {
$key = preg_replace( '/^_(site_)?transient_(timeout_)?/', '', $name );
return ! in_array( $key, $keep, true );
}
);
foreach ( array_chunk( $names, 500 ) as $chunk ) {
$placeholders = implode( ',', array_fill( 0, count( $chunk ), '%s' ) );
$result['rows'] += (int) $wpdb->query(
$wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name IN ($placeholders)", $chunk )
);
foreach ( $chunk as $name ) {
wp_cache_delete( $name, 'options' );
}
}
// Transients without expiry are autoloaded, so drop the cached alloptions too.
wp_cache_delete( 'alloptions', 'options' );
wp_cache_delete( 'notoptions', 'options' );
return $result;
}
add_action( 'admin_bar_menu', 'dpc_clear_transients_admin_bar', 100 );
function dpc_clear_transients_admin_bar( WP_Admin_Bar $bar ): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$bar->add_node(
[
'id' => 'dpc-clear-transients',
'title' => esc_html__( 'Clear transients', 'dpc' ),
'href' => wp_nonce_url( admin_url( 'admin-post.php?action=dpc_clear_transients' ), 'dpc_clear_transients' ),
]
);
}
add_action( 'admin_post_dpc_clear_transients', 'dpc_handle_clear_transients' );
function dpc_handle_clear_transients(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to clear transients.', 'dpc' ), 403 );
}
check_admin_referer( 'dpc_clear_transients' );
$result = dpc_clear_transients();
$back = wp_get_referer() ?: admin_url();
wp_safe_redirect(
add_query_arg(
[
'dpc_transients_cleared' => $result['rows'],
'dpc_cache_flushed' => (int) $result['cache_flushed'],
],
$back
)
);
exit;
}
add_action( 'admin_notices', 'dpc_clear_transients_notice' );
function dpc_clear_transients_notice(): void {
if ( ! isset( $_GET['dpc_transients_cleared'] ) || ! current_user_can( 'manage_options' ) ) {
return;
}
$message = sprintf(
/* translators: %d: number of deleted database rows. */
esc_html__( 'Transients cleared: %d database rows deleted.', 'dpc' ),
absint( wp_unslash( $_GET['dpc_transients_cleared'] ) )
);
if ( ! empty( $_GET['dpc_cache_flushed'] ) ) {
$message .= ' ' . esc_html__( 'Object cache transient groups flushed too.', 'dpc' );
}
wp_admin_notice( $message, [ 'type' => 'success', 'dismissible' => true ] );
}
// Remove the notice arguments from the address bar after the page loads.
add_filter( 'removable_query_args', 'dpc_clear_transients_removable_args' );
function dpc_clear_transients_removable_args( array $args ): array {
$args[] = 'dpc_transients_cleared';
$args[] = 'dpc_cache_flushed';
return $args;
}
How it works
dpc_clear_transients()reads every option name that starts with_transient_or_site_transient_, which also covers the_timeout_rows. The underscores go through$wpdb->esc_like(), so_is matched literally and not as a wildcard.- Rows are deleted 500 at a time with a prepared
IN ()query, so a table with tens of thousands of transients needs only a few queries instead of onedelete_option()call per row. - After deleting, it clears each name from the
optionscache group, plusalloptionsandnotoptions. Transients saved without an expiry are autoloaded. If you skip this step,get_transient()can keep returning the old value for the rest of the request, or until your persistent cache expires. - With a persistent object cache, WordPress keeps transients in the
transientandsite-transientcache groups, not in the database. When the cache backend supportsflush_group, those two groups are flushed and the rest of the cache is left alone. - The admin bar link points to
admin-post.phpand carries a nonce. The handler checksmanage_optionsandcheck_admin_referer()before deleting anything, so nobody can trigger it from a forged link.
Customise it
To protect a transient that is expensive to rebuild, add its key (without the _transient_ prefix) to the keep list:
add_filter( 'dpc_clear_transients_keep', function ( array $keep ): array {
$keep[] = 'my_slow_api_response';
return $keep;
} );
Prefer the terminal? WP-CLI does the same job and also handles the object cache:
# Only the expired ones (what WordPress does daily)
wp transient delete --expired
# Everything, including site transients
wp transient delete --all
To run this on a schedule, call dpc_clear_transients() from your own cron hook. Daily is rarely needed, because core already deletes expired transients every day. You can check that event with WP-CLI’s cron list.
Gotchas
- Clearing transients doesn’t fix a slow site by itself. The first requests afterwards are slower while plugins rebuild their caches. Use it for stale data or a bloated
wp_options, not as a speed button. - If a transient comes back seconds later, something is setting it on every request. That is often a plugin calling
set_transient()with no expiry. See why set_transient seems not to work for how caching layers and expiry interact. - On multisite this clears the current site’s options table only. Network-wide site transients live in
wp_sitemetaand are left alone. - If your object cache can’t flush a single group, the snippet doesn’t flush the whole cache for you. Flush it from your cache plugin or with
wp cache flush. - The cron lock
doing_cronis kept by default, so clearing transients doesn’t start a second cron run in parallel.
For more admin bar tools, see admin shortcuts in the WordPress admin bar.
Snippet poll. Should this snippet be in the Scripts Organizer library for one-click import? 10 votes put it there.
0 of 10 votes