Every hook is prefixed plugixa_crm_. Add listeners from a small plugin or your
theme’s functions.php.
Lifecycle
| Action |
Fires |
plugixa_crm_loaded |
The plugin has booted and the container is built |
plugixa_crm_activated |
On activation |
plugixa_crm_deactivated |
On deactivation |
plugixa_crm_uninstall |
On uninstall, before anything is removed |
plugixa_crm_register_rest_routes |
With the namespace and the container, so your own routes can join it |
plugixa_crm_enqueue_assets |
When the app’s assets are enqueued |
plugixa_crm_admin_submenus |
To add a submenu under the CRM’s menu item |
Records
do_action( 'plugixa_crm_entity_created', $entity, $id, $data );
do_action( 'plugixa_crm_entity_updated', $entity, $id, $data, $old );
do_action( 'plugixa_crm_entity_deleted', $entity, $id );
do_action( 'plugixa_crm_entity_restored', $entity, $id );
do_action( 'plugixa_crm_entity_purged', $entity, $id );
do_action( 'plugixa_crm_entity_assigned', $entity, $id, $user_id );
do_action( 'plugixa_crm_entity_field_changed', $entity, $id, $field, $to, $from );
These fire on every write, from the database layer. Two shipped features were
built entirely on them without touching a single write path: the activity stream
listens to three, the audit log to five. That is the argument for firing events
before anybody consumes them.
The matching filters run before the write:
| Filter |
Purpose |
plugixa_crm_before_insert |
Change the data about to be inserted |
plugixa_crm_before_update |
Change the data about to be updated |
plugixa_crm_load_record |
Change a record as it is loaded |
plugixa_crm_filter_output |
Change what an endpoint returns |
plugixa_crm_entity_types |
Register an entity, with its fields |
plugixa_crm_filterable_fields |
What a list may be narrowed by |
plugixa_crm_record_names |
Human names for records in messages |
plugixa_crm_record_writers |
Register a generic writer, which is what makes an entity importable |
Plus plugixa_crm_before_delete, plugixa_crm_before_purge,
plugixa_crm_records_merged and plugixa_crm_merge_references.
Permissions
| Hook |
Kind |
Purpose |
plugixa_crm_user_can |
filter |
The single permission decision, handed the record |
plugixa_crm_capabilities |
filter |
The capability map |
plugixa_crm_user_team_ids |
filter |
Which teams somebody is on |
plugixa_crm_access_granted / _revoked |
action |
CRM access changed |
plugixa_crm_user_can is the one to know. Every permission decision in the plugin
runs through it, and it is handed the record, not just the action - which is
what makes own and team scopes expressible at all.
Keep any listener on it cheap. It is called constantly.
Sales
| Hook |
Kind |
Fires |
plugixa_crm_stage_changed |
action |
A deal moved stage: the id, the stage, and what it came from |
plugixa_crm_lead_converted |
action |
A lead became an account, contact and opportunity |
plugixa_crm_roll_up_account |
filter |
How figures roll up to an account |
plugixa_crm_stage_changed is the one most integrations want. Reconstructing “a
deal was won” from the generic update action would mean every consumer re-deriving
what a stage change even is.
Activities and reminders
| Hook |
Kind |
Purpose |
plugixa_crm_activity_completed |
action |
A task, call or meeting was completed |
plugixa_crm_set_reminders |
action |
A module announces it saved something with reminders |
plugixa_crm_reminders_for / _reminder_targets |
filter |
Which records a reminder can be set on |
plugixa_crm_notify |
action |
Put something in somebody’s inbox |
plugixa_crm_notify is a public door: anything can notify somebody without
knowing the notifications module exists.
Email
| Hook |
Kind |
Purpose |
plugixa_crm_email_sent / _received |
action |
After a message goes or arrives |
plugixa_crm_before_send_mail |
action |
Before a message leaves |
plugixa_crm_case_opened_by_email |
action |
Email-to-case opened one |
plugixa_crm_mailbox_client |
filter |
Supply your own mailbox transport |
Portal and lead capture
| Hook |
Kind |
Purpose |
plugixa_crm_portal_case_opened |
action |
A customer opened a case |
plugixa_crm_portal_reply |
action |
A customer replied |
plugixa_crm_portal_profile_updated |
action |
A customer corrected their details |
plugixa_crm_portal_self_enrolled |
action |
Somebody was matched by self-enrolment |
plugixa_crm_lead_capture_confirmed |
action |
A double opt-in was confirmed |
plugixa_crm_public_request_captcha |
filter |
Add a challenge to a public request |
Integrations and outbound requests
| Hook |
Kind |
Purpose |
plugixa_crm_integrations |
filter |
Register a connector |
plugixa_crm_integration_handlers |
filter |
What a connector can do |
plugixa_crm_integration_switched |
action |
One was enabled or disabled |
plugixa_crm_outbound_destination_allowed |
filter |
Whether an address may be reached |
plugixa_crm_webhook_destination_allowed |
filter |
The same question for webhooks |
plugixa_crm_webhook_drain_seconds |
filter |
How long a drain run may take |
plugixa_crm_form_submission_processed |
action |
A contact-form submission was handled |
plugixa_crm_woo_order_synced |
action |
A WooCommerce order reached the CRM |
plugixa_crm_sms_received |
action |
An inbound text arrived |
Be careful with the two _destination_allowed filters. They are the guard that
refuses private and reserved addresses, and returning true from one is how a
webhook reaches your own internal network.
Reporting, search and the client
| Hook |
Kind |
Purpose |
plugixa_crm_report_sources |
filter |
Add a source to the report builder |
plugixa_crm_dashboard_stats |
filter |
Change the dashboard figures |
plugixa_crm_search_providers |
filter |
Add something to global search |
plugixa_crm_streamed_entities |
filter |
Which entities have a stream |
plugixa_crm_stream_status_fields |
filter |
Which field changes the stream reports |
plugixa_crm_admin_config |
filter |
What the admin bundle is given |
plugixa_crm_bootstrap_payload |
filter |
What /bootstrap returns |
Documents, PDF and export
| Hook |
Kind |
Purpose |
plugixa_crm_pdf_engine |
filter |
Replace the PDF engine |
plugixa_crm_pdf_data |
filter |
Change what a template is given |
plugixa_crm_pdf_fonts |
filter |
Register a font |
plugixa_crm_export_columns / _export_rows |
filter |
Change what an export contains |
Settings, modules and data
| Hook |
Kind |
Purpose |
plugixa_crm_modules |
filter |
Register a module of your own |
plugixa_crm_sanitize_settings |
filter |
Validate your own setting |
plugixa_crm_settings_saved |
action |
After a save |
plugixa_crm_examples |
filter |
Contribute example content |
plugixa_crm_personal_data_tables |
filter |
Where personal data lives, for erasure |
plugixa_crm_personal_data_erased |
action |
After an erasure |
plugixa_crm_audit_retention_days |
filter |
How long the audit log is kept |
plugixa_crm_currency_rate |
filter |
Supply a rate from your own source |
plugixa_crm_examples is worth noting: the example content is contributed through
it, which is why the seeder can seed twenty-four records across four modules while
naming none of them. An add-on contributes its own the same way.
Front end
| Hook |
Kind |
Purpose |
plugixa_crm_is_app_page |
filter |
Whether this page carries the app |
plugixa_crm_app_page_keep_styles |
filter |
Stylesheets to keep through the style strip |
plugixa_crm_client_ip |
filter |
How the visitor’s address is determined behind a proxy |
plugixa_crm_dev_bar |
filter |
Whether the development bar is drawn |
Example
Open a case when a deal is won:
add_action(
'plugixa_crm_stage_changed',
function ( int $id, array $stage ): void {
if ( 'won' !== ( $stage['type'] ?? '' ) ) {
return;
}
my_open_onboarding_case( $id );
},
10,
2
);
Troubleshooting
| Symptom |
Usual cause |
| A listener never fires |
Register it on plugins_loaded or later, and check the argument count. |
| A filter has no effect |
Another listener runs later. Raise the priority number. |
plugixa_crm_user_can is called constantly |
It is. Every permission decision goes through it. Keep it cheap. |
| A webhook reaches an internal address |
Something is returning true from a _destination_allowed filter. |
| An entity has no CRUD routes |
Register it through plugixa_crm_entity_types. |
| An entity cannot be imported |
It also needs a writer, through plugixa_crm_record_writers. |
| A Pro hook does nothing |
Its connector is absent. The hook fires; nothing listens. |
What to do next