diff --git a/config/install/groupmenu.settings.yml b/config/install/groupmenu.settings.yml
index e69de29..7271838 100644
--- a/config/install/groupmenu.settings.yml
+++ b/config/install/groupmenu.settings.yml
@@ -0,0 +1,2 @@
+groupmenu_hide_list: TRUE
+config_sync_groupmenu_ignore: TRUE
diff --git a/config/optional/views.view.group_menus.yml b/config/optional/views.view.group_menus.yml
index e69de29..1dc927c 100644
--- a/config/optional/views.view.group_menus.yml
+++ b/config/optional/views.view.group_menus.yml
@@ -0,0 +1,262 @@
+uuid: dd568af2-b9ba-4a57-accc-21a45fc2d98e
+langcode: nl
+status: true
+dependencies:
+  module:
+    - groupmenu
+    - user
+id: group_menus
+label: 'Group menus'
+module: views
+description: ''
+tag: ''
+base_table: groupmenu_field_data
+base_field: id
+core: 8.x
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: 0
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'administer groupmenu form display'
+      cache:
+        type: tag
+        options: {  }
+      query:
+        type: views_query
+        options:
+          disable_sql_rewrite: false
+          distinct: false
+          replica: false
+          query_comment: ''
+          query_tags: {  }
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: false
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: true
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: none
+        options:
+          offset: 0
+      style:
+        type: table
+      row:
+        type: fields
+      fields:
+        label:
+          id: label
+          table: groupmenu_field_data
+          field: label
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Title
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: string
+          settings:
+            link_to_entity: true
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: null
+          entity_field: label
+          plugin_id: field
+        operations:
+          id: operations
+          table: groupmenu
+          field: operations
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Bewerkingslinks
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          destination: true
+          entity_type: groupmenu
+          plugin_id: entity_operations
+      filters: {  }
+      sorts: {  }
+      title: 'Group menus'
+      header: {  }
+      footer: {  }
+      empty:
+        area_text_custom:
+          id: area_text_custom
+          table: views
+          field: area_text_custom
+          relationship: none
+          group_type: group
+          admin_label: ''
+          empty: true
+          tokenize: false
+          content: 'No menus are added yet.'
+          plugin_id: text_custom
+      relationships: {  }
+      arguments:
+        gid:
+          id: gid
+          table: groupmenu_field_data
+          field: gid
+          relationship: none
+          group_type: group
+          admin_label: ''
+          default_action: empty
+          exception:
+            value: all
+            title_enable: false
+            title: All
+          title_enable: true
+          title: '{{ arguments.gid|placeholder }} menus'
+          default_argument_type: fixed
+          default_argument_options:
+            argument: ''
+          default_argument_skip_url: false
+          summary_options:
+            base_path: ''
+            count: true
+            items_per_page: 25
+            override: false
+          summary:
+            sort_order: asc
+            number_of_records: 0
+            format: default_summary
+          specify_validation: false
+          validate:
+            type: none
+            fail: 'not found'
+          validate_options: {  }
+          break_phrase: false
+          not: false
+          entity_type: groupmenu
+          entity_field: gid
+          plugin_id: numeric
+      display_extenders: {  }
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - user.permissions
+      tags: {  }
+  page_1:
+    display_plugin: page
+    id: page_1
+    display_title: Page
+    position: 1
+    display_options:
+      display_extenders: {  }
+      path: group/%group/menus
+      menu:
+        type: tab
+        title: Menus
+        description: ''
+        expanded: false
+        parent: ''
+        weight: 0
+        context: '0'
+        menu_name: main
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - user.permissions
+      tags: {  }
diff --git a/groupmenu.group.permissions.yml b/groupmenu.group.permissions.yml
index e69de29..f380cf5 100644
--- a/groupmenu.group.permissions.yml
+++ b/groupmenu.group.permissions.yml
@@ -0,0 +1,3 @@
+access groupmenu overview:
+  title: 'Access group menu overview'
+  description: 'Access the overview of all group menus'
diff --git a/groupmenu.info.yml b/groupmenu.info.yml
index e69de29..711a46c 100644
--- a/groupmenu.info.yml
+++ b/groupmenu.info.yml
@@ -0,0 +1,9 @@
+name: Group Menu
+type: module
+description: Gives the ability to create and manage menus for groups
+package: Group
+core: 8.x
+dependencies:
+ - group
+ - menu_ui
+ - menu_entity_index
\ No newline at end of file
diff --git a/groupmenu.install b/groupmenu.install
index e69de29..3087dad 100644
--- a/groupmenu.install
+++ b/groupmenu.install
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * Add configuration option to exclude group menus from configuration sync.
+ */
+function groupmenu_update_8001() {
+  $config_factory = \Drupal::configFactory();
+  $config = $config_factory->getEditable('groupmenu.settings');
+  $config->set('config_sync_groupmenu_ignore', TRUE);
+  $config->save(TRUE);
+}
+
+/**
+ * Add configuration to control hiding group menus from default menu list.
+ */
+function groupmenu_update_8002() {
+  $config_factory = \Drupal::configFactory();
+  $config = $config_factory->getEditable('groupmenu.settings');
+  $config->set('groupmenu_hide_list', TRUE);
+  $config->save(TRUE);
+}
diff --git a/groupmenu.links.action.yml b/groupmenu.links.action.yml
index e69de29..699e405 100644
--- a/groupmenu.links.action.yml
+++ b/groupmenu.links.action.yml
@@ -0,0 +1,18 @@
+groupmenu.groupmenu_relate_menu:
+  route_name: 'entity.groupmenu.groupmenu_relate_menu'
+  title: 'Relate menu'
+  appears_on:
+    - 'view.group_menus.page_1'
+
+groupmenu.groupmenu_add_menu:
+  route_name: 'entity.groupmenu.groupmenu_add_menu'
+  title: 'Create menu'
+  appears_on:
+    - 'view.group_menus.page_1'
+
+entity.groupmenu.add_link_form:
+  route_name: entity.groupmenu.add_link_form
+  title: 'Add link'
+  class: \Drupal\menu_ui\Plugin\Menu\LocalAction\MenuLinkAdd
+  appears_on:
+    - 'entity.groupmenu.groupmenu_edit_form'
diff --git a/groupmenu.links.menu.yml b/groupmenu.links.menu.yml
index e69de29..1f08d57 100644
--- a/groupmenu.links.menu.yml
+++ b/groupmenu.links.menu.yml
@@ -0,0 +1,6 @@
+groupmenu.settings:
+  title: 'Group menu settings'
+  description: 'Configure the menu module settings.'
+  route_name: 'groupmenu.settings'
+  parent: 'system.admin_group'
+  weight: 15
diff --git a/groupmenu.links.task.yml b/groupmenu.links.task.yml
index e69de29..66909d9 100644
--- a/groupmenu.links.task.yml
+++ b/groupmenu.links.task.yml
@@ -0,0 +1,4 @@
+groupmenu.settings:
+  title: 'Group menu settings'
+  base_route: 'entity.group.collection'
+  route_name: 'groupmenu.settings'
diff --git a/groupmenu.module b/groupmenu.module
index e69de29..c2b9fa9 100644
--- a/groupmenu.module
+++ b/groupmenu.module
@@ -0,0 +1,203 @@
+<?php
+
+/**
+ * @file
+ * Enables menu functionality for the group module.
+ */
+
+use Drupal\group\Entity\GroupContentType;
+use Drupal\system\MenuInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function groupmenu_form_config_export_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  // Add submit handler if proper configuration is set.
+  $config = \Drupal::config('groupmenu.settings');
+  if ($config->get('config_sync_groupmenu_ignore')) {
+    $form['#submit'][] = 'groupmenu_config_export_form_submit';
+  }
+}
+
+/**
+ * Submit callback for config_export_form form.
+ *
+ * @param array $form
+ *   An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *   The current state of the form.
+ * @param string $form_id
+ *   String representing the name of the form itself.
+ */
+function groupmenu_config_export_form_submit($form, FormStateInterface $form_state, $form_id) {
+  $form_state->setRedirect('groupmenu.config.export_download');
+}
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function groupmenu_entity_type_alter(array &$entity_types) {
+  // Use a different list builder to exclude group menus.
+  $config = \Drupal::config('groupmenu.settings');
+  if ($config->get('groupmenu_hide_list')) {
+    $entity_types['menu']->setHandlerClass('storage', 'Drupal\groupmenu\Entity\GroupMenuEntityStorage');
+    $entity_types['menu']->setHandlerClass('list_builder', 'Drupal\groupmenu\GroupMenuListBuilder');
+  }
+
+  /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
+  // Add the menu creation wizard steps as entity forms.
+  $entity_types['menu']->setFormClass('groupmenu-form', 'Drupal\groupmenu\Form\GroupMenuFormStep1');
+  $entity_types['groupmenu']->setFormClass('groupmenu-form', 'Drupal\groupmenu\Form\GroupMenuFormStep2');
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_insert().
+ */
+function groupmenu_menu_insert(MenuInterface $menu) {
+  \Drupal::service('plugin.manager.group_content_enabler')->clearCachedDefinitions();
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function groupmenu_form_menu_link_content_menu_link_content_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  $build_info = $form_state->getBuildInfo();
+  $entity = $build_info['callback_object']->getEntity();
+
+  $form['actions']['delete']['#url'] = Url::fromRoute('entity.groupmenu.menu_link_content.delete_form', ['menu_link_content' => $entity->id()], [
+    'query' => ['destination' => Url::fromRoute('entity.groupmenu.groupmenu_edit_form', ['menu' => $entity->getMenuName()])->toString()],
+  ]);
+  $form['actions']['submit']['#submit'][] = 'groupmenu_menu_link_edit_form_submit';
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function groupmenu_form_menu_link_content_menu_link_content_delete_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  $form['actions']['submit']['#submit'][] = 'groupmenu_menu_link_edit_form_submit';
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function groupmenu_form_menu_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  // Only proceed if we're on group menu route.
+  if (\Drupal::routeMatch()->getRouteName() == 'entity.groupmenu.groupmenu_edit_form') {
+    $build_info = $form_state->getBuildInfo();
+    $entity = $build_info['callback_object']->getEntity();
+
+    // Update the "Add link" link to go to group menu link creation.
+    $form['links']['links']['#empty'] = t('There are no menu links yet. <a href=":url">Add link</a>.', [
+      ':url' => Url::fromRoute('entity.groupmenu.add_link_form', ['menu' => $entity->id()], [
+        'query' => ['destination' => Url::fromRoute('entity.groupmenu.groupmenu_edit_form', ['menu' => $entity->id()])->toString()],
+      ])->toString(),
+    ]);
+
+    // Set redirect to go to non-admin, group-accessible form.
+    $form['actions']['submit']['#submit'][] = 'groupmenu_menu_edit_form_submit';
+
+    // Update edit and delete links appropriately.
+    // @todo I wonder if there is a better way to do this...
+    foreach($form['links']['links'] as &$link) {
+      if (is_array($link) && isset($link['operations']['#links']['edit']) && isset($link['operations']['#links']['delete'])) {
+        $link['operations']['#links']['edit']['url'] = Url::fromRoute('entity.groupmenu.menu_link_content.edit_form', $link['operations']['#links']['edit']['url']->getRouteParameters());
+        $link['operations']['#links']['delete']['url'] = Url::fromRoute('entity.groupmenu.menu_link_content.delete_form', $link['operations']['#links']['delete']['url']->getRouteParameters());
+      }
+    }
+  }
+}
+
+/**
+ * Submit callback to change redirect URL for menu content edit/delete forms.
+ *
+ * @param array $form
+ *   An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *   The current state of the form.
+ * @param string $form_id
+ *   String representing the name of the form itself.
+ */
+function groupmenu_menu_link_edit_form_submit($form, FormStateInterface $form_state, $form_id) {
+  $build_info = $form_state->getBuildInfo();
+  $entity = $build_info['callback_object']->getEntity()->getMenuName();
+
+  $form_state->setRedirectUrl(Url::fromRoute('entity.groupmenu.groupmenu_edit_form', ['menu' => $entity]));
+}
+
+/**
+ * Update edit form redirect Url.
+ */
+function groupmenu_menu_edit_form_submit($form, FormStateInterface $form_state, $form_id) {
+  $build_info = $form_state->getBuildInfo();
+  $entity = $build_info['callback_object']->getEntity();
+
+  $form_state->setRedirectUrl(Url::fromRoute('entity.groupmenu.groupmenu_edit_form', ['menu' => $entity->id()]));
+}
+
+/**
+ * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
+ *
+ * Adds Group Menus to the available menus set by menu_ui.
+ */
+function groupmenu_form_node_form_alter(&$form, FormStateInterface $form_state) {
+  $node = $form_state->getFormObject()->getEntity();
+  $node_type = $node->type->entity;
+  $available_menus = $node_type->getThirdPartySetting('menu_ui', 'available_menus', array('main'));
+
+  // Clear out available menus if user does not have permission so that TRUE
+  // can be set in groupmenu_process_access().
+  if (!\Drupal::currentUser()->hasPermission('administer menu')) {
+    $available_menus = [];
+  }
+
+  $group_content_types = GroupContentType::loadByContentPluginId("groupmenu:menu");
+  if (!empty($group_content_types)) {
+    $group_contents = \Drupal::entityTypeManager()
+      ->getStorage('groupmenu')
+      ->loadByProperties([
+        'type' => array_keys($group_content_types),
+      ]);
+
+    if (!empty($group_contents)) {
+      foreach ($group_contents as $group_content) {
+        /** @var \Drupal\group\Entity\GroupContentInterface $group_content */
+        $group = $group_content->getGroup();
+
+        $account = \Drupal::currentUser();
+        if ($group->hasPermission('edit group menus', $account)) {
+          $entity_id = $group_content->get('entity_id')->getValue()[0]['target_id'];
+          $available_menus[$entity_id] = $entity_id;
+        }
+      }
+    }
+  }
+
+  $node_type->setThirdPartySetting('menu_ui', 'available_menus', array_values(array_filter($available_menus)));
+}
+
+/**
+ * Implements hook_element_info_alter().
+ *
+ * If a user does not have the 'administer menu' permission, the '#access' value
+ * set in menu_ui_form_node_form_alter() needs to be overridden so that a Group
+ * member can edit the Menu Settings on \Drupal\node\NodeForm.
+ */
+function groupmenu_element_info_alter(array &$types) {
+  if (isset($types['details'])) {
+    $types['details']['#process'][] = 'groupmenu_process_access';
+  }
+}
+
+/**
+ * Form element process handler setting menu edit access.
+ *
+ * @see groupmenu_element_info_alter().
+ */
+function groupmenu_process_access($element) {
+  if ($element['#parents'][0] == 'menu') {
+    $element['#access'] = TRUE;
+  }
+  return $element;
+}
diff --git a/groupmenu.routing.yml b/groupmenu.routing.yml
index e69de29..c1c4ca1 100644
--- a/groupmenu.routing.yml
+++ b/groupmenu.routing.yml
@@ -0,0 +1,84 @@
+route_callbacks:
+  - '\Drupal\groupmenu\Routing\GroupMenuRouteProvider::getRoutes'
+
+# General routes for the Group module.
+groupmenu.settings:
+  path: 'admin/group/groupmenu_settings'
+  defaults:
+    _form: '\Drupal\groupmenu\Form\GroupMenuSettingsForm'
+    _title: 'Group menu settings'
+  requirements:
+    _permission: 'administer group'
+
+entity.groupmenu.groupmenu_add_form:
+  path: '/group/{group}/menu/create/new'
+  defaults:
+    _controller: '\Drupal\groupmenu\Controller\GroupMenuWizardController::addForm'
+    _title_callback: '\Drupal\groupmenu\Controller\GroupMenuWizardController::addFormTitle'
+  requirements:
+    _groupmenu_add_access: 'TRUE'
+  options:
+    _group_operation_route: 'TRUE'
+
+entity.groupmenu.groupmenu_edit_form:
+  path: '/group/menu/manage/{menu}'
+  defaults:
+    _entity_form: 'menu.edit'
+    _title_callback: '\Drupal\menu_ui\Controller\MenuController::menuTitle'
+  requirements:
+    _groupmenu_edit_access: 'TRUE'
+
+entity.groupmenu.groupmenu_delete_form:
+  path: '/group/menu/manage/{menu}/delete'
+  defaults:
+    _entity_form: 'menu.delete'
+    _title: 'Delete menu'
+  requirements:
+    _groupmenu_delete_access: 'TRUE'
+
+entity.groupmenu.add_link_form:
+  path: '/group/menu/manage/{menu}/add'
+  defaults:
+    _controller: '\Drupal\menu_link_content\Controller\MenuController::addLink'
+    _title: 'Add menu link'
+  requirements:
+    _groupmenu_edit_access: 'TRUE'
+
+entity.groupmenu.menu_link_content.canonical:
+  path: '/group/menu/item/{menu_link_content}/edit'
+  defaults:
+    _entity_form: 'menu_link_content.default'
+    _title: 'Edit menu link'
+  requirements:
+    _groupmenu_link_edit_access: 'TRUE'
+
+entity.groupmenu.menu_link_content.edit_form:
+  path: '/group/menu/item/{menu_link_content}/edit'
+  defaults:
+    _entity_form: 'menu_link_content.default'
+    _title: 'Edit menu link'
+  requirements:
+    _groupmenu_link_edit_access: 'TRUE'
+
+entity.groupmenu.menu_link_content.delete_form:
+  path: '/group/menu/item/{menu_link_content}/delete'
+  defaults:
+    _entity_form: 'menu_link_content.delete'
+    _title: 'Delete menu link'
+  requirements:
+    _groupmenu_link_edit_access: 'TRUE'
+
+groupmenu.config.export_download:
+  path: '/admin/config/development/configuration/full/group-menu/export-download'
+  defaults:
+    _controller: '\Drupal\groupmenu\Controller\GroupMenuConfigController::downloadExport'
+  requirements:
+    _permission: 'export configuration'
+
+groupmenu.config.sync:
+  path: '/admin/config/development/configuration'
+  defaults:
+    _form: '\Drupal\groupmenu\Form\GroupMenuConfigSync'
+    _title: 'Synchronize'
+  requirements:
+    _permission: 'synchronize configuration'
diff --git a/groupmenu.services.yml b/groupmenu.services.yml
index e69de29..a1a974e 100644
--- a/groupmenu.services.yml
+++ b/groupmenu.services.yml
@@ -0,0 +1,21 @@
+services:
+  route_subscriber:
+    class: Drupal\groupmenu\Routing\GroupMenuRouteSubscriber
+    tags:
+      - { name: event_subscriber }
+  access_check.groupmenu.add:
+    class: Drupal\groupmenu\Access\GroupMenuAddAccessCheck
+    tags:
+      - { name: access_check, applies_to: _groupmenu_add_access }
+  access_check.groupmenu.edit:
+    class: Drupal\groupmenu\Access\GroupMenuEditAccessCheck
+    tags:
+      - { name: access_check, applies_to: _groupmenu_edit_access }
+  access_check.groupmenu.delete:
+    class: Drupal\groupmenu\Access\GroupMenuDeleteAccessCheck
+    tags:
+      - { name: access_check, applies_to: _groupmenu_delete_access }
+  access_check.groupmenu.menu_link.edit:
+    class: Drupal\groupmenu\Access\GroupMenuLinkEditAccessCheck
+    tags:
+      - { name: access_check, applies_to: _groupmenu_link_edit_access }
diff --git a/modules/groupmenu_block/groupmenu_block.info.yml b/modules/groupmenu_block/groupmenu_block.info.yml
index e69de29..568c815 100644
--- a/modules/groupmenu_block/groupmenu_block.info.yml
+++ b/modules/groupmenu_block/groupmenu_block.info.yml
@@ -0,0 +1,8 @@
+name: Group Menu Block
+type: module
+description: Provides a block to display group menus
+package: Group
+core: 8.3.x
+dependencies:
+ - gnode
+ - groupmenu
\ No newline at end of file
diff --git a/modules/groupmenu_block/groupmenu_block.module b/modules/groupmenu_block/groupmenu_block.module
index e69de29..d982f9d 100644
--- a/modules/groupmenu_block/groupmenu_block.module
+++ b/modules/groupmenu_block/groupmenu_block.module
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Adds theme suggestions for group menus
+ */
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter()
+ *
+ * Add suggestions for menus in the groupmenus block
+ */
+function groupmenu_block_theme_suggestions_menu_alter(array &$suggestions, array $variables) {
+  if (isset($variables['attributes']['base_plugin_id']) && $variables['attributes']['base_plugin_id'] == 'groupmenus') {
+    $suggestions[] = 'menu__groupmenu';
+    if (isset($variables['menu_name'])) {
+      $suggestions[] = 'menu__groupmenu__' . strtr($variables['menu_name'], '-', '_');
+    }
+  }
+}
+
+/**
+ * Implements template_preprocess_block()
+ *
+ * Pass block plugin information to menu theme suggestions
+ */
+function groupmenu_block_preprocess_block(&$variables) {
+  if (isset($variables['elements']['#base_plugin_id']) && $variables['elements']['#base_plugin_id'] == 'groupmenus') {
+    $variables['content']['#attributes']['base_plugin_id'] = $variables['elements']['#base_plugin_id'];
+  }
+}
diff --git a/modules/groupmenu_block/src/Plugin/Block/GroupMenuBlock.php b/modules/groupmenu_block/src/Plugin/Block/GroupMenuBlock.php
index e69de29..a307f5b 100644
--- a/modules/groupmenu_block/src/Plugin/Block/GroupMenuBlock.php
+++ b/modules/groupmenu_block/src/Plugin/Block/GroupMenuBlock.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\groupmenu_block\Plugin\Block;
+
+use Drupal\group\Entity\Group;
+use Drupal\group\Entity\GroupContent;
+use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Menu\MenuTreeParameters;
+
+/**
+ * Provides a block for displaying group menus.
+ *
+ * @Block(
+ *   id = "groupmenus",
+ *   admin_label = @Translation("Group menus"),
+ *   context = {
+ *     "group" = @ContextDefinition("entity:group", required = FALSE),
+ *     "node" = @ContextDefinition("entity:node", required = FALSE)
+ *   }
+ * )
+ */
+class GroupMenuBlock extends BlockBase {
+
+  /**
+   * Gets group menu names from group ID.
+   *
+   * @param int $gid
+   *   The group ID you want to load menus for.
+   *
+   * @return array
+   *   Array of machine names for group.
+   */
+  protected function getGroupMenus($gid) {
+    $menus = [];
+    $groupmenus = \Drupal::entityTypeManager()
+      ->getStorage('groupmenu')
+      ->loadByProperties(['gid' => $gid]);
+
+    // Make sure we don't select a menu twice.
+    foreach($groupmenus as $groupmenu) {
+      $menu_name = $groupmenu->entity_id->getString();
+      if (!in_array($menu_name, $menus)) {
+        $menus[] = $menu_name;
+      }
+    }
+    return $menus;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() { 
+    // Get the associated group content for the current node.
+    $node = $this->getContextValue('node');
+    if ($node) {
+      $group_contents = GroupContent::loadByEntity($node);
+
+      $menus = [];
+      // Get each group this node belongs to.
+      foreach($group_contents as $group_content) {
+        $group = $group_content->getGroup();
+        // Make an array of menus to render.
+        $menus = array_merge($menus, $this->getGroupMenus($group->id()));
+      }
+    } else {
+      // Not on a node page, but try to get the group anyway.
+      $group = $this->getContextValue('group');
+      if ($group) {
+        $menus = $this->getGroupMenus($group->id());
+      } else {
+        return [];
+      }
+    }
+
+    // Render the menus.
+    $parameters = new MenuTreeParameters();
+    $parameters->onlyEnabledLinks();
+    $manipulators = array(
+      array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+      array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+    );
+    $build = [];
+    foreach ($menus as $menu_name) {
+      $tree = \Drupal::menuTree()->load($menu_name, $parameters);
+      $tree = \Drupal::menuTree()->transform($tree, $manipulators);
+      $build[] = \Drupal::menuTree()->build($tree);
+    }
+
+    return $build;
+  }
+
+}
diff --git a/src/Access/GroupMenuAddAccessCheck.php b/src/Access/GroupMenuAddAccessCheck.php
index e69de29..50c4656 100644
--- a/src/Access/GroupMenuAddAccessCheck.php
+++ b/src/Access/GroupMenuAddAccessCheck.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\groupmenu\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\group\Entity\GroupInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Determines access to for group node add forms.
+ */
+class GroupMenuAddAccessCheck implements AccessInterface {
+
+  /**
+   * Checks access to the group node creation wizard.
+   *
+   * @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\GroupInterface $group
+   *   The group to create the node in.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(Route $route, AccountInterface $account, GroupInterface $group) {
+    $needs_access = $route->getRequirement('_groupmenu_add_access') === 'TRUE';
+
+    // We can only get the group content type ID if the plugin is installed.
+    if (!$group->getGroupType()->hasContentPlugin('groupmenu:menu')) {
+      return AccessResult::neutral();
+    }
+
+    // Determine whether the user can create nodes of the provided type.
+    $access = $group->hasPermission('create group menus', $account);
+
+    // Only allow access if the user can create group nodes of the provided type
+    // or if he doesn't need access to do so.
+    return AccessResult::allowedIf($access xor !$needs_access);
+  }
+
+}
diff --git a/src/Access/GroupMenuDeleteAccessCheck.php b/src/Access/GroupMenuDeleteAccessCheck.php
index e69de29..4726abc 100644
--- a/src/Access/GroupMenuDeleteAccessCheck.php
+++ b/src/Access/GroupMenuDeleteAccessCheck.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\groupmenu\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\group\Entity\GroupInterface;
+use Drupal\groupmenu\Entity\GroupMenu;
+use Drupal\system\MenuInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Determines access to for group node add forms.
+ */
+class GroupMenuDeleteAccessCheck implements AccessInterface {
+
+  /**
+   * Checks access to the group node creation wizard.
+   *
+   * @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\GroupInterface $group
+   *   The group to create the node in.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(Route $route, AccountInterface $account, MenuInterface $menu) {
+    // If access is not required for this route.
+    if (!$route->getRequirement('_groupmenu_delete_access') === 'TRUE') {
+      return AccessResult::allowed();
+    }
+    
+    // Check to make sure that the groups in question have the groupmenu plugin installed.
+    $groupmenus = GroupMenu::loadByMenu($menu);
+    foreach($groupmenus as $groupmenu) {
+      $group = $groupmenu->getGroup();
+      if (!$group->getGroupType()->hasContentPlugin('groupmenu:menu')) {
+        return AccessResult::neutral();
+      }
+
+      // This user has access to at least one of the groups that this menu belongs to.
+      if ($access = $group->hasPermission('delete group menus', $account)) {
+        return AccessResult::allowed();      
+      }
+    }
+
+    // If route is protected and user does not have permission for at least one group.
+    return AccessResult::forbidden();
+  }
+
+}
diff --git a/src/Access/GroupMenuEditAccessCheck.php b/src/Access/GroupMenuEditAccessCheck.php
index e69de29..e4b0f42 100644
--- a/src/Access/GroupMenuEditAccessCheck.php
+++ b/src/Access/GroupMenuEditAccessCheck.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\groupmenu\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\group\Entity\GroupInterface;
+use Drupal\groupmenu\Entity\GroupMenu;
+use Drupal\system\MenuInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Determines access to for group node add forms.
+ */
+class GroupMenuEditAccessCheck implements AccessInterface {
+
+  /**
+   * Checks access to the group node creation wizard.
+   *
+   * @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\GroupInterface $group
+   *   The group to create the node in.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(Route $route, AccountInterface $account, MenuInterface $menu) {
+    // If access is not required for this route.
+    if (!$route->getRequirement('_groupmenu_edit_access') === 'TRUE') {
+      return AccessResult::allowed();
+    }
+    
+    // Check to make sure that the groups in question have the groupmenu plugin installed.
+    $groupmenus = GroupMenu::loadByMenu($menu);
+    foreach($groupmenus as $groupmenu) {
+      $group = $groupmenu->getGroup();
+      if (!$group->getGroupType()->hasContentPlugin('groupmenu:menu')) {
+        return AccessResult::neutral();
+      }
+
+      // This user has access to at least one of the groups that this menu belongs to.
+      if ($access = $group->hasPermission('edit group menus', $account)) {
+        return AccessResult::allowed();      
+      }
+    }
+
+    // If route is protected and user does not have permission for at least one group.
+    return AccessResult::forbidden();
+  }
+
+}
diff --git a/src/Access/GroupMenuLinkEditAccessCheck.php b/src/Access/GroupMenuLinkEditAccessCheck.php
index e69de29..1a30984 100644
--- a/src/Access/GroupMenuLinkEditAccessCheck.php
+++ b/src/Access/GroupMenuLinkEditAccessCheck.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\groupmenu\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Routing\RouteMatch;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\group\Entity\GroupInterface;
+use Drupal\groupmenu\Entity\GroupMenu;
+use Drupal\system\Entity\Menu;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Determines access to for group node add forms.
+ */
+class GroupMenuLinkEditAccessCheck implements AccessInterface {
+
+  /**
+   * Checks access to the group node creation wizard.
+   *
+   * @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\GroupInterface $group
+   *   The group to create the node in.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(Route $route, RouteMatch $route_match, AccountInterface $account) {
+    // If access is not required for this route.
+    if (!$route->getRequirement('_groupmenu_link_edit_access') === 'TRUE') {
+      return AccessResult::allowed();
+    }
+    
+    // Get the menu this link belongs to
+    $menu = Menu::load($route_match->getParameter('menu_link_content')->getMenuName());
+
+    // Check to make sure that the groups in question have the groupmenu plugin installed.
+    $groupmenus = GroupMenu::loadByMenu($menu);
+    foreach($groupmenus as $groupmenu) {
+      $group = $groupmenu->getGroup();
+      if (!$group->getGroupType()->hasContentPlugin('groupmenu:menu')) {
+        return AccessResult::neutral();
+      }
+
+      // This user has access to at least one of the groups that this menu belongs to.
+      if ($access = $group->hasPermission('edit group menus', $account)) {
+        return AccessResult::allowed();      
+      }
+    }
+
+    // If route is protected and user does not have permission for at least one group.
+    return AccessResult::forbidden();
+  }
+
+}
diff --git a/src/Controller/GroupMenuConfigController.php b/src/Controller/GroupMenuConfigController.php
index e69de29..fb485fd 100644
--- a/src/Controller/GroupMenuConfigController.php
+++ b/src/Controller/GroupMenuConfigController.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\groupmenu\Controller;
+
+use Drupal\Core\Archiver\ArchiveTar;
+use Drupal\Core\Serialization\Yaml;
+use Symfony\Component\HttpFoundation\Request;
+use Drupal\config\Controller\ConfigController;
+
+/**
+ * Extend ConfigController to not include group menus on download.
+ */
+class GroupMenuConfigController extends ConfigController {
+
+  /**
+   * {inheritdoc}
+   */
+  public function downloadExport() {
+    file_unmanaged_delete(file_directory_temp() . '/config.tar.gz');
+
+    $archiver = new ArchiveTar(file_directory_temp() . '/config.tar.gz', 'gz');
+
+    $connection = \Drupal::database();
+    $query = $connection->select('config', 'c');
+    $query->leftJoin('groupmenu_field_data', 'gm', "concat(:prefix, gm.entity_id) = c.name", array(':prefix' => 'system.menu.'));
+    $query->fields('c', array('name'))
+      ->isNull('gm.entity_id');
+    $names = $query->execute()->fetchCol();
+
+    // Get raw configuration data.
+    foreach ($names as $name) {
+      $archiver->addString("$name.yml", Yaml::encode($this->configManager->getConfigFactory()->get($name)->getRawData()));
+    }
+    // Get all override data from the remaining collections.
+    foreach ($this->targetStorage->getAllCollectionNames() as $collection) {
+      $collection_storage = $this->targetStorage->createCollection($collection);
+      foreach ($collection_storage->listAll() as $name) {
+        $archiver->addString(str_replace('.', '/', $collection) . "/$name.yml", Yaml::encode($collection_storage->read($name)));
+      }
+    }
+
+    $request = new Request(array('file' => 'config.tar.gz'));
+    return $this->fileDownloadController->download($request, 'temporary');
+  }
+
+}
diff --git a/src/Controller/GroupMenuController.php b/src/Controller/GroupMenuController.php
index e69de29..21c6028 100644
--- a/src/Controller/GroupMenuController.php
+++ b/src/Controller/GroupMenuController.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\groupmenu\Controller;
+
+use Drupal\group\Entity\Controller\GroupContentController;
+use Drupal\group\Entity\GroupInterface;
+
+/**
+ * Returns responses for 'groupmenu' GroupContent routes.
+ */
+class GroupMenuController extends GroupContentController {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function addPageBundles(GroupInterface $group, $create_mode) {
+    $bundles = [];
+
+    // Retrieve all of the responsible group content types, keyed by plugin ID.
+    $storage = $this->entityTypeManager->getStorage('group_content_type');
+    $properties = ['group_type' => $group->bundle(), 'content_plugin' => 'groupmenu:menu'];
+    foreach ($storage->loadByProperties($properties) as $bundle => $group_content_type) {
+      /** @var \Drupal\group\Entity\GroupContentTypeInterface $group_content_type */
+      $bundles[$group_content_type->getContentPluginId()] = $bundle;
+    }
+
+    return $bundles;
+  }
+  
+}
diff --git a/src/Controller/GroupMenuEditController.php b/src/Controller/GroupMenuEditController.php
index e69de29..17ca695 100644
--- a/src/Controller/GroupMenuEditController.php
+++ b/src/Controller/GroupMenuEditController.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\groupmenu\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Entity\EntityFormBuilderInterface;
+use Drupal\system\MenuInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class GroupMenuEditController extends ControllerBase {
+
+  /**
+   * The entity form builder service.
+   *
+   * @var \Drupal\Core\Entity\EntityFormBuilderInterface
+   */
+  protected $entityFormBuilder;
+
+  /**
+   * Creates a new GroupMenuEditController object.
+   *
+   * @param \Drupal\Core\Entity\EntityFormBuilderInterface $entity_form_builder
+   *   The entity form builder service.
+   */
+  public function __construct(EntityFormBuilderInterface $entity_form_builder) {
+    $this->entityFormBuilder = $entity_form_builder;
+  }
+
+  /**
+   * {inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('entity.form_builder'));
+  }
+
+}
diff --git a/src/Controller/GroupMenuWizardController.php b/src/Controller/GroupMenuWizardController.php
index e69de29..84f9f8c 100644
--- a/src/Controller/GroupMenuWizardController.php
+++ b/src/Controller/GroupMenuWizardController.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace Drupal\groupmenu\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Entity\EntityFormBuilderInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\groupmenu\Entity\GroupMenu;
+use Drupal\group\Entity\GroupInterface;
+use Drupal\group\Plugin\GroupContentEnablerManagerInterface;
+use Drupal\system\Entity\Menu;
+use Drupal\user\PrivateTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Returns responses for 'groupmenu' GroupContent routes.
+ */
+class GroupMenuWizardController extends ControllerBase {
+
+  /**
+   * The private store for temporary group menus.
+   *
+   * @var \Drupal\user\PrivateTempStore
+   */
+  protected $privateTempStore;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity form builder.
+   *
+   * @var \Drupal\Core\Entity\EntityFormBuilderInterface
+   */
+  protected $entityFormBuilder;
+
+  /**
+   * The group content plugin manager.
+   *
+   * @var \Drupal\group\Plugin\GroupContentEnablerManagerInterface
+   */
+  protected $pluginManager;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a new GroupMenuWizardController.
+   *
+   * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
+   *   The factory for the temp store object.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityFormBuilderInterface $entity_form_builder
+   *   The entity form builder.
+   * @param \Drupal\group\Plugin\GroupContentEnablerManagerInterface $plugin_manager
+   *   The group content plugin manager.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
+   */
+  public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, EntityFormBuilderInterface $entity_form_builder, GroupContentEnablerManagerInterface $plugin_manager, RendererInterface $renderer) {
+    $this->privateTempStore = $temp_store_factory->get('groupmenu_add_temp');
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityFormBuilder = $entity_form_builder;
+    $this->pluginManager = $plugin_manager;
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('user.private_tempstore'),
+      $container->get('entity_type.manager'),
+      $container->get('entity.form_builder'),
+      $container->get('plugin.manager.group_content_enabler'),
+      $container->get('renderer')
+    );
+  }
+
+  /**
+   * Provides the form for creating a menu in a group.
+   *
+   * @param \Drupal\group\Entity\GroupInterface $group
+   *   The group to create a menu in.
+   *
+   * @return array
+   *   The form array for either step 1 or 2 of the group menu creation wizard.
+   */
+  public function addForm(GroupInterface $group) {
+    $plugin_id = 'groupmenu:menu';
+    $storage_id = $plugin_id . ':' . $group->id();
+
+    // If we are on step one, we need to build a menu form.
+    if ($this->privateTempStore->get("$storage_id:step") !== 2) {
+      $this->privateTempStore->set("$storage_id:step", 1);
+
+      // Only create a new menu if we have nothing stored.
+      if (!$entity = $this->privateTempStore->get("$storage_id:menu")) {
+        $entity = Menu::create();
+      }
+    }
+    // If we are on step two, we need to build a group content form.
+    else {
+      /** @var \Drupal\group\Plugin\GroupContentEnablerInterface $plugin */
+      $plugin = $group->getGroupType()->getContentPlugin($plugin_id);
+      $entity = GroupMenu::create([
+        'type' => $plugin->getContentTypeConfigId(),
+        'gid' => $group->id(),
+      ]);
+    }
+
+    // Return the form with the group and storage ID added to the form state.
+    $extra = ['group' => $group, 'storage_id' => $storage_id];
+    return $this->entityFormBuilder()->getForm($entity, 'groupmenu-form', $extra);
+  }
+
+  /**
+   * The _title_callback for the add menu form route.
+   *
+   * @param \Drupal\group\Entity\GroupInterface $group
+   *   The group to create a menu in.
+   *
+   * @return string
+   *   The page title.
+   */
+  public function addFormTitle(GroupInterface $group) {
+    return $this->t('Create menu in %label', ['%label' => $group->label()]);
+  }
+
+  /**
+   * Provides the group menu creation overview page.
+   *
+   * @param \Drupal\group\Entity\GroupInterface $group
+   *   The group to add the group menu to.
+   *
+   * @return mixed
+   *   The group menu creation overview page.
+   */
+  public function addPage(GroupInterface $group) {
+    // We do not set the "entity_add_list" template's "#add_bundle_message" key
+    // because we deny access to the page if no bundle is available.
+    $build = ['#theme' => 'entity_add_list', '#bundles' => []];
+    $add_form_route = 'entity.groupmenu.groupmenu_add_form';
+
+    $plugin_id = 'groupmenu:menu';
+
+    $storage = $this->entityTypeManager->getStorage('group_content_type');
+    $properties = [
+      'group_type' => $group->bundle(),
+      'content_plugin' => $plugin_id,
+    ];
+    /** @var \Drupal\group\Entity\GroupContentTypeInterface[] $bundles */
+    $bundles = $storage->loadByProperties($properties);
+
+    // Filter out the bundles the user doesn't have access to.
+    $access_control_handler = $this->entityTypeManager->getAccessControlHandler('groupmenu');
+    foreach (array_keys($bundles) as $bundle) {
+      // Check for access and add it as a cacheable dependency.
+      $access = $access_control_handler->createAccess($bundle, NULL, ['group' => $group], TRUE);
+      $this->renderer->addCacheableDependency($build, $access);
+
+      // Remove inaccessible bundles from the list.
+      if (!$access->isAllowed()) {
+        unset($bundles[$bundle]);
+      }
+    }
+
+    // Redirect if there's only one bundle available.
+    if (count($bundles) == 1) {
+      $group_content_type = reset($bundles);
+      $plugin = $group_content_type->getContentPlugin();
+      $url = Url::fromRoute($add_form_route, ['group' => $group->id()], ['absolute' => TRUE]);
+      return new RedirectResponse($url->toString());
+    }
+
+    // Set the info for all of the remaining bundles.
+    foreach ($bundles as $bundle => $group_content_type) {
+      $plugin = $group_content_type->getContentPlugin();
+      $build['#bundles'][$bundle] = [
+        'label' => 'menu',
+        'description' => $this->t('Create a menu for the group.'),
+        'add_link' => Link::createFromRoute('menu', $add_form_route, ['group' => $group->id()]),
+      ];
+    }
+
+    // Add the list cache tags for the GroupContentType entity type.
+    $bundle_entity_type = $this->entityTypeManager->getDefinition('group_content_type');
+    $build['#cache']['tags'] = $bundle_entity_type->getListCacheTags();
+
+    return $build;
+  }
+
+}
diff --git a/src/Entity/GroupMenu.php b/src/Entity/GroupMenu.php
index e69de29..59fb56a 100644
--- a/src/Entity/GroupMenu.php
+++ b/src/Entity/GroupMenu.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Drupal\groupmenu\Entity;
+
+use Drupal\group\Entity\GroupContentInterface;
+use Drupal\group\Entity\GroupContent;
+use Drupal\group\Entity\GroupContentType;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\system\MenuInterface;
+
+/**
+ * Defines the Group menu entity.
+ *
+ * @ingroup group
+ *
+ * @ContentEntityType(
+ *   id = "groupmenu",
+ *   label = @Translation("Group menu"),
+ *   label_singular = @Translation("group menu item"),
+ *   label_plural = @Translation("group menu items"),
+ *   label_count = @PluralTranslation(
+ *     singular = "@count group menu item",
+ *     plural = "@count group menu items"
+ *   ),
+ *   bundle_label = @Translation("Group menu type"),
+ *   handlers = {
+ *     "storage" = "Drupal\group\Entity\Storage\GroupContentStorage",
+ *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
+ *     "views_data" = "Drupal\group\Entity\Views\GroupContentViewsData",
+ *     "list_builder" = "Drupal\group\Entity\Controller\GroupContentListBuilder",
+ *     "route_provider" = {
+ *       "html" = "Drupal\group\Entity\Routing\GroupContentRouteProvider",
+ *     },
+ *     "form" = {
+ *       "add" = "Drupal\group\Entity\Form\GroupContentForm",
+ *       "edit" = "Drupal\group\Entity\Form\GroupContentForm",
+ *       "delete" = "Drupal\group\Entity\Form\GroupContentDeleteForm",
+ *       "group-join" = "Drupal\group\Form\GroupJoinForm",
+ *       "group-leave" = "Drupal\group\Form\GroupLeaveForm",
+ *     },
+ *     "access" = "Drupal\group\Entity\Access\GroupContentAccessControlHandler",
+ *   },
+ *   base_table = "groupmenu",
+ *   data_table = "groupmenu_field_data",
+ *   translatable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *     "langcode" = "langcode",
+ *     "bundle" = "type",
+ *     "label" = "label"
+ *   },
+ *   links = {
+ *     "add-form" = "/group/{group}/menu/add/{plugin_id}",
+ *     "add-page" = "/group/{group}/menu/add",
+ *     "canonical" = "/group/{group}/menu/{groupmenu}",
+ *     "collection" = "/group/{group}/menu",
+ *     "delete-form" = "/group/{group}/menu/{groupmenu}/delete",
+ *     "edit-form" = "/group/{group}/menu/{groupmenu}/edit"
+ *   },
+ *   bundle_entity_type = "group_content_type",
+ *   field_ui_base_route = "entity.group_content_type.edit_form",
+ *   permission_granularity = "bundle",
+ *   constraints = {
+ *     "GroupContentCardinality" = {}
+ *   }
+ * )
+ */
+class GroupMenu extends GroupContent implements GroupContentInterface {
+
+  /**
+   * Loads the group menu entities by menu.
+   *
+   * @param \Drupal\system\MenuInterface $menu
+   *   The menu to load the groups for.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface[]
+   *   List of group menu entities.
+   */
+  public static function loadByMenu(MenuInterface $menu) {
+    $group_content_types = GroupContentType::loadByEntityTypeId($menu->getEntityTypeId());
+
+    // If no responsible group content types were found, we return nothing.
+    if (empty($group_content_types)) {
+      return [];
+    }
+
+    return \Drupal::entityTypeManager()
+      ->getStorage('groupmenu')
+      ->loadByProperties([
+        'type' => array_keys($group_content_types),
+        'entity_id' => $menu->id(),
+    ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+
+    // Specify menu entity type
+    // Keep an eye out for resolution of 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.'))
+      ->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);
+
+    return $fields;
+  }
+
+}
diff --git a/src/Entity/GroupMenuEntityStorage.php b/src/Entity/GroupMenuEntityStorage.php
index e69de29..88da634 100644
--- a/src/Entity/GroupMenuEntityStorage.php
+++ b/src/Entity/GroupMenuEntityStorage.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\groupmenu\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityStorage;
+
+/**
+ * Extends the ConfigEntityStorage class to not load all menus.
+ */
+class GroupMenuEntityStorage extends ConfigEntityStorage {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doLoadMultiple(array $ids = NULL) {
+    // We only need to change the way Drupal loads the list of ALL menus.
+    if ($ids !== NULL) {
+      return parent::doLoadMultiple($ids);
+    }
+
+    $prefix = $this->getPrefix();
+
+    // Get Drupal database connection.
+    $connection = \Drupal::database();
+
+    // Build query to select non-group menus.
+    $query = $connection->select('config', 'c');
+    $query->leftJoin('groupmenu_field_data', 'gm', "concat(:prefix, gm.entity_id) = c.name", array(':prefix' => $prefix));
+    $query->fields('c', array('name'))
+      ->isNull('gm.entity_id')
+      ->condition('c.name', $connection->escapeLike($prefix) . '%', 'LIKE');
+    $ids = $query->execute()->fetchCol();
+
+    // Strip out the prefixes from the entity names so we can send to parent.
+    $prefixlen = strlen($prefix);
+    foreach($ids as &$id) {
+      $id = substr($id, $prefixlen);
+    }
+
+    return parent::doLoadMultiple($ids);
+  }
+  
+}
diff --git a/src/Form/GroupMenuConfigSync.php b/src/Form/GroupMenuConfigSync.php
index e69de29..ea35687 100644
--- a/src/Form/GroupMenuConfigSync.php
+++ b/src/Form/GroupMenuConfigSync.php
@@ -0,0 +1,161 @@
+<?php
+
+namespace Drupal\groupmenu\Form;
+
+use Drupal\groupmenu\GroupMenuStorageComparer;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Url;
+use Drupal\config\Form\ConfigSync;
+
+/**
+ * Modified ConfigSync form to use custom StorageComparer.
+ */
+class GroupMenuConfigSync extends ConfigSync {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Import all'),
+    );
+    $source_list = $this->syncStorage->listAll();
+    $storage_comparer = new GroupMenuStorageComparer($this->syncStorage, $this->activeStorage, $this->configManager);
+    if (empty($source_list) || !$storage_comparer->createChangelist()->hasChanges()) {
+      $form['no_changes'] = array(
+        '#type' => 'table',
+        '#header' => array($this->t('Name'), $this->t('Operations')),
+        '#rows' => array(),
+        '#empty' => $this->t('There are no configuration changes to import.'),
+      );
+      $form['actions']['#access'] = FALSE;
+      return $form;
+    }
+    elseif (!$storage_comparer->validateSiteUuid()) {
+      drupal_set_message($this->t('The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.'), 'error');
+      $form['actions']['#access'] = FALSE;
+      return $form;
+    }
+    // A list of changes will be displayed, so check if the user should be
+    // warned of potential losses to configuration.
+    if ($this->snapshotStorage->exists('core.extension')) {
+      $snapshot_comparer = new GroupMenuStorageComparer($this->activeStorage, $this->snapshotStorage, $this->configManager);
+      if (!$form_state->getUserInput() && $snapshot_comparer->createChangelist()->hasChanges()) {
+        $change_list = array();
+        foreach ($snapshot_comparer->getAllCollectionNames() as $collection) {
+          foreach ($snapshot_comparer->getChangelist(NULL, $collection) as $config_names) {
+            if (empty($config_names)) {
+              continue;
+            }
+            foreach ($config_names as $config_name) {
+              $change_list[] = $config_name;
+            }
+          }
+        }
+        sort($change_list);
+        $message = [
+          [
+            '#markup' => $this->t('The following items in your active configuration have changes since the last import that may be lost on the next import.')
+          ],
+          [
+            '#theme' => 'item_list',
+            '#items' => $change_list,
+          ]
+        ];
+        drupal_set_message($this->renderer->renderPlain($message), 'warning');
+      }
+    }
+
+    // Store the comparer for use in the submit.
+    $form_state->set('storage_comparer', $storage_comparer);
+
+    // Add the AJAX library to the form for dialog support.
+    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
+
+    foreach ($storage_comparer->getAllCollectionNames() as $collection) {
+      if ($collection != StorageInterface::DEFAULT_COLLECTION) {
+        $form[$collection]['collection_heading'] = array(
+          '#type' => 'html_tag',
+          '#tag' => 'h2',
+          '#value' => $this->t('@collection configuration collection', array('@collection' => $collection)),
+        );
+      }
+      foreach ($storage_comparer->getChangelist(NULL, $collection) as $config_change_type => $config_names) {
+        if (empty($config_names)) {
+          continue;
+        }
+
+        // @todo A table caption would be more appropriate, but does not have the
+        //   visual importance of a heading.
+        $form[$collection][$config_change_type]['heading'] = array(
+          '#type' => 'html_tag',
+          '#tag' => 'h3',
+        );
+        switch ($config_change_type) {
+          case 'create':
+            $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count new', '@count new');
+            break;
+
+          case 'update':
+            $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count changed', '@count changed');
+            break;
+
+          case 'delete':
+            $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count removed', '@count removed');
+            break;
+
+          case 'rename':
+            $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count renamed', '@count renamed');
+            break;
+        }
+        $form[$collection][$config_change_type]['list'] = array(
+          '#type' => 'table',
+          '#header' => array($this->t('Name'), $this->t('Operations')),
+        );
+
+        foreach ($config_names as $config_name) {
+          if ($config_change_type == 'rename') {
+            $names = $storage_comparer->extractRenameNames($config_name);
+            $route_options = array('source_name' => $names['old_name'], 'target_name' => $names['new_name']);
+            $config_name = $this->t('@source_name to @target_name', array('@source_name' => $names['old_name'], '@target_name' => $names['new_name']));
+          }
+          else {
+            $route_options = array('source_name' => $config_name);
+          }
+          if ($collection != StorageInterface::DEFAULT_COLLECTION) {
+            $route_name = 'config.diff_collection';
+            $route_options['collection'] = $collection;
+          }
+          else {
+            $route_name = 'config.diff';
+          }
+          $links['view_diff'] = array(
+            'title' => $this->t('View differences'),
+            'url' => Url::fromRoute($route_name, $route_options),
+            'attributes' => array(
+              'class' => array('use-ajax'),
+              'data-dialog-type' => 'modal',
+              'data-dialog-options' => json_encode(array(
+                'width' => 700
+              )),
+            ),
+          );
+          $form[$collection][$config_change_type]['list']['#rows'][] = array(
+            'name' => $config_name,
+            'operations' => array(
+              'data' => array(
+                '#type' => 'operations',
+                '#links' => $links,
+              ),
+            ),
+          );
+        }
+      }
+    }
+    return $form;
+  }
+
+}
diff --git a/src/Form/GroupMenuForm.php b/src/Form/GroupMenuForm.php
index e69de29..bec15b2 100644
--- a/src/Form/GroupMenuForm.php
+++ b/src/Form/GroupMenuForm.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\groupmenu\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\menu_ui\MenuForm;
+
+/**
+ * Group menu extension of base menu form.
+ */
+class GroupMenuForm extends MenuForm {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function buildOverviewForm(array &$form, FormStateInterface $form_state) {
+    $form = parent::buildOverviewForm($form, $form_state);
+    $form['links']['#empty'] = $this->t('There are no menu links yet. <a href=":url">Add link</a>.', [
+      ':url' => $this->url('entity.groupmenu.menu_link_content.edit_form', ['menu' => $this->entity->id()], [
+        'query' => ['destination' => $this->entity->url('edit-form')],
+      ]),
+    ]);
+    return $form;
+  }
+
+}
diff --git a/src/Form/GroupMenuFormStep1.php b/src/Form/GroupMenuFormStep1.php
index e69de29..fab6d18 100644
--- a/src/Form/GroupMenuFormStep1.php
+++ b/src/Form/GroupMenuFormStep1.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Drupal\groupmenu\Form;
+
+use Drupal\menu_ui\MenuForm;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Utility\LinkGeneratorInterface;
+use Drupal\user\PrivateTempStoreFactory;
+use Drupal\Core\Url;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a creating a menu without it being saved yet.
+ */
+class GroupMenuFormStep1 extends MenuForm {
+
+  /**
+   * The private store for temporary group nodes.
+   *
+   * @var \Drupal\user\PrivateTempStore
+   */
+  protected $privateTempStore;
+
+  /**
+   * Constructs a GroupNodeFormStep1 object.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
+   *   The menu link manager.
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
+   *   The menu tree service.
+   * @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
+   *   The link generator.
+   * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
+   *   The factory for the temp store object.
+   */
+  public function __construct(MenuLinkManagerInterface $menu_link_manager, MenuLinkTreeInterface $menu_tree, LinkGeneratorInterface $link_generator, PrivateTempStoreFactory $temp_store_factory) {
+    parent::__construct($menu_link_manager, $menu_tree, $link_generator);
+    $this->privateTempStore = $temp_store_factory->get('groupmenu_add_temp');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static (
+      $container->get('plugin.manager.menu.link'),
+      $container->get('menu.link_tree'),
+      $container->get('link_generator'),
+      $container->get('user.private_tempstore')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function actions(array $form, FormStateInterface $form_state) {
+    $actions['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Continue to final step'),
+      '#submit' => ['::submitForm', '::saveTemporary'],
+    ];
+
+    $actions['cancel'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Cancel'),
+      '#submit' => ['::cancel'],
+      '#limit_validation_errors' => [],
+    ];
+    return $actions;
+  }
+
+  /**
+   * Saves a temporary node and continues to step 2 of group node creation.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @see \Drupal\groupmenu\Controller\GroupNodeWizardController::add()
+   * @see \Drupal\groupmenu\Form\GroupNodeFormStep2
+   */
+  public function saveTemporary(array &$form, FormStateInterface $form_state) {
+    $storage_id = $form_state->get('storage_id');
+
+    $this->privateTempStore->set("$storage_id:menu", $this->entity);
+    $this->privateTempStore->set("$storage_id:step", 2);
+
+    // Disable any URL-based redirect until the final step.
+    $request = $this->getRequest();
+    $form_state->setRedirectUrl(Url::fromRoute('<current>', [], ['query' => $request->query->all()]));
+    $request->query->remove('destination');
+  }
+
+  /**
+   * Cancels the node creation by emptying the temp store.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @see \Drupal\groupmenu\Controller\GroupNodeWizardController::add()
+   */
+  public function cancel(array &$form, FormStateInterface $form_state) {
+    /** @var \Drupal\group\Entity\GroupInterface $group */
+    $group = $form_state->get('group');
+    $storage_id = $form_state->get('storage_id');
+    $this->privateTempStore->delete("$storage_id:menu");
+
+    // Redirect to the group page if no destination was set in the URL.
+    $form_state->setRedirect('entity.group.canonical', ['group' => $group->id()]);
+  }
+
+}
diff --git a/src/Form/GroupMenuFormStep2.php b/src/Form/GroupMenuFormStep2.php
index e69de29..8eddede 100644
--- a/src/Form/GroupMenuFormStep2.php
+++ b/src/Form/GroupMenuFormStep2.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Drupal\groupmenu\Form;
+
+use Drupal\group\Entity\Form\GroupContentForm;
+use Drupal\user\PrivateTempStoreFactory;
+use Drupal\Core\Url;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form creating a node in a group.
+ */
+class GroupMenuFormStep2 extends GroupContentForm {
+
+  /**
+   * The private store for temporary group nodes.
+   *
+   * @var \Drupal\user\PrivateTempStore
+   */
+  protected $privateTempStore;
+
+  /**
+   * Constructs a GroupNodeFormStep2 object.
+   *
+   * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
+   *   The factory for the temp store object.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityManagerInterface $entity_manager) {
+    parent::__construct($temp_store_factory, $entity_manager);
+    $this->privateTempStore = $temp_store_factory->get('groupmenu_add_temp');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('user.private_tempstore'),
+      $container->get('entity.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+    $form['entity_id']['#access'] = FALSE;
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function actions(array $form, FormStateInterface $form_state) {
+    $actions = parent::actions($form, $form_state);
+
+    $actions['submit']['#value'] = $this->t('Create menu in group');
+    $actions['back'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Back'),
+      '#submit' => ['::submitForm', '::back'],
+      '#limit_validation_errors' => [],
+    ];
+
+    return $actions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    $storage_id = $form_state->get('storage_id');
+
+    // We can now safely save the menu and set its ID on the group content.
+    $menu = $this->privateTempStore->get("$storage_id:menu");
+    $menu->save();
+    $this->entity->set('entity_id', $menu->id());
+
+    // We also clear the private store so we can start fresh next time around.
+    $this->privateTempStore->delete("$storage_id:step");
+    $this->privateTempStore->delete("$storage_id:menu");
+
+    return parent::save($form, $form_state);
+  }
+
+  /**
+   * Goes back to step 1 of group node creation.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @see \Drupal\groupmenu\Controller\GroupNodeWizardController::add()
+   * @see \Drupal\groupmenu\Form\GroupNodeFormStep1
+   */
+  public function back(array &$form, FormStateInterface $form_state) {
+    $storage_id = $form_state->get('storage_id');
+    $this->privateTempStore->set("$storage_id:step", 1);
+
+    // Disable any URL-based redirect when going back to the previous step.
+    $request = $this->getRequest();
+    $form_state->setRedirectUrl(Url::fromRoute('<current>', [], ['query' => $request->query->all()]));
+    $request->query->remove('destination');
+  }
+
+}
diff --git a/src/Form/GroupMenuSettingsForm.php b/src/Form/GroupMenuSettingsForm.php
index e69de29..1f3e482 100644
--- a/src/Form/GroupMenuSettingsForm.php
+++ b/src/Form/GroupMenuSettingsForm.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\groupmenu\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class GroupMenuSettingsForm.
+ */
+class GroupMenuSettingsForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'groupmenu_settings';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['groupmenu.settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildForm($form, $form_state);
+
+    $config = $this->config('groupmenu.settings');
+
+    $form['groupmenu_hide_list'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Hide group menus from menu lists'),
+      '#description' => $this->t("Hide group menus from default menu lists. (recommended)"),
+      '#default_value' => $config->get('groupmenu_hide_list'),
+    ];
+
+    $form['config_sync_groupmenu_ignore'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Configuration sync: ignore group menus'),
+      '#description' => $this->t("Do not export group menus and do not delete group menus on configuration import."),
+      '#default_value' => $config->get('config_sync_groupmenu_ignore'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $config = $this->config('groupmenu.settings');
+    $form_config_sync = $form_state->getValue('config_sync_groupmenu_ignore');
+    $form_hide_list = $form_state->getValue('groupmenu_hide_list');
+
+    $config->set('config_sync_groupmenu_ignore', $form_config_sync)->save();
+    $config->set('groupmenu_hide_list', $form_hide_list)->save();
+
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/src/GroupMenuListBuilder.php b/src/GroupMenuListBuilder.php
index e69de29..28b40cd 100644
--- a/src/GroupMenuListBuilder.php
+++ b/src/GroupMenuListBuilder.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\groupmenu;
+
+use \Drupal\menu_ui\MenuListBuilder;
+
+/**
+ * {@inheritdoc}
+ */
+class GroupMenuListBuilder extends MenuListBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityIds() {
+    // Note: I wanted to use a query tag here but it didn't seem to work...
+    // Also, I do not like the way this is currently working
+    $groupmenu_ids = db_select('groupmenu_field_data', 'gm')
+      ->fields('gm', array('entity_id'))
+      ->execute()
+      ->fetchAll(\PDO::FETCH_COLUMN, 0);
+
+    $query = $this->getStorage()->getQuery()
+    ->condition($this->entityType->getKey('id'), $groupmenu_ids, 'NOT IN')
+    ->sort($this->entityType->getKey('id'));
+
+    // Only add the pager if a limit is specified.
+    if ($this->limit) {
+      $query->pager($this->limit);
+    }
+    return $query->execute();
+  }
+
+}
diff --git a/src/GroupMenuStorageComparer.php b/src/GroupMenuStorageComparer.php
index e69de29..d4049c1 100644
--- a/src/GroupMenuStorageComparer.php
+++ b/src/GroupMenuStorageComparer.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Drupal\groupmenu;
+
+use Drupal\Core\Config\StorageComparer;
+use Drupal\Core\Config\StorageInterface;
+
+/**
+ * Modified StorageComparer to exclude group menus.
+ */
+class GroupMenuStorageComparer extends StorageComparer {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createChangelist() {
+    parent::createChangelist();
+    // Caching prevents placing this check in GroupMenuRouteSubscriber.
+    $config = \Drupal::config('groupmenu.settings');
+    if ($config->get('config_sync_groupmenu_ignore')) {
+      $this->removeChangelistGroupMenus();
+    }
+    return $this;
+  }
+
+  /**
+   * Modifies changelist to remove group menus.
+   */
+  protected function removeChangelistGroupMenus() {
+    // Get Drupal database connection.
+    $connection = \Drupal::database();
+
+    // Build query to select group menu configuration names.
+    $query = $connection->select('config', 'c');
+    $query->join('groupmenu_field_data', 'gm', "concat('system.menu.', gm.entity_id) = c.name");
+    $query->fields('c', array('name'));
+    $gm_names = $query->execute()->fetchCol();
+
+    $changelist = $this->getChangelist();
+
+    // Iterate through each change operation.
+    foreach ($changelist as $op => $names) {
+      // Iterate through each item in operation and remove if group menu.
+      foreach($names as $name) {
+        if (in_array($name, $gm_names)) {
+          $this->removeFromChangelist(StorageInterface::DEFAULT_COLLECTION, $op, $name);
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/Plugin/GroupContentEnabler/GroupMenu.php b/src/Plugin/GroupContentEnabler/GroupMenu.php
index e69de29..31ad2ef 100644
--- a/src/Plugin/GroupContentEnabler/GroupMenu.php
+++ b/src/Plugin/GroupContentEnabler/GroupMenu.php
@@ -0,0 +1,109 @@
+<?php
+
+namespace Drupal\groupmenu\Plugin\GroupContentEnabler;
+
+use Drupal\group\Entity\GroupInterface;
+use Drupal\group\Plugin\GroupContentEnablerBase;
+use Drupal\Core\Url;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a content enabler for menus.
+ *
+ * @GroupContentEnabler(
+ *   id = "groupmenu",
+ *   label = @Translation("Group menu"),
+ *   description = @Translation("Adds menus to groups."),
+ *   entity_type_id = "menu",
+ *   pretty_path_key = "menu",
+ *   deriver = "Drupal\groupmenu\Plugin\GroupContentEnabler\GroupMenuDeriver"
+ * )
+ */
+class GroupMenu extends GroupContentEnablerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getGroupOperations(GroupInterface $group) {
+    $account = \Drupal::currentUser();
+    $operations = [];
+    $route_params = ['group' => $group->id()];
+
+    if ($group->hasPermission("create group menus", $account)) {
+      $operations["group-menu-create-menu"] = [
+        'title' => $this->t('Create menu'),
+        'url' => new Url('entity.groupmenu.groupmenu_add_form', $route_params),
+        'weight' => 31,
+      ];
+    }
+    if ($group->hasPermission("edit group menus", $account)) {
+      $operations["group-menu-update-menu"] = [
+        'title' => $this->t('Edit menus'),
+        'url' => new Url('entity.groupmenu.groupmenu_add_form', $route_params),
+        'weight' => 32,
+      ];
+    }
+
+    return $operations;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPermissions() {
+    // Add our own permissions for managing the menus.
+    $defaults = [
+      'description' => 'Only applies to menus that belong to this group.',
+    ];
+
+    $permissions["view group menus"] = [
+      'title' => 'View menus',
+    ] + $defaults;
+
+    $permissions["create group menus"] = [
+      'title' => 'Create menus',
+      'description' => 'Allows you to create menus that immediately belong to this group.',
+    ] + $defaults;
+
+    $permissions["edit group menus"] = [
+      'title' => 'Edit menus',
+    ] + $defaults;
+
+    $permissions["delete group menus"] = [
+      'title' => 'Delete menus',
+    ] + $defaults;
+
+    return $permissions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    $config = parent::defaultConfiguration();
+    $config['entity_cardinality'] = 1;
+
+    // This string will be saved as part of the group type config entity. We do
+    // not use a t() function here as it needs to be stored untranslated.
+    $config['info_text']['value'] = '<p>By submitting this form you will add this menu to the group.<br />It will then be subject to the access control settings that were configured for the group.<br/>Please fill out any available fields to describe the relation between the content and the group.</p>';
+
+    return $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildConfigurationForm($form, $form_state);
+
+    // Disable the entity cardinality field as the functionality of this module
+    // relies on a cardinality of 1. We don't just hide it, though, to keep a UI
+    // that's consistent with other content enabler plugins.
+    $info = $this->t("This field has been disabled by the plugin to guarantee the functionality that's expected of it.");
+    $form['entity_cardinality']['#disabled'] = TRUE;
+    $form['entity_cardinality']['#description'] .= '<br /><em>' . $info . '</em>';
+
+    return $form;
+  }
+
+}
diff --git a/src/Plugin/GroupContentEnabler/GroupMenuDeriver.php b/src/Plugin/GroupContentEnabler/GroupMenuDeriver.php
index e69de29..b7686fd 100644
--- a/src/Plugin/GroupContentEnabler/GroupMenuDeriver.php
+++ b/src/Plugin/GroupContentEnabler/GroupMenuDeriver.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\groupmenu\Plugin\GroupContentEnabler;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+
+/**
+ * Provides a group menu deriver.
+ */
+class GroupMenuDeriver extends DeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    $this->derivatives['menu'] = [
+      'entity_bundle' => 'menu',
+      'label' => t('Group menu'),
+      'description' => t('Adds menus to groups.'),
+    ] + $base_plugin_definition;
+
+    return $this->derivatives;
+  }
+
+}
diff --git a/src/Routing/GroupMenuRouteProvider.php b/src/Routing/GroupMenuRouteProvider.php
index e69de29..e447e25 100644
--- a/src/Routing/GroupMenuRouteProvider.php
+++ b/src/Routing/GroupMenuRouteProvider.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\groupmenu\Routing;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides routes for groupmenu group content.
+ */
+class GroupMenuRouteProvider {
+
+  /**
+   * Provides the shared collection route for group node plugins.
+   */
+  public function getRoutes() {
+    $routes = $permissions_add = $permissions_create = [];
+
+    $plugin_id = "groupmenu:menu";
+
+    $permissions_add[] = "edit group menus";
+    $permissions_create[] = "create group menus";
+
+    // @todo Conditionally (see above) alter GroupContent info to use this path.
+    $routes['entity.groupmenu.groupmenu_relate_menu'] = new Route('group/{group}/menu/add');
+    $routes['entity.groupmenu.groupmenu_relate_menu']
+      ->setDefaults([
+        '_title' => 'Relate menu',
+        '_controller' => '\Drupal\groupmenu\Controller\GroupMenuController::addPage',
+      ])
+      ->setRequirement('_group_permission', implode('+', $permissions_add))
+      ->setRequirement('_group_installed_content', $plugin_id)
+      ->setOption('_group_operation_route', TRUE);
+
+    // @todo Conditionally (see above) alter GroupContent info to use this path.
+    $routes['entity.groupmenu.groupmenu_add_menu'] = new Route('group/{group}/menu/create');
+    $routes['entity.groupmenu.groupmenu_add_menu']
+      ->setDefaults([
+        '_title' => 'Create menu',
+        '_controller' => '\Drupal\groupmenu\Controller\GroupMenuWizardController::addPage',
+      ])
+      ->setRequirement('_group_permission', implode('+', $permissions_create))
+      ->setRequirement('_group_installed_content', $plugin_id)
+      ->setOption('_group_operation_route', TRUE);
+
+    return $routes;
+  }
+
+}
diff --git a/src/Routing/GroupMenuRouteSubscriber.php b/src/Routing/GroupMenuRouteSubscriber.php
index e69de29..6e1a563 100644
--- a/src/Routing/GroupMenuRouteSubscriber.php
+++ b/src/Routing/GroupMenuRouteSubscriber.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\groupmenu\Routing;
+
+use Drupal\Core\Routing\RouteSubscriberBase;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Modify form for config.sync route.
+ */
+class GroupMenuRouteSubscriber extends RouteSubscriberBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function alterRoutes(RouteCollection $collection) {
+    if ($route = $collection->get('config.sync')) {
+      $route->setDefault('_form', '\Drupal\groupmenu\Form\GroupMenuConfigSync');
+    }
+  }
+
+}
