Auto-clean expired transients
- WordPress
WordPress doesn’t proactively delete expired transients. They accumulate in wp_options as dead rows that bloat the table and slow down queries — especially the autoload query that runs on every page load. Sites without Redis or Memcached are most affected.
<?php
/**
* Auto-Clean Expired Transients
*
* WordPress does not proactively delete expired transients — they sit in
* wp_options until something requests them. On sites without an object cache,
* this leads to thousands of stale rows that slow down autoload queries.
* This snippet schedules a daily cleanup via WP-Cron to purge them.
*
* Usage: Add to functions.php. Activates on next page load.
*
* @category wordpress
* @language php
*
* @author Adame Dahmani https://adame.io
* @support Support my family project: https://adame.io/fundraiser
*/
add_action( 'init', function () {
if ( ! wp_next_scheduled( 'snippet_clean_transients' ) ) {
wp_schedule_event( time(), 'daily', 'snippet_clean_transients' );
}
} );
add_action( 'snippet_clean_transients', function () {
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"DELETE a, b FROM {$wpdb->options} a
INNER JOIN {$wpdb->options} b ON b.option_name = CONCAT('_transient_', SUBSTRING(a.option_name, 20))
WHERE a.option_name LIKE %s
AND a.option_value < %d",
$wpdb->esc_like( '_transient_timeout_' ) . '%',
time()
)
);
} );This schedules a daily WP-Cron job that deletes expired transient pairs (the value row and its timeout row) in a single query. On a busy site with plugins that create many transients, this can clean up thousands of rows per day.