Skip to content

Introduction

Overview

Hooks and Events are a powerful way to extend CiviCRM's functionality, incorporate additional business logic, and even integrate CiviCRM with external systems. Many CiviCRM developers find themselves using them in nearly every customization project.

Let's say you want to send a message to someone in your organization every time a contact is created. An easy way to do this would be to insert code to send the message in the CiviCRM core code right where the contact is created. However, there are places in the code, where you can hook into from your extension. And there are events dispatched from key points in processing.

Hooks

Hooks allow you to do this by defining a function with a specific name and adding it to your organisation's CiviCRM installation. The name of the function indicates the point at which CiviCRM should call it. CiviCRM looks for appropriate function names and calls the functions whenever it performs the indicated operations.

Events

At some point in time CiviCRM started to use the Symfony Event Dispatcher library. Which fullfils a similar objective. And is a commonly used library in the PHP world. And has more functionality than the standard hook system.

Tip

A good test for whether or not to use a hook is to ask yourself whether what you're trying to do can be expressed with a sentence like this: "I want X to happen every time someone does Y."

Hooks and Events

The term hooks and events are both referring to almost the same thing. Orginally CiviCRM had a hook system to which extension developers could hook into later on CiviCRM implemented the Symfony Event Dispatcher in which hooks are called events. An extension developer can declare a listener to an event. Both methods are still in use.

Usage

Historically, hooks in CiviCRM were implemented using a Drupal-7-like mechanism relying on global functions. This traditional method is still supported, but under the hood, all hooks are also simultaneously dispatched via the Symfony EventDispatcher. Developers can choose to implement hooks using either global functions or Symfony event listeners.

Note that several newer events (e.g. civi.afform.prefill) are only dispatched via Symfony, meaning they do not support the traditional global function method and must be implemented using event listeners.

Traditional method (Hooks)

The traditional method of using a hook is to create a global function in your extension's main module/file with a specific naming convention:

function myextension_civicrm_buildForm($formName, &$form) {
  // do something
}

This works well in many cases but has limitations (for example, if two extensions call the same hook, there is no clean way to determine which runs first without changing extension load order).

For details, see Traditional Hooks

Symfony method (Event listeners and subscribers)

The modern, more flexible paradigm is to listen to the events dispatched via Symfony's EventDispatcher. Traditional hooks are dispatched using the event name prefixed with hook_, e.g., hook_civicrm_buildForm.

For example:

Civi::dispatcher()->addListener('hook_civicrm_buildForm', "myextension_buildForm", $priority);

function myextension_buildForm($event) {
  // do something
}

For more details see Hooks with Symfony

Targeting Certain Events

When you create a hook, it will be called for all the types of entities. For instance, a civicrm_post is called after the creation or modification of any object of any type (contact, tag, group, activity, etc.). But usually, you want to launch an action only for a specific type of entity.

So a hook generally starts with a test on the type of entity or type of action. For instance, if you want to act only when an address was edited, start your civicrm_post hook with:

if ($entity != "Address" || $action != "edit") {
  return;
}

Pitfalls of Hooks

Because you have little control over what CiviCRM passes to your hook function, it is very helpful to look inside those objects (especially $object for the database hooks) to make sure you're getting what you expect.

While the hook contract specifies what arrays and objects will be passed out to your hook the arrays and objects themselves may not be subject to an explicit contract and could change over time. Where values are documented in the hook documentation or you are using functions that have been given the docblock annotation @api you should have more certainty. In general, however, you are advised to ensure your custom code has unit tests.

Supported form functions (these will not change in minor versions but they might become available across more forms)

Function Used to Version
getSubmittedValue() get a single submitted value (processed for money formatting) 5.50-ish?
getSubmittedValues() get all submitted values 5.50-ish?
getSubmittableValues() get all available submitted values 5.50-ish?
getAuthenticatedContactID() All Get the id of the user authenticated by login or checksum
getEventID() Get the Event ID in play 5.66+
getEventValue() ---- 5.66ish?
getDiscountID() Get the Discount ID (civicrm_discount.id) in play 5.66+
getPriceSetID() ---- 5.66+
getParticipantID() ---- 5.66+
getParticipantValue() ---- 5.66ish?
getEventValue() ---- 5.66ish?
getContributionPageID() ---- 5.66ish?
getContributionPageValue() ---- 5.66ish?
getContactID() ---- 5.66+
isTest() ---- 5.69 added to contribution page flow
getLineItems() ---- 5.69 added to contribution page flow
setLineItems() ---- 5.69 added to contribution page flow

A good debugger is indispensable here. See the page on debugging for more information on setting up a debugger for your development environment.

Warning

From time to time a new release of CiviCRM can deprecate or change certain hooks. Keep this in mind when upgrading, and make sure you check the release notes before upgrading.

Packaging Hooks

Hooks are packaged in CMS-agnostic extensions.

Organizing Your Hooks

You may find that some of your hooks target a lot of different cases. Such hooks can quickly get out of control, and maintaining them can be a nightmare.

You might find it helpful when implementing a hook to delegate certain operations to different functions instead of lumping it all in together in the main hook.

If you're using Civix to create your extension it will automatically generate wrapper code for your hook.