13.11.2025 | Nikolas Kopp, Pascal Crott

Token Chaining – How to access tokens for nested information

my_group.module
/**
 * Implements hook_tokens().
 */
function example_group_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
  $replacements = [];

  // Automatically expose related entities to group_relationships.
  if ($type == 'group_relationship' && !empty($data[$type])) {
    $token_service = \Drupal::token();

    /** @var \Drupal\example\GroupRelationshipTypeServiceInterface $group_relationship_type_service */
    $group_relationship_type_service = \Drupal::service('example.group_relationship_type_service');

    $group_relationship = $data['group_relationship'];
    assert($group_relationship instanceof GroupRelationshipInterface);

    foreach ($tokens as $name => $original) {
      /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
      foreach ($group_relationship_type_service->getConfiguredEntityTypes() as $entity_type_id => $entity_type) {
        if ($name == $entity_type_id) {
          $entity = $group_relationship->getEntity();
          $bubbleable_metadata->addCacheableDependency($entity);
          $replacements[$original] = $entity->label();
        }

        // Actual chaining of tokens handled below.
        if ($entity_tokens = $token_service->findWithPrefix($tokens, $entity_type_id)) {
          $replacements += $token_service->generate($entity_type_id, $entity_tokens, [$entity_type_id => $group_relationship->getEntity()], $options, $bubbleable_metadata);
        }
      }
    }
  }

  return $replacements;
}

Issue:

Out of the box there are plenty of tokens available from the group module. In case those don't fit your needs, you can chain the group_relationship's entity tokens to get the desired value from the referenced entity.

Solution:

Use the snippet to gain access to chained tokens, e.g. users via group relationships.

This snippet has been turned into the group_relationships_tokens module.