Skip to content

Token Reference

Introduction

CiviCRM tokens enable one to generate personalized emails, reminders, print documents. etc.

When working with tokens as a developer, there are a few major tasks:

  • Composing a message by evaluating a token expression (Hello {contact.first_name}! How do you like {address.city}?).
  • Defining a token and its content.
  • Listing available tokens.

For each task, there are a couple available patterns, and we'll explore them in more depth below. But first, it helps to see a few examples.

Examples

Token Description Example Value
{domain.name} Name of this domain (organization/site/deployment) Do-Gooder International
{domain.address} Meta-token with the full-formed mailing address of this domain (organization/site/deployment)) 100 Roadie St
Townville 54321
{contact.first_name} The contact's first_name Alice
{contact.display_name} The contact's display_name Bob Roberts
{mailing.name} The name of the mailing January Newsletter

For more examples of tokens and token replacement, see User Guide: Common workflows: Tokens and mail merge.

Composing messages

Individual messaging

TokenSmarty was added in v5.41. (History)

The TokenSmarty utility provides a simple way to render one message:

// Usage
$rendered = CRM_Core_TokenSmarty::render(array $templates, array $context);

// Example
$rendered = CRM_Core_TokenSmarty::render(
  ['html' => '<p>Hello {contact.display_name}!</p>'],
  ['contactId' => 123],
);
echo $rendered['html'];

This example shows a few important pieces:

  • The $templates array provides one (or more) templates. Templates should be given as text or html. If you are composing an email message (which may have both text and HTML components), then you may provide multiple templates.
  • The $context array provides data. As you add more context data, it will enable more tokens. (For example, passing contactId will enable {contact.*} tokens.) For more detailed information, see Appendix: Token Context.
  • In CiviCRM, it is common for user-supplied templates to be written with a mix of token and Smarty notations.

The TokenSmarty utility is suited to rendering one message for one recipient. It is a small wrapper for TokenProcessor, which provides additional functionality - such as batch-processing and token-inspection.

Batched messaging

TokenProcessor was added in v4.7 and phased-in gradually, with overall adoption around v5.43. (History)

If you will be composing a large number of concurrent messages (eg an email-blast for thousands of recipients), then you should use TokenProcessor directly. TokenProcessor includes optimizations for handling batched data.

To compose a batch, create an instance of TokenProcessor, fill-in details about the batch, and then evaluate the tokens. For example:

use Civi\Token\TokenProcessor;

$tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
  // Give a unique identifier for this instance.
  'controller' => __CLASS__,

  // Enable or disable handling of Smarty notation.
  'smarty' => FALSE,

  // List any data fields that we plan to provide.
  'schema' => ['contactId'],
]);

// Define the message template(s).
$tokenProcessor->addMessage('body_text', 'Hello {contact.display_name}!', 'text/plain');
$tokenProcessor->addMessage('body_html', '<p>Hello {contact.display_name}!</p>', 'text/html');

// Fill in data about the recipients/targets.
$tokenProcessor->addRow(['contactId' => 123]);
$tokenProcessor->addRow(['contactId' => 456]);
$tokenProcessor->addRow(['contactId' => 789]);

// Evaluate any tokens which are referenced in the message.
// In this example, `evaluate()` will:
//  - Recognize that `{contact.display_name}` needs to be available.
//  - Fetch the display names (`SELECT id, display_name FROM civicrm_contact WHERE id IN (123,456,789)`)
$tokenProcessor->evaluate();

// Display mail-merge data.
foreach ($tokenProcessor->getRows() as $row) {
  echo $row->render('body_text');
}

This example is similar to the TokenSmarty example, but it goes a bit further and provides more structure:

  • It handles several recipents with different context-data (addRow(array $context)).
  • It distinguishes between per-recipient context-data (addRow(array $context)) and shared context-data (new TokenProcesser($dispatcher, array $context)).
  • It handles several message templates with distinct names and MIME types (addMessage(string $name, string $template, string $type)).
  • It splits composition into a couple steps: evaluate() examines the tokens and loads supplemental data. render() gives you the final text.

Defining tokens

To define new tokens, you must subscribe to the token events. Depending on how much legacy support you need to provide you may use the preferred ones, or the legacy ones:

Events Strength Weakness
civi.token.list +
civi.token.eval
More supported.
More forward compatibility.
More complete API.
Works with any entity.
Less backward compatibilty.
hook_civicrm_tokens +
hook_civicrm_tokenValues
More backward compatiblity. Deprecated.
Contact-only.
Less complete API. More DB queries

Let us do an example. This listens to civi.token.* and defines two tokens, {profile.viewUrl} and {profile.viewLink}.

use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Add token services to the container.
 *
 * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
 */
function example_civicrm_container(ContainerBuilder $container) {
  $container->addResource(new FileResource(__FILE__));
  $container->findDefinition('dispatcher')->addMethodCall('addListener',
    ['civi.token.list', 'example_register_tokens']
  )->setPublic(TRUE);
  $container->findDefinition('dispatcher')->addMethodCall('addListener',
    ['civi.token.eval', 'example_evaluate_tokens']
  )->setPublic(TRUE);
}

function example_register_tokens(\Civi\Token\Event\TokenRegisterEvent $e) {
  $e->entity('profile')
    ->register('viewUrl', ts('Profile View URL'))
    ->register('viewLink', ts('Profile View Link'));
}

function example_evaluate_tokens(\Civi\Token\Event\TokenValueEvent $e) {
  foreach ($e->getRows() as $row) {
    /** @var TokenRow $row */
    $row->format('text/html');
    $row->tokens('profile', 'viewUrl', 'http://example.com/profile/' . $row->context['contactId']);
    $row->tokens('profile', 'viewLink', ts("<a href='%1'>Open Profile</a>", array(
      1 => $row->tokens['profile']['viewUrl'],
    )));
  }
}

Some notes on the the above:

  • $row->context['...'] returns contextual data, regardless of whether you declared it at the row level or the processor level.
  • To update a row's data, use the context() and tokens() methods. To read a row's data, use the $context and $tokens properties. These interfaces support several notations, which are described in the TokenRow class.
  • You have control over the loop. You can do individual data-lookups in the loop (for simplicity) – or you can also do prefetches and batched lookups (for performance).
  • To avoid unnecessary computation, you can get a list of tokens which are actually required by this mailing. Call $e->getTokenProcessor()->getMessageTokens().
  • In this example, we defined tokens in HTML format, and we rely on a default behavior that auto-converts between HTML and text (as needed). However, we could explicitly define HTML and plain-text variants by calling $row->format() and $row->tokens() again.
  • The class \Civi\Token\AbstractTokenSubscriber provides a more structured/opinionated way to handle these events.
  • For background on the event dispatcher (e.g. listeners vs subscribers), see Symfony Documentation

The main limitation of this technique is that it only works with TokenProcessor. At time of writing, this is used for Scheduled Reminders and FlexMailer.

Listing tokens

Suppose the application sends an automated "Thank You" letter for each donation. The administrator may customize this letter - choosing their own prose and mixing in different tokens. But which tokens can they use? In the administrative interface, you should provide a list of available tokens (via dropdown, autocomplete, etc).

The list of tokens depends on the use-case and available data, as in:

Use-Case Available Data Available Tokens
Send a donation "Thank You" letter Contact record
Contribution record
{contact.*}
{contribution.*}
Send a newsletter Contact record
Mailing record
{contact.*}
{mailing.*}
{action.*}

This list is also influenced by extensions - an extension may define extra tokens like {contact.foo_bar} or {foo.bar}.

An application may request the list of available tokens via listTokens():

$tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
  'controller' => __CLASS__,
  'smarty' => FALSE,
  'schema' => ['contactId', 'contributionId'],
]);
$available = $tokenProcessor->listTokens();

Note that we do not provide a literal contactId or contributionId -- these values do not exist yet. (We are acting as administrators preparing a template.) However, we will provide them when it matters - and this means various tokens will be valid. The schema gives an important hint to determine available tokens.

Appendix: Token Context

When creating a TokenProcessor and adding rows/recipients, one passes in context data. The data passed in will determine which tokens are available. These are common parameters for enabling common tokens:

Parameter Description
activityId (int) Required for {activity.*} tokens
contactId (int) Required for {contact.*} tokens
contributionId (int) Required for {contribution.*} tokens
mailingId (int) Required for {mailing.*} tokens
mailingActionTarget (array) Required for CiviMail {action.*} tokens
mailingJobId (int) Required for CiviMail {action.*} tokens

Additionally, there are some generic parameters. These provide extra hints to the token-providers about what's going on.

Parameter Description
controller (string) Give a symbolic name for this processor, such as "CiviMail" or "ScheduledReminder". Usually, this is the name of the controller class.
smarty (bool) Enable or disable support for Smarty directives
schema (string[]) List of fields used by the TokenProcessor. By default, this is array_keys($context). However, you may add extra fields here to promise that values will be provided in the future. See Listing Tokens.

Appendix: History

Token functionality originated in CiviMail, which focuses on writing and delivering newsletters to large constituencies. In its original form, the design placed heavy weight on:

  • Performance: Divide mail-composition into batches and split the batches among parallel workers. Moreover, when processing each batch, improve efficiency by minimizing the #SQL queries - i.e. fetch all records in a batch at once, and only fetch columns which are actually used.
  • Security: Do not trust email authors with a fully programmable language.
  • Contact Records: The main data for mail-merge came from contact records. Other data (contribution, event, participant, membership, etc) were not applicable.
  • Adaptive Text/HTML: Email messages often have two renditions, text/plain and text/html. Some tokens, such as {domain.address}, may present different formatting in each medium. Other tokens, such as {action.unsubscribe}, can even present a different user-experience.

Over time, the token functionality evolved:

  • Add optional support for more powerful templates with conditions and loops (Smarty). (In the case of CiviMail, this was still disabled by default as a security consideration, but in other use-cases it might be enabled by default.)
  • Add a hook for custom tokens.
  • Expand to other applications, such as individual mailings, print letters, receipts for contributions, and scheduled reminders.

Up through CiviCRM v4.7, the primary interfaces for working with tokens were CRM_Utils_Token (helpers for composing messages), hook_civicrm_tokens and hook_civicrm_tokenValues (helpers for custom tokens).

CiviCRM v4.7 introduced Civi\Token\TokenProcessor and civi.token.*, which provide a more flexible way to define and process tokens. These preserve the performance, batching, and security virtues of CRM_Utils_Token and also:

  • Allow more contextual information -- enabling tokens for more entities (Contribution, Participant, etc).
  • Loosen the coupling between token-consumers and token-providers.
  • Loosen the coupling between token-content and template-language.
  • Centralize the evaluation of token language.
  • Distinguish token-data which is defined with (or without) HTML content.
  • Further reduce unnecessary computations (custom tokens only need to be prcoessed if they are being used)

In v4.7, the TokenProcessor and civi.token.* were initially used for Scheduled Reminders (CRM-13244), and they could be optionally used for CiviMail (by installing/configuring FlexMailer). Specific use-case have changed during 5.x, with most being converted circa v5.43.

Appendix: CRM_Utils_Token

For most of its history, CiviMail used a helper class, CRM_Utils_Token, with a number of static helper functions. This class is now deprecated and should not be used.

See also