Goal
Register a scheduled task that fires on a fixed external schedule (via FastCron) rather than relying on WordPress’s request-driven wp-cron.php.
Prerequisites
- A sibling plugin with its own slug
- FastCron configured on the customer site (admin task — see Integration: FastCron)
- An understanding that callbacks are invoked via
apply_filters($cron_id, [])— return values are discarded
Steps
1. Choose a cron id
Convention: mam_<plugin_slug>_<task_name> (snake_case). The id is the dispatch handle. Must be unique site-wide.
Example: mam_my_plugin_nightly_sync.
2. Register the cron entry
add_filter( 'mam_cron_manager', function ( array $crons ): array {
$crons['mam_my_plugin_nightly_sync'] = array(
'title' => 'My Plugin: Nightly Sync',
'expression' => '0 2 * * *', // 2am daily, in site timezone
'callback' => 'mam_my_plugin_nightly_sync_action',
);
return $crons;
} );
3. Implement the callback
function mam_my_plugin_nightly_sync_action( $unused = array() ) {
// Do the work.
$this->sync_recent_changes();
// Return value is discarded.
return array();
}
add_filter( 'mam_my_plugin_nightly_sync', 'mam_my_plugin_nightly_sync_action' );
⚠️ The callback is invoked via apply_filters($cron_id, []), not do_action. Use add_filter to register, not add_action.
4. Verify in the admin
Mobile App Manager → Scheduled Jobs lists every registered cron. Your entry appears with the title and expression. The list page is mam_setcron_list (includes/setcron-manager/cron-list.php).
5. Confirm FastCron is configured
If tsl-setting-setcron-api is '1' or empty, FastCron is disabled and your cron never fires. Admin must enter a real FastCron API token in Mobile App Manager → Setcron Manager.
6. Test
The fastest test: trigger the AJAX endpoint directly.
curl 'https://example.com/wp-admin/admin-ajax.php?action=mam_setcron_processor'
This simulates a FastCron tick. Watch your callback’s logs / side effects.
Cron expression cheatsheet
* * * * *
│ │ │ │ └── day of week (0-6, 0=Sunday)
│ │ │ └───── month (1-12)
│ │ └──────── day of month (1-31)
│ └─────────── hour (0-23)
└────────────── minute (0-59)
0 2 * * * → 2am daily
*/15 * * * * → every 15 minutes
0 0 * * 0 → Sunday midnight
0 9-17 * * 1-5 → on the hour, 9am-5pm, Mon-Fri
⚠️ Resolution is bounded by FastCron’s tick frequency. If FastCron pings every 5 minutes, * * * * * runs every 5 minutes, not every minute.
Patterns
Heavy work → queue + drain pattern
function mam_my_plugin_nightly_sync_action( $unused = array() ) {
// Don't do all the work inline; queue and return.
foreach ( $this->get_pending_items() as $item_id ) {
wp_schedule_single_event( time() + 60, 'my_plugin_process_item', array( $item_id ) );
}
}
Idempotent reads / writes
function mam_my_plugin_nightly_sync_action( $unused = array() ) {
$last_run = (int) get_option( 'mam_my_plugin_nightly_last_run', 0 );
if ( time() - $last_run < 12 * HOUR_IN_SECONDS ) {
// Already ran in the last 12 hours — skip (defends against multiple ticks within a window)
return;
}
update_option( 'mam_my_plugin_nightly_last_run', time() );
$this->do_work();
}
Gotchas
apply_filtersnotdo_action. Don’t useadd_actionto register the callback — useadd_filter.- Cron id must be unique. Two plugins registering the same id silently overwrite.
- No retry semantics. A failed callback isn’t retried until the next matching tick.
- Long callbacks block the tick. If your callback runs for 5 minutes, no other crons matching the same tick fire until you return.
- FastCron API-token sentinel value
'1'means “feature disabled” — a real token is non-numeric. - Time-zone resolution for offset zones (
+05:30) can fall through to UTC. Use named timezones.
Related articles
- Integration: FastCron
- Notification queue and cron
- Hook: mam_cron_manager
Metadata
| Field | Value |
|---|---|
| Article type | Recipe (Developer) |
| Plugin slug | mam-main |
| Applies to plugin version | 2.1.11+ |
| Category | Extending MAM Suite |
| Audience | PHP developer |
| Estimated time | 15–30 minutes |
| Last verified | 2026-05-02 |
