Skip to content



This hook is used to add or modify display columns and filters.

The hooks does not have a well defined contract but CiviReport is not really being developed anymore so it is fairly static. This hook can be used for light modification of CiviReports but in general is is more robust to create your own report template or to use SearchKit instead. In general the CiviReport framework is expected to be replaced by SearchKit

As always you should ensure your code has good test cover to avoid breakage on upgrade - especially if you are using getVar() or setVar() to access the internals of the report object.


alterReportVar($varType, &$var, $reportForm) {


  • $varType - a string containing the value "columns", "rows", "sql", "actions", or "outputhandlers" depending on where the hook is called.
  • &$var - a mixed var containing the columns, rows, or SQL, depending on where the hook is called
  • $reportForm - a reference to the CRM_Report_Form object.

Explanation of varType values

  • columns - this is called early in report processing and allows you to add/change the columns in the report. $var will contain the existing $reportForm->_columns member.
  • rows - this is called near the end of report processing, but before custom fields have been formatted. $var will contain an array of rows indexed sequentially.
  • sql - this is called shortly before the query is executed. $var here is the same as $reportForm. You can alter $var->_select, $var->_from, etc.
  • actions - this is called after the actions dropdown is built. $var is an array of rows of the form 'report_instance.csv' => ['title' => 'Export to CSV'].
    • The value arrays may contain other entries besides 'title', e.g. for save/copy operations.
    • If you need your additions to be integrated with outputhandlers below, then the top-level keys should start with report_instance. the same as the core entries.
  • outputhandlers - this is called when building the list of possible candidate implementations for a given output format, e.g. the built-in CSV, PDF, and Print formats. $var is an array of class names with rows of the form '\CRM_Report_OutputHandler_Csv' => '\CRM_Report_OutputHandler_Csv'.
    • The value is duplicated in the key to both avoid duplicates and make it easier to unset one you might want to remove.


  • null

Performance Considerations

It is often more performant to change the report query on $varType == 'sql' than to do database lookups on each row in the rows in the $var array on $varType == 'rows'.

Example 1

From the Mandrill Transactional Email extension, this code checks to see if this is a mailing bounce/open/clicks/detail report. If so, it uses mailing data stored in a custom table "civicrm_mandrill_activity", and sets the SQL and columns appropriately.

 * Implementation of hook_civicrm_alterReportVar
function mte_civicrm_alterReportVar($varType, &$var, $reportForm) {
  // Note that  _instanceValues is not intended to be an external property of the report object.
  // interacting with properties of the report directy (or via `getVar()` or `setVar()`) may 
  // work but make also break on upgrade as the properties the report uses may change 
  $instanceValue = $reportForm->_instanceValues';
  if (!empty($instanceValue) && in_array($instanceValue['report_id'], array(
) {
  if ($varType == 'sql') {
    if (array_key_exists('civicrm_mailing_mailing_name', $var->_columnHeaders)) {
      $var->_columnHeaders['civicrm_mandrill_activity_id'] = array(
        'type' => 1,
        'title' => 'activity',
      $var->_columnHeaders['civicrm_mailing_id'] = array(
        'type' => 1,
        'title' => 'mailing id',
      $var->_select .= ' , civicrm_mandrill_activity.activity_id as civicrm_mandrill_activity_id, as civicrm_mailing_id ';
      $from = $var->getVar('_from');
      $from .= ' LEFT JOIN civicrm_mandrill_activity ON = civicrm_mandrill_activity.mailing_queue_id';
      // Be warned - using setVar() implies you are trying to change a value that is not supported 
      // for external modification
      $var->setVar('_from', $from);
      if ($instanceValue['report_id'] == 'Mailing/opened') {
        $var->_columnHeaders['opened_count'] = array(
          'type' => 1,
          'title' => ts('Opened Count'),
        $var->_select .= ' , count(DISTINCT( as opened_count';
        $var->_groupBy = ' GROUP BY';
  if ($varType == 'rows') {
    $mail = new CRM_Mailing_DAO_Mailing();
    $mail->subject = "***All Transactional Emails***";
    $mail->url_tracking = TRUE;
    $mail->forward_replies = FALSE;
    $mail->auto_responder = FALSE;
    $mail->open_tracking = TRUE;
    if (array_key_exists('civicrm_mailing_mailing_name', $reportForm->_columnHeaders)) {
      foreach ($var as $key => $value) {
        if (!empty($value['civicrm_mandrill_activity_id']) && $mail->id == $value['civicrm_mailing_id']) {
          $var[$key]['civicrm_mailing_mailing_name_link'] = CRM_Utils_System::url(
          $var[$key]['civicrm_mailing_mailing_name_hover'] = ts('View Transactional Email');
      unset($reportForm->_columnHeaders['civicrm_mandrill_activity_id'], $reportForm->_columnHeaders['civicrm_mailing_id']);

Example 2

Implementing $varType 'actions' and 'outputhandlers'.

/// FILE: example.php

 * Implementation of hook_civicrm_alterReportVar.
function example_civicrm_alterReportVar($varType, &$var, $reportForm) {
  switch ($varType) {
    case 'outputhandlers':
      // Add my own
      $var['\My\Namespace\SampleOutputHandler'] = '\My\Namespace\SampleOutputHandler';

      // Don't let people use the built-in pdf output.
      // Disabling it here as well as 'actions' will also prevent the
      // mail_report job from using format=pdf.

      // Override the built-in csv with my own (or you could unset and add
      // your own, and then also add in 'actions')
      $var['\CRM_Report_OutputHandler_Csv'] = '\My\Namespace\CsvOutputHandler';

    case 'actions':
      // Add my own
      $var['report_instance.sample'] = ['title' => 'Export Sample'];

      // Don't let people use the built-in pdf output

 * You may also want to implement hook_civicrm_links to update the
 * links on the report instance listing pages.
 * Implementation of hook_civicrm_links.
function example_civicrm_links($op, $objectName, $objectId, &$links, &$mask, &$values) {
  if ($op == '') {
    // These have slightly different array keys than other links.
    // See CRM/Report/Page/InstanceList.php
    $links['sample'] = [
      'label' => E::ts('Export to Sample'),
      'url' => CRM_Utils_System::url(

/// FILE: My/Namespace/CsvOutputHandler.php
/// Note we're extending the original built-in CSV class in order to
/// override part of its functionality.

namespace My\Namespace;
use CRM_Example_ExtensionUtil as E;
class CsvOutputHandler extends \CRM_Report_OutputHandler_Csv {
  // We just want to override the email body text because here we don't
  // want to include the link to the report and the stock wording,
  // otherwise this class works the same as the core one, so this is all
  // we need to do.
  public function getMailBody():string {
    return $this->getForm()->getReportHeader()
      . E::ts('Here is my custom email body text.')
      . $this->getForm()->getReportFooter();

/// FILE: My/Namespace/SampleOutputHandler.php
/// Here we're adding a whole new type of output format.

namespace My\Namespace;
use CRM_Example_ExtensionUtil as E;
class SampleOutputHandler extends \Civi\Report\OutputHandlerBase {
   * Override functions from the base class as needed.
   * If you really want to start from scratch you don't have to extend,
   * but then the class declaration needs to implement
   * \Civi\Report\OutputHandlerInterface.

  public function isOutputHandlerFor(\CRM_Report_Form $form):bool {
    // You can have more complicated decision-making, e.g. based on the
    // report classname, or the instance id which you can get from
    // $form->getID(), but it should also be related somehow to what
    // you used in the $varType == 'actions' hook.
    return ($form->getOutputMode() === 'sample');

  // override other functions based on what this format is ...

There is also a working sample extension available.