Working on a mailing that includes contacts full address details. And I'm encountering an issue for UK addresses where the county is abbreviated in the same way US states are. I've never seen such 3-char abbreviations used in the UK and it will cause confusion. Omitting the county is not an option either. Sadly the standard address hooks for mailings are limited.

Googling yielded the following page which has code I'm trying to use in a custom Civicrm extension. So far my attempts are failing miserably. I'm not very familiar with PHP, so please excuse and gently correct whatever atrocities I might be committing in the code below.


The extension is meant to provide two new tokens to mailings:

  • {contact.province}: prints the State / Province full name (as in {contact.state_province_name}). Not the same name in case this brings any conflict with the existing one
  • {contact.complete_address}: prints the full contact's address with the same format set in the label address customization

The article is quite old and uses APIv2, so I've resorted to adapt it to APIv4. In doing so my PHP errors have disappeared but I'm not seeing any results in "Preview as HTML" yet when editing a test mailing.

My hooks code below:

// Works, the new hooks show up in the list when editing mailings //
function mailhooks_civicrm_tokens( &$tokens ) {
    $tokens['contact'] = array(
        'contact.province' => 'Province',
        'contact.complete_address' => 'Complete Address',
// Not returning anything, the tags are not interpreted at all //
function mailhooks_civicrm_tokenValues( &$values, &$contactIDs ) {
    require_once 'CRM/Utils/Address.php';
    foreach($contactIDs as $id){
        $address = \Civi\Api4\Address::get()
            ->addSelect('street_address', 'supplemental_address_1', 'supplemental_address_2', 'supplemental_address_3', 'city', 'postal_code', 'state_province_id:label', 'country_id:label')
            ->addWhere('contact_id', '=', $id)
        $values[$id]['contact.complete_address'] = nl2br(CRM_Utils_Address::format($address[$id]));
        $values[$id]['contact.province'] = $address[$id]["state_province_id:label"];

The output from the API Explorer (dummy address):

(1) [
    "id": 3,
    "street_address": "3 Kings Road",
    "supplemental_address_1": null,
    "supplemental_address_2": null,
    "supplemental_address_3": null,
    "city": "Hitchin",
    "postal_code": "SG5 0AZ",
    "state_province_id:label": "Hertfordshire",
    "country_id:label": "United Kingdom"

Partial HTML preview:

SG5 0AZ, {contact.province}
United Kingdom

Email template:

{contact.postal_code}, {contact.province}
  • I must be doing something wrong as when I simplify the return of values, nothing is returned: $values[$cid]['contact.complete_address'] = 'test';
    – dmgeurts
    Nov 4, 2020 at 22:41
  • I'm not following why you want {contact.province} when {contact.state_province_name} already exists with the full UK County name?
    – Aidan
    Nov 9, 2020 at 21:02
  • @Aidan Because for some reason '{contact.state_province_name}' isn't available in mailings.
    – dmgeurts
    Nov 10, 2020 at 9:57

2 Answers 2


It should be like below

// Not returning anything, the tags are not interpreted at all //
function mailhooks_civicrm_tokenValues( &$values, &$contactIDs ) {
  foreach($contactIDs as $id){
    $address = \Civi\Api4\Address::get()
      ->addSelect('street_address', 'supplemental_address_1', 'supplemental_address_2', 'supplemental_address_3', 'city', 'postal_code', 'state_province_id:label', 'country_id:label')
      ->addWhere('contact_id', '=', $id)
    $values[$id]['contact.complete_address'] = nl2br(CRM_Utils_Address::format($address));
    $values[$id]['contact.province'] = $address["state_province_id:label"];



  • Thank you, Pradeep. Sadly the HTML Preview is still not converting the tokens. Is it an issue that on the preview HTML the contactIDs value is likely a single entry and not an array or is this always parsed as an array to tokenValues?
    – dmgeurts
    Nov 3, 2020 at 9:30
  • Hmmm, array vs single entry is unlikely the issue. adding "$contactIDs = (is_array($contactIDs)) ? $contactIDs : [$contactIDs];" to ensure the parsed values are an array doesn't make any difference.
    – dmgeurts
    Nov 3, 2020 at 9:39

This doesn't fix the larger issue (see e.g. https://lab.civicrm.org/dev/core/-/issues/1709) but you can update your database to put the full name in the abbreviation field:

alter table civicrm_state_province modify column abbreviation varchar(64) DEFAULT NULL;

update civicrm_state_province s inner join civicrm_country c ON s.country_id = c.id SET s.abbreviation = s.name WHERE c.iso_code='GB';

You might need to re-run if new state/provinces are added to GB.

  • New counties in the UK are VERY rare! Will check the link, thank you.
    – dmgeurts
    Nov 17, 2020 at 22:53
  • That worked a charm thank you!. Pitty that something like {contact.complete_address} isn't available to mailings either.
    – dmgeurts
    Nov 17, 2020 at 23:05
  • It seems like that would be a reasonable thing to have.
    – Demerit
    Nov 17, 2020 at 23:13
  • Darn it, I assigned the bounty to the wrong answer and can't undo this... Sorry!
    – dmgeurts
    Nov 22, 2020 at 23:32
  • No worries! Thanks for the note.
    – Demerit
    Nov 22, 2020 at 23:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.