diff --git a/group.services.yml b/group.services.yml
index 95b34e7..8fbb99d 100644
--- a/group.services.yml
+++ b/group.services.yml
@@ -33,6 +33,10 @@ services:
     class: 'Drupal\group\Access\GroupContentCreateAnyEntityAccessCheck'
     tags:
       - { name: 'access_check', applies_to: '_group_content_create_any_entity_access' }
+  access_check.group_content.entity_access:
+    class: 'Drupal\group\Access\GroupContentEntityAccessCheck'
+    tags:
+      - { name: 'access_check', applies_to: '_group_content_entity_access' }
 
   cache_context.group:
     class: 'Drupal\group\Cache\Context\GroupCacheContext'
diff --git a/src/Access/GroupContentEntityAccessCheck.php b/src/Access/GroupContentEntityAccessCheck.php
new file mode 100644
index 0000000..ecc213b
--- /dev/null
+++ b/src/Access/GroupContentEntityAccessCheck.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\group\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\group\Entity\GroupContentInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Determines access for group content target entities.
+ */
+class GroupContentEntityAccessCheck implements AccessInterface {
+
+  /**
+   * Checks target entity access for group content routes.
+   *
+   * All routes using this access check should have a group content parameter
+   * and have the _group_content_entity_access requirement set to the name of
+   * the operation to check access for.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check against.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The currently logged in account.
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group content to retrieve the target entity from.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(Route $route, AccountInterface $account, GroupContentInterface $group_content) {
+    $operation = $route->getRequirement('_group_content_entity_access');
+
+    // We first check if the user can access the entity. If another module is
+    // denying access, we do not need to check for the group permission.
+    if (!$group_content->getEntity()->access($operation, $account)) {
+      return AccessResult::forbidden();
+    }
+
+    // Check whether the user has access to the operation in this group.
+    return $group_content
+      ->getContentPlugin()
+      ->checkEntityAccess($group_content, $operation, $account);
+  }
+
+}
diff --git a/src/Entity/Controller/GroupContentEntityController.php b/src/Entity/Controller/GroupContentEntityController.php
new file mode 100644
index 0000000..263e2e3
--- /dev/null
+++ b/src/Entity/Controller/GroupContentEntityController.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Drupal\group\Entity\Controller;
+
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\group\Entity\GroupContentInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns responses for GroupContent entity routes.
+ */
+class GroupContentEntityController implements ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The controller resolver service.
+   *
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
+   */
+  protected $controllerResolver;
+
+  /**
+   * The entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Creates an GroupContentEntityController object.
+   *
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
+   *   The controller resolver.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation.
+   */
+  public function __construct(ControllerResolverInterface $controller_resolver, EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
+    $this->controllerResolver = $controller_resolver;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('controller_resolver'),
+      $container->get('entity_type.manager'),
+      $container->get('string_translation')
+    );
+  }
+
+  /**
+   * Provides a page to render a group content's target entity.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group to add the group content to.
+   * @param string $view_mode
+   *   (optional) The view mode that should be used to display the entity.
+   *   Defaults to 'full'.
+   *
+   * @return array
+   *   A render array as expected by drupal_render().
+   */
+  public function view(GroupContentInterface $group_content, $view_mode = 'full') {
+    $callback = $this->controllerResolver->getControllerFromDefinition('\Drupal\Core\Entity\Controller\EntityViewController::view');
+    return $callback($group_content->getEntity(), $view_mode);
+  }
+
+  /**
+   * The _title_callback for the entity.group_content.entity_view route.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group to create the group content in.
+   *
+   * @return string
+   *   The page title.
+   */
+  public function viewTitle(GroupContentInterface $group_content) {
+    return $group_content->getEntity()->label();
+  }
+
+  /**
+   * Builds the entity edit form for the target entity.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group content entity to retrieve the target entity from.
+   *
+   * @return \Drupal\Core\Entity\EntityFormInterface
+   *   The target entity edit form.
+   */
+  public function editForm(GroupContentInterface $group_content) {
+    $entity = $group_content->getEntity();
+    $operation = $entity->getEntityType()->getFormClass('edit') ? 'edit' : 'default';
+    return $this->getEntityForm($entity, $operation);
+  }
+
+  /**
+   * Provides the page title for the target entity edit form.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group content entity to retrieve the target entity from.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+   *   The page title.
+   */
+  public function editTitle(GroupContentInterface $group_content) {
+    return $this->t('Edit %label', ['%label' => $group_content->getEntity()->label()]);
+  }
+
+  /**
+   * Builds the entity delete form for the target entity.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group content entity to retrieve the target entity from.
+   *
+   * @return \Drupal\Core\Entity\EntityFormInterface
+   *   The target entity delete form.
+   */
+  public function deleteForm(GroupContentInterface $group_content) {
+    return $this->getEntityForm($group_content->getEntity(), 'delete');
+  }
+
+  /**
+   * Provides the page title for the target entity delete form.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group content entity to retrieve the target entity from.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+   *   The page title.
+   */
+  public function deleteTitle(GroupContentInterface $group_content) {
+    return $this->t('Delete %label', ['%label' => $group_content->getEntity()->label()]);
+  }
+
+  /**
+   * Builds an entity form for a given entity and operation.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to build the form for.
+   * @param string $operation
+   *   The operation to build the form for.
+   *
+   * @return \Drupal\Core\Entity\EntityFormInterface
+   *   The entity form.
+   */
+  protected function getEntityForm(EntityInterface $entity, $operation) {
+    $form_object = $this->entityTypeManager->getFormObject($entity->getEntityTypeId(), $operation);
+    $form_object->setEntity($entity);
+    return $form_object;
+  }
+
+}
diff --git a/src/Entity/GroupContent.php b/src/Entity/GroupContent.php
index 645a804..a29698d 100644
--- a/src/Entity/GroupContent.php
+++ b/src/Entity/GroupContent.php
@@ -60,7 +60,10 @@ use Drupal\user\UserInterface;
  *     "create-form" = "/group/{group}/content/create/{plugin_id}",
  *     "create-page" = "/group/{group}/content/create",
  *     "delete-form" = "/group/{group}/content/{group_content}/delete",
- *     "edit-form" = "/group/{group}/content/{group_content}/edit"
+ *     "edit-form" = "/group/{group}/content/{group_content}/edit",
+ *     "entity-delete-form" = "/group/{group}/content/{group_content}/entity/delete",
+ *     "entity-edit-form" = "/group/{group}/content/{group_content}/entity/edit",
+ *     "entity-view" = "/group/{group}/content/{group_content}/entity"
  *   },
  *   bundle_entity_type = "group_content_type",
  *   field_ui_base_route = "entity.group_content_type.edit_form",
diff --git a/src/Entity/Routing/GroupContentRouteProvider.php b/src/Entity/Routing/GroupContentRouteProvider.php
index 71e3c94..4933ab1 100644
--- a/src/Entity/Routing/GroupContentRouteProvider.php
+++ b/src/Entity/Routing/GroupContentRouteProvider.php
@@ -62,6 +62,18 @@ class GroupContentRouteProvider extends DefaultHtmlRouteProvider {
       $collection->add("entity.group_content.create_form", $create_form_route);
     }
 
+    if ($entity_delete_form_route = $this->getEntityDeleteFormRoute($entity_type)) {
+      $collection->add("entity.group_content.entity_delete_form", $entity_delete_form_route);
+    }
+
+    if ($entity_edit_form_route = $this->getEntityEditFormRoute($entity_type)) {
+      $collection->add("entity.group_content.entity_edit_form", $entity_edit_form_route);
+    }
+
+    if ($entity_view_route = $this->getEntityViewRoute($entity_type)) {
+      $collection->add("entity.group_content.entity_view", $entity_view_route);
+    }
+
     return $collection;
   }
 
@@ -214,4 +226,94 @@ class GroupContentRouteProvider extends DefaultHtmlRouteProvider {
       ]);
   }
 
+  /**
+   * Gets the route for deleting the grouped entity.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getEntityDeleteFormRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('entity-delete-form')) {
+      $route = new Route($entity_type->getLinkTemplate('entity-delete-form'));
+      $route
+        ->addDefaults([
+          '_controller' => '\Drupal\group\Entity\Controller\GroupContentEntityController::deleteForm',
+          '_title_callback' => '\Drupal\group\Entity\Controller\GroupContentEntityController::deleteFormTitle',
+        ])
+        ->setRequirement('group', '\d+')
+        ->setRequirement('group_content', '\d+')
+        ->setRequirement('_group_owns_content', 'TRUE')
+        ->setRequirement('_group_content_entity_access', 'delete')
+        ->setOption('parameters', [
+          'group' => ['type' => 'entity:group'],
+          'group_content' => ['type' => 'entity:group_content'],
+        ]);
+
+      return $route;
+    }
+  }
+
+  /**
+   * Gets the route for editing the grouped entity.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getEntityEditFormRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('entity-edit-form')) {
+      $route = new Route($entity_type->getLinkTemplate('entity-edit-form'));
+      $route
+        ->addDefaults([
+          '_controller' => '\Drupal\group\Entity\Controller\GroupContentEntityController::editForm',
+          '_title_callback' => '\Drupal\group\Entity\Controller\GroupContentEntityController::editFormTitle',
+        ])
+        ->setRequirement('group', '\d+')
+        ->setRequirement('group_content', '\d+')
+        ->setRequirement('_group_owns_content', 'TRUE')
+        ->setRequirement('_group_content_entity_access', 'update')
+        ->setOption('parameters', [
+          'group' => ['type' => 'entity:group'],
+          'group_content' => ['type' => 'entity:group_content'],
+        ]);
+
+      return $route;
+    }
+  }
+
+  /**
+   * Gets the route for viewing the grouped entity.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getEntityViewRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('entity-view')) {
+      $route = new Route($entity_type->getLinkTemplate('entity-view'));
+      $route
+        ->addDefaults([
+          '_controller' => '\Drupal\group\Entity\Controller\GroupContentEntityController::view',
+          '_title_callback' => '\Drupal\group\Entity\Controller\GroupContentEntityController::viewTitle',
+        ])
+        ->setRequirement('group', '\d+')
+        ->setRequirement('group_content', '\d+')
+        ->setRequirement('_group_owns_content', 'TRUE')
+        ->setRequirement('_group_content_entity_access', 'view')
+        ->setOption('parameters', [
+          'group' => ['type' => 'entity:group'],
+          'group_content' => ['type' => 'entity:group_content'],
+        ]);
+
+      return $route;
+    }
+  }
+
 }
diff --git a/src/Plugin/GroupContentEnablerBase.php b/src/Plugin/GroupContentEnablerBase.php
index cf3b4b8..5c7f851 100644
--- a/src/Plugin/GroupContentEnablerBase.php
+++ b/src/Plugin/GroupContentEnablerBase.php
@@ -2,20 +2,23 @@
 
 namespace Drupal\group\Plugin;
 
-use Drupal\Core\Access\AccessResult;
-use Drupal\group\Access\GroupAccessResult;
-use Drupal\group\Entity\GroupType;
-use Drupal\group\Entity\GroupInterface;
-use Drupal\group\Entity\GroupContentInterface;
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\group\Access\GroupAccessResult;
+use Drupal\group\Entity\GroupContentInterface;
+use Drupal\group\Entity\GroupInterface;
+use Drupal\group\Entity\GroupType;
+use Drupal\user\EntityOwnerInterface;
 
 /**
  * Provides a base class for GroupContentEnabler plugins.
  *
+ * @todo This class is HUGE and should be using handlers like entity types do.
+ *
  * @see \Drupal\group\Annotation\GroupContentEnabler
  * @see \Drupal\group\GroupContentEnablerManager
  * @see \Drupal\group\Plugin\GroupContentEnablerInterface
@@ -316,6 +319,116 @@ abstract class GroupContentEnablerBase extends PluginBase implements GroupConten
   }
 
   /**
+   * Performs access check for the view operation.
+   *
+   * This method is supposed to be overwritten by extending classes that
+   * do their own custom access checking.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group content for which to check access.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user for which to check access.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  protected function entityViewAccess(GroupContentInterface $group_content, AccountInterface $account) {
+    $group = $group_content->getGroup();
+    $plugin_id = $this->getPluginId();
+
+    // We can only allow access to view an entity if it has a view builder.
+    if (!$group_content->getEntity()->getEntityType()->hasViewBuilderClass()) {
+      return AccessResult::forbidden();
+    }
+
+    return GroupAccessResult::allowedIfHasGroupPermission($group, $account, "view $plugin_id entity");
+  }
+
+  /**
+   * Performs access check for the update operation.
+   *
+   * This method is supposed to be overwritten by extending classes that
+   * do their own custom access checking.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group content for which to check access.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user for which to check access.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  protected function entityUpdateAccess(GroupContentInterface $group_content, AccountInterface $account) {
+    $group = $group_content->getGroup();
+    $plugin_id = $this->getPluginId();
+
+    // Check for access to update own target entities.
+    $entity = $group_content->getEntity();
+    if ($entity instanceof EntityOwnerInterface && $entity->getOwnerId() == $account->id()) {
+      if ($group->hasPermission("update own $plugin_id entity", $account)) {
+        return AccessResult::allowed();
+      }
+    }
+
+    return GroupAccessResult::allowedIfHasGroupPermission($group, $account, "update any $plugin_id entity");
+  }
+
+  /**
+   * Performs access check for the delete operation.
+   *
+   * This method is supposed to be overwritten by extending classes that
+   * do their own custom access checking.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group content for which to check access.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user for which to check access.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  protected function entityDeleteAccess(GroupContentInterface $group_content, AccountInterface $account) {
+    $group = $group_content->getGroup();
+    $plugin_id = $this->getPluginId();
+
+    // Check for access to delete own target entities.
+    $entity = $group_content->getEntity();
+    if ($entity instanceof EntityOwnerInterface && $entity->getOwnerId() == $account->id()) {
+      if ($group->hasPermission("delete own $plugin_id entity", $account)) {
+        return AccessResult::allowed();
+      }
+    }
+
+    return GroupAccessResult::allowedIfHasGroupPermission($group, $account, "delete any $plugin_id entity");
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkEntityAccess(GroupContentInterface $group_content, $operation, AccountInterface $account) {
+    // Do not check for target entity access if the plugin does not support it.
+    if (!$this->definesEntityAccess()) {
+      return AccessResult::neutral();
+    }
+
+    switch ($operation) {
+      case 'view':
+        $result = $this->entityViewAccess($group_content, $account);
+        break;
+      case 'update':
+        $result = $this->entityUpdateAccess($group_content, $account);
+        break;
+      case 'delete':
+        $result = $this->entityDeleteAccess($group_content, $account);
+        break;
+      default:
+        $result = AccessResult::neutral();
+    }
+
+    return $result;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function createAccess(GroupInterface $group, AccountInterface $account) {
@@ -363,7 +476,9 @@ abstract class GroupContentEnablerBase extends PluginBase implements GroupConten
 
     // Allow members to edit their own group content.
     if ($group_content->getOwnerId() == $account->id()) {
-      return GroupAccessResult::allowedIfHasGroupPermission($group, $account, "update own $plugin_id content");
+      if ($group->hasPermission("update own $plugin_id content", $account)) {
+        return AccessResult::allowed();
+      }
     }
 
     return GroupAccessResult::allowedIfHasGroupPermission($group, $account, "update any $plugin_id content");
@@ -389,7 +504,9 @@ abstract class GroupContentEnablerBase extends PluginBase implements GroupConten
 
     // Allow members to delete their own group content.
     if ($group_content->getOwnerId() == $account->id()) {
-      return GroupAccessResult::allowedIfHasGroupPermission($group, $account, "delete own $plugin_id content");
+      if ($group->hasPermission("delete own $plugin_id content", $account)) {
+        return AccessResult::allowed();
+      }
     }
 
     return GroupAccessResult::allowedIfHasGroupPermission($group, $account, "delete any $plugin_id content");
@@ -410,7 +527,7 @@ abstract class GroupContentEnablerBase extends PluginBase implements GroupConten
         $result = $this->deleteAccess($group_content, $account);
         break;
       default:
-        $result = GroupAccessResult::neutral();
+        $result = AccessResult::neutral();
     }
 
     return $result;
diff --git a/src/Plugin/GroupContentEnablerInterface.php b/src/Plugin/GroupContentEnablerInterface.php
index 6b4fcf0..265a002 100644
--- a/src/Plugin/GroupContentEnablerInterface.php
+++ b/src/Plugin/GroupContentEnablerInterface.php
@@ -222,6 +222,25 @@ interface GroupContentEnablerInterface extends PluginInspectionInterface, Deriva
   public function createEntityAccess(GroupInterface $group, AccountInterface $account);
 
   /**
+   * Checks access to an operation on a given group content's target entity.
+   *
+   * Use \Drupal\group\Plugin\GroupContentEnablerInterface::createEntityAccess()
+   * to check access to create a group content's target entity.
+   *
+   * @param \Drupal\group\Entity\GroupContentInterface $group_content
+   *   The group content for which to check access for the target entity.
+   * @param string $operation
+   *   The operation access should be checked for. Usually one of "view",
+   *   "update" or "delete".
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user session for which to check access.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function checkEntityAccess(GroupContentInterface $group_content, $operation, AccountInterface $account);
+
+  /**
    * Performs access check for the create operation.
    *
    * This method is supposed to be overwritten by extending classes that
