Extending Eventonomy
Eventonomy is built to be molded. Every behavior worth changing has a documented seam. Pro is built on the same seams - your add-on has exactly the same extension depth.
Full reference:
docs/EXTENDING.mdin the plugin root. This page covers the most important patterns with working examples.
What You Will Learn
- The stable public API (SemVer guarantee)
- How to bind contracts and register services
- How to add a payment gateway
- How to add a notification channel
- How to use the Meta API for custom fields
- Internationalization (WPML / Polylang)
- Programmatic event creation
The Stability Promise
These surfaces are stable across major versions (no breaking changes without a deprecation cycle):
- All
evnm_*hooks - All
Eventonomy\Contracts\*interfaces - The
eventonomy/v1REST routes and envelope - The
eventonomyInteractivity store stable surface - Template override paths and
$view_datashapes - The Meta API functions
- The
wp eventonomyCLI group - The
evnm_setting()/evnm_settings()/evnm_feature_enabled()accessors
Never depend on concrete classes in Eventonomy\Repository\*, Services\*, or Admin\*; the DB column layout; or anything marked @internal.
Binding Services
Bind on the evnm_register_services action. The container fires before any hooks are registered.
add_action( 'evnm_register_services', function ( $c ) {
// swap-one: replace the default with your implementation
$c->bind( \Eventonomy\Contracts\RecurrenceEngineInterface::class, MyRecurrenceEngine::class );
// add-to-collection: add a payment gateway alongside the existing ones
$c->tag( \Eventonomy\Contracts\PaymentGatewayInterface::class, MyGateway::class );
} );
Later bindings override earlier ones (how Pro layers on Free).
Adding a Payment Gateway
Implement PaymentGatewayInterface:
class MyGateway implements \Eventonomy\Contracts\PaymentGatewayInterface {
public function id(): string { return 'my-gateway'; }
public function label(): string { return 'My Payment Gateway'; }
public function supported_currencies(): array { return [ 'USD', 'EUR' ]; }
public function charge( array $order, array $args ): array {
// Call your payment processor; return [ 'txn_id' => '...', 'status' => 'completed' ].
}
public function capture( string $txn, array $args ): array { /* ... */ }
public function refund( string $txn, int $cents ): bool { /* ... */ }
}
add_action( 'evnm_register_services', function ( $c ) {
$c->tag( \Eventonomy\Contracts\PaymentGatewayInterface::class, MyGateway::class );
} );
The gateway appears automatically in the checkout flow once registered.
Adding a Notification Channel
Implement NotificationChannelInterface to add SMS, push, Slack, or any other channel:
class SlackChannel implements \Eventonomy\Contracts\NotificationChannelInterface {
public function id(): string { return 'slack'; }
public function label(): string { return 'Slack'; }
public function supports( array $recipient ): bool { return ! empty( $recipient['slack_handle'] ); }
public function send( array $recipient, array $message, array $context ): bool {
// Post to Slack API.
return true;
}
}
add_action( 'evnm_register_services', function ( $c ) {
$c->tag( \Eventonomy\Contracts\NotificationChannelInterface::class, SlackChannel::class );
} );
The notification service fans out to every channel whose supports() returns true.
Meta API - Custom Fields
Store custom data against any Eventonomy resource:
// Read
$vimeo_url = evnm_get_meta( 'event', $event_id, 'vimeo_url', '' );
// Write
evnm_update_meta( 'event', $event_id, 'vimeo_url', $url );
// Delete
evnm_delete_meta( 'event', $event_id, 'vimeo_url' );
$type can be event, occurrence, rsvp, ticket, or order.
Registering Meta for REST Exposure
add_action( 'evnm_register_meta', function () {
evnm_register_meta( 'event', 'vimeo_url', [
'type' => 'string',
'sanitize_callback' => 'esc_url_raw',
'show_in_rest' => true,
] );
} );
Adding Custom Fields to the RSVP Form
add_filter( 'evnm_registration_fields', function ( $fields, $event ) {
$fields[] = [
'id' => 'my_tshirt_size',
'label' => 'T-shirt size',
'type' => 'select',
'options' => [ 'S', 'M', 'L', 'XL' ],
'required' => false,
];
return $fields;
}, 10, 2 );
Submitted values validate, store against the RSVP, and appear in the attendee admin.
Programmatic Event Creation
Use EventService::create() as the canonical entry point:
$service = evnm( \Eventonomy\Services\EventService::class );
$result = $service->create( [
'title' => 'My Programmatic Event',
'start_local' => '2026-09-01 18:00:00',
'end_local' => '2026-09-01 21:00:00',
'timezone' => 'America/New_York',
'status' => 'published',
'venue' => [ 'name' => 'Downtown Hub', 'city' => 'Boston' ],
], [ 'user_id' => 1, 'source' => 'cli' ] );
if ( is_wp_error( $result ) ) {
// handle error
}
Required fields: title, start_local. All other fields are optional - see docs/EXTENDING.md §13 for the full field reference.
Internationalization
Translatable Event URL Slug (Polylang)
add_action( 'init', function () {
if ( function_exists( 'pll_register_string' ) ) {
pll_register_string( 'evnm_permalink_base', 'event', 'Eventonomy' );
}
} );
add_filter( 'evnm_permalink_base', function ( string $base ): string {
return function_exists( 'pll__' ) ? pll__( $base ) : $base;
} );
After adding this, flush rewrite rules (Settings → Permalinks → Save Changes).
WPML
add_action( 'init', function () {
if ( function_exists( 'icl_register_string' ) ) {
icl_register_string( 'Eventonomy', 'evnm_permalink_base', 'event' );
}
} );
add_filter( 'evnm_permalink_base', function ( string $base ): string {
return function_exists( 'icl_t' ) ? icl_t( 'Eventonomy', 'evnm_permalink_base', $base ) : $base;
} );
Worked End-to-End Example
A Vimeo virtual-event add-on - no Eventonomy concrete-class imports:
- Feature flag -
add_filter('evnm_default_features', fn($f) => $f + ['vimeo' => true]). - Meta field -
evnm_register_meta('event', 'vimeo_url', …). - Form field -
evnm_event_form_fieldsto add the input; persist onevnm_after_create_event. - REST -
evnm_rest_prepare_eventadds avimeoobject; every block sees it. - Store - merge
state.vimeoEmbed+actions.openStreaminto theeventonomystore. - Block - register via
evnm_blocks;render.phpreadsevnm_get_view_data('event', $id)and wiresdata-wp-*.
Result: admin field + persistence + REST exposure + frontend state + rendered block - using only hooks, the Meta API, the evnm_blocks filter, the shared store, and the canonical envelope.