Plugin Resource for WordPress and WPMU
Scheduling with WordPress Cron Functions

I’ve been struggling to understand exactly how the cron functions of WordPress work.

I’ve been working on a plugin for some friends of mine that need a mailing list manager, this plugin in turn requires the ability to schedule tasks. So, I spent a long time puzzling over the various bits of documentation and posts on other blogs about WP-Cron, pouring over code and experimenting with my own, until I finally got a working plugin.

And while I’m sure that the information that is out there now about Cron functions is adequate for some, it’s clear to me that others may need a different set of explanations, which is where this blog post comes in. I’m going to attempt to lay out the WP-Cron world in a different way in hopes of improving slightly on what’s out there.

Let me start by saying that the best article I’ve read on the subject of crons is definitely “Timing is everything: scheduling in WordPress,” by Glenn Slaven. I highly recommend that, even before reading this article, you take a gander at that one. There are really just a few things on which I would like to expand and revise slightly, because they threw me off. I’m also slightly reorganizing the order of discussion into something more akin to a step-by-step set of instructions.

The Scenario

The example we’re going to work with is the mailing list manager plugin I’m developing. We’re not going to get into too much of the actual code of that plugin, but the scenario is doubtless a common need. We’ll be assuming that we’re working with a blog which has many, many users. (real world example: 400+) We want to keep our users up to date with periodic email blasts. Rather than those blasts being separated from the regular content, we want to specify that certain posts will become both posts on the site and email messages.

But of course, sending emails out to 400+ recipients at once is going to make the SMTP server very, very suspicious. In order to avoid problems with the mail server, we need to throttle the messages by sending blasts every twenty minutes ((or at least, approximately twenty minutes, remember that WordPress cannot be exact about timing)) until the last user group has been emailed. To do this, we will be using the wp_schedule_single_event() function within the WP-Cron suite of tools.

Scheduling: What You’ll Need

In order to schedule an event of any kind, you’re going to need a few basic components. We’ll get into more detail further down the line, but it’s important to be able to identify the landmarks as we proceed:

  • Your Cron Function ~ This is the actual function that does the stuff you want when the scheduled event is fired
  • A Custom Action Hook ~ Here’s where we get into the nitty-gritty of how WordPress operates. It’s very simple, actually, but understanding how to create custom action hooks is key to understanding WordPress cron functions. And a great deal more about WordPress!!
  • Your Scheduling Function ~ This is the function that initially assigns the scheduled task.


Your Cron Function

my_scheduled_function([$arg1, [$arg2, [$arg3. . . ]]])

This function lies at the heart of your scheduling world! It is here that you put all the stuff you want to get done at a specified time. In our example, this is where you actually send the email to your latest batch of users. Note that this function can include an arbitrary number of arguments. We’ll get into how you pass those arguments on to the function later. For now, just notice that this is a standard function like pretty much any other. In fact, in the case of the mailing list manager I created, it’s as simple a function as you could ask for:

function bhd_cron_send($sender_email, $subject, $mail_text, $send_headers) {
	wp_mail($sender_email, $subject, $mail_text, $send_headers);
}

It’s probably advisable to create your function and make sure it works without adding in the cron functionality first. This is because a) there’s no need adding extra complexity to the system until you’re sure the function works and b) you’ll need to know how many arguments will need to be passed to the function and in what order.

Your Custom Action Hook


This is what really threw me. I’ve never created my own custom action hooks and never really understood them, but you’ll need to in order to get things done with cron, so this is important. Creating a custom hook is as simple as typing the following:

add_action('my_custom_hook', 'some_function' [, $priority, [$num_args]]);

WordPress assumes that anything added as the first argument exists without any additional registration of that new hook. In other words, ‘my_custom_hook’ actually is the declaration of the action hook in this example. The second argument should be a valid function name and will be the function associated with ‘my_custom_hook’ whenever ‘my_custom_hook’ is invoked anywhere in the system.

Obviously, since you’re writing your custom action, you’ll also need to invoke that custom action somewhere. That’s the last piece of the puzzle, which is explained down the page a bit.

But before we leave this topic, it’s worth pointing out that, just like any other action, it’s entirely possible to hook more than one function into the custom action you’ve created. It’s also possible to invoke the hook somewhere else in your code, separate from your Cron function. This bit of WordPress theory may prove helpful to you down the line, so it’s just an FYI.

The last two arguments of the add_action function are important. Recall the number of arguments you needed for your scheduling function. That’s the number you’re going to want to put in the $num_args add_action parameter. Otherwise, add_action doesn’t look for and doesn’t accept any arguments provided, which will doubtless render your function either inoperable or ineffective.

Late Update: Per jeremyclark in the comments, this is not really the reason to mess with priority! Thanks Jeremy! If you’re function is particularly computationally expensive (if it’s complex and relies on a lot of processing), it might be worth it to back the priority down a bit. You can do this by setting the $priority argument. The default is 10. In our case, the actual function is trivial, so this is what the add_action looks like for our mailing list:

add_action( 'bhd_cron_send_hook', 'bhd_cron_send', 10, 4 );


Your Scheduling Function


Finally, there is the function which will do the scheduling. Depending on the complexity of your plugin, this may not be very complex at all. But the one thing that is required of your function is that it bundle up all the arguments to your cron function into an associative array in the proper order. In our example, the parameters for our sending function are $sender_email, $subject, $mail_text, $send_headers. Thus, we create an array like so:

$mail_bundle = array(
     'sender_email' => $sender_email,
     'subject' => $subject,
     'mail_text' => $mailtext,
     'send_headers' => $send_headers
);

Next, we need to assign the scheduled task. For this, we use our custom action hook. Now we understand why it was so important to pay close attention to the number of parameters and their order: the hook only allows you to set the number of arguments and the function – which is now removed by one degree of separation from the schedule – needs those arguments sent to it in the proper order. We call the WordPress scheduling function like so:

wp_schedule_single_event(time()+$time, 'bhd_cron_send_hook', $mail_bundle);

The first argument is simply the Unix system time plus an interval of seconds after which we would like the scheduled task to be fired. Since we want the first of our batch email events to happen twenty or so minutes after the post has been published, $time was defined as follows:

$time = rand(1000, 1200);

“Wait,” you say, “why the random number?”

If you look at the multi-dimensional array of scheduled events created by the WP-Cron system, you will find that the primary key for identifying events is the Unix timestamp. The second-level identifier is an MD5 of the arguments. In our example, it’s possible that someone could inadvertently set up the cron events twice by correcting the post after they’ve published it: both events fire the ‘publish_post’ event, which the mailing list uses to start it’s process.

However it happens, the possibility is that two events could have exactly the same timestamp and arguments, which would mean that one overwrites the other in the array. To avoid this, I included the slight randomization, which only alters the schedule by a maximum of three minutes.

So, now let’s look at the entire process in code (with some pseudo-code) in order to understand how everything fits together:

//======================================
// Description: Our scheduling function.  Sends the email in 50-recipient chunks
function bhd_schedule_message($sender_email, $subject, $list, $headers, $mailtext) {
// Establish our random time interval between the publishing of the post and the first email blast:
	$time = rand(1000, 1200);
	for($x = 0; $x  $sender_email,
				'subject' => $subject,
				'mail_text' => $mailtext,
				'send_headers' => $send_headers
			);
// schedule the email blast, increment the $time variable by twenty minutes for the next loop:
			wp_schedule_single_event(time()+$time, 'bhd_cron_send_hook', $mail_bundle);
			$time = $time + 1200;
		}
	}
}

//======================================
// Description: here is our actual cron job.
function bhd_cron_send($sender_email, $subject, $mail_text, $send_headers) {
	wp_mail($sender_email, $subject, $mail_text, $send_headers);
}

//======================================
// Description: Finally, here is our custom action.
add_action( 'bhd_cron_send_hook', 'bhd_cron_send', 10, 4 );

Note: we have not included the function that will be called when the post is published, because of course we’re not dealing with adding actions onto that particular hook. But for reference’s sake, such a function would look something like this:

add_action('publish_post', 'my_mailinglist_function');
Tags: , , , , ,

This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

18 Responses to “Scheduling with WordPress Cron Functions”

  1. November 24th, 2008 | 7:28 pm

    Do you know if this function could be used to have wordpress display different content based on the time? As a church, we would like to display the live video of the service at 9am on Sunday morning. The rest of the time we would like to show a different video. Any idea if this is possible?

  2. November 24th, 2008 | 8:22 pm

    Steve,

    No, cron is more meant to make an event happen once and is therefore more compatible with database backups or other single events. However, maybe cracking open the cron.php might give you an idea of how WordPress works with time, which might be illuminating.

    I’d say what you want to do is create a plugin that sets the time of the first occurrence and then compares the current time when it’s called to your start time + some multiple of 24*7 hours.

    Good luck! Let me know if you have any other questions.

  3. saguado
    March 11th, 2009 | 7:49 pm

    Hi!

    I’m working on the same so, did you publish the entire code or plugin ?

    Thanks in advance.

    Santi

  4. March 11th, 2009 | 7:58 pm

    Saguado ~ I actually have a fully-functional, non-proprietary version working right now, but I haven’t had a chance to post it to WordPress.org. Thanks for reminding me, I’ll get it posted as quickly as maybe and update this page.

  5. flanker
    July 26th, 2009 | 12:39 pm

    Just wanted to say thanks for taking the time to write this all out. The WP Codex is lousy in this area, and I never would’ve figured out the bit about the $num_args parameter to add_action without this post.

  6. July 26th, 2009 | 3:41 pm

    @flanker ~ glad you found the info useful! I agree that the Codex can be kind of vague. We should probably be updating it, each on our own, except that it makes more sense to describe specific situations as private posts, which is the best way for me to illustrate what I’m doing.

  7. August 26th, 2009 | 11:09 am

    hey man, great article so far, just wanted to point out that I think you might be misinterpreting ‘priority’ in the action hooks. Based on your post it sounds like you think its related to how much CPU power your function will consume, which I don’t think is the point at all.

    Rather, ‘priority’ (-10 to 10) defines when that specific function will fire within the hook. If there are five functions added to a given hook with add_action() then they will by default all have a priority of 10 and will fire in the order they were added. If any of them are given a lower priority then they will fire FIRST. This is useful because sometimes you notice that another plugin is using the same hook and that they screw up what you were planning to do because they come first (for example, by chewing up a [shortcode] you need to filter). This mostly applies to more general hooks like the_filter or ‘init’ that other plugins are likely to use, not custom hooks like those you set up when using cron.

    Ideally all plugins should work with the default because depending on priority inevidably leads to situations where some plugin out there can break your stuff, but it can be useful as a workaround and best practice.

    Thanks for the article! I too have gotten very lost trying to use wp-cron, I ended up writing a whole seperate cron framework for myself :S

  8. August 26th, 2009 | 11:37 am

    Thanks for the clarification, Jeremy, it’s an important point to make.

    I guess I’ve always been under the impression that one of the points of priority is to make sure that processes that take longer happen last. But now that I think about it, that doesn’t really make much sense! 8{D>

  9. deigo
    September 16th, 2009 | 4:15 pm

    Hi,
    I´m looking for a cron code to do a automatic export…
    …anyone knows wath is the function to include it?

    thx

  10. September 16th, 2009 | 5:45 pm

    If by automatic export you’re referring to backing up your posts or database, you’re definitely looking in the wrong area. Something like that needs to be handled by an actual Cron job on the server, which WP Cron does not do. If you don’t have access to the server directly, you might want to check with your web host to see if there is a scheduling tool available to you.

  11. September 16th, 2009 | 5:59 pm

    Diego, that’s also something that can be pretty easily handled entirely by a plugin. Search the plugin repo for ‘backup’ and you’ll find lots of ideas.

  12. September 19th, 2009 | 1:07 pm

    You could simple reschedule the same event as part of your event/function itself if you wanted it to happen FOREVER AND FOREVER!

  13. September 19th, 2009 | 1:57 pm

    Thanks for that, Ryan. Not sure, though, that this is exactly the way to do it….

  14. December 18th, 2009 | 7:43 pm

    Is it a true cron that will start on its own, or on every page load it checks if some job needs to be run?

    Another way to put it:
    When you set a cron to run every hour and no one visits your site for 3 days, will it run every hour during that time?

  15. December 18th, 2009 | 8:02 pm

    Answered my own question. From the codex:

    The action will trigger when someone visits your WordPress site, if the scheduled time has passed.

    This is an important nuance. In my case, I need to fetch data from an external site every hour. The first visitor after the scheduled time will see the old data. It will trigger the cron, and only then will the new data be available.

  16. December 18th, 2009 | 8:30 pm

    One word of caution about the hourly crons: WP will run all the crons whose time has expired once someone actually does cause the WP-cron function to fire. So, if you're not getting hourly visits, hourly crons could have a pretty negative impact on your site performance or do something you didn't expect. Its not a guarantee, just a word of caution.

  17. December 18th, 2009 | 9:27 pm

    Jeremy,

    Thanks for that! I'd not seen it before, myself. Very handy for a couple plugins I've got brewin'.

  18. December 18th, 2009 | 8:56 pm

    Hey people who are subscribed to this comment thread. Just wanted to point out that in many cases where you're scheduling data saving it might be easier/more effective to use the transients api instead. It allows you to set options similar to update_option() but with a time limit, after which they will be deleted. It's been around a copule of versions but there was no documentation till now: http://codex.wordpress.org/Transients_API

Leave a reply