Eventonomy

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.md in 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/v1 REST routes and envelope
  • The eventonomy Interactivity store stable surface
  • Template override paths and $view_data shapes
  • The Meta API functions
  • The wp eventonomy CLI 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:

  1. Feature flag - add_filter('evnm_default_features', fn($f) => $f + ['vimeo' => true]).
  2. Meta field - evnm_register_meta('event', 'vimeo_url', …).
  3. Form field - evnm_event_form_fields to add the input; persist on evnm_after_create_event.
  4. REST - evnm_rest_prepare_event adds a vimeo object; every block sees it.
  5. Store - merge state.vimeoEmbed + actions.openStream into the eventonomy store.
  6. Block - register via evnm_blocks; render.php reads evnm_get_view_data('event', $id) and wires data-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.