Skip to content

What you can extend in Marker CRM, when your add-on's code runs, and which hooks are a promise.

Marker CRM is built to be extended from the outside. An add-on is an ordinary WordPress plugin: it waits for Marker CRM to load, then registers what it brings through filters. You never edit a file of ours or alter one of our tables.

You want to Use Read
Start an automation when something happens in your plugin marker_crm/automation_triggers Triggers
Add a step an automation can run marker_crm/automation_actions Actions
Give a step or trigger a settings panel, with no JavaScript the fields schema Settings fields
Let automations wait for, jump to or stop on your event met on your trigger Goals and events
Add a {{token}} to emails marker_crm/merge_tags Merge tags
Add a new kind of named group beside tags and lists marker_crm/term_types Term types
Add a rule to segments and automation conditions marker_crm/segment_fields Segment fields
React to contacts, emails, campaigns and runs actions such as marker_crm/contact_created Hooks
Read or write CRM data from JavaScript in the admin the markercrm/v2 REST API REST API

Everything an add-on registers shows up next to the core features: a trigger you register is in the same picker as Tag is added, and its settings panel is drawn by the same renderer.

Register everything from marker_crm/loaded:

add_action('marker_crm/loaded', function ($app) {
add_filter('marker_crm/automation_triggers', 'acme_triggers');
add_filter('marker_crm/automation_actions', 'acme_actions');
});

marker_crm/loaded fires on plugins_loaded, after Marker CRM has set itself up and before init. Your filters are in place before anything reads them.

  • If Marker CRM is not active, marker_crm/loaded never fires and your add-on does nothing. You need no class_exists() checks around your registrations.
  • Do not hook plugins_loaded yourself and assume Marker CRM is there. Plugin load order is not guaranteed.
  • Automation triggers are attached to their hooks at init (priority 20) and again at wp_loaded, so a trigger registered a little late is still heard. A hook that fires during init before priority 20 would be missed.

The plugin version is the constant MARKER_CRM_VERSION. The automation block contract has its own version, MarkerCRM\App\Services\Automation\AutomationRegistry::VERSION (currently 1.0), raised only when the shape of a trigger or action changes.

add_action('marker_crm/loaded', function () {
if (version_compare(MARKER_CRM_VERSION, '1.0.0', '<')) {
return;
}
// register here
});
  • Hook names and signatures are a promise. Once a marker_crm/ hook has shipped, its name and the parameters it passes do not change. A new parameter may be appended to the end — that is not a breaking change. Renaming or removing one waits for a major version.
  • The registry shapes are a promise: the trigger and action arrays, the field schema, StepResult, and the marker_crm/loaded hook and its timing.
  • Not a promise: our Vue components and CSS classes, the internals of service classes beyond the methods these pages name, and any REST response field that is not documented.
  • One write path for contacts. Anything that creates or changes a contact goes through ContactService::createOrUpdate(). It de-duplicates by address, fires the events automations listen to, and keeps the contact’s status in step with its subscriptions.
  • Consent is not implied. A purchase or an account is not permission to send marketing email. Give a contact you create the transactional status unless the person actually opted in. See Concepts.
  • Never break the host. Wrap your own hook callbacks so an exception in your add-on cannot stop a checkout or a login.
  • Never alter our tables. Keep your own data in your own tables or in meta.
  1. Your first add-on — a complete plugin with one trigger and one action.
  2. Concepts — contacts, subscriptions, tags and lists, runs and the queue, as an add-on sees them.
  3. Hooks — every action and filter Marker CRM fires.