Cron Hooks
Filters
pre_schedule_event
Short-circuits event scheduling before it occurs.
apply_filters(
'pre_schedule_event',
null|bool|WP_Error $result,
object $event,
bool $wp_error
): null|bool|WP_Error
Parameters:
$result— Return non-null to short-circuit.true= scheduled,false= blocked$event— Event object withhook,timestamp,schedule,args,interval$wp_error— Whether caller expects WP_Error on failure
Used by: wp_schedule_single_event(), wp_schedule_event()
// Block scheduling of specific hooks
add_filter( 'pre_schedule_event', function( $result, $event, $wp_error ) {
if ( $event->hook === 'blocked_hook' ) {
if ( $wp_error ) {
return new WP_Error( 'blocked', 'This hook cannot be scheduled.' );
}
return false;
}
return $result;
}, 10, 3 );
// External cron system integration
add_filter( 'pre_schedule_event', function( $result, $event ) {
// Store in external queue system instead
my_external_queue_add( $event );
return true; // Tell WordPress it's "scheduled"
}, 10, 2 );
Since: 5.1.0
schedule_event
Modifies an event before it’s scheduled.
apply_filters(
'schedule_event',
object|false $event
): object|false
Parameters:
$event— Event object, orfalseto prevent scheduling
Event object properties:
hook(string) — Action hook nametimestamp(int) — Unix timestamp (UTC)schedule(string|false) — Recurrence name orfalsefor single eventsargs(array) — Callback argumentsinterval(int) — Seconds between runs (recurring events only)
// Modify timestamp of specific hooks
add_filter( 'schedule_event', function( $event ) {
if ( $event && $event->hook === 'my_delayed_hook' ) {
// Always delay by an extra hour
$event->timestamp += HOUR_IN_SECONDS;
}
return $event;
});
// Block events by returning false
add_filter( 'schedule_event', function( $event ) {
$blocked_hooks = [ 'spam_hook', 'unwanted_task' ];
if ( $event && in_array( $event->hook, $blocked_hooks, true ) ) {
return false;
}
return $event;
});
Since: 3.1.0
pre_reschedule_event
Short-circuits rescheduling of recurring events.
apply_filters(
'pre_reschedule_event',
null|bool|WP_Error $result,
object $event,
bool $wp_error
): null|bool|WP_Error
Parameters:
$result— Return non-null to short-circuit$event— Event object withhook,timestamp,schedule,args,interval$wp_error— Whether caller expects WP_Error on failure
// Prevent rescheduling of exhausted tasks
add_filter( 'pre_reschedule_event', function( $result, $event ) {
$run_count = get_option( "cron_runs_{$event->hook}", 0 );
if ( $run_count >= 10 ) {
// Stop after 10 runs
return false;
}
return $result;
}, 10, 2 );
Since: 5.1.0
pre_unschedule_event
Short-circuits event unscheduling.
apply_filters(
'pre_unschedule_event',
null|bool|WP_Error $result,
int $timestamp,
string $hook,
array $args,
bool $wp_error
): null|bool|WP_Error
// Log all unschedule attempts
add_filter( 'pre_unschedule_event', function( $result, $timestamp, $hook, $args ) {
error_log( sprintf(
'Unscheduling: %s at %s with args %s',
$hook,
date( 'c', $timestamp ),
wp_json_encode( $args )
) );
return $result;
}, 10, 4 );
// Protect critical hooks from being unscheduled
add_filter( 'pre_unschedule_event', function( $result, $timestamp, $hook, $args, $wp_error ) {
if ( $hook === 'critical_system_task' ) {
if ( $wp_error ) {
return new WP_Error( 'protected', 'Cannot unschedule critical task.' );
}
return false;
}
return $result;
}, 10, 5 );
Since: 5.1.0
pre_clear_scheduled_hook
Short-circuits clearing all events for a hook with specific args.
apply_filters(
'pre_clear_scheduled_hook',
null|int|false|WP_Error $result,
string $hook,
array $args,
bool $wp_error
): null|int|false|WP_Error
Returns: Number of events cleared, or false/WP_Error on failure
// External cron system: clear from external queue
add_filter( 'pre_clear_scheduled_hook', function( $result, $hook, $args ) {
$cleared = my_external_queue_clear( $hook, $args );
return $cleared; // Return count of cleared events
}, 10, 3 );
Since: 5.1.0
pre_unschedule_hook
Short-circuits clearing ALL events for a hook (regardless of args).
apply_filters(
'pre_unschedule_hook',
null|int|false|WP_Error $result,
string $hook,
bool $wp_error
): null|int|false|WP_Error
// Sync with external cron system
add_filter( 'pre_unschedule_hook', function( $result, $hook ) {
my_external_queue_clear_all( $hook );
return $result; // null = continue with WordPress cron
}, 10, 2 );
Since: 5.1.0
pre_get_scheduled_event
Short-circuits retrieving a scheduled event.
apply_filters(
'pre_get_scheduled_event',
null|false|object $result,
string $hook,
array $args,
int|null $timestamp
): null|false|object
Returns: Event object if found, false if not, null to continue normal retrieval
// Merge events from external queue
add_filter( 'pre_get_scheduled_event', function( $result, $hook, $args, $timestamp ) {
$external_event = my_external_queue_get( $hook, $args, $timestamp );
if ( $external_event ) {
return (object) [
'hook' => $hook,
'timestamp' => $external_event['time'],
'schedule' => $external_event['recurrence'],
'args' => $args,
'interval' => $external_event['interval'] ?? null,
];
}
return $result;
}, 10, 4 );
Since: 5.1.0
pre_get_ready_cron_jobs
Short-circuits retrieving ready-to-run cron jobs.
apply_filters(
'pre_get_ready_cron_jobs',
null|array $result
): null|array
Returns: Array of ready jobs, or null to continue normal retrieval
// Completely replace with external queue
add_filter( 'pre_get_ready_cron_jobs', function() {
return my_external_queue_get_due_jobs();
});
Since: 5.1.0
wp_next_scheduled
Filters the timestamp returned by wp_next_scheduled().
apply_filters(
'wp_next_scheduled',
int $timestamp,
object $next_event,
string $hook,
array $args
): int
Parameters:
$timestamp— Unix timestamp of next occurrence$next_event— Full event object$hook— Action hook name$args— Callback arguments
// Delay all occurrences of a specific hook
add_filter( 'wp_next_scheduled', function( $timestamp, $next_event, $hook ) {
if ( $hook === 'my_adjustable_hook' ) {
// Report as 5 minutes later than actual
return $timestamp + ( 5 * MINUTE_IN_SECONDS );
}
return $timestamp;
}, 10, 3 );
Since: 6.8.0
get_schedule
Filters the schedule name for an event.
apply_filters(
'get_schedule',
string|false $schedule,
string $hook,
array $args
): string|false
// Override schedule names for display
add_filter( 'get_schedule', function( $schedule, $hook ) {
if ( $hook === 'my_custom_hook' && $schedule === 'hourly' ) {
return 'custom_hourly'; // For display purposes
}
return $schedule;
}, 10, 2 );
Since: 5.1.0
cron_schedules
Adds custom recurrence schedules.
apply_filters(
'cron_schedules',
array $schedules
): array
Parameters:
$schedules— Array of custom schedules to merge with defaults
add_filter( 'cron_schedules', function( $schedules ) {
// Every 5 minutes
$schedules['every_five_minutes'] = [
'interval' => 5 * MINUTE_IN_SECONDS,
'display' => __( 'Every 5 Minutes' ),
];
// Every 15 minutes
$schedules['quarterly_hour'] = [
'interval' => 15 * MINUTE_IN_SECONDS,
'display' => __( 'Every 15 Minutes' ),
];
// Twice weekly
$schedules['twice_weekly'] = [
'interval' => 3.5 * DAY_IN_SECONDS,
'display' => __( 'Twice Weekly' ),
];
return $schedules;
});
Note: Hook early (plugins_loaded or init) so schedules exist when cron runs.
Since: 2.1.0
cron_request
Filters the HTTP request arguments for spawning cron.
apply_filters(
'cron_request',
array $cron_request,
string $doing_wp_cron
): array
Parameters:
$cron_request— Request configuration array$doing_wp_cron— Lock timestamp string
Request structure:
[
'url' => 'https://example.com/wp-cron.php?doing_wp_cron=...',
'key' => '1707465600.1234567890123456789012',
'args' => [
'timeout' => 0.01,
'blocking' => false,
'sslverify' => false,
],
]
// Increase spawn timeout
add_filter( 'cron_request', function( $request ) {
$request['args']['timeout'] = 1; // 1 second instead of 0.01
return $request;
});
// Use alternative cron URL
add_filter( 'cron_request', function( $request, $doing_wp_cron ) {
$request['url'] = add_query_arg(
'doing_wp_cron',
$doing_wp_cron,
'https://cron.example.com/wp-cron.php'
);
return $request;
}, 10, 2 );
// Enable SSL verification for cron requests
add_filter( 'cron_request', function( $request ) {
$request['args']['sslverify'] = true;
return $request;
});
Since: 3.5.0
Actions
WordPress cron doesn’t define built-in actions, but your scheduled hooks ARE actions:
// When you schedule this:
wp_schedule_event( time(), 'hourly', 'my_custom_cron_hook' );
// You need to register the callback:
add_action( 'my_custom_cron_hook', 'my_cron_callback' );
function my_cron_callback() {
// This runs when cron executes
}
Built-in Cron Hooks
WordPress core schedules these hooks:
| Hook | Schedule | Purpose |
|---|---|---|
wp_scheduled_delete |
Daily | Empty trash |
wp_scheduled_auto_draft_delete |
Daily | Delete old auto-drafts |
wp_version_check |
Twice daily | Check for core updates |
wp_update_plugins |
Twice daily | Check for plugin updates |
wp_update_themes |
Twice daily | Check for theme updates |
wp_site_health_scheduled_check |
Weekly | Site health check |
delete_expired_transients |
Daily | Clean expired transients |
recovery_mode_clean_expired_keys |
Daily | Clean recovery mode keys |
Filter Flow Diagram
wp_schedule_event()
│
├── pre_schedule_event ──► [short-circuit possible]
│
├── schedule_event ──► [modify or block]
│
└── Save to cron array
wp_unschedule_event()
│
├── pre_unschedule_event ──► [short-circuit possible]
│
└── Remove from cron array
spawn_cron()
│
├── cron_request ──► [modify URL/timeout]
│
└── wp_remote_post()
External Cron Integration Pattern
For plugins replacing WP-Cron with external systems:
// Intercept all scheduling
add_filter( 'pre_schedule_event', function( $result, $event ) {
external_queue_add( $event );
return true;
}, 10, 2 );
// Intercept all unscheduling
add_filter( 'pre_unschedule_event', function( $result, $timestamp, $hook, $args ) {
external_queue_remove( $timestamp, $hook, $args );
return true;
}, 10, 4 );
// Provide scheduled event data
add_filter( 'pre_get_scheduled_event', function( $result, $hook, $args, $timestamp ) {
return external_queue_get_event( $hook, $args, $timestamp );
}, 10, 4 );
// Provide ready jobs
add_filter( 'pre_get_ready_cron_jobs', function() {
return external_queue_get_due();
});