Custom views field handler with relationship support, but without table I created a custom field handler following the very useful explanations here,
Custom views handler without table

The field handler picks the entity from each row and renders it in some way. (For simplicity lets just assume it renders a link to the entity using entity_label() and entity_uri(). It actually does more awesome stuff, but this is irrelevant here.)

Unfortunately, I found that I cannot configure a relationship when using this field handler. E.g. if I have a relationship "The node author", then I might want to render the title + link for the node author instead of the node.

Is there anything more I need to enable support for relationships?

/**
 * Implements hook_views_data().
 */
function EXAMPLE_views_data() {
  $data = array();
  $data['EXAMPLE']['table']['group'] = t('Custom');
  // #global is a special flag which let's a table appear all the time.
  $data['EXAMPLE']['table']['join']['#global'] = array();

  $data['EXAMPLE']['custom_handler'] = array(
    'title' =&gt; t('EXAMPLE field handler'),
    'help' =&gt; t('EXAMPLE field handler'),
    'field' =&gt; array(
      'handler' =&gt; ExampleViewsFieldHandler::class,
    ),
  );

  return $data;
}


I could also post the code of the field handler class, but I supposed it is not really that relevant..
Ok, I found one way to do this: Instead of registering just one handler, you register one handler per entity type. To save time, we can reuse the stuff already registered by Entity API, with entity_views_data(). This means, of course, the module needs to add Entity API as a dependency.

/**
 * Implements hook_views_data_alter().
 */
function EXAMPLE_views_data_alter(&amp;$data) {

  foreach (entity_get_info() as $entity_type =&gt; $info) {
    if (!isset($data['views_entity_' . $entity_type]['rendered_entity'])) {
      // Only register for those entity types where Entity API already did its job.
      continue;
    }
    $data['views_entity_' . $entity_type]['EXAMPLE'] = array(
      'title' =&gt; t('Entity display plugin'),
      'help' =&gt; t('The @entity-type of the current relationship rendered using an entity display plugin.', array('@entity-type' =&gt; $info['label'])),
      'field' =&gt; array(
        'handler' =&gt; ExampleViewsFieldHandler::class,
        'type' =&gt; $entity_type,
        // The EntityFieldHandlerHelper treats the 'entity object' data
        // selector as special case for loading the base entity.
        // @todo Not sure if we really need this.
        'real field' =&gt; 'entity object',
      ),
    );
  }
}


And below is the field handler class, just a bit simplified.

Maybe it does a bit more than we really need here. E.g. we don't really need to create a render array for each row, we could work with html directly. But I think it is a good start.

class ExampleViewsFieldHandler extends \views_handler_field {

  function query() {
    // do nothing -- to override the parent query.
  }

  /**
   * Run before any fields are rendered.
   *
   * This gives the handlers some time to set up before any handler has
   * been rendered.
   *
   * @param object[] $rows
   *   An array of all objects returned from the query.
   */
  function pre_render(&amp;$rows) {
    /**
     * @var string $entityType
     * @var object[] $entities
     */
    list($entityType, $entities) = $this-&gt;getResultEntities($rows);
    // Build the entities.
    $builds = $this-&gt;buildMultiple($entityType, $entities);
    foreach ($rows as $rowIndex =&gt; $row) {
      if (isset($builds[$rowIndex])) {
        /** @noinspection PhpUndefinedFieldInspection */
        $row-&gt;example_field_builds[$this-&gt;position] = $builds[$rowIndex];
      }
      else {
        /** @noinspection PhpUndefinedFieldInspection */
        unset($row-&gt;example_field_builds[$this-&gt;position]);
      }
    }
  }

  /**
   * @param object[] $rows
   *
   * @return mixed[]
   *
   * @see EntityFieldHandlerHelper::pre_render()
   */
  protected function getResultEntities(array $rows) {
    $relationship = !empty($this-&gt;relationship) ? $this-&gt;relationship : NULL;
    $field_alias = isset($this-&gt;real_field) ? $this-&gt;real_field : NULL;
    // Some views query classes want/allow a third parameter specifying the field name.
    /** @noinspection PhpMethodParametersCountMismatchInspection */
    list($entityType, $entities) = $this-&gt;view-&gt;query-&gt;get_result_entities($rows, $relationship, $field_alias);
    return array($entityType, $entities);
  }

  /**
   * @param object $row
   *
   * @return string
   */
  function render($row) {
    /** @noinspection PhpUndefinedFieldInspection */
    return isset($row-&gt;example_field_builds[$this-&gt;position])
      ? drupal_render($row-&gt;example_field_builds[$this-&gt;position])
      : NULL;
  }

  /**
   * @param string $entityType
   * @param object[] $entities
   *
   * @return array[]
   *   A render array for each entity.
   */
  protected function buildMultiple($entityType, array $entities) {
    $builds = array();
    foreach ($entities as $rowIndex =&gt; $entity) {
      $builds[$rowIndex] = array(
        '#markup' =&gt; entity_label($entityType, $entity),
      );
    }
    return $builds;
  }
}