How to Schedule Tasks with WP-Cron in WordPress

Scheduling recurring background tasks with WP-Cron

WP-Cron is WordPress’s pseudo-cron system. Unlike a real system cron, it fires when a page is loaded — meaning tasks run approximately on schedule, not exactly. For most WordPress tasks (cleanup, emails, cache rebuilds) this is sufficient. For precise timing, combine WP-Cron with a real system cron.

Problem: How do you schedule a recurring background task in WordPress that runs reliably without a system cron job?

Solution: Register a custom interval, schedule the event on activation, hook your function to it, and clean up on deactivation:

// 1. Add a custom interval (every 15 minutes)
add_filter( 'cron_schedules', 'add_fifteen_min_interval' );

function add_fifteen_min_interval( $schedules ) {
    $schedules['fifteen_minutes'] = [
        'interval' => 15 * MINUTE_IN_SECONDS,
        'display'  => __( 'Every 15 Minutes', 'textdomain' ),
    ];
    return $schedules;
}

// 2. Schedule the event on plugin activation
register_activation_hook( __FILE__, 'schedule_my_task' );

function schedule_my_task() {
    if ( ! wp_next_scheduled( 'my_hourly_task' ) ) {
        wp_schedule_event( time(), 'fifteen_minutes', 'my_hourly_task' );
    }
}

// 3. Hook your callback to the event
add_action( 'my_hourly_task', 'run_my_task' );

function run_my_task() {
    // your logic here, e.g. delete old transients
    global $wpdb;
    $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '%_transient_timeout_%' AND option_value < UNIX_TIMESTAMP()" );
}

// 4. Remove the scheduled event on plugin deactivation
register_deactivation_hook( __FILE__, function() {
    wp_clear_scheduled_hook( 'my_hourly_task' );
} );

To trigger the event immediately from the command line (useful for testing):

wp cron event run my_hourly_task

NOTE: Always check wp_next_scheduled() before calling wp_schedule_event() — without this guard, each activation adds a duplicate event. Always call wp_clear_scheduled_hook() on deactivation to prevent orphaned cron entries.