Tuesday, March 6, 2012 - 06:11
Written by

<Cross posted from The code blocks will be easier to read there.>

Sometimes after launching a new site our clients find that there are fields and features in CiviCRM that they don't use.  We are working with a client that wants to remove all fields and features that aren't useful in order to simplify their user interface and make it easier to use.  This includes things like SMS features, email signatures, and demographics.  There are also several fields that they wanted renamed to be more consistent with the legacy system that they migrated from.  To fulfill this requirement I used a combination of template overrides, and CiviCRM's translation system.  

First up I should point out that if you do want to go down this path you need to make clear to the client that this will take a fair amount of effort up front (hopefully less for you now that you are reading this recipe).  Additionally, if/when you upgrade CiviCRM these customizations will need to be reviewed at the very least, and possibly even re-done to some extent.  So while this customization will make things more usable for CiviCRM administrators, it will add cost to both the initial site build, and to ongoing maintenance.  

The first step is the template overrides.  Here is where we will manually remove fields from the various CiviCRM screens. First we need to tell CiviCRM where to search for our overrides.  You can setup the template override location at
We use the following directory:
/[path to web root]/sites/all/civicrm_templates/

Then we will need to find out which template we need to override.  This step is a bit like panning for gold.  At one point in time all templates began with an HTML comment listing the file location. This doesn't seem to be the case any longer.  So the method I've been using is look for any static text, or unique looking HTML near the field in question (not including field labels).  We can then search for that string in the directory

If for example on the main contact edit form
if we want to remove the 'source' field we can search for
'contact_source-section' which is a class of the containing table.

$ cd sites/all/modules/civicrm/templates $ fgrep -R contact_source-section . ./CRM/Contact/Form/Contact.tpl:

Now that we've found the applicable template we can copy it to our override directory, mirroring the existing directory structure.

$ cd sites/all/civicrm_templates $ mkdir -p CRM/Contact/Form $ cp ../modules/civicrm/CRM/Contact/Form/Contact.tpl CRM/Contact/Form

Whenever you override a template file I recommend adding a comment to the top explaining what you are doing. This should make it easier when it's time to recreate this modification in a future version of CiviCRM

{* What's changed from the original: - Remove Source (There's now a custom field on the Attributes tab). *}

Then I recommend commenting out the field that you are removing rather than deleting it. Again, your future self will thank you.

{* *}

Now if you've done everything correctly you can navigate to the page in your browser and the field will be gone.  

This technique works great to remove fields from various pages, but you'll find that the fields still exist in places like the import and export wizards, and the search builder.  Here is where we will use the translation system to remove the fields.  Typically the translation system is used for changing the CiviCRM interface to use a language other than American English.  But with a little creativity it can be used for a lot of other things as well.   

First off we need to figure out what the string is we need to translate.  Unfortunately strings in CiviCRM are not very consistent.  For example the following strings are all in use:
Do not SMS
Do not Sms
Do not sms
In addition, depending on the field that you are working with there may also be occurrences with a trailing question mark.  To get a list of all strings that need to be replaced I downloaded the CiviCRM *.po files and searched through those.  You can get a copy of the latest *.po files from Transifex (you'll need an account):

Once you've got the *.po files you can search for variations of the string:

$ fgrep -Rni "Current Employer" . | fgrep msgi #snip ./for_use_civicrm_common-base_en_US.po:782:msgid "Current Employer" ./for_use_civicrm_common-base_en_US.po:3094:msgid "Current Employer ID" # snip ./for_use_civicrm_common-base_en_US.po:10221:msgid "Current Employer?" ./for_use_civicrm_common-base_en_US.po:11280:msgid "view current employer" ./for_use_civicrm_menu_en_US.po:1394:msgid "Current Employer Report"

You can probably ignore any occurrences of the string in help text. Then we have to decide how to translate these strings. The simplest way is to use the built-in translation tool:
However I wanted to be able to version my changes so I wrote up a custom translation function instead.

function oa_custom_civicrm_translate($text, $params) { static $map, $search, $replace; if (empty($map) || empty($search)) { $map = array( // Empty strings. 'Do Not Sms' => '', 'Do Not Trade' => '', 'Gender' => '', 'Birth Date' => '', 'Deceased Date' => '', 'Preferred Communication Method' => '', 'Nick Name' => '', 'Preferred Language' => '', 'Preferred Mail Format' => '', 'Is Deceased' => '', 'OpenID' => '', 'SMS' => '', 'IM Provider' => '', 'Unique ID (OpenID)' => '', 'Date of birth' => '', 'Contact is deceased' => '', 'Deceased date' => '', 'Signature (Text)' => '', 'Signature (HTML)' => '', 'Birth Dates - From' => '', 'Deceased Dates - From' => '', 'Signature Text' => '', 'Signature Html' => '', 'IM Screen Name' => '', 'Openid' => '', 'Signature' => '', 'Label for Signature' => '', 'Do not sms' => '', 'Do not trade' => '', 'Birth-Date' => '', 'Text Message' => '', 'Date Deceased' => '', 'Contact is Deceased' => '', 'Nickname' => '', 'Signature HTML' => '', 'Deceased' => '', 'IM Location Type' => '', 'OpenID Location Type' => '', 'Is OpenID Primary?' => '', 'Source of Contact Data' => '', 'Contact Source' => '', // Exact replace. 'Bulk' => 'Use for CiviMail', 'Bulk Mailings?' => 'Use for CiviMail?', 'Do not mail' => 'No hard copies', 'Current Employer' => 'Current Employer Organization', 'Current Employer?' => 'Current Employer Organization?', 'Current Employer ID' => 'Current Employer Organization ID', ); // These search strings will be replaced even if they are only part of the source string. ex. // 'Does the user have a CMS account?' => 'Does the user have a Drupal account?' $search_replace = array( 'CMS' => 'Drupal', 'User Opt Out' => 'Contact has opted out', ); // End of configuration. The rest of this function is the heavy lifting. $search = array_keys($search_replace); $replace = array_values($search_replace); } if ($params) { // @see crm_translate() if (isset($params['escape'])) { $escape = $params['escape']; unset($params['escape']); } if (isset($params['skip']) and $params['skip']) { if (isset($escape) and ($escape == 'sql')) $text = mysql_escape_string($text); if (isset($escape) and ($escape == 'js')) $text = addcslashes($text, "'"); return $text; } // Setup plural. if (isset($params['plural'])) { $plural = $params['plural']; unset($params['plural']); if (isset($params['count'])) { $count = $params['count']; } } // Remaining non-arg params that aren't useful for our simple translation scheme. foreach(array('context', 'plural', 'count') as $param) { if (isset($params[$param])) { unset($params[$param]); } } } // Translations. $exactMatch = false; if (isset($map[$text])) { $text = $map[$text]; } if (!$exactMatch) { $text = str_replace($search, $replace, $text); } // Plural. if (isset($count) && isset($plural)) { if ($count != 1) { $text = $plural; } $text = strtr($text, array('%count' => $count)); } // Arguments. if ($params) { $tr = array(); $position = 1; foreach ($params as $param) { $tr['%' . $position++] = $param; } $text = strtr($text, $tr); } return $text; }

You can tell CiviCRM about your custom translation function at

In the above code block you might be wondering why we are translating several fields to an empty string. This is where the trickery happens. With the above translation in place there will now be several blank options in the select lists.

But with a bit of jQuery we can clean that up.

(function ($) { $(document).ready(function(){ // Deal with Null Translated strings. function cleanUpOptions() { $('option').each(function(){ if ($(this).text() == '') { $(this).remove(); } }); } // Run our cleanup function on doc ready, and when these funky parent selectors update. cleanUpOptions(); $('td.form-item select').change(function(){ cleanUpOptions(); }); $('.type-selector').change(function(){ cleanUpOptions(); }); }); })(jQuery);

Voila! The fields are now removed from CiviCRM.


I wonder - would it be possible to accomplish this task entirely via jquery? If so - you could use the .extra.tpl method outlined in <a href=" blog</a> and not bother with modifying templates at all (makes it much easier with upgrades).

Or even more elegant - would it be possible to remove fields via the <a href=" hook</a> by unsetting keys in the $form array?

Perhaps some of the pesky strings are hardcoded in the templates... but these methods might be a good place to start before editing templates.



Thank you for this guide. Can anyone explain though how to implement the custom translation function? The guide says

"You can tell CiviCRM about your custom translation function at"

But I don't understand where to put the funciton in my (Drupal 7) install or what to point admin/setting/localization to. Whatever I put in that field gives the error "Please define the custom translation function first" and I cannot find any documentation at all on this feature. Everything I think of a 'sensible' thing to do on this isn't working.. Can anyone explain exactly where I should put the custom translate php file in a drupal 7/civi install and what I should point the localization settings page to? Sorry if I'm missing something obvious..


Thank you


Regarding localization tools, I want to recommend to all the readers and software translators a really nice tool that has a lot of potential: It feels nice to work with it and it eases the work a lot. You should really try it.