SponsoredHosinger Special Offer

WP+ WordPress Plugin Development

Build WordPress plugins from scratch: actions and filters, shortcodes, settings pages, custom post types, meta boxes, AJAX, REST endpoints, custom tables, cron and blocks.

Transients and WP-Cron

WordPress Plugin Development Lesson 21 of 27 ~3 min read

Overview

Transients cache the result of slow work — an API call, a heavy query — for a set time, and WP-Cron runs your functions on a schedule, such as a daily cleanup; together they keep a plugin fast and its data tidy.

Transients: cache with an expiry

<?php
function gdc_books_get_stats() {
    $stats = get_transient( 'gdc_books_stats' );

    if ( false === $stats ) {
        // Cache miss: do the expensive work once...
        $stats = array(
            'books'      => (int) wp_count_posts( 'gdc_book' )->publish,
            'genres'     => (int) wp_count_terms( array( 'taxonomy' => 'gdc_genre' ) ),
            'favourites' => gdc_books_count_favourites(),
        );

        // ...then keep it for 12 hours.
        set_transient( 'gdc_books_stats', $stats, 12 * HOUR_IN_SECONDS );
    }

    return $stats;
}
  • get_transient() returns false when the value is missing or expired, so test with false === — a cached 0 or empty array is a valid hit.
  • Use the time constants: MINUTE_IN_SECONDS, HOUR_IN_SECONDS, DAY_IN_SECONDS, WEEK_IN_SECONDS.
  • A transient may disappear before it expires — an object cache can evict it. Treat it as a cache, never as storage.

Invalidating at the right moment

Twelve hours is a maximum, not a requirement. Clear the cache the moment its data changes:

<?php
function gdc_books_clear_stats() {
    delete_transient( 'gdc_books_stats' );
}
add_action( 'save_post_gdc_book', 'gdc_books_clear_stats' );
add_action( 'deleted_post', 'gdc_books_clear_stats' );

WP-Cron: scheduled tasks

<?php
// 1. Schedule on activation (only once).
function gdc_books_schedule_cleanup() {
    if ( ! wp_next_scheduled( 'gdc_books_daily_cleanup' ) ) {
        wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'gdc_books_daily_cleanup' );
    }
}
register_activation_hook( __FILE__, 'gdc_books_schedule_cleanup' );

// 2. The job itself: an ordinary action.
function gdc_books_run_cleanup() {
    global $wpdb;
    $wpdb->query(
        $wpdb->prepare(
            "DELETE FROM {$wpdb->prefix}gdc_books_favourites WHERE book_id NOT IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = %s)",
            'gdc_book'
        )
    );
    delete_transient( 'gdc_books_stats' );
}
add_action( 'gdc_books_daily_cleanup', 'gdc_books_run_cleanup' );

// 3. Unschedule on deactivation.
function gdc_books_unschedule_cleanup() {
    wp_clear_scheduled_hook( 'gdc_books_daily_cleanup' );
}
register_deactivation_hook( __FILE__, 'gdc_books_unschedule_cleanup' );

Built-in recurrences are hourly, twicedaily, daily and weekly. Add your own with the cron_schedules filter:

<?php
add_filter( 'cron_schedules', function ( $schedules ) {
    $schedules['gdc_every_fifteen_minutes'] = array(
        'interval' => 15 * MINUTE_IN_SECONDS,
        'display'  => __( 'Every 15 minutes', 'gdc-books' ),
    );
    return $schedules;
} );

WP-Cron is not a real cron

WP-Cron only runs when someone visits the site. A quiet site might run a "daily" job hours late; a busy one checks on every page load. For reliable timing, disable the visit trigger and call WP-Cron from the server's real cron:

// In wp-config.php
define( 'DISABLE_WP_CRON', true );
# Server crontab: run due events every 5 minutes
*/5 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
Tip: Install the WP Crontrol plugin to see every scheduled event, run one immediately, and check whether yours is registered.

Practice

  • Cache the stats with a transient and clear it on save_post_gdc_book.
  • Schedule the daily cleanup on activation and see it in WP Crontrol.
  • Deactivate the plugin and confirm the event is gone.
Create a free account to save which lessons you've finished. Save my progress