Skip to content

Hooks Introduction

Overview

Hooks are a common way to extend systems. 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, as soon as we upgrade to a newer version all this code will be overwritten. This is where hooks come in to save the day.

At key points in processing - for example saving something to the database - CiviCRM checks to see whether you've "hooked in" some custom code, and runs any valid code it finds.

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.

Hooks 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.

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."

Usage

There are two ways to use hooks: the traditional method and the Symfony events method.

Traditional method

The traditional method of using a hook is to create a function with a specific name such as:

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

This works well in many cases but has its limitations. For example, if two extensions call the same hook there is no way to determine which runs first.

For details, see Traditional Hooks

Symfony method

A newer method that provides greater flexibility is to use Symfony events.

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 ($objectName != "Address" || $op != "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 $objectRef) 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.