CiviCRM Developer Guide

Developing Custom Searches

A custom search is a method of providing new functionality within the standard CiviCRM navigation structure. This chapter looks at how to develop a Custom Search.

Custom searches produce a screen showing a set of contacts, from where you can execute actions such as sending email, printing mailing labels, and all the other actions that are available for contact search results within CiviCRM. The results page displays the same as any other search results page, such as results delivered from an Advanced Search, however, a predefined set of functions controls which information is delivered to the result page.

Custom Searches follow the Hollywood principle, "Don't call me, I'll call you." In this case CiviCRM calls your functions at the appropriate time.




When to use Custom Searches

A custom search is the right choice when ...

  • you need to access the Actions list after running your search, to send email, add the contacts to a group or other actions (since the list of actions and list of tokens can be extended with developer-created hooks, the combination of a custom search + custom action + custom token is a powerful tool for implementing a special requirement. If you are interested in creating new actions or tokens, read the section on CiviCRM hooks in the chapter Extending CiviCRM.)
  • you want to create a Smart Group based on the parameters of the custom search
  • you want to use the search results to drive a mass mailing in CiviMail
  • you want results that can be sorted by clicking any column heading in the results page.

A custom search is the wrong choice when ...

  • you can use the Advanced Search or the Search Builder to create the same results
  • the information you need is not primarily accessible from the CiviCRM database tables (for example, the information needs to be retrieved from a third-party system or a different database)
  • the information you need does not include the CiviCRM contact ID (for example, information related to summarised event income). 

CiviReport is a better choice if ...

  • you need to schedule and send the entire result as an email to one or more people
  • you need a summary/detail drill-down style of results page where the detail is not just the contact summary screen
  • you want to use the results as a dashlet on your CiviCRM dashboard
  • you need multiple report break areas within the results page, such as subtotals after every 5 records as well as grand totals
  • you need to include information not related to contacts.

Getting started creating a new custom search

In this section we will create a new custom search called "BirthdaySearch" that will find all contacts whose birthdays fall in June. 

Custom searches are written using PHP and SQL. Very little knowledge of PHP is needed, as you start with a template file and only make minor changes. In many cases the only changes are the SQL Select statement and which columns to display in the results.

Plan and test

Before writing the code, it is important to plan and test the SQL query and verify the results. It is valuable at this stage to review the database tables and test the SQL select statements within the database using an SQL tool such as PHPMyAdmin.

It may be helpful for you to review the information at:

Important Note

It is important to include the contact_id field from the table civicrm_contact in your select statement. Even if you do intend to display the contact_id field, include it. The reason is that the view and edit links need the value for the cid attribute of the url. Without a value, you can not edit or view information about the contact. Follow the select statement below for guidance.

Setting the filepath

Your custom search files can be stored almost anywhere, but you must tell CiviCRM where these files are. 

  1. Within CiviCRM, go to: Administer > Configure > Global Settings > Directories
  2. Fill in the Custom PHP Path Directory. This needs to be an absolute path to your PHP directory, such as /home2/jsmith/public_html/civicrm_custom_code/
  3. Create a copy of an existing custom search file, such as the EventAggregate.php file. You will find this file at: 
    <joomla root>/administrator/components/com_civicrm/civicrm/CRM/Contact/Form/Search/Custom  
    <drupal root>/sites/all/modules/civicrm/CRM/Contact/Form/Search/Custom
  4.  Place the EventAggregate.php file in the directory: /home2/jsmith/public_html/civicrm_custom_code/CRM/Contact/Form/Search/Custom
  5. Rename the copied EventAggregate.php file to BirthdaySearch.php

Understanding and updating the search code

Start by opening the file BirthdaySearch.php in a text editor. The first change needed is to change the class declaration to:

class CRM_Contact_Form_SearCustom_UpcomingBirthdays
   implements CRM_Contact_Form_SearInterface {

Functions you will probably need to modify:

  • function_construct: this controls the columns that are part of the results page.
  • function all: this function is responsible for returning the SQL select statement to execute that gets the entire set of information. The select statement MUST include a field named "contact_id". Normally it also contains fields named "name" and "sort_name". For example:
if ( $onlyIDs ) {
     $select  = "DISTINCT as contact_id,
      civicrm_contact.display_name as name";
    } else {
    $select = "DISTINCT as contact_id, CONCAT(
     monthname(civicrm_contact.birth_date) , ' ', day(civicrm_contact.birth_date))
     as sort_name , civicrm_contact.display_name as name, 'birthday' as oc_type" ;

    $from  = $this->from( );
    $where = $this->where( $includeContactIDs ) ;
    $sql = "SELECT $select FROM  $from WHERE $where ";

  • function from: this function is responsible for returning the from clause of the SQL select statement. It is normally called from the "all" function as well as by CiviCRM.
  • function where: this function is responsible for returning the where clause of the SQL select statement.
  • function buildForm: this controls the results title as well as the parameters that are available to the person running the search. For this birthday search, you may want the user to be able to choose the month.   

Functions you may need to modify:

  • function summary: this function is needed if some or all columns have summary information, such as total number of birthdays in June.
  • function alterRow: this function allows you to alter the contents of a piece of information before the results are displayed.
  • templateFile: this function returns the name of the Smarty template that CiviCRM will use to present the results. For most purposes, the template CRM/Contact/Form/Search/Custom/Sample.tpl will suffice. 

Prepare to run the custom search

Before you can run the custom search, CiviCRM needs to be informed that it exists. This is accomplished by the following steps:

  1. Go to: Administer > Customize > Manage Custom Searches.
  2. Scroll to the bottom of the page and click the button New Custom Search.
  3. Provide the class path as: CRM_Contact_Form_SearCustom_BirthdaySearch
  4. Provide the title as: Birthday Search

The new Birthday Search should now appear in (and can be run from) the list of custom searches in the navigation menu. It will also appear on the page reached by going to Search > Custom Searches.

The new custom search will not appear in the black navigation menu unless the navigation menu is edited.  This can be done by going to Administer > Customize > Navigation Menu.

Testing the custom search

 Always test the following behaviors of the new search:

  • Test for a variety of form values, especially for invalid data. Errors in validation can lead to serious security breaches. Just because there is a drop-down list of valid months, do not assume that only valid months are passed to your custom search. Also test for values with apostrophes and other special characters.
  • Test the previous and next page links for a variety of different size results. 
  •  Test the first/last page links for a variety of different size results. 
  •  Make a Smart Group from it and send a CiviMailing to that group.
  •  Test other actions in the Action menu such as Send an Email, or create PDF letters with mail merge tokens.

An example of creating a custom search extension

Once you’ve created your custom search, you can start packaging it. Let's say you will be doing an activity search.You need to prepare the info file as described in the Extensions Framework Chapter.

Sample info.xml file

<?xml version="1.0" encoding="UTF-8" ?>
<extension key="org.civicrm.activity" type="search">
  <name>Activity Search</name>
   This custom search allows to search through activities
   and returns activities as results (not contacts as
   regular search).
  <maintainer>CiviCRM Core Team &lt;;</maintainer>
  <comments>For support, please contact project team on the forums. (</comments>


Then once you have the info file, you can start putting the extension package together. We'll choose "org.civicrm.activity" to be the unique identifier of the extension, so we need to give the same name to the directory that will contain our extension. Once you've created that, put the info.xml file in it.

Remember the "callback" section in info file? We've put the value "ActivitySearch" in there. Now it's time to prepare our custom search PHP class to fit in the package. First of all, put it in the file named exactly after the callback value minus the file extension part. So now we have a second file in our extension directory: ActivitySearch.php. The name of the class that is inside this file should be constructed the following way: "Extension_<Type>_<key>_<callback>" - which means in our case it will be: Extension_Searorg_civicrm_activity_ActivitySearch. It's rather long, but we want to avoid any problems with class name duplication. Please also note, that the extension type is capitalised, but extension key is not.

Ok, we've got the info file, there is a custom search class ready, so the last thing is a template. Just create a subdirectory of org.civicrm.activity named templates and put your template file there. You should name the template the same as your PHP file - so it should be ActivitySearch.tpl.

You should end up with the following structure:

|-- ActivitySearch.php
|-- README.txt 
|-- templates
|   `-- ActivitySearch.tpl
`-- info.xml
Let's come back to the PHP class for a minute. There are two small