arraypress/wp-register-cron

Declarative WordPress cron jobs and custom schedules, cleaned up properly on uninstall.

Maintainers

Package info

github.com/arraypress/wp-register-cron

pkg:composer/arraypress/wp-register-cron

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

dev-main 2026-08-25 15:44 UTC

This package is auto-updated.

Last update: 2026-08-25 15:47:23 UTC


README

Declarative WordPress cron jobs and custom schedules, cleaned up properly on uninstall.

Install

composer require arraypress/wp-register-cron

Requires PHP 8.3.

Use

add_action( 'init', function () {
	register_cron_job( 'myplugin_sync', [
		'schedule' => 'daily',
		'callback' => 'myplugin_run_sync',
	] );
} );

On init, on every request — not on activation. That is the part everyone gets wrong, and it is worth being clear about why.

A cron event fires on some later request than the one that scheduled it, and WordPress fires it by doing do_action( 'myplugin_sync' ). If the callback was attached only at activation, nothing is listening when that happens: the event runs, the hook fires into an empty room, WordPress marks it done and schedules the next one. It repeats for ever, does nothing, and reports nothing.

So register_cron_job() attaches the callback every time it is called, and queues the event only if it is not queued already. Calling it on every request is correct and cheap.

Options

Option Type What it does
callback callable What to run. Required.
schedule string A recurrence. Omit for a job that runs once.
args array Passed to the callback, and part of what identifies the event.
start int When it first runs. Defaults to now.

Schedules

WordPress ships hourly, twicedaily, daily and weekly. Anything else:

register_cron_schedule( 'every_five_minutes', 5 * MINUTE_IN_SECONDS, __( 'Every five minutes', 'my-plugin' ) );

A recurrence that nothing has registered is refused rather than scheduled. WordPress accepts an unknown one happily — the event runs once, and then never again, because rescheduling it needs an interval that does not exist. There is nothing anywhere to say so, which is why this refuses at the point the mistake is made.

An existing recurrence is never replaced. A plugin quietly redefining what daily means for every other plugin on the site is not something to do by accident.

Uninstall

unregister_cron_jobs();                  // everything this plugin registered
unregister_cron_job( 'myplugin_sync' );  // one of them

This is the reason to have the library. An orphaned cron event outlives the plugin that created it: it stays in the queue, fires into code that is no longer installed, and keeps costing the site something on every visit. It is the most common way an uninstalled plugin is still there.

Clearing removes every occurrence of the hook, not the next one. A hook that ended up in the queue twice — which is exactly the mess uninstall is there to tidy — was otherwise left half present.

Arguments identify an event, so a hook scheduled for two different feeds is two events and clearing one leaves the other:

unregister_cron_job( 'myplugin_sync', [ 'feed-a' ] );

Asking

$next = cron_job_next_run( 'myplugin_sync' );   // timestamp, or null

Null rather than false, because wp_next_scheduled() returns false for "not queued" and a timestamp otherwise, and false is also what a careless read of a zero timestamp looks like.

Testing

composer test          # phpunit
composer lint          # phpcs, defect sniffs
composer format:check  # phpcs, formatting