Build a Pro Extension
Since Jetonomy Pro 1.9.0, the extension system that powers all bundled Pro modules (Private Messaging, Polls, Analytics, and the rest) is open to third-party developers. Your plugin can register an extension that gets the exact same lifecycle the bundled ones do: an admin toggle on the Extensions screen, table creation on the migration guard, boot() when enabled, and cleanup on deactivation.
Requires: Jetonomy Pro active (the SDK classes ship with Pro). Your extension is not gated by the customer’s Jetonomy Pro license — if you sell your extension separately, gate your own boot().
The contract
Section titled “The contract”Extend the abstract Jetonomy_Pro\Extension and register the instance through the jetonomy_pro_register_extensions filter:
add_filter( 'jetonomy_pro_register_extensions', function ( $extensions ) { if ( ! class_exists( 'Jetonomy_Pro\\Extension' ) ) { return $extensions; // Pro not active. } require_once __DIR__ . '/class-my-extension.php'; $ext = new \Acme\Jetonomy\My_Extension(); $extensions[ $ext->get_id() ] = $ext; return $extensions;} );namespace Acme\Jetonomy;
class My_Extension extends \Jetonomy_Pro\Extension {
/** * REQUIRED for external extensions. The base id() derives the id from * your class NAMESPACE (second-to-last segment, Pascal_Case → kebab-case), * which almost never matches for third-party code — return it explicitly. */ public function id(): string { return 'acme-kudos'; }
public function meta(): array { return array( 'id' => 'acme-kudos', 'name' => __( 'Acme Kudos', 'acme-kudos' ), 'version' => '1.0.0', 'description' => __( 'Send kudos to helpful members.', 'acme-kudos' ), 'category' => __( 'Engagement', 'acme-kudos' ), 'requires' => 'starter', ); }
/** Called only when the extension is enabled on the Extensions screen. */ public function boot(): void { add_action( 'rest_api_init', array( $this, 'register_routes' ) ); }
/** Called on the version-change migration guard — dbDelta your tables here. Idempotent. */ public function activate(): void {}
/** Called on Jetonomy Pro deactivation — clear cron, etc. */ public function deactivate(): void {}}Lifecycle guarantees
Section titled “Lifecycle guarantees”| Moment | What runs |
|---|---|
Pro loads (plugins_loaded:20) |
Your filter callback registers the instance |
| Stored DB version ≠ Pro version | activate() on every registered extension (including yours) |
| Extension enabled + each request | boot() |
| Pro deactivated | deactivate() on enabled extensions |
Enable/disable state lives in the jetonomy_pro_extensions option and is toggled from Jetonomy → Extensions, where your extension appears alongside the bundled ones using your meta() values.
Rules that keep you compatible
Section titled “Rules that keep you compatible”- Never reuse a bundled id (
private-messaging,polls,reactions, …) — duplicate ids are ignored, first registration wins. - Prefix everything: options and user meta
acme_kudos_*, tables{$wpdb->prefix}acme_kudos_*, cron hooksacme_kudos_*. Do not use thejetonomy_pro_prefixes — those namespaces belong to bundled modules. - REST permission callbacks: use
$this->rest_auth_mutation( $caps )from the base class for mutation routes — it resolves free’sREST_Authlazily and fails closed (never reference\Jetonomy\API\REST_Authdirectly at registration time). - Background jobs go through
Jetonomy_Pro\Queue(async()/recurring()/cancel()) — Action-Scheduler-first with WP-Cron fallback; cancel your hooks indeactivate(). - Licensing is yours: the customer’s Jetonomy Pro license never disables your extension. If yours is paid, check your own license at the top of
boot()and return early. - Frontend surfaces must follow the Frontend Interactivity Standard (
docs/standards/frontend-interactivity.mdin the plugin repo) (declarative store actions,restFetch, re-init onjetonomy:navigated) and use the--jt-*design tokens.
Hooking free instead
Section titled “Hooking free instead”If you don’t need an admin toggle, tables, or the lifecycle — just behavior — you may not need an extension at all. The free plugin fires 190+ documented hooks (hooks reference) and supports REST, frontend, and adapter extension without Pro. Reach for the extension SDK when you want your feature to feel like a first-class Pro module.

