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_thankyouis a reliable hook here, however, since it allows us to use an$order_idinside 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 theDAY_IN_SECONDSconstant which means86400seconds,time() + 2 * DAY_IN_SECONDSmeans 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:
| Constant | Meaning |
|---|---|
MINUTE_IN_SECONDS | 60 |
HOUR_IN_SECONDS | 3600 60 * MINUTE_IN_SECONDS |
DAY_IN_SECONDS | 86400 24 * HOUR_IN_SECONDS |
WEEK_IN_SECONDS | 604800 7 * DAY_IN_SECONDS |
MONTH_IN_SECONDS | 2592000 30 * DAY_IN_SECONDS |
YEAR_IN_SECONDS | 31104000 365 * DAY_IN_SECONDS |
Custom time intervals
By default, WordPress has the following time intervals:
| Time interval name | Value |
|---|---|
hourly | HOUR_IN_SECONDS |
twicedaily | 12 * HOUR_IN_SECONDS |
daily | DAY_IN_SECONDS |
weekly | WEEK_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.

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
$groupparameter (in my examples it hasrudr-auto-complete-ordersvalue), 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$uniqueparameter which we can set totrue. - 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:

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
Thank you for your detailed articles. Just jumped on Action Scheduler and was missing a point on how to schedule recurring tasks :)
I have one issue though. Maybe you ran into something similar?
1) I have disabled the default WordPress cron by adding it to wp-config.php:
define('DISABLE_WP_CRON', true);2) I have configured system cron to call wp-cron.php every minute:
* * * * * sudo -u www-data /usr/local/lsws/lsphp83/bin/lsphp /var/www/html/wp-cron.php >/dev/null 2?&1
3) I have configured a recurring task every 3 minutes with Action Scheduler as:
As a test, I just logged some text and timestamp to the WooCommerce logger. And I can see that instead of being called every 3 minutes it is being called and executed every 4 minutes.
However, if I use
wp_schedule_event()with a custom interval defined for 3 minutes and log something – in such case I can see that the event happens strictly every 3 minutesP.S. Even if I disable default Action Scheduler default Queue runner and call it every minute via WP CLI – I still get executed recurring events every 4th minute instead of every 3rd :(
* * * * * sudo -u www-data /usr/local/bin/wp action-scheduler run –path=”/var/www/html”
Eventually, I have managed to achieve desired behavior by using
as_schedule_cron_actioninstead ofas_schedule_recurring_action. For sure, something is escaping my understanding of Action Scheduler and the bahavior of latter hook is correct.as_schedule_cron_action(time() + 180, '*/3 * * * *', 'my_hook', array(), 'wm-test', true);Hey Denis,
Thank you for your comments. Interesting indeed, maybe someone else faced with this issue and will reply to your commens, hopefully with a solution.
Maybe you got a chance to understand this strange behavior? :D