Hooks & Filters
Every write operation fires a before-filter (abortable) and an after-action (full data). Every list endpoint has a query-args filter. Every REST response has a prepare-filter.
Full reference:
docs/EXTENDING.md§2 in the plugin root. This page covers the most frequently used hooks with examples.
What You Will Learn
- The
evnm_before_*/evnm_after_*naming convention - Event lifecycle hooks
- RSVP, ticket, and order hooks
- Query-args and REST-prepare filters
- App config, email, capability, and feature-flag hooks
Naming Conventions
| Pattern | Type | Use |
|---|---|---|
evnm_before_{verb}_{resource} |
Filter | Receives validated payload; return WP_Error to abort. |
evnm_after_{verb}_{resource} |
Action | Fires post-commit; receives full result. Never read $_POST. |
evnm_{resource}_query_args |
Filter | Modify read args before the query runs. |
evnm_rest_prepare_{resource} |
Filter | Add/redact/compute fields on a REST response item. |
Event Lifecycle
// Abort if the event is in the past.
add_filter( 'evnm_before_create_event', function ( array $event, array $context ) {
if ( strtotime( $event['start_utc'] ) < time() ) {
return new WP_Error( 'evnm_event_in_past', 'Events cannot start in the past.', [ 'status' => 422 ] );
}
return $event;
}, 10, 2 );
// Push to CRM after creation.
add_action( 'evnm_after_create_event', function ( array $event, array $context ) {
my_crm_push( $event['id'], $event['title'], $event['start_utc'] );
}, 10, 2 );
// Abort if capacity would drop below current RSVPs.
add_filter( 'evnm_before_update_event', function ( array $changes, array $existing, array $context ) {
if ( isset( $changes['capacity'] ) && $changes['capacity'] < $existing['rsvp_count'] ) {
return new WP_Error( 'evnm_capacity_below_rsvps', 'Capacity cannot drop below current RSVPs.', [ 'status' => 409 ] );
}
return $changes;
}, 10, 3 );
// Detect rescheduling.
add_action( 'evnm_after_update_event', function ( array $event, array $changed_keys, array $context ) {
if ( in_array( 'start_utc', $changed_keys, true ) ) {
do_action( 'my_plugin_event_rescheduled', $event['id'] );
}
}, 10, 3 );
// Archive on delete.
add_action( 'evnm_after_delete_event', function ( int $event_id, array $snapshot, array $context ) {
my_archive( $snapshot ); // full pre-delete snapshot
}, 10, 3 );
RSVP, Ticket, Order, Occurrence Lifecycle
Same before_ / after_ pattern for every resource:
// Watch for paid order completion.
add_action( 'evnm_after_update_order', function ( $order, $changed_keys, $context ) {
if ( in_array( 'status', $changed_keys, true ) && 'paid' === $order['status'] ) {
foreach ( $order['line_items'] as $item ) {
my_grant_perk( $order['user_id'], $item['ticket_id'] );
}
}
}, 10, 3 );
Available hooks: evnm_before_create_rsvp / evnm_after_create_rsvp, …_update_rsvp, …_delete_rsvp; same pattern for ticket, order, occurrence.
Magic-link actions: evnm_magic_link_requested, evnm_magic_link_verified, evnm_rsvp_promoted.
Query-Args Filters
Use these to modify SQL queries without touching the repository:
// Restrict public REST reads to published events only.
add_filter( 'evnm_events_query_args', function ( $args, $context ) {
if ( 'rest' === $context['source'] && ! is_user_logged_in() ) {
$args['status'] = 'published';
}
return $args;
}, 10, 2 );
Available: evnm_events_query_args, evnm_occurrences_query_args, evnm_rsvps_query_args, evnm_tickets_query_args, evnm_orders_query_args, evnm_attendees_query_args, evnm_calendar_ics_query_args.
REST Response Filters
Add, redact, or compute fields on every API response for that resource:
add_filter( 'evnm_rest_prepare_event', function ( $data, $event, $request ) {
$data['weather_hint'] = my_forecast( $event['start_utc'], $event['city'] );
return $data;
}, 10, 3 );
Available: evnm_rest_prepare_event, evnm_rest_prepare_occurrence, evnm_rest_prepare_rsvp, evnm_rest_prepare_ticket, evnm_rest_prepare_order.
App Config
// Expose a feature flag to the frontend store.
add_filter( 'evnm_rest_app_config', function ( array $cfg ): array {
$cfg['features']['my_feature'] = my_feature_enabled();
return $cfg;
} );
add_filter( 'evnm_email_subject', function( $subject, $email_key, $data ) {
if ( 'rsvp_confirmation' === $email_key ) {
return 'You are going to: ' . $data['event']['title'];
}
return $subject;
}, 10, 3 );
Valid $email_key values: rsvp_confirmation, magic_link, order_receipt, event_reminder, waitlist_promoted, event_cancelled, approval_result.
Also: evnm_email_body, evnm_email_recipients - same ($value, $email_key, $data) signature.
Capabilities
// Grant co-organizers edit access.
add_filter( 'evnm_user_can_manage_event', function ( $can, $user_id, $event, $cap ) {
$co = (array) evnm_get_meta( 'event', $event['id'], 'co_organizers' );
return in_array( $user_id, array_map( 'intval', $co ), true ) ? true : $can;
}, 10, 4 );
Add-On Accessors (stable across major versions)
$currency = evnm_setting( 'currency', 'USD' );
$all = evnm_settings();
$has_wait = evnm_feature_enabled( 'waitlist', [ 'event_id' => 42 ] );
Never import \Eventonomy\Core\Settings or \Eventonomy\Core\Features directly - these accessors are the stable contract.
Formatting Filters
evnm_event_jsonld-($data, $event, $view)- modify or suppress Schema.org JSON-LD on single-event pages.evnm_permalink_base-($base)- translate or override the single-event URL slug.evnm_format_money-($formatted, $amount, $args)- override currency formatting.evnm_format_event_datetime-($formatted, $utc, $event_tz, $args)- override date/time display.
Cron / Notification Actions
evnm_cron_send_reminders (hourly), evnm_cron_recompute_occurrences (daily, $event_id|null), evnm_cron_expire_holds (5-min), evnm_cron_cleanup (daily), evnm_notification_dispatch ($notification, $channel).
What's Next?
Learn how to work with blocks, templates, and the Interactivity store.