How to Use Cron in WordPress (WP Cron and Action Scheduler)

In this tutorial, I will show you how you can use both WP Cron and Action Scheduler when developing plugins for WordPress.

WP Cron

WP Cron – is a standard way of how WordPress handles scheduled tasks, for example, when you create a scheduled post, or when the WordPress core checks for updates.

Many third-party plugins also rely on WP Cron. For example, my plugins that rely on WP Cron are Simple WP Crossposting (only when syncing posts in bulk), Multisite Indexer, and Simple Mailchimp Sync (re-sync feature).

Also, if you want to dive into how WP Cron works under the hood, I recommend checking my article about WP Cron performance. If you think that it is not working on your site or not working as expected, check my another article about how to fix it.

But today we’re going to learn how tap into it when developing WordPress plugins or themes.

Creating a scheduled task

It can be done pretty easily with the wp_schedule_single_event() function. The only thing is that the function accepts not a callback PHP function but a callback WordPress hook.

Let’s jump straight to the example. Since I want to show you a useful one, let’s say, we need to auto-complete WooCommerce orders after 2 days they were created.

add_action( 'woocommerce_thankyou', 'rudr_auto_complete_orders' );

function rudr_auto_complete_orders( $order_id ) {
 
	wp_schedule_single_event( 
		time() + 2 * DAY_IN_SECONDS, // after 2 days 
		'rudr_auto_complete_order', 
		array( $order_id ) 
	);
 
}
 
add_action( 'rudr_auto_complete_order', 'rudr_auto_complete_order' );
function rudr_auto_complete_order( $order_id ) {
 
	$order = wc_get_order( $order_id );
	if( ! $order ) {
		return;
	}
 
	if( 'processing' === $order->get_status() ) {
		$order->update_status( 'completed' );
	}
 
}

Some notes about the code above:

  • I don’t think that woocommerce_thankyou is a reliable hook here, however, since it allows us to use an $order_id inside its callback function, it is ok for the sake of the example.
  • As a first argument, we need to pass to wp_schedule_single_event() a timestamp when the task needs to be run. I used the DAY_IN_SECONDS constant which means 86400 seconds, time() + 2 * DAY_IN_SECONDS means two days since now. More about time constants you can read below.
  • The callback function and hook are pretty straightforward, I think.

Creating a recurring scheduled task

When we need to create a recurring scheduled action in WordPress, we use the wp_schedule_event() function. This function also has an argument where you pass a timestamp when this function should run the first time, after that it will be executed after a specific timeframe repeatedly until it is stopped.

Let’s say, that we have the same task (to auto-complete orders after two days) but this time we need to use the wp_schedule_event() function. How we can do that?

add_action( 'woocommerce_thankyou', function( $order_id ) {
 	
	if( ! wp_next_scheduled( 'rudr_auto_complete_all_orders' ) ) {
		wp_schedule_event( 
			time() + DAY_IN_SECONDS * 2, // after 2 days 
			'every2days', // custom time interval, or you can use 'daily'
			'rudr_auto_complete_all_orders' 
		);
	}
 
} );

add_action( 'rudr_auto_complete_all_orders', 'rudr_auto_complete_all_orders' );
function rudr_auto_complete_all_orders() {

	$order_ids = wc_get_orders( 
		array(
			'status' => array( 'wc-processing' ),
			'date_created' => '>' . ( time() - 2 * DAY_IN_SECONDS ),
			'created_via' => 'checkout',
			'return' => 'ids',
		) 
	);
	
	if( $order_ids ) {
		foreach( $order_ids as $order_id ) {
			$order = wc_get_order( $order_id );
			$order->update_status( 'completed' );
		}
	}

}

Time constants in WordPress

When creating your scheduled tasks you can use predefined WordPress constants for your convenience. Here is the list of them:

ConstantMeaning
MINUTE_IN_SECONDS60
HOUR_IN_SECONDS3600 60 * MINUTE_IN_SECONDS
DAY_IN_SECONDS86400 24 * HOUR_IN_SECONDS
WEEK_IN_SECONDS604800 7 * DAY_IN_SECONDS
MONTH_IN_SECONDS2592000 30 * DAY_IN_SECONDS
YEAR_IN_SECONDS31104000 365 * DAY_IN_SECONDS

Custom time intervals

By default, WordPress has the following time intervals:

Time interval nameValue
hourlyHOUR_IN_SECONDS
twicedaily12 * HOUR_IN_SECONDS
dailyDAY_IN_SECONDS
weeklyWEEK_IN_SECONDS

But in our example above, we need 2 * DAY_IN_SECONDS, which, as you can see, doesn’t exist. How can we use it?

Easily – we can register any custom time interval using the hook cron_schedules.

add_filter( 'cron_schedules', function( $intervals ) {

	$intervals[ 'every2days' ] = array(
		'interval' => 2 * DAY_IN_SECONDS,
		'display' => 'Every 2 days',
	);
	return $intervals;

} );

View and manage all scheduled tasks

The scheduled tasks are stored in the WordPress option cron. In theory, you can get and print them using either get_option( 'cron' ) or the _get_cron_array() function. However, I do not recommend you do that, instead, let’s install a free plugin that allows us to view and manage cron jobs.

Today my go-to plugin for this purpose is – Advanced Cron Manager.

How to manage WP Cron jobs in WordPress admin dashboard.

Removing tasks from the schedule

For unscheduling tasks, we can just use two WordPress functions: wp_unschedule_event() and also wp_clear_scheduled_hook().

Right now, let’s try to use these functions to remove the tasks we scheduled before.

Let’s try the wp_unschedule_event() function first.

wp_unschedule_event( 
	wp_next_scheduled( 'rudr_auto_complete_order' ), 
	'rudr_auto_complete_order',
	array( $order_id )
);

$next_scheduled = wp_next_scheduled( 'rudr_auto_complete_all_orders' );
if( $next_scheduled ) {
	wp_unschedule_event( $next_scheduled, 'rudr_auto_complete_all_orders' );
}

We need to use the wp_next_scheduled() function to obtain the first argument for the wp_unschedule_event() function which is a timestamp of the next event occurrence.

Examples of the usage of the wp_clear_scheduled_hook() function:

wp_clear_scheduled_hook( 'rudr_auto_complete_order', array( $order_id ) );

wp_clear_scheduled_hook( 'rudr_auto_complete_all_orders' );

Action Scheduler

Action Scheduler – is a WooCommerce default way of handling scheduled tasks. Since WooCommerce may have a lot of them (for example, your store may have hundreds of orders per day, and each order has its own scheduled tasks), using the standard WP Cron may be not effective.

I don’t want to dive much into the details here, but the difference between WP Cron and Action Scheduler is that the latter uses just a single cron job action_scheduler_run_queue which runs every minute (can be changed).

My plugins that rely on Action Scheduler are Order Sync for WooCommerce (because of the way the woocommerce_update_order hook works) and Inventory Sync for WooCommerce.

Scheduling tasks with Action Scheduler

Action Scheduler has its own documentation, so we can forget about using functions like wp_scheduler_event() or something.

Let’s do the same examples with the auto-completion of WooCommerce orders we tried before.

Not recurring task:

as_schedule_single_action( 
	time() + 2 * DAY_IN_SECONDS,
	'rudr_auto_complete_order', // $hook
	array( $order_id ),
	'rudr-auto-complete-orders', // $group
	true // $unique
);

Recurring task:

as_schedule_recurring_action( 
	time() + 2 * DAY_IN_SECONDS, 
	2 * DAY_IN_SECONDS, 
	'rudr_auto_complete_all_orders', // $hook
	array(), 
	'rudr-auto-complete-orders', // $group
	true // $unique
);

So, what do we have here:

  • We have a new $group parameter (in my examples it has rudr-auto-complete-orders value), you can find it displayed on the page with the scheduled tasks and you will also need to use it when unscheduling tasks.
  • There is no need to double-check whether the same tasks have already been scheduled (using as_next_scheduled_action(), for example), because we have the $unique parameter which we can set to true.
  • Also, there is no need to register a custom time interval, we can just pass a time interval as a UNIX timestamp.

Unscheduling tasks in Action Scheduler

It can be done with the help of the following two functions – as_unschedule_action() and as_unschedule_all_actions().

For example, to remove the next occurrence of a scheduled action:

as_unschedule_action( 
	'rudr_auto_complete_order', // $hook
	array( $order_id ), // $args
	'rudr-auto-complete-orders' // $group
);

Or to remove all occurrences:

as_unschedule_all_actions( 
	'rudr_auto_complete_all_orders', // $hook
	array(), // $args
	'rudr-auto-complete-orders' // $group
);

Action Scheduler can not remove its actions directly from the callback function.

But what can we do if we need it so much? The first thought may be to remove the scheduled actions directly from the database table wp_actionscheduler_actions or at least to change their statuses there. In my case, it resulted in failed actions (when changing statuses) and fatal errors in logs (when completely removing them), so if you have found a solution to how to do it directly from the database, let me know in the comments below. As for now, I will show you another way – it is to create a new, non-recurring action that will unschedule our target action.

For this purpose, it will be convenient to use the as_enqueue_async_action() function – it creates a scheduled action and fires it as soon as possible.

// we use this part inside the callback function
// (particulary, when we finished and want to cancel the recurring action)
as_enqueue_async_action( 'rudr_cancel_action', array(), '', true );
add_action( 'rudr_cancel_action', function() {
	as_unschedule_action( 
		'rudr_auto_complete_all_orders', 
		array(), 
		'rudr-auto-complete-orders' 
	);
} );

View and manage scheduled tasks

Good news – you don’t need to install any extra plugins for that, everything can be managed in a standard WooCommerce settings.

You just need to go to WooCommerce > Status > Scheduled Actions:

Manage scheduled actions in WooCommerce
Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 15 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X