diff --git a/config/optional/views.view.group_members.yml b/config/optional/views.view.group_members.yml
index b76705f..71a7bb9 100644
--- a/config/optional/views.view.group_members.yml
+++ b/config/optional/views.view.group_members.yml
@@ -646,7 +646,7 @@ display:
       relationships:
         gc__user:
           id: gc__user
-          table: group_content_field_data
+          table: group_content__entity_id
           field: gc__user
           relationship: none
           group_type: group
diff --git a/group.install b/group.install
index c4cb24e..d05dbb1 100644
--- a/group.install
+++ b/group.install
@@ -12,6 +12,7 @@ use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\group\Entity\GroupContent;
 use Drupal\group\Entity\Storage\GroupContentStorageSchema;
+use Drupal\group\Field\GroupContentReferenceDefinition;
 
 /**
  * Resave all GroupContent labels and remove orphaned entities.
@@ -588,3 +589,136 @@ function group_update_8023() {
     $group_type->save(TRUE);
   }
 }
+
+/**
+ * Copy entity_id field to temporary table.
+ */
+function group_update_8024(&$sandbox) {
+  // Update hooks have been re-numbered (was 8016) so must check if run already.
+  $entity_id_field_exists = \Drupal::database()->schema()->fieldExists('group_content_field_data', 'entity_id');
+  if ($entity_id_field_exists) {
+    $query = \Drupal::database()
+      ->select('group_content_field_data', 'g')
+      ->fields('g', ['id', 'entity_id']);
+
+    // Initialize the update process, create a temporary table.
+    if (!isset($sandbox['total'])) {
+      $sandbox['total'] = $query->countQuery()->execute()->fetchField();
+      $sandbox['current'] = 0;
+
+      \Drupal::database()
+        ->schema()
+        ->createTable('group_content_entity_id_update', [
+          'fields' => [
+            'id' => [
+              'type' => 'int',
+              'unsigned' => TRUE,
+              'not null' => TRUE,
+            ],
+            'entity_id' => [
+              'type' => 'int',
+              'unsigned' => TRUE,
+              'not null' => TRUE,
+            ],
+          ],
+          'primary key' => [
+            'id',
+          ],
+          'description' => 'Temporary storage for group_content entity_id field migration',
+        ]);
+    }
+
+    // We're doing one SELECT and one INSERT, so 500 rows at once should be fine.
+    $rows_per_operation = 500;
+    $query->condition('id', $sandbox['current'], '>');
+    $query->range(0, $rows_per_operation);
+    $query->orderBy('id', 'ASC');
+
+    // We're not doing INSERT ... SELECT since I'm not sure how it would perform
+    // on highly loaded databases with much data. One such big query could even
+    // lock a database for a long time.
+    // Core is using the same approach essentially.
+    // @see taxonomy_update_8502()
+    $rows = $query->execute()->fetchAll(PDO::FETCH_ASSOC);
+    if ($rows) {
+      $insert = \Drupal::database()
+        ->insert('group_content_entity_id_update')
+        ->fields(['id', 'entity_id']);
+
+      foreach ($rows as $row) {
+        $insert->values($row);
+        $sandbox['current'] = $row['id'];
+      }
+
+      $insert->execute();
+      $temp_count = \Drupal::database()
+        ->select('group_content_entity_id_update')
+        ->countQuery()->execute()->fetchField();
+      $sandbox['#finished'] = ($temp_count / $sandbox['total']);
+    }
+    else {
+      $sandbox['#finished'] = 1;
+    }
+
+    if ($sandbox['#finished'] >= 1) {
+      // Delete the field schema once data is copied.
+      $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+      if ($entity_definition_update_manager->getEntityType('group_content')) {
+        $storage_definition = $entity_definition_update_manager->getFieldStorageDefinition('entity_id', 'group_content');
+        if ($storage_definition) {
+          $entity_definition_update_manager->uninstallFieldStorageDefinition($storage_definition);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Install new group_content entity_id and entity_id_str fields schema.
+ */
+function group_update_8025(&$sandbox) {
+  $entity_id_definition = GroupContentReferenceDefinition::create('entity_reference')
+    ->setName('entity_id')
+    ->setTargetEntityTypeId('group_content')
+    ->setTargetBundle(NULL)
+    ->setLabel(t('Content'))
+    ->setDescription(t('The entity to add to the group.'))
+    ->setDisplayOptions('form', [
+      'type' => 'entity_reference_autocomplete',
+      'weight' => 5,
+      'settings' => [
+        'match_operator' => 'CONTAINS',
+        'size' => '60',
+        'placeholder' => '',
+      ],
+    ])
+    ->setDisplayConfigurable('view', TRUE)
+    ->setDisplayConfigurable('form', TRUE)
+    ->setRequired(TRUE);
+
+  \Drupal::entityDefinitionUpdateManager()
+    ->installFieldStorageDefinition('entity_id', 'group_content', 'group_content', $entity_id_definition);
+
+  $entity_id_str_definition = GroupContentReferenceDefinition::create('entity_reference')
+    ->setName('entity_id_str')
+    ->setTargetEntityTypeId('group_content')
+    ->setTargetBundle(NULL)
+    ->setLabel(t('Content'))
+    ->setDescription(t('The entity to add to the group.'))
+    ->setSetting('target_type', 'menu')
+    ->setDisplayOptions('form', [
+      'type' => 'entity_reference_autocomplete',
+      'weight' => 5,
+      'settings' => [
+        'match_operator' => 'CONTAINS',
+        'size' => '60',
+        'placeholder' => '',
+      ],
+    ])
+    ->setDisplayConfigurable('view', TRUE)
+    ->setDisplayConfigurable('form', TRUE)
+    ->setRequired(TRUE);
+
+  \Drupal::entityDefinitionUpdateManager()
+    ->installFieldStorageDefinition('entity_id_str', 'group_content', 'group_content', $entity_id_str_definition);
+}
diff --git a/group.module b/group.module
index 3b440cd..47970e3 100644
--- a/group.module
+++ b/group.module
@@ -11,6 +11,7 @@ use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityPublishedInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
@@ -19,6 +20,7 @@ use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\group\Entity\GroupContent;
+use Drupal\group\Field\GroupContentReferenceDefinition;
 use Drupal\group\QueryAccess\EntityQueryAlter;
 use Drupal\user\RoleInterface;
 use Drupal\views\Plugin\views\query\QueryPluginBase;
@@ -359,12 +361,10 @@ function group_entity_access(EntityInterface $entity, $operation, AccountInterfa
  * Implements hook_entity_delete().
  */
 function group_entity_delete(EntityInterface $entity) {
-  if ($entity instanceof ContentEntityInterface) {
-    if ($group_contents = GroupContent::loadByEntity($entity)) {
-      /** @var \Drupal\group\Entity\GroupContent $group_content */
-      foreach ($group_contents as $group_content) {
-        $group_content->delete();
-      }
+  if ($group_contents = GroupContent::loadByEntity($entity)) {
+    /** @var \Drupal\group\Entity\GroupContent $group_content */
+    foreach ($group_contents as $group_content) {
+      $group_content->delete();
     }
   }
 }
@@ -674,6 +674,17 @@ function group_content_entity_submit($form, FormStateInterface $form_state) {
   }
 }
 
+/**
+ * Implements hook_entity_field_storage_info().
+ */
+function group_entity_field_storage_info(EntityTypeInterface $entity_type) {
+  if ($entity_type->id() == 'group_content') {
+    $field_storages['entity_id'] = GroupContentReferenceDefinition::createNumericalReference();
+    $field_storages['entity_id_str'] = GroupContentReferenceDefinition::createStringReference();
+    return $field_storages;
+  }
+}
+
 /**
  * @defgroup group_access Group access rights
  * @{
diff --git a/group.post_update.php b/group.post_update.php
index 7980634..0b6a74c 100644
--- a/group.post_update.php
+++ b/group.post_update.php
@@ -155,3 +155,79 @@ function group_post_update_make_group_revisionable(&$sandbox) {
 
   return new TranslatableMarkup('Groups have been converted to be revisionable.');
 }
+
+/**
+ * Restore the data for group_content entity_id.
+ */
+function group_post_update_restore_entity_id_data(&$sandbox) {
+  $query = \Drupal::database()
+    ->select('group_content_entity_id_update', 'g')
+    ->fields('g', ['id', 'entity_id']);
+
+  // Initialize the update process, install the field schema.
+  if (!isset($sandbox['total'])) {
+    $sandbox['total'] = $query->countQuery()->execute()->fetchField();
+    $sandbox['current'] = 0;
+  }
+
+  // We're now inserting new fields data which may be tricky. We're updating
+  // group_content entities instead of inserting fields data directly to make
+  // sure field data is stored correctly.
+  $rows_per_operation = 50;
+  $query->condition('id', $sandbox['current'], '>');
+  $query->range(0, $rows_per_operation);
+  $query->orderBy('id', 'ASC');
+
+  $rows = $query->execute()->fetchAllKeyed();
+  if ($rows) {
+    /** @var \Drupal\group\Entity\GroupContentInterface[] $group_contents */
+    $group_contents = \Drupal::entityTypeManager()
+      ->getStorage('group_content')
+      ->loadMultiple(array_keys($rows));
+
+    foreach ($group_contents as $id => $group_content) {
+      $group_content->entity_id->target_id = $rows[$id];
+      $group_content->save();
+    }
+
+    end($rows);
+    $sandbox['current'] = key($rows);
+    $moved_rows = Drupal::database()
+      ->select('group_content__entity_id')
+      ->countQuery()->execute()->fetchField();
+    $sandbox['#finished'] = ($moved_rows / $sandbox['total']);
+  }
+  else {
+    $sandbox['#finished'] = 1;
+  }
+
+  if ($sandbox['#finished'] >= 1) {
+    // Delete the temporary table once data is copied.
+    \Drupal::database()->schema()->dropTable('group_content_entity_id_update');
+  }
+}
+
+/**
+ * Fix existing group content views relationships.
+ */
+function group_post_update_fix_group_content_views_relations() {
+  $config_factory = \Drupal::configFactory();
+  foreach ($config_factory->listAll('views.view.') as $name) {
+    $view = $config_factory->getEditable($name);
+    $changed = FALSE;
+    foreach ($view->get('display') as $display_id => $display) {
+      if (isset($display['display_options']['relationships'])) {
+        foreach ($display['display_options']['relationships'] as $relation_id => $relation) {
+          if ($relation['table'] == 'group_content_field_data') {
+            $trail = "display.$display_id.display_options.relationships.$relation_id.table";
+            $view->set($trail, 'group_content__entity_id')->save();
+            $changed = TRUE;
+          }
+        }
+      }
+    }
+    if ($changed) {
+      $view->save();
+    }
+  }
+}
diff --git a/group.views.inc b/group.views.inc
index 9fd0ef7..5a7e32d 100644
--- a/group.views.inc
+++ b/group.views.inc
@@ -5,6 +5,10 @@
  * Contains Views hooks.
  */
 
+use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
+use Drupal\group\Entity\GroupContent;
+use Drupal\group\Field\GroupContentReferenceDefinition;
+
 /**
  * Implements hook_views_data_alter().
  */
@@ -12,14 +16,22 @@ function group_views_data_alter(array &$data) {
   $entity_type_manager = \Drupal::entityTypeManager();
   $entity_types = $entity_type_manager->getDefinitions();
 
-  // Get the data table for GroupContent entities.
-  $data_table = $entity_types['group_content']->getDataTable();
+  /** @var \Drupal\group\Entity\Storage\GroupContentStorageInterface $group_content_storage */
+  $group_content_storage = $entity_type_manager->getStorage('group_content');
+  if (!$group_content_storage instanceof SqlContentEntityStorage) {
+    // Unlike \Drupal\group\Entity\Views\GroupContentViewsData::getViewsData(),
+    // the entity storage is not guaranteed to use SQL backend. Skip processing
+    // in this case since the code below is SQL specific.
+    return;
+  }
+
+  /** @var \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping */
+  $table_mapping = $group_content_storage->getTableMapping();
 
   /** @var \Drupal\group\Plugin\GroupContentEnablerManagerInterface $plugin_manager */
   $plugin_manager = \Drupal::service('plugin.manager.group_content_enabler');
 
-  // Add views data for all defined plugins so modules can provide default
-  // views even though their plugins may not have been installed yet.
+  /** @var \Drupal\group\Plugin\GroupContentEnablerInterface $plugin */
   foreach ($plugin_manager->getAll() as $plugin) {
     $entity_type_id = $plugin->getEntityTypeId();
     if (!isset($entity_types[$entity_type_id])) {
@@ -28,24 +40,31 @@ function group_views_data_alter(array &$data) {
     $entity_type = $entity_types[$entity_type_id];
     $entity_data_table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
 
-    // We only add one 'group_content' entry per entity type.
     if (isset($data[$entity_data_table]['group_content'])) {
+      // Skip further processing if the relationship is already defined by a
+      // different plugin.
       continue;
     }
 
-    $t_args = [
-      '@entity_type' => $entity_type->getLabel(),
-    ];
+    $field_name = GroupContent::getEntityFieldNameForEntityType($entity_type_id);
+    $field_definition = $field_name === 'entity_id'
+      ? GroupContentReferenceDefinition::createNumericalReference()
+      : GroupContentReferenceDefinition::createStringReference();
 
     // This relationship will allow a content entity to easily map to the group
     // content entity that ties it to a group, optionally filtering by plugin.
+    $t_args = ['@entity_type' => $entity_type->getLabel()];
     $data[$entity_data_table]['group_content'] = [
       'title' => t('Group content for @entity_type', $t_args),
       'help' => t('Relates to the group content entities that represent the @entity_type.', $t_args),
       'relationship' => [
         'group' => t('Group content'),
-        'base' => $data_table,
-        'base field' => 'entity_id',
+        'base' => $group_content_storage->getDataTable(),
+        'base field' => 'id',
+        'entity_type' => 'group_content',
+        'field table' => 'group_content__' . $field_name,
+        'field field' => $table_mapping->getFieldColumnName($field_definition->getFieldStorageDefinition(), 'target_id'),
+        'field_name' => $field_name,
         'relationship field' => $entity_type->getKey('id'),
         'id' => 'group_content_to_entity_reverse',
         'label' => t('@entity_type group content', $t_args),
diff --git a/src/Entity/Form/GroupContentForm.php b/src/Entity/Form/GroupContentForm.php
index 713ea88..27918d9 100644
--- a/src/Entity/Form/GroupContentForm.php
+++ b/src/Entity/Form/GroupContentForm.php
@@ -4,6 +4,7 @@ namespace Drupal\group\Entity\Form;
 
 use Drupal\Core\Entity\ContentEntityForm;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\group\Entity\GroupContent;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -51,7 +52,8 @@ class GroupContentForm extends ContentEntityForm {
     // Do not allow to edit the group content subject through the UI. Also hide
     // the field when we are on step 2 of a creation wizard.
     if ($this->operation !== 'add' || $form_state->get('group_wizard')) {
-      $form['entity_id']['#access'] = FALSE;
+      $entity_id_field = $this->getContentEntityField();
+      $form[$entity_id_field]['#access'] = FALSE;
     }
 
     return $form;
@@ -183,7 +185,7 @@ class GroupContentForm extends ContentEntityForm {
     $form_object->save($form, $form_state);
 
     // Add the newly saved entity's ID to the group content entity.
-    $property = $wizard_id == 'group_creator' ? 'gid' : 'entity_id';
+    $property = $wizard_id == 'group_creator' ? 'gid' : $this->getContentEntityField();
     $this->entity->set($property, $entity->id());
 
     // We also clear the temp store so we can start fresh next time around.
@@ -191,4 +193,15 @@ class GroupContentForm extends ContentEntityForm {
     $store->delete("$store_id:entity");
   }
 
+  /**
+   * Returns the name of the group content entity reference field.
+   *
+   * @return string
+   *   The name of the group content entity reference field.
+   */
+  protected function getContentEntityField() {
+    $entity_type_id = $this->getContentPlugin()->getPluginDefinition()['entity_type_id'];
+    return GroupContent::getEntityFieldNameForEntityType($entity_type_id);
+  }
+
 }
diff --git a/src/Entity/Group.php b/src/Entity/Group.php
index a615e84..eb9b4f4 100644
--- a/src/Entity/Group.php
+++ b/src/Entity/Group.php
@@ -4,6 +4,10 @@ namespace Drupal\group\Entity;
 
 use Drupal\Core\Entity\EditorialContentEntityBase;
 use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityPublishedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -151,7 +155,7 @@ class Group extends EditorialContentEntityBase implements GroupInterface {
   /**
    * {@inheritdoc}
    */
-  public function addContent(ContentEntityInterface $entity, $plugin_id, $values = []) {
+  public function addContent(EntityInterface $entity, $plugin_id, $values = []) {
     $storage = $this->groupContentStorage();
     $group_content = $storage->createForEntityInGroup($entity, $this, $plugin_id, $values);
     $storage->save($group_content);
@@ -168,7 +172,10 @@ class Group extends EditorialContentEntityBase implements GroupInterface {
    * {@inheritdoc}
    */
   public function getContentByEntityId($plugin_id, $id) {
-    return $this->getContent($plugin_id, ['entity_id' => $id]);
+    $plugin = $this->getGroupType()->getContentPlugin($plugin_id);
+    $entity_type_id = $plugin->getPluginDefinition()['entity_type_id'];
+    $ref_field_name = GroupContent::getEntityFieldNameForEntityType($entity_type_id);
+    return $this->getContent($plugin_id, [$ref_field_name => $id]);
   }
 
   /**
diff --git a/src/Entity/GroupContent.php b/src/Entity/GroupContent.php
index 75583e5..13b19d2 100644
--- a/src/Entity/GroupContent.php
+++ b/src/Entity/GroupContent.php
@@ -4,13 +4,14 @@ namespace Drupal\group\Entity;
 
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\ContentEntityBase;
-use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\group\Field\GroupContentReferenceDefinition;
 use Drupal\user\EntityOwnerTrait;
-use Drupal\user\UserInterface;
 
 /**
  * Defines the Group content entity.
@@ -97,7 +98,34 @@ class GroupContent extends ContentEntityBase implements GroupContentInterface {
    * {@inheritdoc}
    */
   public function getEntity() {
-    return $this->entity_id->entity;
+    return $this->{$this->getEntityFieldName()}->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getEntityFieldNameForEntityType($entity_type_id) {
+    // If the entity type is fieldable and has a numeric ID field, use an entity
+    // reference field with an integer-based schema.
+    $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+    if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
+      /** @var \Drupal\Core\Field\FieldDefinitionInterface[] $base_fields */
+      $base_fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($entity_type_id);
+      if ($base_fields[$entity_type->getKey('id')]->getType() === 'integer') {
+        return 'entity_id';
+      }
+    }
+
+    // Default to a string reference.
+    return 'entity_id_str';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityFieldName() {
+    $entity_type_id = $this->getContentPlugin()->getPluginDefinition()['entity_type_id'];
+    return static::getEntityFieldNameForEntityType($entity_type_id);
   }
 
   /**
@@ -119,7 +147,7 @@ class GroupContent extends ContentEntityBase implements GroupContentInterface {
   /**
    * {@inheritdoc}
    */
-  public static function loadByEntity(ContentEntityInterface $entity) {
+  public static function loadByEntity(EntityInterface $entity) {
     /** @var \Drupal\group\Entity\Storage\GroupContentStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage('group_content');
     return $storage->loadByEntity($entity);
@@ -240,8 +268,9 @@ class GroupContent extends ContentEntityBase implements GroupContentInterface {
   public function getListCacheTagsToInvalidate() {
     $tags = parent::getListCacheTagsToInvalidate();
 
+    $field_name = $this->getEntityFieldName();
     $group_id = $this->get('gid')->target_id;
-    $entity_id = $this->get('entity_id')->target_id;
+    $entity_id = $this->get($field_name)->target_id;
     $plugin_id = $this->getGroupContentType()->getContentPluginId();
 
     // A specific group gets any content, regardless of plugin used.
@@ -281,24 +310,6 @@ class GroupContent extends ContentEntityBase implements GroupContentInterface {
       ->setReadOnly(TRUE)
       ->setRequired(TRUE);
 
-    // Borrowed this logic from the Comment module.
-    // Warning! May change in the future: https://www.drupal.org/node/2346347
-    $fields['entity_id'] = BaseFieldDefinition::create('entity_reference')
-      ->setLabel(t('Content'))
-      ->setDescription(t('The entity to add to the group.'))
-      ->setDisplayOptions('form', [
-        'type' => 'entity_reference_autocomplete',
-        'weight' => 5,
-        'settings' => [
-          'match_operator' => 'CONTAINS',
-          'size' => '60',
-          'placeholder' => '',
-        ],
-      ])
-      ->setDisplayConfigurable('view', TRUE)
-      ->setDisplayConfigurable('form', TRUE)
-      ->setRequired(TRUE);
-
     $fields['label'] = BaseFieldDefinition::create('string')
       ->setLabel(t('Title'))
       ->setReadOnly(TRUE)
@@ -345,37 +356,37 @@ class GroupContent extends ContentEntityBase implements GroupContentInterface {
    * {@inheritdoc}
    */
   public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
-    // Borrowed this logic from the Comment module.
-    // Warning! May change in the future: https://www.drupal.org/node/2346347
+    /** @var \Drupal\group\Field\GroupContentReferenceDefinition[] $fields */
+    $fields = [];
+
+    // Depending on whether the entity type that can be grouped by this bundle
+    // has a numerical or string ID field, we add an entity reference field that
+    // has either an integer or varchar schema, respectively.
+    /** @var \Drupal\group\Entity\GroupContentTypeInterface $group_content_type */
     if ($group_content_type = GroupContentType::load($bundle)) {
       $plugin = $group_content_type->getContentPlugin();
+      if ($plugin) {
 
-      /** @var \Drupal\Core\Field\BaseFieldDefinition $original */
-      $original = $base_field_definitions['entity_id'];
-
-      // Recreated the original entity_id field so that it does not contain any
-      // data in its "propertyDefinitions" or "schema" properties because those
-      // were set based on the base field which had no clue what bundle to serve
-      // up until now. This is a bug in core because we can't simply unset those
-      // two properties, see: https://www.drupal.org/node/2346329
-      $fields['entity_id'] = BaseFieldDefinition::create('entity_reference')
-        ->setLabel($plugin->getEntityReferenceLabel() ?: $original->getLabel())
-        ->setDescription($plugin->getEntityReferenceDescription() ?: $original->getDescription())
-        ->setConstraints($original->getConstraints())
-        ->setDisplayOptions('view', $original->getDisplayOptions('view'))
-        ->setDisplayOptions('form', $original->getDisplayOptions('form'))
-        ->setDisplayConfigurable('view', $original->isDisplayConfigurable('view'))
-        ->setDisplayConfigurable('form', $original->isDisplayConfigurable('form'))
-        ->setRequired($original->isRequired());
-
-      foreach ($plugin->getEntityReferenceSettings() as $name => $setting) {
-        $fields['entity_id']->setSetting($name, $setting);
-      }
+        $field_name = static::getEntityFieldNameForEntityType($plugin->getEntityTypeId());
+        $fields[$field_name] = $field_name === 'entity_id'
+          ? GroupContentReferenceDefinition::createNumericalReference()
+          : GroupContentReferenceDefinition::createStringReference();
+
+        if ($label = $plugin->getEntityReferenceLabel()) {
+          $fields[$field_name]->setLabel($label);
+        }
+
+        if ($description = $plugin->getEntityReferenceDescription()) {
+          $fields[$field_name]->setDescription($description);
+        }
 
-      return $fields;
+        foreach ($plugin->getEntityReferenceSettings() as $name => $setting) {
+          $fields[$field_name]->setSetting($name, $setting);
+        }
+      }
     }
 
-    return [];
+    return $fields;
   }
 
 }
diff --git a/src/Entity/GroupContentInterface.php b/src/Entity/GroupContentInterface.php
index 28965aa..e2db46e 100644
--- a/src/Entity/GroupContentInterface.php
+++ b/src/Entity/GroupContentInterface.php
@@ -2,9 +2,10 @@
 
 namespace Drupal\group\Entity;
 
-use Drupal\user\EntityOwnerInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\user\EntityOwnerInterface;
 
 /**
  * Provides an interface defining a Group content entity.
@@ -34,6 +35,27 @@ interface GroupContentInterface extends ContentEntityInterface, EntityOwnerInter
    */
   public function getEntity();
 
+  /**
+   * Returns the name of the entity reference field for a given entity type.
+   *
+   * @param string $entity_type_id
+   *   The ID of the entity type to retrieve the field name for.
+   *
+   * @return string
+   *   The name of the field referencing group content.
+   */
+  public static function getEntityFieldNameForEntityType($entity_type_id);
+
+  /**
+   * Returns the name of the group content entity reference field.
+   *
+   * @return string
+   *   The name of the field referencing group content.
+   *
+   * @see \Drupal\group\Entity\GroupContentInterface::getEntityFieldNameForEntityType()
+   */
+  public function getEntityFieldName();
+
   /**
    * Returns the content enabler plugin that handles the group content.
    *
@@ -55,12 +77,12 @@ interface GroupContentInterface extends ContentEntityInterface, EntityOwnerInter
   /**
    * Loads group content entities which reference a given entity.
    *
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   * @param \Drupal\Core\Entity\EntityInterface $entity
    *   An entity which may be within one or more groups.
    *
    * @return \Drupal\group\Entity\GroupContentInterface[]
    *   An array of group content entities which reference the given entity.
    */
-  public static function loadByEntity(ContentEntityInterface $entity);
+  public static function loadByEntity(EntityInterface $entity);
 
 }
diff --git a/src/Entity/GroupInterface.php b/src/Entity/GroupInterface.php
index bbfdee5..90cbf5f 100644
--- a/src/Entity/GroupInterface.php
+++ b/src/Entity/GroupInterface.php
@@ -2,12 +2,13 @@
 
 namespace Drupal\group\Entity;
 
-use Drupal\user\EntityOwnerInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityPublishedInterface;
 use Drupal\Core\Entity\RevisionLogInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\user\EntityOwnerInterface;
 use Drupal\user\UserInterface;
 
 /**
@@ -35,7 +36,7 @@ interface GroupInterface extends ContentEntityInterface, EntityOwnerInterface, E
   /**
    * Adds a content entity as a group content entity.
    *
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The content entity to add to the group.
    * @param string $plugin_id
    *   The ID of the content enabler plugin to add the entity with.
@@ -43,7 +44,7 @@ interface GroupInterface extends ContentEntityInterface, EntityOwnerInterface, E
    *   (optional) Extra values to add to the group content relationship. You
    *   cannot overwrite the group ID (gid) or entity ID (entity_id).
    */
-  public function addContent(ContentEntityInterface $entity, $plugin_id, $values = []);
+  public function addContent(EntityInterface $entity, $plugin_id, $values = []);
 
   /**
    * Retrieves all GroupContent entities for the group.
diff --git a/src/Entity/Storage/GroupContentStorage.php b/src/Entity/Storage/GroupContentStorage.php
index 490d3a6..130c0d0 100644
--- a/src/Entity/Storage/GroupContentStorage.php
+++ b/src/Entity/Storage/GroupContentStorage.php
@@ -2,9 +2,10 @@
 
 namespace Drupal\group\Entity\Storage;
 
-use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
+use Drupal\group\Entity\GroupContent;
 use Drupal\group\Entity\GroupInterface;
 
 /**
@@ -25,7 +26,7 @@ class GroupContentStorage extends SqlContentEntityStorage implements GroupConten
   /**
    * {@inheritdoc}
    */
-  public function createForEntityInGroup(ContentEntityInterface $entity, GroupInterface $group, $plugin_id, $values = []) {
+  public function createForEntityInGroup(EntityInterface $entity, GroupInterface $group, $plugin_id, $values = []) {
     // An unsaved entity cannot have any group content.
     if ($entity->id() === NULL) {
       throw new EntityStorageException("Cannot add an unsaved entity to a group.");
@@ -50,11 +51,14 @@ class GroupContentStorage extends SqlContentEntityStorage implements GroupConten
       }
     }
 
+    // Retrieve the entity reference field name.
+    $field_name = GroupContent::getEntityFieldNameForEntityType($entity->getEntityTypeId());
+
     // Set the necessary keys for a valid GroupContent entity.
     $keys = [
       'type' => $plugin->getContentTypeConfigId(),
       'gid' => $group->id(),
-      'entity_id' => $entity->id(),
+      $field_name => $entity->id(),
     ];
 
     // Return an unsaved GroupContent entity.
@@ -85,7 +89,7 @@ class GroupContentStorage extends SqlContentEntityStorage implements GroupConten
   /**
    * {@inheritdoc}
    */
-  public function loadByEntity(ContentEntityInterface $entity) {
+  public function loadByEntity(EntityInterface $entity) {
     // An unsaved entity cannot have any group content.
     $entity_id = $entity->id();
     if ($entity_id === NULL) {
@@ -100,13 +104,19 @@ class GroupContentStorage extends SqlContentEntityStorage implements GroupConten
 
       // Statically cache all group content IDs for the group content types.
       if (!empty($group_content_types)) {
-        // Use an optimized plain query to avoid the overhead of entity and SQL
-        // query builders.
-        $query = "SELECT id from {{$this->dataTable}} WHERE entity_id = :entity_id AND type IN (:types[])";
+        // Retrieve the entity reference field name.
+        $field_name = GroupContent::getEntityFieldNameForEntityType($entity->getEntityTypeId());
+        // Contruct the table name from the field name.
+        $table_name = 'group_content__' . $field_name;
+        // Add "_target_id" to the field name because that's the column where
+        // the id is stored.
+        $field_name = $field_name . '_target_id';
+
+        $query = "SELECT entity_id from {{$table_name}} WHERE {$field_name} = :{$field_name} AND bundle IN (:bundles[])";
         $this->loadByEntityCache[$entity_type_id][$entity_id] = $this->database
           ->query($query, [
-            ':entity_id' => $entity_id,
-            ':types[]' => array_keys($group_content_types),
+            $field_name => $entity_id,
+            ':bundles[]' => array_keys($group_content_types),
           ])
           ->fetchCol();
       }
diff --git a/src/Entity/Storage/GroupContentStorageInterface.php b/src/Entity/Storage/GroupContentStorageInterface.php
index f61001b..3f3a84b 100644
--- a/src/Entity/Storage/GroupContentStorageInterface.php
+++ b/src/Entity/Storage/GroupContentStorageInterface.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\group\Entity\Storage;
 
-use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\ContentEntityStorageInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\group\Entity\GroupInterface;
 
 /**
@@ -14,8 +14,8 @@ interface GroupContentStorageInterface extends ContentEntityStorageInterface {
   /**
    * Creates a GroupContent entity for placing a content entity in a group.
    *
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
-   *   The content entity to add to the group.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to add to the group.
    * @param \Drupal\group\Entity\GroupInterface $group
    *   The group to add the content entity to.
    * @param string $plugin_id
@@ -26,7 +26,7 @@ interface GroupContentStorageInterface extends ContentEntityStorageInterface {
    * @return \Drupal\group\Entity\GroupContentInterface
    *   A new GroupContent entity.
    */
-  public function createForEntityInGroup(ContentEntityInterface $entity, GroupInterface $group, $plugin_id, $values = []);
+  public function createForEntityInGroup(EntityInterface $entity, GroupInterface $group, $plugin_id, $values = []);
 
   /**
    * Retrieves all GroupContent entities for a group.
@@ -47,13 +47,13 @@ interface GroupContentStorageInterface extends ContentEntityStorageInterface {
   /**
    * Retrieves all GroupContent entities that represent a given entity.
    *
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   * @param \Drupal\Core\Entity\EntityInterface $entity
    *   An entity which may be within one or more groups.
    *
    * @return \Drupal\group\Entity\GroupContentInterface[]
    *   A list of GroupContent entities which refer to the given entity.
    */
-  public function loadByEntity(ContentEntityInterface $entity);
+  public function loadByEntity(EntityInterface $entity);
 
   /**
    * Retrieves all GroupContent entities by their responsible plugin ID.
diff --git a/src/Entity/Storage/GroupContentStorageSchema.php b/src/Entity/Storage/GroupContentStorageSchema.php
index 46b644b..29907d9 100644
--- a/src/Entity/Storage/GroupContentStorageSchema.php
+++ b/src/Entity/Storage/GroupContentStorageSchema.php
@@ -18,7 +18,7 @@ class GroupContentStorageSchema extends SqlContentEntityStorageSchema {
 
     if ($data_table = $this->storage->getDataTable()) {
       $schema[$data_table]['indexes'] += [
-        'group_content__entity_fields' => ['type', 'entity_id'],
+        'group_content__entity_fields' => ['type'],
       ];
     }
 
diff --git a/src/Entity/Views/GroupContentViewsData.php b/src/Entity/Views/GroupContentViewsData.php
index b324b9d..c59a6cb 100644
--- a/src/Entity/Views/GroupContentViewsData.php
+++ b/src/Entity/Views/GroupContentViewsData.php
@@ -2,7 +2,18 @@
 
 namespace Drupal\group\Entity\Views;
 
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Render\Markup;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\group\Entity\GroupContent;
+use Drupal\group\Field\GroupContentReferenceDefinition;
+use Drupal\group\Plugin\GroupContentEnablerManagerInterface;
 use Drupal\views\EntityViewsData;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -18,14 +29,53 @@ class GroupContentViewsData extends EntityViewsData {
    */
   protected $pluginManager;
 
+  /**
+   * TODO.
+   *
+   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
+   */
+  protected $fieldTypePluginManager;
+
+  /**
+   * @inheritDoc
+   */
+  public function __construct(
+    EntityTypeInterface $entity_type,
+    SqlEntityStorageInterface $storage_controller,
+    EntityTypeManagerInterface $entity_type_manager,
+    ModuleHandlerInterface $module_handler,
+    TranslationInterface $translation_manager,
+    EntityFieldManagerInterface $entity_field_manager,
+    GroupContentEnablerManagerInterface $content_enabler_manager,
+    FieldTypePluginManagerInterface $field_type_plugin_manager
+  ) {
+    parent::__construct(
+      $entity_type,
+      $storage_controller,
+      $entity_type_manager,
+      $module_handler,
+      $translation_manager,
+      $entity_field_manager
+    );
+
+    $this->pluginManager = $content_enabler_manager;
+    $this->fieldTypePluginManager = $field_type_plugin_manager;
+  }
+
   /**
    * {@inheritdoc}
    */
   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    /** @var static $views_data */
-    $views_data = parent::createInstance($container, $entity_type);
-    $views_data->pluginManager = $container->get('plugin.manager.group_content_enabler');
-    return $views_data;
+    return new static(
+      $entity_type,
+      $container->get('entity_type.manager')->getStorage($entity_type->id()),
+      $container->get('entity_type.manager'),
+      $container->get('module_handler'),
+      $container->get('string_translation'),
+      $container->get('entity_field.manager'),
+      $container->get('plugin.manager.group_content_enabler'),
+      $container->get('plugin.manager.field.field_type')
+    );
   }
 
   /**
@@ -41,14 +91,7 @@ class GroupContentViewsData extends EntityViewsData {
       'numeric' => TRUE,
     ];
 
-    // Get the data table for GroupContent entities.
-    $data_table = $this->entityType->getDataTable();
-
-    // Unset the 'entity_id' field relationship as we want a more powerful one.
-    // @todo Eventually, we may want to replace all of 'entity_id'.
-    unset($data[$data_table]['entity_id']['relationship']);
-
-    /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
+    $table_mapping = $this->storage->getTableMapping();
     $entity_types = $this->entityTypeManager->getDefinitions();
 
     // Add views data for all defined plugins so modules can provide default
@@ -62,27 +105,41 @@ class GroupContentViewsData extends EntityViewsData {
       $entity_data_table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
 
       // Create a unique field name for this views field.
-      $field_name = 'gc__' . $entity_type_id;
+      // This is a Views field, not entity field!
+      $views_field_name = 'gc__' . $entity_type_id;
+      $field_name = GroupContent::getEntityFieldNameForEntityType($entity_type_id);
+      $field_table = $this->getFieldTableName($field_name);
 
-      // We only add one 'group_content' relationship per entity type.
-      if (isset($data[$entity_data_table][$field_name])) {
+      if (isset($data[$field_table][$views_field_name])) {
+        // Skip further processing if the relationship is already defined by a
+        // different plugin.
         continue;
       }
 
-      $t_args = [
-        '@entity_type' => $entity_type->getLabel(),
-      ];
+      $field_definition = $field_name === 'entity_id'
+        ? GroupContentReferenceDefinition::createNumericalReference()
+        : GroupContentReferenceDefinition::createStringReference();
+
+      // Avoid reload field default views data to preserve existing relations.
+      if (!isset($data[$field_table])) {
+        // Fill in default Views data for a field, just like Views does
+        // for fields defined via config entities.
+        $default_data = $this->defaultFieldViewsData($field_definition);
+        $data[$field_table] = $default_data[$field_table];
+      }
 
-      // This relationship will allow a group content entity to easily map to a
-      // content entity that it ties to a group, optionally filtering by plugin.
-      $data[$data_table][$field_name] = [
+      // This relationship will allow a content entity to easily map to the
+      // group content entity that ties it to a group, optionally filtering by
+      // plugin.
+      $t_args = ['@entity_type' => $entity_type->getLabel()];
+      $data[$field_table][$views_field_name] = [
         'title' => $this->t('@entity_type from group content', $t_args),
         'help' => $this->t('Relates to the @entity_type entity the group content represents.', $t_args),
         'relationship' => [
           'group' => $entity_type->getLabel(),
           'base' => $entity_data_table,
           'base field' => $entity_type->getKey('id'),
-          'relationship field' => 'entity_id',
+          'relationship field' => $table_mapping->getFieldColumnName($field_definition->getFieldStorageDefinition(), 'target_id'),
           'id' => 'group_content_to_entity',
           'label' => $this->t('Group content @entity_type', $t_args),
           'target_entity_type' => $entity_type_id,
@@ -90,7 +147,342 @@ class GroupContentViewsData extends EntityViewsData {
       ];
     }
 
+    // Add the entity type metadata to each table generated.
+    $entity_type_id = $this->entityType->id();
+    array_walk($data, function (&$table_data) use ($entity_type_id) {
+      $table_data['table']['entity type'] = $entity_type_id;
+      $table_data['table']['entity revision'] = FALSE;
+    });
+
+    return $data;
+  }
+
+  /**
+   * Defaults Views data implementation for the group content field.
+   *
+   * This method is mostly a copy-paste of views_field_default_views_data().
+   * Unfortunately, we can't re-use is since it only accepts
+   * FieldStorageConfigInterface as an argument.
+   *
+   * Group content does not support revisions so everything related to
+   * revisions is stripped for simplicity.
+   * The group content entity reference field is not translatable so everything
+   * related to translations is stripped as well.
+   *
+   * @see views_field_default_views_data()
+   *
+   * @param \Drupal\group\Field\GroupContentReferenceDefinition $field_storage
+   *   Group content reference field definition.
+   *
+   * @return array
+   *   The default views data for the field.
+   *
+   * @TODO Review this code once https://www.drupal.org/node/3016026 lands.
+   */
+  protected function defaultFieldViewsData(GroupContentReferenceDefinition $field_storage) {
+    $data = [];
+
+    // Check the field type is available.
+    if (!$this->fieldTypePluginManager->hasDefinition($field_storage->getType())) {
+      return $data;
+    }
+
+    $field_name = $field_storage->getName();
+    $field_columns = $field_storage->getColumns();
+
+    // Grab information about the entity type tables.
+    // We need to join to both the base table and the data table, if available.
+    // Check whether the entity type storage is supported.
+    $storage = $this->entityTypeManager->getStorage($field_storage->getTargetEntityTypeId());
+    if (!$storage) {
+      return $data;
+    }
+
+    $entity_type_id = $field_storage->getTargetEntityTypeId();
+    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+    if (!$base_table = $entity_type->getBaseTable()) {
+      // We cannot do anything if for some reason there is no base table.
+      return $data;
+    }
+    // Some entities may not have a data table.
+    $data_table = $entity_type->getDataTable();
+
+    // Description of the field tables.
+    /** @var \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping */
+    $table_mapping = $storage->getTableMapping();
+    $field_tables = [
+      EntityStorageInterface::FIELD_LOAD_CURRENT => [
+        'table' => $table_mapping->getDedicatedDataTableName($field_storage),
+        'alias' => "{$entity_type_id}__{$field_name}",
+      ],
+    ];
+
+    // Build the relationships between the field table and the entity tables.
+    $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_CURRENT]['alias'];
+    if ($data_table) {
+      // Tell Views how to join to the base table, via the data table.
+      $data[$table_alias]['table']['join'][$data_table] = [
+        'table' => $table_mapping->getDedicatedDataTableName($field_storage),
+        'left_field' => $entity_type->getKey('id'),
+        'field' => 'entity_id',
+        'extra' => [
+          ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
+        ],
+      ];
+    }
+    else {
+      // If there is no data table, just join directly.
+      $data[$table_alias]['table']['join'][$base_table] = [
+        'table' => $table_mapping->getDedicatedDataTableName($field_storage),
+        'left_field' => $entity_type->getKey('id'),
+        'field' => 'entity_id',
+        'extra' => [
+          ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
+        ],
+      ];
+    }
+
+    $group_name = $entity_type->getLabel();
+
+    // Build the list of additional fields to add to queries.
+    $add_fields = ['delta', 'langcode', 'bundle'];
+    foreach (array_keys($field_columns) as $column) {
+      $add_fields[] = $table_mapping->getFieldColumnName($field_storage, $column);
+    }
+    // Determine the label to use for the field. We don't have a label available
+    // at the field level, so we just go through all fields and take the one
+    // which is used the most frequently.
+    [$label, $all_labels] = views_entity_field_label($entity_type_id, $field_name);
+
+    // Expose data for the field as a whole.
+    foreach ($field_tables as $type => $table_info) {
+      $table = $table_info['table'];
+      $table_alias = $table_info['alias'];
+
+      if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
+        $group = $group_name;
+        $field_alias = $field_name;
+      }
+      else {
+        $group = t('@group (historical data)', ['@group' => $group_name]);
+        $field_alias = $field_name . '-revision_id';
+      }
+
+      $data[$table_alias][$field_alias] = [
+        'group' => $group,
+        'title' => $label,
+        'title short' => $label,
+      ];
+
+      // Go through and create a list of aliases for all possible combinations
+      // of entity type + name.
+      $aliases = [];
+      $also_known = [];
+      foreach ($all_labels as $label_name => $true) {
+        if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT && $label != $label_name) {
+          $aliases[] = [
+            'base' => $base_table,
+            'group' => $group_name,
+            'title' => $label_name,
+            'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
+          ];
+          $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $label_name]);
+        }
+      }
+
+      if ($aliases) {
+        $data[$table_alias][$field_alias]['aliases'] = $aliases;
+        // The $also_known variable contains markup that is HTML escaped and
+        // that loses safeness when imploded. The help text is used in
+        // #description and therefore XSS admin filtered by default. Escaped
+        // HTML is not altered by XSS filtering, therefore it is safe to just
+        // concatenate the strings. Afterwards we mark the entire string as
+        // safe, so it won't be escaped, no matter where it is used.
+        // Considering the dual use of this help data (both as metadata and as
+        // help text), other patterns such as use of #markup would not be
+        // correct here.
+        $data[$table_alias][$field_alias]['help'] = Markup::create($data[$table_alias][$field_alias]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
+      }
+
+      $keys = array_keys($field_columns);
+      $real_field = reset($keys);
+      $data[$table_alias][$field_alias]['field'] = [
+        'table' => $table,
+        'id' => 'field',
+        'field_name' => $field_name,
+        'entity_type' => $entity_type_id,
+        // Provide a real field for group by.
+        'real field' => $field_alias . '_' . $real_field,
+        'additional fields' => $add_fields,
+        // Default the element type to div, let the UI change it if necessary.
+        'element type' => 'div',
+        'is revision' => $type == EntityStorageInterface::FIELD_LOAD_REVISION,
+      ];
+    }
+
+    // Expose data for each field property individually.
+    foreach ($field_columns as $column => $attributes) {
+      $allow_sort = TRUE;
+
+      // Identify likely filters and arguments for each column based on field
+      // type.
+      switch ($attributes['type']) {
+        case 'int':
+        case 'mediumint':
+        case 'tinyint':
+        case 'bigint':
+        case 'serial':
+        case 'numeric':
+        case 'float':
+          $filter = 'numeric';
+          $argument = 'numeric';
+          $sort = 'standard';
+          if ($field_storage->getType() == 'boolean') {
+            $filter = 'boolean';
+          }
+          break;
+
+        case 'text':
+        case 'blob':
+          // It does not make sense to sort by blob or text.
+          $allow_sort = FALSE;
+
+        default:
+          $filter = 'string';
+          $argument = 'string';
+          $sort = 'standard';
+          break;
+      }
+
+      if (count($field_columns) == 1 || $column == 'value') {
+        $title = t('@label (@name)', ['@label' => $label, '@name' => $field_name]);
+        $title_short = $label;
+      }
+      else {
+        $title = t('@label (@name:@column)', [
+          '@label' => $label,
+          '@name' => $field_name,
+          '@column' => $column,
+        ]);
+        $title_short = t('@label:@column', ['@label' => $label, '@column' => $column]);
+      }
+
+      // Expose data for the property.
+      foreach ($field_tables as $type => $table_info) {
+        $table = $table_info['table'];
+        $table_alias = $table_info['alias'];
+
+        if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
+          $group = $group_name;
+        }
+        else {
+          $group = t('@group (historical data)', ['@group' => $group_name]);
+        }
+        $column_real_name = $table_mapping->getFieldColumnName($field_storage, $column);
+
+        // Load all the fields from the table by default.
+        $additional_fields = $table_mapping->getAllColumns($table);
+
+        $data[$table_alias][$column_real_name] = [
+          'group' => $group,
+          'title' => $title,
+          'title short' => $title_short,
+        ];
+
+        // Go through and create a list of aliases for all possible
+        // combinations of entity type + name.
+        $aliases = [];
+        $also_known = [];
+        foreach ($all_labels as $label_name => $true) {
+          if ($label != $label_name) {
+            if (count($field_columns) == 1 || $column == 'value') {
+              $alias_title = t('@label (@name)', ['@label' => $label_name, '@name' => $field_name]);
+            }
+            else {
+              $alias_title = t('@label (@name:@column)', [
+                '@label' => $label_name,
+                '@name' => $field_name,
+                '@column' => $column,
+              ]);
+            }
+            $aliases[] = [
+              'group' => $group_name,
+              'title' => $alias_title,
+              'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $title]),
+            ];
+            $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $title]);
+          }
+        }
+        if ($aliases) {
+          $data[$table_alias][$column_real_name]['aliases'] = $aliases;
+          // The $also_known variable contains markup that is HTML escaped and
+          // that loses safeness when imploded. The help text is used in
+          // #description and therefore XSS admin filtered by default. Escaped
+          // HTML is not altered by XSS filtering, therefore it is safe to just
+          // concatenate the strings. Afterwards we mark the entire string as
+          // safe, so it won't be escaped, no matter where it is used.
+          // Considering the dual use of this help data (both as metadata and as
+          // help text), other patterns such as use of #markup would not be
+          // correct here.
+          $data[$table_alias][$column_real_name]['help'] = Markup::create($data[$table_alias][$column_real_name]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
+        }
+
+        $data[$table_alias][$column_real_name]['argument'] = [
+          'field' => $column_real_name,
+          'table' => $table,
+          'id' => $argument,
+          'additional fields' => $additional_fields,
+          'field_name' => $field_name,
+          'entity_type' => $entity_type_id,
+          'empty field name' => t('- No value -'),
+        ];
+        $data[$table_alias][$column_real_name]['filter'] = [
+          'field' => $column_real_name,
+          'table' => $table,
+          'id' => $filter,
+          'additional fields' => $additional_fields,
+          'field_name' => $field_name,
+          'entity_type' => $entity_type_id,
+          'allow empty' => TRUE,
+        ];
+        if (!empty($allow_sort)) {
+          $data[$table_alias][$column_real_name]['sort'] = [
+            'field' => $column_real_name,
+            'table' => $table,
+            'id' => $sort,
+            'additional fields' => $additional_fields,
+            'field_name' => $field_name,
+            'entity_type' => $entity_type_id,
+          ];
+        }
+
+        // Set click sortable if there is a field definition.
+        if (isset($data[$table_alias][$field_name]['field'])) {
+          $data[$table_alias][$field_name]['field']['click sortable'] = $allow_sort;
+        }
+      }
+    }
+
     return $data;
   }
 
+  /**
+   * Returns the SQL table name for a given field.
+   *
+   * @param string $field_name
+   *   The field name.
+   *
+   * @return string
+   *   The field table name.
+   */
+  protected function getFieldTableName($field_name) {
+    // The getTableMapping() method assumes field storage definitions are
+    // static. This is causing an error while installing the gnode module.
+    // As a workaround, we're field storage definitions from the entity manager
+    // to bypass cache.
+    // @see https://www.drupal.org/project/drupal/issues/3016059
+    $table_mapping = $this->storage->getTableMapping($this->getFieldStorageDefinitions('group_content'));
+    return $table_mapping->getFieldTableName($field_name);
+  }
+
 }
diff --git a/src/Field/GroupContentReferenceDefinition.php b/src/Field/GroupContentReferenceDefinition.php
new file mode 100644
index 0000000..e5e33e7
--- /dev/null
+++ b/src/Field/GroupContentReferenceDefinition.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\group\Field;
+
+use Drupal\Core\Field\BaseFieldDefinition;
+
+/**
+ * A custom field storage definition class.
+ *
+ * This class is used as a workaround until core issue is fixed.
+ *
+ * @todo Implement FieldStorageDefinition (https://www.drupal.org/node/2280639).
+ */
+class GroupContentReferenceDefinition extends BaseFieldDefinition {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create($type) {
+    if ($type !== 'entity_reference') {
+      throw new \LogicException('GroupContentReferenceDefinition should always be of type entity_reference');
+    }
+    return parent::create($type)
+      ->setTargetEntityTypeId('group_content')
+      ->setTargetBundle(NULL)
+      ->setLabel(t('Content'))
+      ->setDescription(t('The entity to add to the group.'))
+      ->setDisplayOptions('form', [
+        'type' => 'entity_reference_autocomplete',
+        'weight' => 5,
+        'settings' => [
+          'match_operator' => 'CONTAINS',
+          'size' => '60',
+          'placeholder' => '',
+        ],
+      ])
+      ->setDisplayConfigurable('view', TRUE)
+      ->setDisplayConfigurable('form', TRUE)
+      ->setRequired(TRUE);
+  }
+
+  /**
+   * Creates a new group content reference field definition for numerical IDs.
+   *
+   * @return static
+   *   A new group content reference field definition object.
+   */
+  public static function createNumericalReference() {
+    return static::create('entity_reference')->setName('entity_id');
+  }
+
+  /**
+   * Creates a new group content reference field definition for string IDs.
+   *
+   * @return static
+   *   A new group content reference field definition object.
+   */
+  public static function createStringReference() {
+    return static::create('entity_reference')
+      ->setName('entity_id_str')
+      // This can be replaced by the right entity type when defining the group
+      // content bundle fields, but needs to be set to a string ID entity type
+      // for \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem to
+      // do the hard work for us.
+      ->setSetting('target_type', 'menu');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isBaseField() {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSchema() {
+    if (!isset($this->schema)) {
+      parent::getSchema();
+
+      // Entity reference items usually determine the schema column length by
+      // the target entity type. If it's a bundle entity type, the length is far
+      // shorter (32) than for other entity types (255). We can't know for sure
+      // what the target entity type will be, so we forcibly set it to 255.
+      //
+      // This is essentially also achieved by setting the target_type to menu in
+      // ::createStringReference(), but we can't rely on the fact that the menu
+      // entity type does not act as a bundle as that behavior might change in
+      // future Drupal releases.
+      if ($this->schema['columns']['target_id']['type'] == 'varchar_ascii') {
+        $this->schema['columns']['target_id']['length'] = 255;
+      }
+    }
+
+    return $this->schema;
+  }
+
+}
diff --git a/src/Plugin/Validation/Constraint/GroupContentCardinalityValidator.php b/src/Plugin/Validation/Constraint/GroupContentCardinalityValidator.php
index 0994caf..e0c4a62 100644
--- a/src/Plugin/Validation/Constraint/GroupContentCardinalityValidator.php
+++ b/src/Plugin/Validation/Constraint/GroupContentCardinalityValidator.php
@@ -4,6 +4,8 @@ namespace Drupal\group\Plugin\Validation\Constraint;
 
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\group\Entity\GroupContent;
+use Drupal\group\Plugin\GroupContentEnablerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
@@ -78,13 +80,17 @@ class GroupContentCardinalityValidator extends ConstraintValidator implements Co
       return;
     }
 
-    // Get the entity_id field label for error messages.
-    $field_name = $group_content->getFieldDefinition('entity_id')->getLabel();
+    // Get the content reference field name. May be either entity_id or
+    // entity_id_str depending on the referenced content type.
+    $ref_field = $this->getContentReferenceField($plugin);
+
+    // Get the content reference field label for error messages.
+    $field_name = $group_content->getFieldDefinition($ref_field)->getLabel();
 
     // Enforce the group cardinality if it's not set to unlimited.
     if ($group_cardinality > 0) {
       // Get the group content entities for this piece of content.
-      $properties = ['type' => $plugin->getContentTypeConfigId(), 'entity_id' => $entity->id()];
+      $properties = ['type' => $plugin->getContentTypeConfigId(), $ref_field => $entity->id()];
       $group_instances = $this->entityTypeManager
         ->getStorage('group_content')
         ->loadByProperties($properties);
@@ -108,7 +114,7 @@ class GroupContentCardinalityValidator extends ConstraintValidator implements Co
           // We manually flag the entity reference field as the source of the
           // violation so form API will add a visual indicator of where the
           // validation failed.
-          ->atPath('entity_id.0')
+          ->atPath($ref_field . '.0')
           ->addViolation();
       }
     }
@@ -139,10 +145,24 @@ class GroupContentCardinalityValidator extends ConstraintValidator implements Co
           // We manually flag the entity reference field as the source of the
           // violation so form API will add a visual indicator of where the
           // validation failed.
-          ->atPath('entity_id.0')
+          ->atPath($ref_field . '.0')
           ->addViolation();
       }
     }
   }
 
+  /**
+   * Returns the name of the group content entity reference field.
+   *
+   * @param \Drupal\group\Plugin\GroupContentEnablerInterface $plugin
+   *   Group content enable plugin.
+   *
+   * @return string
+   *   The name of the content reference field.
+   */
+  protected function getContentReferenceField(GroupContentEnablerInterface $plugin) {
+    $entity_type_id = $plugin->getPluginDefinition()['entity_type_id'];
+    return GroupContent::getEntityFieldNameForEntityType($entity_type_id);
+  }
+
 }
diff --git a/src/Plugin/views/relationship/GroupContentToEntity.php b/src/Plugin/views/relationship/GroupContentToEntity.php
index 1cfeec9..2ab8c51 100644
--- a/src/Plugin/views/relationship/GroupContentToEntity.php
+++ b/src/Plugin/views/relationship/GroupContentToEntity.php
@@ -2,13 +2,6 @@
 
 namespace Drupal\group\Plugin\views\relationship;
 
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\group\Entity\GroupContentType;
-use Drupal\group\Plugin\GroupContentEnablerManagerInterface;
-use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
-use Drupal\views\Plugin\ViewsHandlerManager;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
 /**
  * A relationship handler for group content entity references.
  *
@@ -31,8 +24,47 @@ class GroupContentToEntity extends GroupContentToEntityBase {
   /**
    * {@inheritdoc}
    */
-  protected function getJoinFieldType() {
-    return 'left_field';
+  public function query() {
+    $this->ensureMyTable();
+
+    // Build the join definition.
+    $def = $this->definition;
+    $def['table'] = $this->definition['base'];
+    $def['field'] = $this->definition['base field'];
+    $def['left_table'] = $this->tableAlias;
+    $def['left_field'] = $this->realField;
+    $def['adjusted'] = TRUE;
+
+    // Change the join to INNER if the relationship is required.
+    if (!empty($this->options['required'])) {
+      $def['type'] = 'INNER';
+    }
+
+    // If there were extra join conditions added in the definition, use them.
+    if (!empty($this->definition['extra'])) {
+      $def['extra'] = $this->definition['extra'];
+    }
+
+    // Then add our own join condition, namely the group content type IDs.
+    $def['extra'][] = [
+      'left_field' => 'bundle',
+      'value' => $this->getGroupContentTypesValue(),
+    ];
+
+    // Use the standard join plugin unless instructed otherwise.
+    $join_id = !empty($def['join_id']) ? $def['join_id'] : 'standard';
+    $join = $this->joinManager->createInstance($join_id, $def);
+
+    // Add the join using a more verbose alias.
+    $alias = $def['table'] . '_' . $this->table;
+    $this->alias = $this->query->addRelationship($alias, $join, $this->definition['base'], $this->relationship);
+
+    // Add access tags if the base table provides it.
+    $table_data = $this->viewsData->get($def['table']);
+    if (empty($this->query->options['disable_sql_rewrite']) && isset($table_data['table']['base']['access query tag'])) {
+      $access_tag = $table_data['table']['base']['access query tag'];
+      $this->query->addTag($access_tag);
+    }
   }
 
 }
diff --git a/src/Plugin/views/relationship/GroupContentToEntityBase.php b/src/Plugin/views/relationship/GroupContentToEntityBase.php
index 63dea76..2cd1e16 100644
--- a/src/Plugin/views/relationship/GroupContentToEntityBase.php
+++ b/src/Plugin/views/relationship/GroupContentToEntityBase.php
@@ -31,6 +31,12 @@ abstract class GroupContentToEntityBase extends RelationshipPluginBase {
   /**
    * Constructs an GroupContentToEntityBase object.
    *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
    * @param \Drupal\views\Plugin\ViewsHandlerManager $join_manager
    *   The views plugin join manager.
    * @param \Drupal\group\Plugin\GroupContentEnablerManagerInterface $plugin_manager
@@ -64,17 +70,7 @@ abstract class GroupContentToEntityBase extends RelationshipPluginBase {
    * @return string
    *   The target entity type ID.
    */
-  protected abstract function getTargetEntityType();
-
-  /**
-   * Retrieves type of join field to use.
-   *
-   * Can be either 'field' or 'left_field'.
-   *
-   * @return string
-   *   The type of join field to use.
-   */
-  protected abstract function getJoinFieldType();
+  abstract protected function getTargetEntityType();
 
   /**
    * {@inheritdoc}
@@ -110,61 +106,6 @@ abstract class GroupContentToEntityBase extends RelationshipPluginBase {
     ];
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function query() {
-    $this->ensureMyTable();
-
-    // Build the join definition.
-    $def = $this->definition;
-    $def['table'] = $this->definition['base'];
-    $def['field'] = $this->definition['base field'];
-    $def['left_table'] = $this->tableAlias;
-    $def['left_field'] = $this->realField;
-    $def['adjusted'] = TRUE;
-
-    // Change the join to INNER if the relationship is required.
-    if (!empty($this->options['required'])) {
-      $def['type'] = 'INNER';
-    }
-
-    // If there were extra join conditions added in the definition, use them.
-    if (!empty($this->definition['extra'])) {
-      $def['extra'] = $this->definition['extra'];
-    }
-
-    // We can't run an IN-query on an empty array. So if there are no group
-    // content types yet, we need to make sure the JOIN does not return any GCT
-    // that does not serve the entity type that was configured for this handler
-    // instance.
-    $group_content_type_ids = $this->getGroupContentTypeIds();
-    if (empty($group_content_type_ids)) {
-      $group_content_type_ids = ['***'];
-    }
-
-    // Then add our own join condition, namely the group content type IDs.
-    $def['extra'][] = [
-      $this->getJoinFieldType() => 'type',
-      'value' => $group_content_type_ids,
-    ];
-
-    // Use the standard join plugin unless instructed otherwise.
-    $join_id = !empty($def['join_id']) ? $def['join_id'] : 'standard';
-    $join = $this->joinManager->createInstance($join_id, $def);
-
-    // Add the join using a more verbose alias.
-    $alias = $def['table'] . '_' . $this->table;
-    $this->alias = $this->query->addRelationship($alias, $join, $this->definition['base'], $this->relationship);
-
-    // Add access tags if the base table provides it.
-    $table_data = $this->viewsData->get($def['table']);
-    if (empty($this->query->options['disable_sql_rewrite']) && isset($table_data['table']['base']['access query tag'])) {
-      $access_tag = $table_data['table']['base']['access query tag'];
-      $this->query->addTag($access_tag);
-    }
-  }
-
   /**
    * Returns the group content types this relationship should filter on.
    *
@@ -193,4 +134,23 @@ abstract class GroupContentToEntityBase extends RelationshipPluginBase {
     return $plugin_ids ? $group_content_type_ids : array_keys(GroupContentType::loadByEntityTypeId($this->getTargetEntityType()));
   }
 
+  /**
+   * Returns the list of group content types for a query.
+   *
+   * We can't run an IN-query on an empty array. So if there are no group
+   * content types yet, we need to make sure the JOIN does not return any GCT
+   * that does not serve the entity type that was configured for this handler
+   * instance.
+   *
+   * @return array
+   *   The list of group content types to be used as extra JOIN condition.
+   */
+  protected function getGroupContentTypesValue() {
+    $group_content_type_ids = $this->getGroupContentTypeIds();
+    if (empty($group_content_type_ids)) {
+      $group_content_type_ids = ['***'];
+    }
+    return $group_content_type_ids;
+  }
+
 }
diff --git a/src/Plugin/views/relationship/GroupContentToEntityReverse.php b/src/Plugin/views/relationship/GroupContentToEntityReverse.php
index dfad5b9..31d5f44 100644
--- a/src/Plugin/views/relationship/GroupContentToEntityReverse.php
+++ b/src/Plugin/views/relationship/GroupContentToEntityReverse.php
@@ -2,16 +2,15 @@
 
 namespace Drupal\group\Plugin\views\relationship;
 
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\group\Entity\GroupContentType;
-use Drupal\group\Plugin\GroupContentEnablerManagerInterface;
-use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
-use Drupal\views\Plugin\ViewsHandlerManager;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\views\Views;
 
 /**
  * A relationship handler which reverses group content entity references.
  *
+ * This handler is mostly a copy-paste of core "entity_reverse" relationship
+ * handler. We are not extending the EntityReverse class because we'd have to
+ * replace the query() method with a 90% copy of the method so it's worthless.
+ *
  * @ingroup views_relationship_handlers
  *
  * @ViewsRelationship("group_content_to_entity_reverse")
@@ -28,8 +27,73 @@ class GroupContentToEntityReverse extends GroupContentToEntityBase {
   /**
    * {@inheritdoc}
    */
-  protected function getJoinFieldType() {
-    return 'field';
+  public function query() {
+    $this->ensureMyTable();
+    // First, relate our base table to the current base table to the
+    // field, using the base table's id field to the field's column.
+    $views_data = Views::viewsData()->get($this->table);
+    $left_field = $views_data['table']['base']['field'];
+
+    $first = [
+      'left_table' => $this->tableAlias,
+      'left_field' => $left_field,
+      'table' => $this->definition['field table'],
+      'field' => $this->definition['field field'],
+      'adjusted' => TRUE,
+    ];
+    if (!empty($this->options['required'])) {
+      $first['type'] = 'INNER';
+    }
+
+    if (!empty($this->definition['join_extra'])) {
+      $first['extra'] = $this->definition['join_extra'];
+    }
+
+    // Add our own join condition, namely the group content type IDs.
+    // This is the only thing which differs this handler from core
+    // "entity_reverse".
+    $first['extra'][] = [
+      'field' => 'bundle',
+      'value' => $this->getGroupContentTypesValue(),
+    ];
+
+    if (!empty($def['join_id'])) {
+      $id = $def['join_id'];
+    }
+    else {
+      $id = 'standard';
+    }
+    $first_join = $this->joinManager->createInstance($id, $first);
+
+    $this->first_alias = $this->query->addTable($this->definition['field table'], $this->relationship, $first_join);
+
+    // Second, relate the field table to the entity specified using
+    // the entity id on the field table and the entity's id field.
+    $second = [
+      'left_table' => $this->first_alias,
+      'left_field' => 'entity_id',
+      'table' => $this->definition['base'],
+      'field' => $this->definition['base field'],
+      'adjusted' => TRUE,
+    ];
+
+    if (!empty($this->options['required'])) {
+      $second['type'] = 'INNER';
+    }
+
+    if (!empty($def['join_id'])) {
+      $id = $def['join_id'];
+    }
+    else {
+      $id = 'standard';
+    }
+    $second_join = $this->joinManager->createInstance($id, $second);
+    $second_join->adjusted = TRUE;
+
+    // Use a short alias.
+    $alias = $this->definition['field_name'] . '_' . $this->table;
+
+    $this->alias = $this->query->addRelationship($alias, $second_join, $this->definition['base'], $this->relationship);
   }
 
 }
diff --git a/src/QueryAccess/EntityQueryAlter.php b/src/QueryAccess/EntityQueryAlter.php
index a0c060b..c9763d0 100644
--- a/src/QueryAccess/EntityQueryAlter.php
+++ b/src/QueryAccess/EntityQueryAlter.php
@@ -228,10 +228,17 @@ class EntityQueryAlter implements ContainerInjectionInterface {
         break;
       }
     }
+    $query->leftJoin(
+      'group_content__entity_id',
+      'gcfdss',
+      "$base_table.$id_key=gcfdss.entity_id_target_id AND gcfdss.bundle IN (:group_content_type_ids_in_use[])",
+      [':group_content_type_ids_in_use[]' => $group_content_type_ids_in_use]
+    );
+
     $query->leftJoin(
       'group_content_field_data',
       'gcfd',
-      "$base_table.$id_key=gcfd.entity_id AND gcfd.type IN (:group_content_type_ids_in_use[])",
+      "gcfdss.entity_id=gcfd.id AND gcfd.type IN (:group_content_type_ids_in_use[])",
       [':group_content_type_ids_in_use[]' => $group_content_type_ids_in_use]
     );
 
@@ -331,7 +338,7 @@ class EntityQueryAlter implements ContainerInjectionInterface {
 
     // If no group type or group gave access, we deny access altogether.
     if (empty($allowed_any_ids) && empty($allowed_own_ids) && empty($allowed_any_by_status_ids) && empty($allowed_own_by_status_ids)) {
-      $query->isNull('gcfd.entity_id');
+      $query->isNull('gcfd.id');
       return;
     }
 
@@ -340,7 +347,7 @@ class EntityQueryAlter implements ContainerInjectionInterface {
     // access checks apply.
     $query->condition(
       $query->orConditionGroup()
-        ->isNull('gcfd.entity_id')
+        ->isNull('gcfd.id')
         ->condition($group_conditions = $query->orConditionGroup())
     );
 
diff --git a/tests/modules/group_test_content/config/schema/group_test_content.schema.yml b/tests/modules/group_test_content/config/schema/group_test_content.schema.yml
new file mode 100644
index 0000000..72b1557
--- /dev/null
+++ b/tests/modules/group_test_content/config/schema/group_test_content.schema.yml
@@ -0,0 +1,9 @@
+# Schema for the configuration files of the Configuration Test module.
+
+group_test_content.group_test_config_entity_string.*:
+  type: config_entity
+  label: 'Config test dynamic settings'
+  mapping:
+    id:
+      type: string
+      label: 'ID'
diff --git a/tests/modules/group_test_content/group_test_content.info.yml b/tests/modules/group_test_content/group_test_content.info.yml
new file mode 100644
index 0000000..eac3bad
--- /dev/null
+++ b/tests/modules/group_test_content/group_test_content.info.yml
@@ -0,0 +1,8 @@
+name: 'Group test content'
+description: 'Provides entity types and plugins for Group content tests.'
+package: 'Testing'
+type: 'module'
+version: VERSION
+core_version_requirement: ^8.8 || ^9
+dependencies:
+  - 'group'
diff --git a/tests/modules/group_test_content/src/Entity/IntegerContent.php b/tests/modules/group_test_content/src/Entity/IntegerContent.php
new file mode 100644
index 0000000..79a178d
--- /dev/null
+++ b/tests/modules/group_test_content/src/Entity/IntegerContent.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\group_test_content\Entity;
+
+use Drupal\Core\Entity\ContentEntityBase;
+
+/**
+ * Integer identified content entity type for GroupContent tests.
+ *
+ * @ContentEntityType(
+ *   id = "group_test_content_entity_int",
+ *   label = @Translation("Integer identified test content entity type"),
+ *   base_table = "group_test_content_entity_int",
+ *   fieldable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class IntegerContent extends ContentEntityBase {
+
+}
diff --git a/tests/modules/group_test_content/src/Entity/StringConfig.php b/tests/modules/group_test_content/src/Entity/StringConfig.php
new file mode 100644
index 0000000..2fb5060
--- /dev/null
+++ b/tests/modules/group_test_content/src/Entity/StringConfig.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\group_test_content\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+
+/**
+ * String identified config entity type for GroupContent tests.
+ *
+ * @ConfigEntityType(
+ *   id = "group_test_config_entity_string",
+ *   label = @Translation("String identified test config entity type"),
+ *   entity_keys = {
+ *     "id" = "id",
+ *   },
+ *   config_export = {
+ *     "id"
+ *   }
+ * )
+ */
+class StringConfig extends ConfigEntityBase {
+
+  /**
+   * The test ID.
+   *
+   * @var string
+   */
+  public $id;
+
+}
diff --git a/tests/modules/group_test_content/src/Entity/StringContent.php b/tests/modules/group_test_content/src/Entity/StringContent.php
new file mode 100644
index 0000000..8c4329f
--- /dev/null
+++ b/tests/modules/group_test_content/src/Entity/StringContent.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\group_test_content\Entity;
+
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * String identified content entity type for GroupContent tests.
+ *
+ * @ContentEntityType(
+ *   id = "group_test_content_entity_string",
+ *   label = @Translation("String identified test content entity type"),
+ *   base_table = "group_test_content_entity_string",
+ *   fieldable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class StringContent extends ContentEntityBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+
+    $fields[$entity_type->getKey('id')] = BaseFieldDefinition::create('string')
+      ->setLabel(new TranslatableMarkup('String ID'))
+      ->setSetting('is_ascii', TRUE);
+
+    return $fields;
+  }
+
+}
diff --git a/tests/modules/group_test_content/src/Plugin/GroupContentEnabler/IntegerContentEntityAsContent.php b/tests/modules/group_test_content/src/Plugin/GroupContentEnabler/IntegerContentEntityAsContent.php
new file mode 100644
index 0000000..578dafb
--- /dev/null
+++ b/tests/modules/group_test_content/src/Plugin/GroupContentEnabler/IntegerContentEntityAsContent.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\group_test_content\Plugin\GroupContentEnabler;
+
+use Drupal\group\Plugin\GroupContentEnablerBase;
+
+/**
+ * Provides a content enabler for groups.
+ *
+ * @GroupContentEnabler(
+ *   id = "integer_content_entity_as_content",
+ *   label = @Translation("IntegerContent"),
+ *   description = @Translation("Adds test integer identified content entities to groups."),
+ *   entity_type_id = "group_test_content_entity_int",
+ *   pretty_path_key = "content_entity_int",
+ *   reference_label = @Translation("NA"),
+ *   reference_description = @Translation("NA")
+ * )
+ */
+class IntegerContentEntityAsContent extends GroupContentEnablerBase {
+}
diff --git a/tests/modules/group_test_content/src/Plugin/GroupContentEnabler/StringConfigEntityAsContent.php b/tests/modules/group_test_content/src/Plugin/GroupContentEnabler/StringConfigEntityAsContent.php
new file mode 100644
index 0000000..99c3853
--- /dev/null
+++ b/tests/modules/group_test_content/src/Plugin/GroupContentEnabler/StringConfigEntityAsContent.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\group_test_content\Plugin\GroupContentEnabler;
+
+use Drupal\group\Plugin\GroupContentEnablerBase;
+
+/**
+ * Provides a content enabler for groups.
+ *
+ * @GroupContentEnabler(
+ *   id = "string_config_entity_as_content",
+ *   label = @Translation("StringConfig"),
+ *   description = @Translation("Adds test string identified content entities to groups."),
+ *   entity_type_id = "group_test_config_entity_string",
+ *   pretty_path_key = "config_entity_str",
+ *   reference_label = @Translation("NA"),
+ *   reference_description = @Translation("NA")
+ * )
+ */
+class StringConfigEntityAsContent extends GroupContentEnablerBase {
+}
diff --git a/tests/modules/group_test_content/src/Plugin/GroupContentEnabler/StringContentEntityAsContent.php b/tests/modules/group_test_content/src/Plugin/GroupContentEnabler/StringContentEntityAsContent.php
new file mode 100644
index 0000000..0ef031f
--- /dev/null
+++ b/tests/modules/group_test_content/src/Plugin/GroupContentEnabler/StringContentEntityAsContent.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\group_test_content\Plugin\GroupContentEnabler;
+
+use Drupal\group\Plugin\GroupContentEnablerBase;
+
+/**
+ * Provides a content enabler for groups.
+ *
+ * @GroupContentEnabler(
+ *   id = "string_content_entity_as_content",
+ *   label = @Translation("StringContent"),
+ *   description = @Translation("Adds test string identified config entities to groups."),
+ *   entity_type_id = "group_test_content_entity_string",
+ *   pretty_path_key = "content_entity_str",
+ *   reference_label = @Translation("NA"),
+ *   reference_description = @Translation("NA")
+ * )
+ */
+class StringContentEntityAsContent extends GroupContentEnablerBase {
+}
diff --git a/tests/modules/group_test_views/test_views/views.view.test_group_content_to_entity_relationship.yml b/tests/modules/group_test_views/test_views/views.view.test_group_content_to_entity_relationship.yml
index 954c68b..41c3ebb 100644
--- a/tests/modules/group_test_views/test_views/views.view.test_group_content_to_entity_relationship.yml
+++ b/tests/modules/group_test_views/test_views/views.view.test_group_content_to_entity_relationship.yml
@@ -202,7 +202,7 @@ display:
       relationships:
         gc__user:
           id: gc__user
-          table: group_content_field_data
+          table: group_content__entity_id
           field: gc__user
           relationship: none
           group_type: group
diff --git a/tests/src/Kernel/GroupContentStorageTest.php b/tests/src/Kernel/GroupContentStorageTest.php
index 20a5c9f..c94dc25 100644
--- a/tests/src/Kernel/GroupContentStorageTest.php
+++ b/tests/src/Kernel/GroupContentStorageTest.php
@@ -2,7 +2,9 @@
 
 namespace Drupal\Tests\group\Kernel;
 
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
+use Drupal\group\Entity\GroupContentInterface;
 
 /**
  * Tests the behavior of group content storage handler.
@@ -15,7 +17,7 @@ class GroupContentStorageTest extends GroupKernelTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['group_test_plugin'];
+  protected static $modules = ['group_test_plugin', 'group_test_content'];
 
   /**
    * The group content storage handler.
@@ -38,6 +40,15 @@ class GroupContentStorageTest extends GroupKernelTestBase {
     $storage = $this->entityTypeManager->getStorage('group_content_type');
     $storage->createFromPlugin($group_type, 'user_as_content')->save();
     $storage->createFromPlugin($group_type, 'group_as_content')->save();
+
+    $this->installConfig(['group_test_content']);
+    $storage->createFromPlugin($group_type, 'integer_content_entity_as_content')->save();
+    $storage->createFromPlugin($group_type, 'string_config_entity_as_content')->save();
+    $storage->createFromPlugin($group_type, 'string_content_entity_as_content')->save();
+
+    $this->installEntitySchema('group_test_content_entity_int');
+    $this->installEntitySchema('group_test_config_entity_string');
+    $this->installEntitySchema('group_test_content_entity_string');
   }
 
   /**
@@ -209,4 +220,110 @@ class GroupContentStorageTest extends GroupKernelTestBase {
     $this->assertCount(1, $this->storage->loadByContentPluginId('group_membership'), 'Managed to load the group creator membership by plugin ID.');
   }
 
+  /**
+   * Tests the loading of GroupContent entities for a group.
+   *
+   * @dataProvider testEntityTypesDataProvider
+   * @covers ::loadByGroup
+   */
+  public function testLoadContentByGroup($entity_type, $plugin_id) {
+    $group = $this->createGroup();
+    $entity = $this->createTestEntity($entity_type);
+    $this->storage->createForEntityInGroup($entity, $group, $plugin_id)->save();
+    $loaded_entities = $this->storage->loadByGroup($group);
+    $this->assertCount(2, $loaded_entities, 'Managed to load the group contents by group.');
+    $loaded_users_contents = array_filter($loaded_entities, function (GroupContentInterface $group_content) {
+      return $group_content->getEntity()->getEntityTypeId() == 'user';
+    });
+    $loaded_group_contents = array_filter($loaded_entities, function (GroupContentInterface $group_content) use ($entity_type) {
+      return $group_content->getEntity()->getEntityTypeId() == $entity_type;
+    });
+    $this->assertCount(1, $loaded_users_contents);
+    $this->assertCount(1, $loaded_group_contents);
+
+    $group_content = reset($loaded_group_contents);
+    $this->assertSameEntity($entity, $group_content->getEntity());
+  }
+
+  /**
+   * Tests the loading of GroupContent entities for an entity.
+   *
+   * @dataProvider testEntityTypesDataProvider
+   * @covers ::loadByEntity
+   */
+  public function testLoadContentByEntity($entity_type, $plugin_id) {
+    $group = $this->createGroup();
+    $entity = $this->createTestEntity($entity_type);
+    $this->storage->createForEntityInGroup($entity, $group, $plugin_id)->save();
+    $loaded_entities = $this->storage->loadByEntity($entity);
+    $this->assertCount(1, $loaded_entities, 'Managed to load the group content by entity.');
+    $group_content = reset($loaded_entities);
+    $this->assertSameEntity($entity, $group_content->getEntity());
+  }
+
+  /**
+   * Tests the loading of GroupContent entities for an entity.
+   *
+   * @dataProvider testEntityTypesDataProvider
+   * @covers ::loadByContentPluginId
+   */
+  public function testLoadContentByContentPluginId($entity_type, $plugin_id) {
+    $group = $this->createGroup();
+    $entity = $this->createTestEntity($entity_type);
+    $this->storage->createForEntityInGroup($entity, $group, $plugin_id)->save();
+    $loaded_entities = $this->storage->loadByContentPluginId($plugin_id);
+    $this->assertCount(1, $loaded_entities, 'Managed to load the group content by plugin ID.');
+    $group_content = reset($loaded_entities);
+    $this->assertSameEntity($entity, $group_content->getEntity());
+  }
+
+  /**
+   * Data provider returning test entity types and corresponding plugins.
+   */
+  public function testEntityTypesDataProvider() {
+    return [
+      ['group_test_content_entity_int', 'integer_content_entity_as_content'],
+      ['group_test_content_entity_string', 'string_content_entity_as_content'],
+      ['group_test_config_entity_string', 'string_config_entity_as_content'],
+    ];
+  }
+
+  /**
+   * Creates test entity to be used as a test group content.
+   *
+   * @param string $entity_type
+   *   Entity type.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The new saved entity of a given type.
+   */
+  protected function createTestEntity($entity_type) {
+    $storage = $this->entityTypeManager->getStorage($entity_type);
+    $values = [];
+
+    switch ($entity_type) {
+      case 'group_test_content_entity_string':
+      case 'group_test_config_entity_string':
+        $values['id'] = $this->randomMachineName();
+        break;
+    }
+
+    $entity = $storage->create($values);
+    $entity->save();
+    return $entity;
+  }
+
+  /**
+   * Asserts two entity objects are same.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $expected
+   *   The entity which was expected.
+   * @param \Drupal\Core\Entity\EntityInterface $actual
+   *   The entity which was retrieved during the test.
+   */
+  protected function assertSameEntity(EntityInterface $expected, EntityInterface $actual) {
+    $this->assertEquals($expected->getEntityTypeId(), $actual->getEntityTypeId());
+    $this->assertEquals($expected->id(), $actual->id());
+  }
+
 }
diff --git a/tests/src/Kernel/GroupContentTest.php b/tests/src/Kernel/GroupContentTest.php
index cae780f..7bdac5b 100644
--- a/tests/src/Kernel/GroupContentTest.php
+++ b/tests/src/Kernel/GroupContentTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\group\Kernel;
 
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\group\Entity\GroupContent;
 
 /**
  * Tests for the GroupContent entity.
@@ -16,7 +17,18 @@ class GroupContentTest extends GroupKernelTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['group_test', 'group_test_plugin'];
+  protected static $modules = ['group_test', 'group_test_plugin', 'group_test_content'];
+
+  /**
+   * Tests getting entity reference field name.
+   *
+   * @covers ::getEntityFieldNameForEntityType
+   */
+  public function testGetGroupContentTypeField() {
+    $this->assertEquals('entity_id', GroupContent::getEntityFieldNameForEntityType('group_test_content_entity_int'));
+    $this->assertEquals('entity_id_str', GroupContent::getEntityFieldNameForEntityType('group_test_content_entity_string'));
+    $this->assertEquals('entity_id_str', GroupContent::getEntityFieldNameForEntityType('group_test_config_entity_string'));
+  }
 
   /**
    * Tests that entity url templates are functional.
