diff --git a/src/Configs/EntityTransformDisplayInterface.php b/src/Configs/EntityTransformDisplayInterface.php
index 6e4f8393530903cffc60ffeca23ba86866a563bc..b56cea899ee813780c668d31257302d5b9355977 100644
--- a/src/Configs/EntityTransformDisplayInterface.php
+++ b/src/Configs/EntityTransformDisplayInterface.php
@@ -3,10 +3,48 @@
 namespace Drupal\transform_api\Configs;
 
 use Drupal\Core\Entity\Display\EntityDisplayInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
 
 /**
  * Interface for transform modes.
  */
 interface EntityTransformDisplayInterface extends EntityDisplayInterface {
 
+  /**
+   * Builds a transform array for the components of an entity.
+   *
+   * See the buildMultiple() method for details.
+   *
+   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+   *   The entity being displayed.
+   *
+   * @return array
+   *   A transform array for the entity.
+   *
+   * @see \Drupal\transform_api\Configs\EntityTransformDisplayInterface::buildMultiple()
+   */
+  public function build(FieldableEntityInterface $entity);
+
+  /**
+   * Builds a transform array for the components of a set of entities.
+   *
+   * This only includes the components handled by the Display object, but
+   * excludes 'extra fields', that are typically transformed through specific,
+   * ad-hoc code in EntityTransformBuilderInterface::buildComponents() or in
+   * hook_entity_transform() implementations.
+   *
+   * hook_entity_transform_build_alter() is invoked on each entity, allowing 3rd
+   * party code to alter the transform array.
+   *
+   * @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities
+   *   The entities being transformed.
+   *
+   * @return array
+   *   A transform array for the entities, indexed by the same keys as the
+   *   $entities array parameter.
+   *
+   * @see hook_entity_transform_build_alter()
+   */
+  public function buildMultiple(array $entities);
+
 }
diff --git a/src/Entity/EntityTransformBuilder.php b/src/Entity/EntityTransformBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea4ad52386a824793f5c58b1c93829dbdb5bca13
--- /dev/null
+++ b/src/Entity/EntityTransformBuilder.php
@@ -0,0 +1,570 @@
+<?php
+
+namespace Drupal\transform_api\Entity;
+
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Entity\EntityHandlerBase;
+use Drupal\Core\Entity\EntityHandlerInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRepositoryInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Field\FieldItemInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Security\TrustedCallbackInterface;
+use Drupal\Core\TypedData\TranslatableInterface as TranslatableDataInterface;
+use Drupal\transform_api\Configs\EntityTransformDisplayInterface;
+use Drupal\transform_api\EntityTransformBuilderInterface;
+use Drupal\transform_api\EventSubscriber\TransformationCache;
+use Drupal\transform_api\Repository\EntityTransformRepositoryInterface;
+use Drupal\transform_api\Transform;
+use Drupal\transform_api\Transform\EntityTransform;
+use Drupal\transform_api\Transform\TransformInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for entity transform handlers.
+ *
+ * @ingroup entity_api
+ */
+class EntityTransformBuilder extends EntityHandlerBase implements EntityHandlerInterface, EntityTransformBuilderInterface, TrustedCallbackInterface {
+
+  /**
+   * The type of entities for which this transform handler is instantiated.
+   *
+   * @var string
+   */
+  protected $entityTypeId;
+
+  /**
+   * Information about the entity type.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected $entityType;
+
+  /**
+   * The entity repository service.
+   *
+   * @var \Drupal\transform_api\Repository\EntityTransformRepositoryInterface
+   */
+  protected $entityRepository;
+
+  /**
+   * The entity display repository.
+   *
+   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
+   */
+  protected $entityDisplayRepository;
+
+  /**
+   * The cache bin used to store the transformation cache.
+   *
+   * @var string
+   */
+  protected $cacheBin = 'transform';
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * The transformation caching service.
+   *
+   * @var \Drupal\transform_api\EventSubscriber\TransformationCache
+   */
+  protected TransformationCache $transformationCache;
+
+  /**
+   * The EntityTransformDisplay objects created for individual field transforms.
+   *
+   * @var \Drupal\transform_api\Configs\EntityTransformDisplayInterface[]
+   *
+   * @see \Drupal\transform_api\Entity\EntityTransformBuilder::getSingleFieldDisplay()
+   */
+  protected $singleFieldDisplays;
+
+  /**
+   * Constructs a new EntityTransformBuilder.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
+   *   The entity repository service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Drupal\transform_api\EventSubscriber\TransformationCache $transformation_cache
+   *   The transformation cache.
+   * @param \Drupal\transform_api\Repository\EntityTransformRepositoryInterface $entity_display_repository
+   *   The entity display repository.
+   */
+  public function __construct(EntityTypeInterface $entity_type, EntityRepositoryInterface $entity_repository, LanguageManagerInterface $language_manager, TransformationCache $transformation_cache, EntityTransformRepositoryInterface $entity_display_repository) {
+    $this->entityTypeId = $entity_type->id();
+    $this->entityType = $entity_type;
+    $this->entityRepository = $entity_repository;
+    $this->languageManager = $language_manager;
+    $this->transformationCache = $transformation_cache;
+    $this->entityDisplayRepository = $entity_display_repository;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('entity.repository'),
+      $container->get('language_manager'),
+      $container->get('transform_api.transformation_cache'),
+      $container->get('transform_api.entity_display.repository')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function configureTransform(EntityTransform $transform, EntityInterface $entity, $transform_mode = 'full', $langcode = NULL) {
+    // Allow modules to change the transform mode.
+    $this->moduleHandler()->alter('entity_transform_mode', $transform_mode, $entity);
+
+    // Ensure that from now on we are dealing with the proper translation
+    // object.
+    $entity = $this->entityRepository->getTranslationFromContext($entity, $langcode);
+    $transform->setEntity($entity, FALSE);
+
+    // Cache the transformed output if permitted by the transform mode and
+    // global entity type configuration.
+    if ($this->isTransformModeCacheable($transform_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
+      $transform->setValues([
+        'entity_type' => $this->entityTypeId,
+        'ids' => $entity->id(),
+        'transform_mode' => $transform_mode,
+      ]);
+      $transform->addCacheTags(Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags()));
+      $transform->addCacheContexts($entity->getCacheContexts());
+      $transform->setCacheMaxAge($entity->getCacheMaxAge());
+      // @todo Add support for other cache bins.
+      /* $transform->setCacheBin($this->cacheBin) */
+
+      if ($entity instanceof TranslatableDataInterface && count($entity->getTranslationLanguages()) > 1) {
+        $transform->setValue('langcode', $entity->language()->getId());
+      }
+    }
+
+    // Allow modules to reconfigure.
+    $this->moduleHandler()->invokeAll($entity->getEntityTypeId() . '_transform_configure', [
+      $transform, $entity, $transform_mode, $langcode,
+    ]);
+    $this->moduleHandler()->invokeAll('entity_transform_configure', [$transform, $entity, $transform_mode, $langcode]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform(EntityInterface $entity, $transform_mode = 'full', $langcode = NULL) {
+    $build = $this->transformEntity($entity, $transform_mode, $langcode);
+    $build['#pre_transform'][] = [$this, 'build'];
+
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function trustedCallbacks() {
+    return ['build', 'buildMultiple'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformMultiple(array $entities = [], $transform_mode = 'full', $langcode = NULL) {
+    $build_list = [
+      '#sorted' => TRUE,
+      '#pre_transform' => [[$this, 'buildMultiple']],
+    ];
+    $weight = 0;
+    foreach ($entities as $key => $entity) {
+      $transform = EntityTransform::createFromEntity($entity);
+      $cached = $this->transformationCache->get($transform);
+      if ($cached !== FALSE) {
+        $transform->setBuild($cached);
+        $transform->setWeight($weight++);
+        $build_list[$key] = $transform;
+      }
+      else {
+        $build_list[$key] = $this->transformEntity($transform->getEntity(), $transform_mode, $langcode);
+        $build_list[$key]['#transform'] = $transform;
+        $build_list[$key]['#weight'] = $weight++;
+      }
+    }
+
+    return $build_list;
+  }
+
+  /**
+   * Builds the transform array for an entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to transform.
+   * @param string $transform_mode
+   *   (optional) The transform mode that used to transform the entity.
+   * @param string $langcode
+   *   (optional) For which language the entity should be transformed, defaults
+   *   to the current content language.
+   *
+   * @return array
+   *   A transform array for the entity.
+   */
+  protected function transformEntity(EntityInterface $entity, $transform_mode = 'full', $langcode = NULL) {
+    // Set build defaults.
+    $transformation = $this->getBuildDefaults($entity, $transform_mode);
+    $entityType = $this->entityTypeId;
+    $this->moduleHandler()->alter([$entityType . '_transform_defaults', 'entity_transform_defaults'], $transformation, $entity, $transform_mode);
+    return $transformation;
+  }
+
+  /**
+   * Provides entity-specific defaults to the build process.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity for which the defaults should be provided.
+   * @param string $transform_mode
+   *   The transform mode that should be used.
+   *
+   * @return array
+   *   Transformation array with defaults.
+   */
+  protected function getBuildDefaults(EntityInterface $entity, $transform_mode) {
+    return [
+      'type' => 'entity',
+      'entity_type' => $entity->getEntityTypeId(),
+      'bundle' => $entity->bundle(),
+      'id' => $entity->id(),
+      'transform_mode' => $transform_mode,
+      "#{$this->entityTypeId}" => $entity,
+      // @todo This is deprecated and will be removed at some point.
+      "#entity" => $entity,
+    ];
+  }
+
+  /**
+   * Builds an entity's transform; augments entity defaults.
+   *
+   * This function is assigned as a #pre_transform callback in ::transform().
+   *
+   * It transforms the transformable array for a single entity to the same
+   * structure as if we were transforming multiple entities, and then calls the
+   * default ::buildMultiple() #pre_transform callback.
+   *
+   * @param array $build
+   *   A transformable array containing build information and context for an
+   *   entity transform.
+   *
+   * @return array
+   *   The updated transformable array.
+   *
+   * @see \Drupal\Core\Render\RendererInterface::render()
+   */
+  public function build(array $build) {
+    $build_list = [$build];
+    $build_list = $this->buildMultiple($build_list);
+    return $build_list[0];
+  }
+
+  /**
+   * Builds multiple entities' transforms; augments entity defaults.
+   *
+   * This function is assigned as a #pre_transform callback
+   * in ::transformMultiple().
+   *
+   * By delaying the building of an entity until the #pre_transform processing
+   * in \Drupal::service('transform_api.transformer')->transform(), the
+   * processing cost of assembling an entity's transformable array is saved
+   * on cache-hit requests.
+   *
+   * @param array $build_list
+   *   A transformable array containing build information and context for an
+   *   entity transform.
+   *
+   * @return array
+   *   The updated transformable array.
+   *
+   * @see \Drupal\Core\Render\RendererInterface::render()
+   */
+  public function buildMultiple(array $build_list) {
+    // Build the transform modes and display objects.
+    $transform_modes = [];
+    $entity_type_key = "#{$this->entityTypeId}";
+    $transform_hook = "{$this->entityTypeId}_transform";
+
+    // Find the keys for the ContentEntities in the build; Store entities for
+    // transformation by transform_mode.
+    $children = Transform::children($build_list);
+    foreach ($children as $key) {
+      if ($build_list[$key] instanceof TransformInterface) {
+        continue;
+      }
+      if (isset($build_list[$key][$entity_type_key])) {
+        $entity = $build_list[$key][$entity_type_key];
+        if ($entity instanceof FieldableEntityInterface) {
+          $transform_modes[$build_list[$key]['transform_mode']][$key] = $entity;
+        }
+      }
+    }
+
+    // Build content for the displays represented by the entities.
+    foreach ($transform_modes as $transform_mode => $transform_mode_entities) {
+      $displays = EntityTransformDisplay::collectTransformDisplays($transform_mode_entities, $transform_mode);
+      $this->buildComponents($build_list, $transform_mode_entities, $displays, $transform_mode);
+      foreach (array_keys($transform_mode_entities) as $key) {
+        // Allow for alterations while building, before transforming.
+        $entity = $build_list[$key][$entity_type_key];
+        $display = $displays[$entity->bundle()];
+
+        $this->moduleHandler()->invokeAll($transform_hook, [&$build_list[$key], $entity, $display, $transform_mode]);
+        $this->moduleHandler()->invokeAll('entity_transform', [&$build_list[$key], $entity, $display, $transform_mode]);
+
+        $this->alterBuild($build_list[$key], $entity, $display, $transform_mode);
+      }
+    }
+
+    foreach ($build_list as $key => $build) {
+      if ($build instanceof TransformInterface) {
+        continue;
+      }
+      if (isset($build['#transform'])) {
+        /** @var \Drupal\transform_api\Transform\EntityTransform $transform */
+        $transform = $build['#transform'];
+        unset($build['#transform']);
+        $transform->setBuild($build);
+        $build_list[$key] = $transform;
+      }
+    }
+
+    return $build_list;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildComponents(array &$build, array $entities, array $displays, $transform_mode) {
+    $entities_by_bundle = [];
+    foreach ($entities as $id => $entity) {
+      // Initialize the field item attributes for the fields being displayed.
+      // The entity can include fields that are not displayed, and the display
+      // can include components that are not fields, so we want to act on the
+      // intersection. However, the entity can have many more fields than are
+      // displayed, so we avoid the cost of calling $entity->getProperties()
+      // by iterating the intersection as follows.
+      foreach ($displays[$entity->bundle()]->getComponents() as $name => $options) {
+        if ($entity->hasField($name)) {
+          foreach ($entity->get($name) as $item) {
+            $item->_attributes = [];
+          }
+        }
+      }
+
+      // Group the entities by bundle.
+      $entities_by_bundle[$entity->bundle()][$id] = $entity;
+    }
+
+    // Invoke hook_entity_prepare_transform().
+    $this->moduleHandler()->invokeAll('entity_prepare_transform', [
+      $this->entityTypeId,
+      $entities,
+      $displays,
+      $transform_mode,
+    ]);
+
+    // Let the displays build their transform arrays.
+    foreach ($entities_by_bundle as $bundle => $bundle_entities) {
+      $display = $displays[$bundle];
+      $display_build = $display->buildMultiple($bundle_entities);
+      foreach ($bundle_entities as $id => $entity) {
+        $build[$id] += $display_build[$id];
+      }
+
+      if ($display->getComponent('label')) {
+        $build[$id] += [
+          'label' => $entity->label(),
+        ];
+      }
+
+      if ($display->getComponent('links')) {
+        $build[$id] += [
+          'url' => $entity->toUrl()->toString(),
+        ];
+      }
+    }
+  }
+
+  /**
+   * Specific per-entity building.
+   *
+   * @param array $build
+   *   The transform array that is being created.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to be prepared.
+   * @param \Drupal\transform_api\Configs\EntityTransformDisplayInterface $display
+   *   The entity transform display holding the display options configured for
+   *   the entity components.
+   * @param string $transform_mode
+   *   The transform mode that should be used to prepare the entity.
+   */
+  protected function alterBuild(array &$build, EntityInterface $entity, EntityTransformDisplayInterface $display, $transform_mode) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return [$this->entityTypeId . '_transform'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetCache(array $entities = NULL) {
+    // If no set of specific entities is provided, invalidate the entity
+    // transform builder's cache tag. This will invalidate all entities
+    // transformed by this transform builder.
+    // Otherwise, if a set of specific entities is provided, invalidate those
+    // specific entities only, plus their list cache tags, because any lists in
+    // which these entities are transformed, must be invalidated as well.
+    // However, even in this case, we might invalidate more cache items than
+    // necessary.
+    // When we have a way to invalidate only those cache items that have both
+    // the individual entity's cache tag and the transform builder's cache tag,
+    // we'll be able to optimize this further.
+    if (isset($entities)) {
+      $tags = [];
+      foreach ($entities as $entity) {
+        $tags = Cache::mergeTags($tags, $entity->getCacheTags());
+        $tags = Cache::mergeTags($tags, $entity->getEntityType()->getListCacheTags());
+      }
+      Cache::invalidateTags($tags);
+    }
+    else {
+      Cache::invalidateTags($this->getCacheTags());
+    }
+  }
+
+  /**
+   * Determines whether the transform mode is cacheable.
+   *
+   * @param string $transform_mode
+   *   Name of the transform mode that should be transformed.
+   *
+   * @return bool
+   *   TRUE if the transform mode can be cached, FALSE otherwise.
+   */
+  protected function isTransformModeCacheable($transform_mode) {
+    if ($transform_mode == 'default') {
+      // The 'default' is not an actual transform mode.
+      return TRUE;
+    }
+    $transform_modes_info = $this->entityDisplayRepository->getTransformModes($this->entityTypeId);
+    return !empty($transform_modes_info[$transform_mode]['cache']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformField(FieldItemListInterface $items, $display_options = []) {
+    $entity = $items->getEntity();
+    // If the field is not translatable and the entity is, then the field item
+    // list always points to the default translation of the entity. Attempt to
+    // fetch it in the current content language.
+    if (!$items->getFieldDefinition()->isTranslatable() && $entity->isTranslatable()) {
+      $entity = $this->entityRepository->getTranslationFromContext($entity);
+    }
+
+    $field_name = $items->getFieldDefinition()->getName();
+    $display = $this->getSingleFieldDisplay($entity, $field_name, $display_options);
+
+    $output = [];
+    $build = $display->build($entity);
+    if (isset($build[$field_name])) {
+      $output = $build[$field_name];
+    }
+
+    return $output;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformFieldItem(FieldItemInterface $item, $display_options = []) {
+    $entity = $item->getEntity();
+    $field_name = $item->getFieldDefinition()->getName();
+
+    // Clone the entity since we are going to modify field values.
+    $clone = clone $entity;
+
+    // Push the item as the single value for the field, and defer
+    // to transformField() to build the transform array for the whole list.
+    $clone->{$field_name}->setValue([$item->getValue()]);
+    $elements = $this->transformField($clone->{$field_name}, $display_options);
+
+    // Extract the part of the transform array we need.
+    $output = $elements[0] ?? [];
+    if (isset($elements['#access'])) {
+      $output['#access'] = $elements['#access'];
+    }
+
+    return $output;
+  }
+
+  /**
+   * Gets an EntityTransformDisplay for transforming an individual field.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   * @param string $field_name
+   *   The field name.
+   * @param string|array $display_options
+   *   The display options passed to the transformField() method.
+   *
+   * @return \Drupal\transform_api\Configs\EntityTransformDisplayInterface
+   *   An EntityTransformDisplay with a single field.
+   */
+  protected function getSingleFieldDisplay($entity, $field_name, $display_options) {
+    if (is_string($display_options)) {
+      // Transform mode: use the Display configured for the transform mode.
+      $transform_mode = $display_options;
+      $display = EntityTransformDisplay::collectTransformDisplay($entity, $transform_mode);
+      // Hide all fields except the current one.
+      foreach (array_keys($entity->getFieldDefinitions()) as $name) {
+        if ($name != $field_name) {
+          $display->removeComponent($name);
+        }
+      }
+    }
+    else {
+      // Array of custom display options: use a runtime Display for the
+      // '_custom' transform mode. Persist the displays created, to reduce
+      // the number of objects (displays and transformer plugins) created
+      // when transforming a series of fields individually for cases such
+      // as views tables.
+      $entity_type_id = $entity->getEntityTypeId();
+      $bundle = $entity->bundle();
+      $key = $entity_type_id . ':' . $bundle . ':' . $field_name . ':' . Crypt::hashBase64(serialize($display_options));
+      if (!isset($this->singleFieldDisplays[$key])) {
+        $this->singleFieldDisplays[$key] = EntityTransformDisplay::create([
+          'targetEntityType' => $entity_type_id,
+          'bundle' => $bundle,
+          'status' => TRUE,
+        ])->setComponent($field_name, $display_options);
+      }
+      $display = $this->singleFieldDisplays[$key];
+    }
+
+    return $display;
+  }
+
+}
diff --git a/src/Entity/EntityTransformDisplay.php b/src/Entity/EntityTransformDisplay.php
index 8a45febe000a66849b257d6a876bb71e6e147aee..aac3647c21c1e3831a0eb826d140a3d5ba18e7cf 100644
--- a/src/Entity/EntityTransformDisplay.php
+++ b/src/Entity/EntityTransformDisplay.php
@@ -3,9 +3,13 @@
 namespace Drupal\transform_api\Entity;
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Entity\EntityDisplayBase;
 use Drupal\Core\Entity\EntityDisplayPluginCollection;
 use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\TypedData\TranslatableInterface as TranslatableDataInterface;
+use Drupal\layout_builder\Field\LayoutSectionItemList;
 use Drupal\transform_api\Configs\EntityTransformDisplayInterface;
 
 /**
@@ -171,6 +175,9 @@ class EntityTransformDisplay extends EntityDisplayBase implements EntityTransfor
     return $displays[$entity->bundle()];
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public function __construct(array $values, $entity_type) {
     $this->pluginManager = \Drupal::service('plugin.manager.transform_api.field_transform');
     $this->transformer = \Drupal::service('transform_api.transformer');
@@ -224,4 +231,90 @@ class EntityTransformDisplay extends EntityDisplayBase implements EntityTransfor
     ];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function build(FieldableEntityInterface $entity) {
+    $build = $this->buildMultiple([$entity]);
+    return $build[0];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildMultiple(array $entities) {
+    $build_list = [];
+    foreach ($entities as $key => $entity) {
+      $build_list[$key] = [];
+    }
+
+    // Run field transformers.
+    foreach ($this->getComponents() as $name => $options) {
+      /** @var \Drupal\transform_api\FieldTransformInterface $transformer */
+      if ($transformer = $this->getRenderer($name)) {
+        // Group items across all entities and pass them to the transformer's
+        // prepareTransform() method.
+        $grouped_items = [];
+        foreach ($entities as $id => $entity) {
+          $items = $entity->get($name);
+          $items->filterEmptyItems();
+          $grouped_items[$id] = $items;
+        }
+        $transformer->prepareTransform($grouped_items);
+
+        // Then let the transformer build the output for each entity.
+        foreach ($entities as $id => $entity) {
+          $items = $grouped_items[$id];
+          // Making an exception for LayoutSectionItemList.
+          // @todo Find a better solution or wait and see
+          //   what https://www.drupal.org/node/2942975 might bring
+          if ($items instanceof LayoutSectionItemList) {
+            /** @var \Drupal\Core\Access\AccessResultInterface $field_access */
+            $field_access = AccessResult::allowed();
+          }
+          else {
+            /** @var \Drupal\Core\Access\AccessResultInterface $field_access */
+            $field_access = $items->access('view', NULL, TRUE);
+          }
+          // The language of the field values to display is already determined
+          // in the incoming $entity. The transformer should build its output of
+          // those values using:
+          // - the entity language if the entity is translatable,
+          // - the current "content language" otherwise.
+          if ($entity instanceof TranslatableDataInterface && $entity->isTranslatable()) {
+            $transform_langcode = $entity->language()->getId();
+          }
+          else {
+            $transform_langcode = NULL;
+          }
+          $build_list[$id][$name] = $field_access->isAllowed() ? $transformer->transform($items, $transform_langcode) : [];
+          // Apply the field access cacheability metadata to the
+          // transformation array.
+          $this->renderer->addCacheableDependency($build_list[$id][$name], $field_access);
+          // Let other modules alter the field transformation array.
+          \Drupal::moduleHandler()->alter('field_transform', $build_list[$id][$name]);
+        }
+      }
+    }
+
+    foreach ($entities as $id => $entity) {
+      // Assign the configured weights.
+      foreach ($this->getComponents() as $name => $options) {
+        if (isset($build_list[$id][$name]) && !Element::isEmpty($build_list[$id][$name])) {
+          $build_list[$id][$name]['#weight'] = $options['weight'];
+        }
+      }
+
+      // Let other modules alter the transformable array.
+      $context = [
+        'entity' => $entity,
+        'transform_mode' => $this->originalMode,
+        'display' => $this,
+      ];
+      \Drupal::moduleHandler()->alter('entity_transform_build', $build_list[$id], $context);
+    }
+
+    return $build_list;
+  }
+
 }
diff --git a/src/EntityTransformBuilderInterface.php b/src/EntityTransformBuilderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..2896d98724c4ff6002e07a65f29db2718c89d7d8
--- /dev/null
+++ b/src/EntityTransformBuilderInterface.php
@@ -0,0 +1,172 @@
+<?php
+
+namespace Drupal\transform_api;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Field\FieldItemInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\transform_api\Transform\EntityTransform;
+
+/**
+ * Defines an interface for entity transform builders.
+ *
+ * @ingroup entity_api
+ */
+interface EntityTransformBuilderInterface {
+
+  /**
+   * Builds the component fields and properties of a set of entities.
+   *
+   * @param array &$build
+   *   The transformable array representing the entity content.
+   * @param \Drupal\Core\Entity\EntityInterface[] $entities
+   *   The entities whose content is being built.
+   * @param \Drupal\transform_api\Configs\EntityTransformDisplayInterface[] $displays
+   *   The array of entity transform displays holding the display options
+   *   configured for the entity components, keyed by bundle name.
+   * @param string $transform_mode
+   *   The transform mode in which the entity is being transformed.
+   */
+  public function buildComponents(array &$build, array $entities, array $displays, $transform_mode);
+
+  /**
+   * Configure a transform for an entity.
+   *
+   * @param \Drupal\transform_api\Transform\EntityTransform $transform
+   *   The transform to configure.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to configure the transform for.
+   * @param string $transform_mode
+   *   (optional) The transform mode that used to transform the entity.
+   * @param string $langcode
+   *   (optional) For which language the entity should be transformed, defaults
+   *   to the current content language.
+   */
+  public function configureTransform(EntityTransform $transform, EntityInterface $entity, $transform_mode = 'full', $langcode = NULL);
+
+  /**
+   * Builds the transform array for the provided entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to transform.
+   * @param string $transform_mode
+   *   (optional) The transform mode that used to transform the entity.
+   * @param string $langcode
+   *   (optional) For which language the entity should be transformed, defaults
+   *   to the current content language.
+   *
+   * @return array
+   *   A transform array for the entity.
+   *
+   * @throws \InvalidArgumentException
+   *   Can be thrown when the set of parameters is inconsistent, like when
+   *   trying to transform a Comment and passing a Node which is not the one the
+   *   comment belongs to, or not passing one, and having the comment node not
+   *   be available for loading.
+   */
+  public function transform(EntityInterface $entity, $transform_mode = 'full', $langcode = NULL);
+
+  /**
+   * Builds the transform array for the provided entities.
+   *
+   * @param array $entities
+   *   An array of entities implementing EntityInterface to transform.
+   * @param string $transform_mode
+   *   (optional) The transform mode that should be used to transform
+   *   the entity.
+   * @param string $langcode
+   *   (optional) For which language the entity should be transformed, defaults
+   *   to the current content language.
+   *
+   * @return array
+   *   A transform array for the entities, indexed by the same keys as the
+   *   entities array passed in $entities.
+   *
+   * @throws \InvalidArgumentException
+   *   Can be thrown when the set of parameters is inconsistent, like when
+   *   trying to transform Comments and passing a Node which is not the one the
+   *   comments belongs to, or not passing one, and having the comments node not
+   *   be available for loading.
+   */
+  public function transformMultiple(array $entities = [], $transform_mode = 'full', $langcode = NULL);
+
+  /**
+   * Resets the entity transform cache.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface[] $entities
+   *   (optional) If specified, the cache is reset for the given entities only.
+   */
+  public function resetCache(array $entities = NULL);
+
+  /**
+   * Builds a transformable array for the value of a single field in an entity.
+   *
+   * The resulting output is a fully themed field with label and multiple
+   * values.
+   *
+   * This function can be used by third-party modules that need to output an
+   * isolated field.
+   * - The FieldTransformInterface::transform() method can be used to output a
+   *   single formatted field value, without label or wrapping field
+   *   transformation.
+   *
+   * The function takes care of invoking the prepare_transform steps. It also
+   * respects field access permissions.
+   *
+   * @param \Drupal\Core\Field\FieldItemListInterface $items
+   *   FieldItemList containing the values to be displayed.
+   * @param string|array $display_options
+   *   Can be either:
+   *   - The name of a transform mode. The field will be displayed according to
+   *     the display settings specified for this transform mode in the $field
+   *     definition for the field in the entity's bundle. If no display settings
+   *     are found for the transform mode, the settings for the 'default'
+   *     transform mode will be used.
+   *   - An array of display options. The following key/value pairs are allowed:
+   *     - label: (string) Position of the label. The default 'field' theme
+   *       implementation supports the values 'include' and 'omit'.
+   *       Defaults to 'omit'.
+   *     - type: (string) The transformer to use. Defaults to the
+   *       'default_transformer' for the field type. The default transformer
+   *       will also be used if the requested transformer is not available.
+   *     - settings: (array) Settings specific to the transformer. Defaults to
+   *       the transformer's default settings.
+   *     - weight: (float) The weight to assign to the transformable element.
+   *       Defaults to 0.
+   *
+   * @return array
+   *   A transform array for the field values.
+   *
+   * @see \Drupal\Core\Entity\EntityTransformBuilderInterface::viewFieldItem()
+   */
+  public function transformField(FieldItemListInterface $items, $display_options = []);
+
+  /**
+   * Builds a transformable array for a single field item.
+   *
+   * @param \Drupal\Core\Field\FieldItemInterface $item
+   *   FieldItem to be displayed.
+   * @param string|array $display_options
+   *   Can be either the name of a transform mode, or an array of display
+   *   settings. See EntityTransformBuilderInterface::transformField() for
+   *   more information.
+   *
+   * @return array
+   *   A transform array for the field item.
+   *
+   * @see \Drupal\Core\Entity\EntityTransformBuilderInterface::viewField()
+   */
+  public function transformFieldItem(FieldItemInterface $item, $display_options = []);
+
+  /**
+   * The cache tag associated with this entity transform builder.
+   *
+   * An entity transform builder is instantiated on a per-entity type basis,
+   * so the cache tags are also per-entity type.
+   *
+   * @return array
+   *   An array of cache tags.
+   */
+  public function getCacheTags();
+
+}
diff --git a/src/Plugin/Transform/Type/Entity.php b/src/Plugin/Transform/Type/Entity.php
index 38632d1bd88e760955ae821689acc472ca01ccac..aeec0e5a2110d1c31b7da0251613f37d44b9faa6 100644
--- a/src/Plugin/Transform/Type/Entity.php
+++ b/src/Plugin/Transform/Type/Entity.php
@@ -29,6 +29,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *  id = "entity",
  *  title = "Entity transform"
  * )
+ *
+ * @deprecated in transform_api:1.1.0 and is removed from transform_api:2.0.0.
+ * Use the EntityTransformBuilder instead.
  */
 class Entity extends TransformationTypeBase {
 
@@ -130,7 +133,7 @@ class Entity extends TransformationTypeBase {
     if (is_array($transform->getValue('ids'))) {
       return $this->prepareMultipleTransforms($transform->getValue('entity_type'), $transform->getValue('ids'), $transform->getValue('transform_mode') ?? EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE, $langcode);
     }
-    elseif (!is_null($entity_transform->getEntity())) {
+    elseif (!is_null($entity_transform->getEntities())) {
       return $this->transformEntity($entity_transform->getEntity(), $entity_transform->getFields(), $entity_transform->getDisplay());
     }
     else {
diff --git a/src/Transform/EntityTransform.php b/src/Transform/EntityTransform.php
index 374fe21a8bf3a2ed5ebf2a8f15fa3cc7639bd1aa..52a992fdf5041dac2fb8f2b1aba1b7282e9fb3fb 100644
--- a/src/Transform/EntityTransform.php
+++ b/src/Transform/EntityTransform.php
@@ -3,38 +3,55 @@
 namespace Drupal\transform_api\Transform;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\transform_api\Configs\EntityTransformDisplayInterface;
+use Drupal\transform_api\EntityTransformBuilderInterface;
 use Drupal\transform_api\Repository\EntityTransformRepositoryInterface;
 
 /**
  * A transform of one or more entities.
  */
-class EntityTransform extends PluginTransformBase {
+class EntityTransform extends TransformBase {
 
   /**
-   * The entity to transform.
+   * The entities to transform.
    *
-   * @var \Drupal\Core\Entity\EntityInterface|null
+   * @var \Drupal\Core\Entity\EntityInterface[]
    */
-  protected EntityInterface|null $entity = NULL;
+  protected array $entities = [];
 
   /**
    * The transform mode to use for transformation.
    *
-   * @var \Drupal\transform_api\Configs\EntityTransformDisplayInterface|null
+   * @var string
    */
-  protected EntityTransformDisplayInterface|null $display = NULL;
+  protected string $transformMode = EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE;
 
   /**
-   * Field transforms for the fields of the entity.
+   * The language to use for transformation.
    *
-   * @var \Drupal\transform_api\FieldTransformInterface[]
+   * @var string|null
    */
-  protected array $fields = [];
+  protected ?string $langcode = NULL;
 
   /**
-   * Construct a EntityTransform.
+   * Whether this transform is for multiple entities.
+   *
+   * @var bool
+   */
+  protected bool $multiple = FALSE;
+
+  /**
+   * Prepared transformation array for the entity.
+   *
+   * @var array
+   */
+  protected array $build = [];
+
+  /**
+   * Construct an EntityTransform.
+   *
+   * $entity_type and $ids can be left empty, but then you must call either
+   * setEntity or setEntities() afterward.
    *
    * @param string $entity_type
    *   The type of the entity.
@@ -46,19 +63,17 @@ class EntityTransform extends PluginTransformBase {
    *   (Optional) The language to use for transformation.
    */
   public function __construct($entity_type, $ids, $transform_mode = EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE, $langcode = NULL) {
-    $this->values = [
-      'entity_type' => $entity_type,
-      'ids' => $ids,
-      'transform_mode' => $transform_mode,
-      'langcode' => $langcode ?? \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(),
-    ];
-    if (is_array($ids)) {
-      foreach ($ids as $id) {
-        $this->cacheTags[] = $entity_type . ':' . $id;
+    $this->transformMode = $transform_mode;
+    $this->langcode = $langcode;
+    if (!empty($entity_type) && !empty($ids)) {
+      if (is_array($ids)) {
+        $entities = \Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple($ids);
+        $this->setEntities($entities);
+      }
+      else {
+        $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($ids);
+        $this->setEntity($entity);
       }
-    }
-    else {
-      $this->cacheTags[] = $entity_type . ':' . $ids;
     }
   }
 
@@ -73,14 +88,14 @@ class EntityTransform extends PluginTransformBase {
    * {@inheritdoc}
    */
   public function getAlterIdentifiers() {
-    return [$this->getTransformType(), $this->values['entity_type']];
+    return [$this->getTransformType(), $this->getEntity()->getEntityTypeId()];
   }
 
   /**
    * {@inheritdoc}
    */
   public function isMultiple() {
-    return is_array($this->getValue('ids'));
+    return $this->multiple;
   }
 
   /**
@@ -93,61 +108,129 @@ class EntityTransform extends PluginTransformBase {
   /**
    * Return the entity to be transformed.
    *
-   * @return \Drupal\Core\Entity\EntityInterface|null
+   * @return \Drupal\Core\Entity\EntityInterface
    *   The entity or NULL if not found.
    */
-  public function getEntity(): ?EntityInterface {
-    return $this->entity;
+  public function getEntity(): EntityInterface {
+    return reset($this->entities);
   }
 
   /**
    * Set the entity to be transformed.
    *
-   * @param \Drupal\Core\Entity\EntityInterface|null $entity
+   * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The entity to be transformed.
+   * @param bool $configure
+   *   Whether to configure the transform via EntityTransformBuilder.
    */
-  public function setEntity(?EntityInterface $entity): void {
-    $this->entity = $entity;
+  public function setEntity(EntityInterface $entity, $configure = TRUE) {
+    $this->multiple = FALSE;
+    $this->entities = [$entity];
+    if ($configure) {
+      $this->getTransformBuilder()->configureTransform($this, $this->getEntity(), $this->getTransformMode(), $this->getLangcode());
+    }
   }
 
   /**
-   * Return the field transforms for the fields on the entity.
+   * Return the entities to be transformed.
    *
-   * @return \Drupal\transform_api\FieldTransformInterface[]
-   *   Array of field transforms.
+   * @return \Drupal\Core\Entity\EntityInterface[] The entities or an empty array if not found.
+   *   The entities or an empty array if not found.
    */
-  public function getFields(): array {
-    return $this->fields;
+  public function getEntities(): array {
+    return $this->entities;
   }
 
   /**
-   * Set the field transforms for the fields on the entity.
+   * Set the entities to be transformed.
    *
-   * @param \Drupal\transform_api\FieldTransformInterface[] $fields
-   *   Array of field transforms.
+   * @param \Drupal\Core\Entity\EntityInterface[] $entities
+   *   The entities to be transformed.
    */
-  public function setFields(array $fields): void {
-    $this->fields = $fields;
+  public function setEntities(array $entities) {
+    $this->multiple = TRUE;
+    $this->entities = $entities;
   }
 
   /**
    * Return the transform mode used to be used for transformation.
    *
-   * @return \Drupal\transform_api\Configs\EntityTransformDisplayInterface|null
-   *   The transform mode.
+   * @return string
+   *   The transform mode used to be used for transformation.
    */
-  public function getDisplay(): ?EntityTransformDisplayInterface {
-    return $this->display;
+  public function getTransformMode(): string {
+    return $this->transformMode;
   }
 
   /**
-   * Set the transform mode to be used for transformation.
+   * Set the transform mode used to be used for transformation.
    *
-   * @param \Drupal\transform_api\Configs\EntityTransformDisplayInterface|null $display
-   *   The transform mode.
+   * @param string $transformMode
+   *   The transform mode used to be used for transformation.
    */
-  public function setDisplay(?EntityTransformDisplayInterface $display): void {
-    $this->display = $display;
+  public function setTransformMode(string $transformMode) {
+    $this->transformMode = $transformMode;
+  }
+
+  /**
+   * Return the language code used to be used for transformation.
+   *
+   * @return string|null
+   *   The language code used to be used for transformation.
+   */
+  public function getLangcode(): ?string {
+    return $this->langcode;
+  }
+
+  /**
+   * Set the language code used to be used for transformation.
+   *
+   * @param string $langcode
+   *   The language code used to be used for transformation.
+   */
+  public function setLangcode(string $langcode) {
+    $this->langcode = $langcode;
+  }
+
+  /**
+   * Get the EntityTransformBuilder for the entity's type.
+   *
+   * @return \Drupal\transform_api\EntityTransformBuilderInterface
+   *   The EntityTransformBuilder for the entity's type.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  protected function getTransformBuilder(): EntityTransformBuilderInterface {
+    /** @var \Drupal\transform_api\EntityTransformBuilderInterface $handler */
+    $handler = \Drupal::entityTypeManager()->getHandler($this->getEntity()->getEntityTypeId(), 'transform_builder');
+    return $handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform(): array {
+    if (!empty($this->build)) {
+      $transformation = $this->build;
+    }
+    elseif ($this->isMultiple()) {
+      $transformation = $this->getTransformBuilder()->transformMultiple($this->getEntities(), $this->getTransformMode(), $this->getLangcode());
+    }
+    else {
+      $transformation = $this->getTransformBuilder()->transform($this->getEntity(), $this->getTransformMode(), $this->getLangcode());
+    }
+    $this->applyTo($transformation);
+    return $transformation;
+  }
+
+  /**
+   * Set the prepared transformation of the entity.
+   *
+   * @param array $build
+   *   The prepared build.
+   */
+  public function setBuild(array $build) {
+    $this->build = $build;
   }
 
   /**
@@ -155,19 +238,20 @@ class EntityTransform extends PluginTransformBase {
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The entity to be transformed.
-   * @param \Drupal\transform_api\FieldTransformInterface[] $fields
-   *   The field transforms for the entity fields.
-   * @param \Drupal\transform_api\Configs\EntityTransformDisplayInterface $display
-   *   The transform display mode to be used for transformation.
+   * @param array $build
+   *   The prepared transformation of the entity.
+   * @param string $transform_mode
+   *   (Optional) The transform mode to be used for transformation.
    *
    * @return EntityTransform
    *   A fully prepared EntityTransform.
+   *
+   * @internal
    */
-  public static function createPrepared(EntityInterface $entity, array $fields, EntityTransformDisplayInterface $display): EntityTransform {
-    $transform = new self($entity->getEntityTypeId(), $entity->id(), $display->getMode());
+  public static function createPrepared(EntityInterface $entity, array $build, $transform_mode = EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE): EntityTransform {
+    $transform = new self('', 0, $transform_mode);
     $transform->setEntity($entity);
-    $transform->setFields($fields);
-    $transform->setDisplay($display);
+    $transform->setBuild($build);
     return $transform;
   }
 
@@ -183,7 +267,9 @@ class EntityTransform extends PluginTransformBase {
    *   An EntityTransform based on the entity.
    */
   public static function createFromEntity(EntityInterface $entity, $transform_mode = EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE) {
-    return new self($entity->getEntityTypeId(), $entity->id(), $transform_mode, $entity->language()->getId());
+    $transform = new self('', 0, $transform_mode);
+    $transform->setEntity($entity);
+    return $transform;
   }
 
   /**
@@ -198,15 +284,9 @@ class EntityTransform extends PluginTransformBase {
    *   An EntityTransform based on the entities.
    */
   public static function createFromMultipleEntities(array $entities, $transform_mode = EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE) {
-    $ids = [];
-    $entityTypeId = '';
-    $langcode = NULL;
-    foreach ($entities as $entity) {
-      $ids[] = $entity->id();
-      $entityTypeId = $entity->getEntityTypeId();
-      $langcode = $entity->language()->getId();
-    }
-    return new self($entityTypeId, $ids, $transform_mode, $langcode);
+    $transform = new self('', 0, $transform_mode);
+    $transform->setEntities($entities);
+    return $transform;
   }
 
 }
diff --git a/src/Transform/FieldTransform.php b/src/Transform/FieldTransform.php
index 43613f67ce870ae9176fa541bb0fb12969675a69..de12d43ad21b2a5ea2168747f40b80cd320c0c1f 100644
--- a/src/Transform/FieldTransform.php
+++ b/src/Transform/FieldTransform.php
@@ -9,6 +9,9 @@ use Drupal\transform_api\Repository\EntityTransformRepositoryInterface;
 
 /**
  * A transform for a field.
+ *
+ * @deprecated in transform_api:1.1.0 and is removed from transform_api:2.0.0.
+ * Use the EntityTransformBuilder instead.
  */
 class FieldTransform extends TransformBase {
 
diff --git a/src/Transform/PluginTransformBase.php b/src/Transform/PluginTransformBase.php
index 53fe2fea9b63e2210575eed8ccb8e8f69a9f087c..a0656fb48379aebe97591632dcba0a3d6c8eed2f 100644
--- a/src/Transform/PluginTransformBase.php
+++ b/src/Transform/PluginTransformBase.php
@@ -2,21 +2,44 @@
 
 namespace Drupal\transform_api\Transform;
 
+use Drupal\transform_api\TransformationTypeInterface;
+
 /**
  * Base class for transforms with a transform type plugin.
  */
 abstract class PluginTransformBase extends TransformBase {
 
+  /**
+   * The plugin belonging to this transform.
+   *
+   * @var \Drupal\transform_api\TransformationTypeInterface
+   */
+  protected TransformationTypeInterface $plugin;
+
+  /**
+   * Return the plugin belonging to this transform.
+   *
+   * @return \Drupal\transform_api\TransformationTypeInterface
+   *   The plugin belonging to this transform.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  protected function getPlugin(): TransformationTypeInterface {
+    if (empty($this->plugin)) {
+      /** @var \Drupal\transform_api\TransformationTypeManager $transformationTypeManager */
+      $transformationTypeManager = \Drupal::service('plugin.manager.transform_api.transformation_type');
+      $this->plugin = $transformationTypeManager->createInstance($this->getTransformType(), $this->getValues());
+    }
+    return $this->plugin;
+  }
+
   /**
    * {@inheritDoc}
    *
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    */
   public function transform() {
-    /** @var \Drupal\transform_api\TransformationTypeManager $transformationTypeManager */
-    $transformationTypeManager = \Drupal::service('plugin.manager.transform_api.transformation_type');
-    $plugin = $transformationTypeManager->createInstance($this->getTransformType(), $this->getValues());
-    return $plugin->transform($this);
+    return $this->getPlugin()->transform($this);
   }
 
 }
diff --git a/src/Transform/RefinableTransform.php b/src/Transform/RefinableTransform.php
new file mode 100644
index 0000000000000000000000000000000000000000..9a25d8496977d95aeef150d7cef3877acb2e41c7
--- /dev/null
+++ b/src/Transform/RefinableTransform.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Drupal\transform_api\Transform;
+
+use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
+
+/**
+ *
+ */
+abstract class RefinableTransform extends TransformBase {
+
+  use RefinableCacheableDependencyTrait;
+
+  /**
+   * The transformation array.
+   *
+   * @var array
+   */
+  protected array $transformation = [];
+
+  /**
+   * The transformation weight.
+   *
+   * @var int|null
+   */
+  protected $weight = NULL;
+
+  /**
+   * Gets the transformation array.
+   *
+   * @return array
+   *   The transformation array.
+   */
+  public function getTransformation(): array {
+    return $this->transformation;
+  }
+
+  /**
+   * Set the transformation array to output.
+   *
+   * @param array $transformation
+   *   The transformation array to output.
+   */
+  public function setTransformation(array $transformation) {
+    $this->transformation = $transformation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWeight() {
+    return $this->weight;
+  }
+
+  /**
+   * Set the transformation weight.
+   *
+   * @param int|null $weight
+   *   The transformation weight or NULL for no weight.
+   */
+  public function setWeight($weight) {
+    $this->weight = $weight;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform() {
+    $this->applyTo($this->transformation);
+    return $this->transformation;
+  }
+
+}
diff --git a/src/Transform/TransformBase.php b/src/Transform/TransformBase.php
index a1b285940ad363eb2ec18e15ef5449e41342f5ba..3136b3a2058ba2733d6c645bd63fb1c1385eb5ca 100644
--- a/src/Transform/TransformBase.php
+++ b/src/Transform/TransformBase.php
@@ -14,7 +14,14 @@ abstract class TransformBase extends CacheableMetadata implements TransformInter
    *
    * @var array
    */
-  protected $values = [];
+  protected array $values = [];
+
+  /**
+   * The weight of the transform.
+   *
+   * @var int|null
+   */
+  protected $weight = NULL;
 
   /**
    * {@inheritdoc}
@@ -71,14 +78,24 @@ abstract class TransformBase extends CacheableMetadata implements TransformInter
    * {@inheritdoc}
    */
   public function getWeight() {
-    return NULL;
+    return $this->weight;
+  }
+
+  /**
+   * Sets the weight of the transform.
+   *
+   * @param int $weight
+   *   The weight of the transform.
+   */
+  public function setWeight(int $weight) {
+    $this->weight = $weight;
   }
 
   /**
    * {@inheritdoc}
    */
   public function shouldBeCached() {
-    return TRUE;
+    return !empty($this->values);
   }
 
   /**
diff --git a/src/Transformer.php b/src/Transformer.php
index c769bb4d7e3862abc345698f3e1593f25a322146..5c6e84d5f304ea8e30f750f55fa2f6c5985026a7 100644
--- a/src/Transformer.php
+++ b/src/Transformer.php
@@ -49,7 +49,7 @@ class Transformer {
    */
   protected ControllerResolverInterface $controllerResolver;
 
-  protected const RESERVED_WORDS = ['#cache', '#collapse', '#lazy_transformer'];
+  protected const RESERVED_WORDS = ['#cache', '#collapse', '#lazy_transformer', '#sorted'];
 
   /**
    * Constructs a Transformer service.
@@ -211,17 +211,20 @@ class Transformer {
       $this->handleLazyTransformers($transformation);
     }
     $collapse = $transformation['#collapse'] ?? FALSE;
-    unset($transformation['#cache']);
-    unset($transformation['#collapse']);
-    unset($transformation['#lazy_transformer']);
+    $sorted = $transformation['#sorted'] ?? FALSE;
+    foreach (self::RESERVED_WORDS as $word) {
+      unset($transformation[$word]);
+    }
     foreach ($transformation as $key => $value) {
       if (is_array($value)) {
         $this->cleanupCacheMetadata($transformation[$key]);
       }
     }
+    if ($sorted) {
+      $transformation = array_values($transformation);
+    }
     if ($collapse) {
-      $count = count($transformation);
-      if ($count == 1) {
+      if (count($transformation) == 1) {
         $transformation = $transformation[array_key_first($transformation)];
       }
     }
diff --git a/transform_api.api.php b/transform_api.api.php
index 115c2049bacff57b1f27d8aee2c2e54ed984ad45..73bdb2cd973124bb02bc08f321421eefe9658a70 100644
--- a/transform_api.api.php
+++ b/transform_api.api.php
@@ -9,9 +9,10 @@
  * @addtogroup hooks
  * @{
  */
+use Drupal\transform_api\Transform\EntityTransform;
 
 /**
- * Alter all transformation arrays as they are transformed.
+ * Alter all transformation arrays after they are transformed.
  *
  * @param array $transformation
  *   The transformation array of the transform.
@@ -21,12 +22,14 @@ function hook_transform_alter(&$transformation) {
 }
 
 /**
- * Alter transformation arrays with a specific HOOK identifier.
+ * Alter transformation arrays with a specific TRANSFORM_TYPE identifier.
+ *
+ * These can include 'entity', 'field', 'block' and more.
  *
  * @param array $transformation
  *   The transformation array of the transform.
  */
-function hook_HOOK_transform_alter(&$transformation) {
+function hook_TRANSFORM_TYPE_transform_alter(&$transformation) {
 
 }
 
@@ -40,6 +43,231 @@ function hook_blocks_transform_config_alter(&$blocks) {
 
 }
 
+/**
+ * Alter the transform array generated by an EntityDisplay for an entity.
+ *
+ * @param array $build
+ *   The transform array generated by the EntityDisplay.
+ * @param array $context
+ *   An associative array containing:
+ *   - entity: The entity being transformed.
+ *   - transform_mode: The transform mode; for example, 'full' or 'teaser'.
+ *   - display: The EntityDisplay holding the display options.
+ *
+ * @ingroup entity_crud
+ */
+function hook_entity_transform_build_alter(&$build, $context) {
+
+}
+
+/**
+ * Change the transform mode of an entity that is being transformed.
+ *
+ * @param string $transform_mode
+ *   The transform_mode that is to be used to transform the entity.
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity that is being transformed.
+ *
+ * @ingroup entity_crud
+ */
+function hook_entity_transform_mode_alter(&$transform_mode, \Drupal\Core\Entity\EntityInterface $entity) {
+  // For nodes, change the transform mode when it is teaser.
+  if ($entity->getEntityTypeId() == 'node' && $transform_mode == 'teaser') {
+    $transform_mode = 'my_custom_transform_mode';
+  }
+}
+
+/**
+ * Change the default configuration of an EntityTransform before transformation.
+ *
+ * This happens just before cache checking during transformation.
+ *
+ * Invoked for a specific entity type.
+ *
+ * @param \Drupal\transform_api\Transform\EntityTransform $transform
+ *   The entity transform.
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity to be transformed.
+ * @param string $transform_mode
+ *   The transform mode used for transformation.
+ * @param string|null $langcode
+ *   The language code used for transformation.
+ *
+ * @see hook_entity_transform_configure()
+ */
+function hook_ENTITY_TYPE_transform_configure(EntityTransform $transform, \Drupal\Core\Entity\EntityInterface $entity, $transform_mode, $langcode) {
+}
+
+/**
+ * Change the default configuration of an EntityTransform before transformation.
+ *
+ * This happens just before cache checking during transformation.
+ *
+ * @param \Drupal\transform_api\Transform\EntityTransform $transform
+ *   The entity transform.
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity to be transformed.
+ * @param string $transform_mode
+ *   The transform mode used for transformation.
+ * @param string|null $langcode
+ *   The language code used for transformation.
+ *
+ * @see hook_ENTITY_TYPE_transform_configure()
+ */
+function hook_entity_transform_configure(EntityTransform $transform, \Drupal\Core\Entity\EntityInterface $entity, $transform_mode, $langcode) {
+}
+
+/**
+ * Alter entity transformation array default values.
+ *
+ * Invoked for a specific entity type.
+ *
+ * @param array &$build
+ *   A transform array containing the entity's default values.
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity that is being transformed.
+ * @param string $transform_mode
+ *   The transform_mode that is to be used to transform the entity.
+ *
+ * @see \Drupal\Core\Render\RendererInterface::render()
+ * @see \Drupal\Core\Entity\EntityViewBuilder
+ * @see hook_entity_transform_defaults_alter()
+ * @see hook_ENTITY_TYPE_transform_configure()
+ *
+ * @ingroup entity_crud
+ */
+function hook_ENTITY_TYPE_transform_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $transform_mode) {
+
+}
+
+/**
+ * Alter entity transformation array default values.
+ *
+ * @param array &$build
+ *   A transform array containing the entity's default values.
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity that is being transformed.
+ * @param string $transform_mode
+ *   The transform_mode that is to be used to transform the entity.
+ *
+ * @see \Drupal\transform_api\Transformer::transform()
+ * @see \Drupal\transform_api\Entity\EntityTransformBuilder
+ * @see hook_ENTITY_TYPE_build_defaults_alter()
+ * @see hook_entity_transform_configure()
+ *
+ * @ingroup entity_crud
+ */
+function hook_entity_transform_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $transform_mode) {
+
+}
+
+/**
+ * Act on entities of a particular type being assembled before transforming.
+ *
+ * @param array &$build
+ *   A transformable array representing the entity content. The module may add
+ *   elements to $build prior to transforming. The structure of $build is a
+ *   transformable array as expected by
+ *   \Drupal\transform_api\TransformInterface::transform().
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity object.
+ * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
+ *   The entity transform display holding the display options configured for the
+ *   entity components.
+ * @param string $transform_mode
+ *   The transform mode the entity is rendered in.
+ *
+ * @see hook_entity_transform_alter()
+ * @see hook_ENTITY_TYPE_transform()
+ *
+ * @ingroup entity_crud
+ */
+function hook_entity_transform(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $transform_mode) {
+  // Only do the extra work if the component is configured to be transformed.
+  // This assumes a 'mymodule_addition' extra field has been defined for the
+  // entity bundle in hook_entity_extra_field_info().
+  if ($display->getComponent('mymodule_addition')) {
+    $build['mymodule_addition'] = [
+      'value' => mymodule_addition($entity),
+      '#collapse' => TRUE,
+    ];
+  }
+}
+
+/**
+ * Act on entities of a particular type being assembled before transforming.
+ *
+ * @param array &$build
+ *   A transformable array representing the entity content. The module may add
+ *   elements to $build prior to transforming. The structure of $build is a
+ *   transformable array as expected by
+ *   \Drupal\transform_api\TransformInterface::transform().
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity object.
+ * @param \Drupal\transform_api\Configs\EntityTransformDisplayInterface $display
+ *   The entity transform display holding the display options configured for the
+ *   entity components.
+ * @param string $transform_mode
+ *   The transform mode the entity is transformed in.
+ *
+ * @see hook_ENTITY_TYPE_transform_alter()
+ * @see hook_entity_transform()
+ *
+ * @ingroup entity_crud
+ */
+function hook_ENTITY_TYPE_transform(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $transform_mode) {
+  // Only do the extra work if the component is configured to be displayed.
+  // This assumes a 'mymodule_addition' extra field has been defined for the
+  // entity bundle in hook_entity_extra_field_info().
+  if ($display->getComponent('mymodule_addition')) {
+    $build['mymodule_addition'] = [
+      'value' => mymodule_addition($entity),
+      '#collapse' => TRUE,
+    ];
+  }
+}
+
+/**
+ * Act on entities as they are being prepared for transformation.
+ *
+ * Allows you to operate on multiple entities as they are being prepared for
+ * transformation. Only use this if attaching the data during the entity loading
+ * phase is not appropriate, for example when attaching other 'entity' style
+ * objects.
+ *
+ * @param string $entity_type_id
+ *   The type of entities being transformed (i.e. node, user, comment).
+ * @param array $entities
+ *   The entities keyed by entity ID.
+ * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] $displays
+ *   The array of entity transform displays holding the display options
+ *   configured for the entity components, keyed by bundle name.
+ * @param string $transform_mode
+ *   The transform mode.
+ *
+ * @ingroup entity_crud
+ */
+function hook_entity_prepare_transform($entity_type_id, array $entities, array $displays, $transform_mode) {
+  // Load a specific node into the user object for later transformation.
+  if (!empty($entities) && $entity_type_id == 'user') {
+    // Only do the extra work if the component is configured to be
+    // displayed. This assumes a 'mymodule_addition' extra field has been
+    // defined for the entity bundle in hook_entity_extra_field_info().
+    $ids = [];
+    foreach ($entities as $id => $entity) {
+      if ($displays[$entity->bundle()]->getComponent('mymodule_addition')) {
+        $ids[] = $id;
+      }
+    }
+    if ($ids) {
+      $nodes = mymodule_get_user_nodes($ids);
+      foreach ($ids as $id) {
+        $entities[$id]->user_node = $nodes[$id];
+      }
+    }
+  }
+}
+
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/transform_api.module b/transform_api.module
index da409e897ae428b7b83bc39ea39c170968a50665..8b6fe359ff7b1c9e7194e230626c02ea9e7b5155 100644
--- a/transform_api.module
+++ b/transform_api.module
@@ -8,6 +8,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Url;
+use Drupal\transform_api\Entity\EntityTransformBuilder;
 use Drupal\transform_api\EntityTransformModeInterface;
 use Drupal\transform_api\Plugin\Derivative\TransformLocalTask;
 
@@ -99,27 +100,84 @@ function transform_api_entity_base_field_info_alter(&$fields, EntityTypeInterfac
 }
 
 /**
- * Implements hook_ENTITY_TYPE_insert().
+ * Implements hook_entity_extra_field_info().
  */
-function transform_api_menu_insert(EntityInterface $entity) {
-  // Invalidate the transform block cache to update menu-based derivatives.
-  \Drupal::service('plugin.manager.transform_api.transform_block')->clearCachedDefinitions();
-}
+function transform_api_entity_extra_field_info() {
+  $extra = [];
+  /** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entityTypeBundleInfo */
+  $entityTypeBundleInfo = \Drupal::service('entity_type.bundle.info');
+  $entity_types = \Drupal::entityTypeManager()->getDefinitions();
+  foreach ($entity_types as $entity_type) {
+    if (!$entity_type->hasViewBuilderClass()) {
+      continue;
+    }
+    $components = [];
+    if ($entity_type->hasKey('label')) {
+      $components['label'] = TRUE;
+    }
+    if ($entity_type->hasLinkTemplate('canonical')) {
+      $components['url'] = TRUE;
+    }
 
-/**
- * Implements hook_ENTITY_TYPE_update().
- */
-function transform_api_menu_update(EntityInterface $entity) {
-  // Invalidate the transform block cache to update menu-based derivatives.
-  \Drupal::service('plugin.manager.transform_api.transform_block')->clearCachedDefinitions();
+    // There are some exceptions among core modules.
+    // Users may not have a label field, but it makes
+    // the display name available.
+    if ($entity_type->id() == 'user') {
+      $components['label'] = TRUE;
+    }
+    // Media should only have canonical urls if enabled in media settings.
+    if ($entity_type->id() == 'media' && !\Drupal::config('media.settings')->get('standalone_url')) {
+      unset($components['url']);
+    }
+    // Block content does not have its own URL.
+    if ($entity_type->id() == 'block_content') {
+      unset($components['url']);
+    }
+
+    // For testing purposes.
+    $components['label'] = TRUE;
+
+    if (empty($components)) {
+      continue;
+    }
+
+    foreach ($entityTypeBundleInfo->getBundleInfo($entity_type->id()) as $bundle_id => $bundle) {
+      if ($components['label'] ?? FALSE) {
+        $extra[$entity_type->id()][$bundle_id]['transform']['label'] = [
+          'label' => t('Label'),
+          'description' => t('The label of the entity.'),
+          'weight' => -100,
+          'visible' => TRUE,
+        ];
+      }
+      if ($components['url'] ?? FALSE) {
+        $extra[$entity_type->id()][$bundle_id]['transform']['url'] = [
+          'label' => t('Canonical URL'),
+          'description' => t('The URL of the entity.'),
+          'weight' => 100,
+          'visible' => FALSE,
+        ];
+      }
+    }
+  }
+
+  return $extra;
 }
 
 /**
- * Implements hook_ENTITY_TYPE_delete().
+ * Implements hook_entity_type_alter().
  */
-function transform_api_menu_delete(EntityInterface $entity) {
-  // Invalidate the transform block cache to update menu-based derivatives.
-  \Drupal::service('plugin.manager.transform_api.transform_block')->clearCachedDefinitions();
+function transform_api_entity_type_alter(array &$entity_types) {
+  /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
+  foreach ($entity_types as $entity_type_id => $entity_type) {
+    if (!$entity_type->isInternal()) {
+      if (!$entity_type->hasHandlerClass('transform_builder') && $entity_type->hasViewBuilderClass()) {
+        $entity_type->setHandlerClass('transform_builder', EntityTransformBuilder::class);
+      }
+
+      $entity_types[$entity_type_id] = $entity_type;
+    }
+  }
 }
 
 /**