diff --git a/composer.json b/composer.json
index deebc954fe9ccf8b7d93bdc232675e59e69978d3..bb9f35412c1d9a0891352d6d5994bdb4c266258d 100644
--- a/composer.json
+++ b/composer.json
@@ -23,6 +23,7 @@
     "drupal/dynamic_entity_reference": "^3",
     "drupal/eck": "^2",
     "drupal/geofield": "^1",
+    "drupal/menu_item_extras": "^3",
     "drupal/metatag": "^2",
     "drupal/paragraphs": "^1",
     "drupal/layout_paragraphs": "^2",
diff --git a/modules/graphql_compose_comments/src/CommentableTrait.php b/modules/graphql_compose_comments/src/CommentableTrait.php
index 50958cdc266cdba13612814e3c786a8b7115d0ec..371c1ea27cda1c02c7fdf464cdca221833530eec 100644
--- a/modules/graphql_compose_comments/src/CommentableTrait.php
+++ b/modules/graphql_compose_comments/src/CommentableTrait.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose_comments;
 
 use Drupal\Core\Entity\Entity\EntityFormDisplay;
diff --git a/modules/graphql_compose_comments/src/Plugin/GraphQL/DataProducer/CreateComment.php b/modules/graphql_compose_comments/src/Plugin/GraphQL/DataProducer/CreateComment.php
index d4e98859e1f642c809e8e69898e52576bf317c24..58baeb30894bb8fd50802f196af77f8b380a3c63 100644
--- a/modules/graphql_compose_comments/src/Plugin/GraphQL/DataProducer/CreateComment.php
+++ b/modules/graphql_compose_comments/src/Plugin/GraphQL/DataProducer/CreateComment.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose_comments\Plugin\GraphQL\DataProducer;
 
 use Drupal\comment\CommentInterface;
diff --git a/modules/graphql_compose_edges/src/Filters/EdgeFilterBase.php b/modules/graphql_compose_edges/src/Filters/EdgeFilterBase.php
index a03016ccaa30665a04d03220b8a4afb6f99825ca..b07c4f4e3d99d60fa68d06ec7ecda76972baa67b 100644
--- a/modules/graphql_compose_edges/src/Filters/EdgeFilterBase.php
+++ b/modules/graphql_compose_edges/src/Filters/EdgeFilterBase.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose_edges\Filters;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
diff --git a/modules/graphql_compose_edges/src/Filters/EdgeFilterInterface.php b/modules/graphql_compose_edges/src/Filters/EdgeFilterInterface.php
index 64dbe3cb3fa6b88f360267e5c38b5b27879ca6ae..94a5aad0e3ee9f1cd20e18afe1de90fca84d5a31 100644
--- a/modules/graphql_compose_edges/src/Filters/EdgeFilterInterface.php
+++ b/modules/graphql_compose_edges/src/Filters/EdgeFilterInterface.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose_edges\Filters;
 
 use Drupal\Core\Entity\Query\QueryInterface;
diff --git a/modules/graphql_compose_edges/src/Filters/EntityFilterLanguage.php b/modules/graphql_compose_edges/src/Filters/EntityFilterLanguage.php
index 6c6714d08478eedc8e6e51aef5d5223a188f5037..6904cd16fcbe280d9034b5527916ec1c99ddf535 100644
--- a/modules/graphql_compose_edges/src/Filters/EntityFilterLanguage.php
+++ b/modules/graphql_compose_edges/src/Filters/EntityFilterLanguage.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose_edges\Filters;
 
 use Drupal\Core\Entity\Query\QueryInterface;
diff --git a/modules/graphql_compose_edges/src/Filters/EntityFilterPublished.php b/modules/graphql_compose_edges/src/Filters/EntityFilterPublished.php
index f370cca8eb089f955e3663a3e98c7bd4b12ae511..69a7ba1eda3e720193308cca35d0b6a08258fe11 100644
--- a/modules/graphql_compose_edges/src/Filters/EntityFilterPublished.php
+++ b/modules/graphql_compose_edges/src/Filters/EntityFilterPublished.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose_edges\Filters;
 
 use Drupal\Core\Entity\Query\QueryInterface;
diff --git a/modules/graphql_compose_menus/graphql_compose_menus.module b/modules/graphql_compose_menus/graphql_compose_menus.module
index 017791278c41c2be77a5d83ee36472faebc1cce5..37eb55b5b0483b1c2e0d48a7dfdfc54a518a054a 100644
--- a/modules/graphql_compose_menus/graphql_compose_menus.module
+++ b/modules/graphql_compose_menus/graphql_compose_menus.module
@@ -46,9 +46,29 @@ function graphql_compose_menus_graphql_compose_entity_type_form_alter(array &$fo
         '%path' => 'route.entity',
       ]),
     ];
+
+    // Get the fields for the menu if menu_item_extras is installed.
+    // Squashed in here to try and improve UX on the form.
+    if (\Drupal::moduleHandler()->moduleExists('menu_item_extras')) {
+      $ml_entity_type = \Drupal::entityTypeManager()->getDefinition('menu_link_content');
+      /** @var \Drupal\graphql_compose\Form\SchemaForm $instance */
+      $instance = $form_state->getFormObject();
+      $instance->buildEntityTypeBundleFields($form, $form_state, $ml_entity_type, $bundle_id);
+    }
   }
 }
 
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Hide the menu link content settings in an attempt to improve UX.
+ * This is intended to be used with the menu_item_extras module.
+ */
+function graphql_compose_menus_form_graphql_compose_schema_alter(array &$form, FormStateInterface $form_state) {
+  unset($form['settings']['menu_link_content']);
+  unset($form['layout']['entity_tabs']['entity_type__menu_link_content']);
+}
+
 /**
  * Implements hook_config_schema_info_alter().
  */
diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlTranslated.php b/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkEntity.php
similarity index 55%
rename from modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlTranslated.php
rename to modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkEntity.php
index 3465bd5819bee0463aeddf3726bd3f77a7eca21e..3f0cf683c35874c3846df37b81b4eb9ab34ce1c3 100644
--- a/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlTranslated.php
+++ b/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkEntity.php
@@ -4,34 +4,34 @@ declare(strict_types=1);
 
 namespace Drupal\graphql_compose_menus\Plugin\GraphQL\DataProducer;
 
+use Drupal\Component\Uuid\Uuid;
 use Drupal\Core\Entity\EntityRepositoryInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Menu\MenuLinkInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\Core\Url;
 use Drupal\graphql\GraphQL\Buffers\EntityUuidBuffer;
 use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
+use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;
 use GraphQL\Deferred;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
- * Returns the translated URL object of a menu link.
+ * Returns the menu link content entity of a menu link.
  *
  * @DataProducer(
- *   id = "menu_link_url_translated",
- *   name = @Translation("Menu link translated url"),
- *   description = @Translation("Returns the translated URL of a menu link."),
+ *   id = "menu_link_entity",
+ *   name = @Translation("Menu link content entity"),
+ *   description = @Translation("Returns the menu link content of a menu link."),
  *   produces = @ContextDefinition("any",
- *     label = @Translation("URL"),
+ *     label = @Translation("Menu Link Content entity"),
  *   ),
  *   consumes = {
  *     "link" = @ContextDefinition("any",
- *       label = @Translation("Menu link"),
+ *       label = @Translation("Menu link tree element")
  *     ),
  *   },
  * )
  */
-class MenuLinkUrlTranslated extends DataProducerPluginBase implements ContainerFactoryPluginInterface {
+class MenuLinkEntity extends DataProducerPluginBase implements ContainerFactoryPluginInterface {
 
   /**
    * Create the menu link translated URL resolver.
@@ -42,8 +42,6 @@ class MenuLinkUrlTranslated extends DataProducerPluginBase implements ContainerF
    *   The plugin id.
    * @param mixed $plugin_definition
    *   The plugin definition.
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
-   *   The module handler service.
    * @param \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository
    *   The entity repository service.
    * @param \Drupal\graphql\GraphQL\Buffers\EntityUuidBuffer $entityBuffer
@@ -53,7 +51,6 @@ class MenuLinkUrlTranslated extends DataProducerPluginBase implements ContainerF
     array $configuration,
     $plugin_id,
     $plugin_definition,
-    protected ModuleHandlerInterface $moduleHandler,
     protected EntityRepositoryInterface $entityRepository,
     protected EntityUuidBuffer $entityBuffer,
   ) {
@@ -68,49 +65,40 @@ class MenuLinkUrlTranslated extends DataProducerPluginBase implements ContainerF
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('module_handler'),
       $container->get('entity.repository'),
       $container->get('graphql.buffer.entity_uuid'),
     );
   }
 
   /**
-   * Resolve the translated menu link url.
+   * Resolve the language of the menu item.
    *
    * @param \Drupal\Core\Menu\MenuLinkInterface $link
-   *   The menu link to resolve the url off of.
+   *   The menu link plugin to resolve the entity.
    *
-   * @return \GraphQL\Deferred|\Drupal\Core\Url
-   *   The Url or the deferred Url.
+   * @return \GraphQL\Deferred|null
+   *   The menu link content entity or null.
    */
-  public function resolve(MenuLinkInterface $link): Deferred|Url {
+  public function resolve(MenuLinkInterface $link): ?Deferred {
 
-    if (!$this->moduleHandler->moduleExists('translatable_menu_link_uri')) {
-      return $link->getUrlObject();
+    if (!$link instanceof MenuLinkContent) {
+      return NULL;
     }
 
-    $plugin_id = $link->getBaseId();
     $derivative_id = $link->getDerivativeId();
 
-    if ($plugin_id !== 'menu_link_content' || empty($derivative_id)) {
-      return $link->getUrlObject();
+    if (!Uuid::isValid($derivative_id)) {
+      return NULL;
     }
 
     $resolver = $this->entityBuffer->add('menu_link_content', $derivative_id);
 
-    return new Deferred(function () use ($link, $resolver) {
+    return new Deferred(function () use ($resolver) {
       if ($entity = $resolver()) {
-        $translated_entity = $this->entityRepository->getTranslationFromContext($entity);
-
-        /** @var \Drupal\link\Plugin\Field\FieldType\LinkItem $link_item|null */
-        $link_item = $translated_entity->link_override->first();
-
-        if (!$link_item->isEmpty()) {
-          return $link_item->getUrl();
-        }
+        return $this->entityRepository->getTranslationFromContext($entity);
       }
 
-      return $link->getUrlObject();
+      return NULL;
     });
   }
 
diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlOverride.php b/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlOverride.php
new file mode 100644
index 0000000000000000000000000000000000000000..134ae0ac1cc35f2e3d8979ac98f0a2eb83b3f265
--- /dev/null
+++ b/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlOverride.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\graphql_compose_menus\Plugin\GraphQL\DataProducer;
+
+use Drupal\Core\Url;
+use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
+use Drupal\menu_link_content\Entity\MenuLinkContent;
+
+/**
+ * Returns the translated URL object of a menu link.
+ *
+ * @DataProducer(
+ *   id = "menu_link_url_override",
+ *   name = @Translation("Menu link translated url"),
+ *   description = @Translation("Returns the translated URL of a menu link."),
+ *   produces = @ContextDefinition("any",
+ *     label = @Translation("URL"),
+ *   ),
+ *   consumes = {
+ *     "entity" = @ContextDefinition("any",
+ *       label = @Translation("Menu link content entity"),
+ *     ),
+ *   },
+ * )
+ */
+class MenuLinkUrlOverride extends DataProducerPluginBase {
+
+  /**
+   * Resolve the translated menu link url.
+   *
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $entity
+   *   The menu link content entity to resolve the url off of.
+   *
+   * @return \Drupal\Core\Url
+   *   The Url.
+   */
+  public function resolve(MenuLinkContent $entity): Url {
+    if ($entity->hasField('link_override')) {
+      /** @var \Drupal\link\LinkItemInterface|null $link_override */
+      $link_override = $entity->get('link_override')->first();
+
+      if ($link_override && !$link_override->isEmpty()) {
+        return $link_override->getUrl();
+      }
+    }
+
+    return $entity->getUrlObject();
+  }
+
+}
diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQL/SchemaExtension/MenusSchemaExtension.php b/modules/graphql_compose_menus/src/Plugin/GraphQL/SchemaExtension/MenusSchemaExtension.php
index ac388d2df098165a5774c616cad006ec596ca882..4c1e0509a8391dcae5ed44564c11bec974e09e06 100644
--- a/modules/graphql_compose_menus/src/Plugin/GraphQL/SchemaExtension/MenusSchemaExtension.php
+++ b/modules/graphql_compose_menus/src/Plugin/GraphQL/SchemaExtension/MenusSchemaExtension.php
@@ -4,11 +4,13 @@ declare(strict_types=1);
 
 namespace Drupal\graphql_compose_menus\Plugin\GraphQL\SchemaExtension;
 
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Menu\MenuLinkInterface;
 use Drupal\Core\Url;
 use Drupal\graphql\GraphQL\ResolverBuilder;
 use Drupal\graphql\GraphQL\ResolverRegistryInterface;
 use Drupal\graphql_compose\Plugin\GraphQL\SchemaExtension\ResolverOnlySchemaExtensionPluginBase;
+use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;
 
 use function Symfony\Component\String\u;
 
@@ -43,6 +45,8 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase {
             ->map('type', $builder->fromValue('MenuAvailable'))
             ->map('value', $builder->fromArgument('name')),
         ),
+
+        $builder->context('menu', $builder->fromParent()),
       )
     );
 
@@ -69,31 +73,87 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase {
     // Menu title.
     $registry->addFieldResolver('MenuItem', 'title',
       $builder->produce('menu_link_label')
-        ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent()))
+        ->map('link', $builder->produce('menu_tree_link')
+          ->map('element', $builder->fromParent())),
       );
 
     // Menu description.
     $registry->addFieldResolver('MenuItem', 'description',
       $builder->produce('menu_link_description')
-        ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent()))
+        ->map('link', $builder->produce('menu_tree_link')
+          ->map('element', $builder->fromParent())),
     );
 
     // Menu url.
     $registry->addFieldResolver('MenuItem', 'url',
       $builder->compose(
-        $builder->produce('menu_link_url_translated')
-          ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent())),
+        $builder->produce('menu_tree_link')
+          ->map('element', $builder->fromParent()),
+
+        $builder->cond([
+          [
+            // Condition: Does the translatable_menu_link_uri module exist?
+            $builder->callback(function (MenuLinkInterface $link) {
+              $module_exists = $this->moduleHandler->moduleExists('translatable_menu_link_uri');
+              return $module_exists && $link instanceof MenuLinkContent;
+            }),
+
+            $builder->produce('menu_link_url_override')
+              ->map('entity', $builder->produce('menu_link_entity')
+                ->map('link', $builder->fromParent())),
+          ], [
+            // Condition: Default. Url as normal.
+            $builder->fromValue(TRUE),
+
+            $builder->produce('menu_link_url')
+              ->map('link', $builder->fromParent()),
+          ],
+        ]),
 
         $builder->produce('url_path')
           ->map('url', $builder->fromParent()),
       )
     );
 
+    // Menu link language.
+    $registry->addFieldResolver('MenuItem', 'langcode',
+      $builder->compose(
+        $builder->produce('menu_tree_link')
+          ->map('element', $builder->fromParent()),
+
+        $builder->cond([
+          [
+            // Condition: Is the link a MenuLinkContent entity?
+            $builder->callback(function (MenuLinkInterface $link) {
+              return $link instanceof MenuLinkContent;
+            }),
+
+            $builder->produce('entity_language')
+              ->map('entity', $builder->produce('menu_link_entity')
+                ->map('link', $builder->fromParent())),
+          ], [
+            // Condition: Default. The parent menu language.
+            $builder->fromValue(TRUE),
+            $builder->produce('entity_language')
+              ->map('entity', $builder->fromContext('menu')),
+          ],
+        ]),
+
+        // Match the shape of the language type.
+        $builder->callback(fn(LanguageInterface $language) => [
+          'id' => $language->getId(),
+          'name' => $language->getName(),
+          'direction' => $language->getDirection(),
+        ]),
+      )
+    );
+
     // Menu internal.
     $registry->addFieldResolver('MenuItem', 'internal',
       $builder->compose(
         $builder->produce('menu_link_url')
-          ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent())),
+          ->map('link', $builder->produce('menu_tree_link')
+            ->map('element', $builder->fromParent())),
 
         $builder->callback(fn(Url $url) => $url->isRouted()),
       )
@@ -102,7 +162,8 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase {
     // Menu expanded.
     $registry->addFieldResolver('MenuItem', 'expanded',
       $builder->produce('menu_link_expanded')
-        ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent()))
+        ->map('link', $builder->produce('menu_tree_link')
+          ->map('element', $builder->fromParent())),
     );
 
     // Menu children.
@@ -111,12 +172,12 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase {
         ->map('element', $builder->fromParent())
     );
 
-    // Menu children.
+    // Menu attributes.
     $registry->addFieldResolver('MenuItem', 'attributes',
-      $builder->produce('menu_tree_link')->map('element', $builder->fromParent()),
+      $builder->produce('menu_tree_link')
+        ->map('element', $builder->fromParent()),
     );
 
-    // Menu attributes.
     $attributes = ['class'];
     if ($this->moduleHandler->moduleExists('menu_link_attributes')) {
       $menu_link_attributes = $this->configFactory->get('menu_link_attributes.config')->get('attributes') ?: [];
@@ -133,11 +194,21 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase {
       );
     }
 
+    // Menu link extras.
+    if ($this->moduleHandler->moduleExists('menu_item_extras')) {
+      $registry->addFieldResolver('MenuItem', 'extras',
+        $builder->produce('menu_link_entity')
+          ->map('link', $builder->produce('menu_tree_link')
+            ->map('element', $builder->fromParent())),
+      );
+    }
+
     // Menu route.
     // This is toggled by users.
     $registry->addFieldResolver('MenuItem', 'route',
       $builder->compose(
-        $builder->produce('menu_tree_link')->map('element', $builder->fromParent()),
+        $builder->produce('menu_tree_link')
+          ->map('element', $builder->fromParent()),
 
         $builder->cond([
           [
diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/EntityType/MenuLinkContent.php b/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/EntityType/MenuLinkContent.php
new file mode 100644
index 0000000000000000000000000000000000000000..a5198eb21d2a2df652da44277d7ce9f3cf99db0e
--- /dev/null
+++ b/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/EntityType/MenuLinkContent.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\graphql_compose_menus\Plugin\GraphQLCompose\EntityType;
+
+use Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeEntityTypeBase;
+use Drupal\graphql_compose\Wrapper\EntityTypeWrapper;
+use Drupal\graphql_compose_menus\Wrapper\MenuLinkContentWrapper;
+
+/**
+ * {@inheritdoc}
+ *
+ * Re-wrap the bundles in a utility wrap to change what is enabled.
+ * This is intended to be used with the menu_item_extras module.
+ *
+ * @GraphQLComposeEntityType(
+ *   id = "menu_link_content",
+ *   prefix = "MenuLinkContent",
+ *   base_fields = {},
+ * )
+ */
+class MenuLinkContent extends GraphQLComposeEntityTypeBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function wrapBundle($bundle): EntityTypeWrapper {
+    return new MenuLinkContentWrapper($this, $bundle);
+  }
+
+}
diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/SchemaType/MenuItem.php b/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/SchemaType/MenuItem.php
index b26a42b63e01cff16231775dfa93e9cd7b9fcff0..89cb75d3dabc09f6bc544f1156d70da082dc4cb1 100644
--- a/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/SchemaType/MenuItem.php
+++ b/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/SchemaType/MenuItem.php
@@ -43,6 +43,10 @@ class MenuItem extends GraphQLComposeSchemaTypeBase {
           'type' => Type::string(),
           'description' => (string) $this->t('The URL of the menu item.'),
         ],
+        'langcode' => [
+          'type' => Type::nonNull(static::type('Language')),
+          'description' => (string) $this->t('The language of the menu item.'),
+        ],
         'internal' => [
           'type' => Type::nonNull(Type::boolean()),
           'description' => (string) $this->t('Whether this menu item links to an internal route.'),
@@ -69,4 +73,25 @@ class MenuItem extends GraphQLComposeSchemaTypeBase {
     return $types;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getExtensions(): array {
+    $extensions = parent::getExtensions();
+
+    if ($this->moduleHandler->moduleExists('menu_item_extras')) {
+      $extensions[] = new ObjectType([
+        'name' => $this->getPluginId(),
+        'fields' => fn() => [
+          'extras' => [
+            'type' => Type::nonNull(static::type('MenuLinkContentUnion')),
+            'description' => (string) $this->t('The menu link content entity associated with this menu link.'),
+          ],
+        ],
+      ]);
+    }
+
+    return $extensions;
+  }
+
 }
diff --git a/modules/graphql_compose_menus/src/Wrapper/MenuLinkContentWrapper.php b/modules/graphql_compose_menus/src/Wrapper/MenuLinkContentWrapper.php
new file mode 100644
index 0000000000000000000000000000000000000000..4243b4f8c3b7c27e900f3bd94563f07d21e6865d
--- /dev/null
+++ b/modules/graphql_compose_menus/src/Wrapper/MenuLinkContentWrapper.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\graphql_compose_menus\Wrapper;
+
+use Drupal\graphql_compose\Wrapper\EntityTypeWrapper;
+
+/**
+ * Override EntityTypeWrapper for MenuLinkContent.
+ */
+class MenuLinkContentWrapper extends EntityTypeWrapper {
+
+  /**
+   * Enable the menu link content type if the menu is enabled.
+   *
+   * @return bool
+   *   True if the bundle is enabled.
+   */
+  public function isEnabled(): bool {
+    $settings = $this->configFactory->get('graphql_compose.settings');
+    return $settings->get('entity_config.menu.' . $this->entity->id() . '.enabled') ?: FALSE;
+  }
+
+}
diff --git a/modules/graphql_compose_routes/src/GraphQL/Buffers/EntityPreviewBuffer.php b/modules/graphql_compose_routes/src/GraphQL/Buffers/EntityPreviewBuffer.php
index fe821ca83b0e6da4fab2cdf5d74a9e862a2a6fbe..924e8382d711d40c4e8f3eae69700b49fff2babc 100644
--- a/modules/graphql_compose_routes/src/GraphQL/Buffers/EntityPreviewBuffer.php
+++ b/modules/graphql_compose_routes/src/GraphQL/Buffers/EntityPreviewBuffer.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose_routes\GraphQL\Buffers;
 
 use Drupal\Core\ParamConverter\ParamConverterInterface;
diff --git a/modules/graphql_compose_routes/src/GraphQL/Buffers/SubrequestBuffer.php b/modules/graphql_compose_routes/src/GraphQL/Buffers/SubrequestBuffer.php
index d7edbc93dd5344d8f8b9258398000ad8fe7e2b66..a33d21082a438e2c09b3a8f1bd962766f8ae9e5a 100644
--- a/modules/graphql_compose_routes/src/GraphQL/Buffers/SubrequestBuffer.php
+++ b/modules/graphql_compose_routes/src/GraphQL/Buffers/SubrequestBuffer.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose_routes\GraphQL\Buffers;
 
 use Drupal\Core\Cache\CacheableDependencyInterface;
diff --git a/src/Form/SchemaForm.php b/src/Form/SchemaForm.php
index ab30d37631b6ad46d92ea79d909ef4e7a431aceb..cfd9a34f7dc8e527a2ed3c2fd80c69dee1039b67 100644
--- a/src/Form/SchemaForm.php
+++ b/src/Form/SchemaForm.php
@@ -174,7 +174,7 @@ class SchemaForm extends ConfigFormBase {
         foreach ($entity_bundles as $bundle) {
           $this->buildEntityTypeBundle($form, $form_state, $entity_type, $bundle);
           if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
-            $this->buildEntityTypeBundleFields($form, $form_state, $entity_type, $bundle);
+            $this->buildEntityTypeBundleFields($form, $form_state, $entity_type, $bundle->id());
           }
         }
       }
@@ -195,7 +195,7 @@ class SchemaForm extends ConfigFormBase {
    * @param \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\EntityTypeInterface $bundle
    *   The entity bundle.
    */
-  protected function buildEntityTypeBundle(array &$form, FormStateInterface $form_state, EntityTypeInterface $entity_type, EntityInterface|EntityTypeInterface $bundle) {
+  public function buildEntityTypeBundle(array &$form, FormStateInterface $form_state, EntityTypeInterface $entity_type, EntityInterface|EntityTypeInterface $bundle) {
     $entity_type_id = $entity_type->id();
     $bundle_id = $bundle->id();
     $settings = $this->getConfig()->get("entity_config.$entity_type_id.$bundle_id") ?: [];
@@ -255,11 +255,10 @@ class SchemaForm extends ConfigFormBase {
    *   The form state.
    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
    *   The entity type.
-   * @param \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\EntityTypeInterface $bundle
-   *   The entity bundle.
+   * @param string $bundle_id
+   *   The entity bundle id.
    */
-  protected function buildEntityTypeBundleFields(array &$form, FormStateInterface $form_state, EntityTypeInterface $entity_type, EntityInterface|EntityTypeInterface $bundle) {
-    $bundle_id = $bundle->id();
+  public function buildEntityTypeBundleFields(array &$form, FormStateInterface $form_state, EntityTypeInterface $entity_type, string $bundle_id) {
     $entity_type_id = $entity_type->id();
 
     $fields = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_id);
diff --git a/src/Plugin/GraphQL/DataProducer/ContextLanguage.php b/src/Plugin/GraphQL/DataProducer/ContextLanguage.php
index 8f1705e25d46be15d60c47322d66b429a7faa75a..bc26257e3f0ecce3da80fde55c546f7410ce8420 100644
--- a/src/Plugin/GraphQL/DataProducer/ContextLanguage.php
+++ b/src/Plugin/GraphQL/DataProducer/ContextLanguage.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose\Plugin\GraphQL\DataProducer;
 
 use Drupal\Core\Language\LanguageManagerInterface;
diff --git a/src/Plugin/GraphQL/DataProducer/EntityLoadByUuidOrId.php b/src/Plugin/GraphQL/DataProducer/EntityLoadByUuidOrId.php
index bd680e5d7ac045095d7813981cf273611b8cb101..28139b9dc779d4fbbb563c83cb88a644e00f6e09 100644
--- a/src/Plugin/GraphQL/DataProducer/EntityLoadByUuidOrId.php
+++ b/src/Plugin/GraphQL/DataProducer/EntityLoadByUuidOrId.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Drupal\graphql_compose\Plugin\GraphQL\DataProducer;
 
 use Drupal\Component\Uuid\Uuid;
diff --git a/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php b/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php
index e2b69b188cf4597dccc3538939945b53614c17f0..06576905c68121c5a5f1f4c8f1d126e8f2f35efb 100644
--- a/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php
+++ b/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php
@@ -191,6 +191,19 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ
     return $bundles[$bundle_id] ?? NULL;
   }
 
+  /**
+   * Wrap a bundle into a utility wrapper.
+   *
+   * @param mixed $bundle
+   *   The bundle to wrap.
+   *
+   * @return \Drupal\graphql_compose\Wrapper\EntityTypeWrapper
+   *   The wrapped bundle.
+   */
+  protected function wrapBundle($bundle): EntityTypeWrapper {
+    return new EntityTypeWrapper($this, $bundle);
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -209,7 +222,7 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ
     }
 
     foreach (array_keys($bundle_info) as $bundle_id) {
-      $bundle = new EntityTypeWrapper($this, $entity_types[$bundle_id] ?? $entity_type);
+      $bundle = $this->wrapBundle($entity_types[$bundle_id] ?? $entity_type);
       if ($bundle->isEnabled()) {
         $this->bundles[$bundle_id] = $bundle;
       }
diff --git a/src/Plugin/GraphQLComposeFieldTypeManager.php b/src/Plugin/GraphQLComposeFieldTypeManager.php
index 26229c0e47bdaeb849dfdb47d9d77b3785e5033f..02ad82d666e6e3df2d27596413c90462b542f262 100644
--- a/src/Plugin/GraphQLComposeFieldTypeManager.php
+++ b/src/Plugin/GraphQLComposeFieldTypeManager.php
@@ -149,7 +149,7 @@ class GraphQLComposeFieldTypeManager extends DefaultPluginManager {
       throw new \Exception('Property config missing field_name');
     }
 
-    // Set required property config if not set yet.
+    // Set required property config is not set yet.
     $config = array_merge([
       'field_type' => 'property',
       'name_sdl' => $config['field_name'],
@@ -191,8 +191,9 @@ class GraphQLComposeFieldTypeManager extends DefaultPluginManager {
   /**
    * All defined fields that have been created at time of invocation.
    *
-   * @return \Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeFieldTypeInterface[]
-   *   An array of fields that have been initialized.
+   * @return array
+   *   An array of fields that have been initialized,
+   *   keyed by entity type and bundle.
    */
   public function getFields(): array {
     return $this->fields;
diff --git a/tests/src/Functional/Contrib/MenuItemExtrasTest.php b/tests/src/Functional/Contrib/MenuItemExtrasTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fec884968995cadb9831baaf87942ef479bd1bb8
--- /dev/null
+++ b/tests/src/Functional/Contrib/MenuItemExtrasTest.php
@@ -0,0 +1,212 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\graphql_compose\Functional\Core;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\menu_link_content\Entity\MenuLinkContent;
+use Drupal\system\Entity\Menu;
+use Drupal\Tests\graphql_compose\Functional\GraphQLComposeBrowserTestBase;
+
+/**
+ * Tests specific to GraphQL Compose menus with menu item extras.
+ *
+ * @group legacy
+ */
+class MenuItemExtrasTest extends GraphQLComposeBrowserTestBase {
+
+  /**
+   * The test menu.
+   *
+   * @var \Drupal\system\MenuInterface[]
+   */
+  protected array $menus;
+
+  /**
+   * The test links.
+   *
+   * @var \Drupal\menu_link_content\Entity\MenuLinkContent[]
+   */
+  protected array $links;
+
+  /**
+   * The test nodes.
+   *
+   * @var \Drupal\node\NodeInterface[]
+   */
+  protected array $nodes;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'menu_link_content',
+    'menu_item_extras',
+    'graphql_compose_menus',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->nodes[1] = $this->createNode([
+      'title' => 'Test node 1',
+    ]);
+
+    $this->nodes[2] = $this->createNode([
+      'title' => 'Test node 2',
+    ]);
+
+    $this->menus[1] = Menu::create([
+      'id' => 'test_with',
+      'label' => 'Test Menu with fields',
+    ]);
+
+    $this->menus[2] = Menu::create([
+      'id' => 'test_without',
+      'label' => 'Test Menu without fields',
+    ]);
+
+    $this->menus[1]->save();
+    $this->menus[2]->save();
+
+    FieldStorageConfig::create([
+      'field_name' => 'bingo',
+      'type' => 'string',
+      'entity_type' => 'menu_link_content',
+    ])->save();
+
+    FieldConfig::create([
+      'field_name' => 'bingo',
+      'entity_type' => 'menu_link_content',
+      'bundle' => $this->menus[1]->id(),
+      'label' => 'Bingo',
+      'required' => FALSE,
+    ])->save();
+
+    $this->links[1] = MenuLinkContent::create([
+      'title' => 'Test link 1',
+      'link' => ['uri' => 'internal:/node/' . $this->nodes[1]->id()],
+      'menu_name' => $this->menus[1]->id(),
+      'bingo' => 'Bingo!',
+      'weight' => 1,
+    ]);
+
+    $this->links[2] = MenuLinkContent::create([
+      'title' => 'Test link 2',
+      'link' => ['uri' => 'internal:/node/' . $this->nodes[2]->id()],
+      'menu_name' => $this->menus[1]->id(),
+      'weight' => 2,
+    ]);
+
+    $this->links[3] = MenuLinkContent::create([
+      'title' => 'Test external',
+      'link' => ['uri' => 'https://www.google.com'],
+      'menu_name' => $this->menus[1]->id(),
+      'weight' => 3,
+    ]);
+
+    $this->links[4] = MenuLinkContent::create([
+      'title' => 'Test link on without menu',
+      'link' => ['uri' => 'internal:/node/' . $this->nodes[1]->id()],
+      'menu_name' => $this->menus[2]->id(),
+      'weight' => 1,
+    ]);
+
+    foreach ($this->links as $link) {
+      $link->save();
+    }
+
+    $this->setEntityConfig('menu', 'test_with', [
+      'enabled' => TRUE,
+    ]);
+
+    $this->setEntityConfig('menu', 'test_without', [
+      'enabled' => TRUE,
+    ]);
+
+    $this->setFieldConfig('menu_link_content', 'test_with', 'bingo', [
+      'enabled' => TRUE,
+    ]);
+  }
+
+  /**
+   * Test menu loads a menu with a field.
+   */
+  public function testMenuLoadWithField(): void {
+    $query = <<<GQL
+      query {
+        menu(name: TEST_WITH) {
+          id
+          name
+          items {
+            title
+            extras {
+              ... on MenuLinkContentInterface {
+                id
+              }
+              ... on MenuLinkContentTestWith {
+                bingo
+              }
+            }
+          }
+        }
+      }
+    GQL;
+
+    $content = $this->executeQuery($query);
+
+    $menu = $content['data']['menu'];
+
+    $this->assertEquals($this->menus[1]->uuid(), $menu['id']);
+    $this->assertEquals($this->menus[1]->label(), $menu['name']);
+
+    $this->assertCount(3, $menu['items']);
+
+    $this->assertEquals('Test link 1', $menu['items'][0]['title']);
+    $this->assertEquals('Test link 2', $menu['items'][1]['title']);
+
+    $this->assertEquals('Bingo!', $menu['items'][0]['extras']['bingo']);
+    $this->assertNull($menu['items'][1]['extras']['bingo']);
+  }
+
+  /**
+   * Test menu loads a menu with a field.
+   */
+  public function testMenuLoadWithoutField(): void {
+    $query = <<<GQL
+      query {
+        menu(name: TEST_WITHOUT) {
+          id
+          name
+          items {
+            title
+            extras {
+              ... on MenuLinkContentInterface {
+                id
+              }
+            }
+          }
+        }
+      }
+    GQL;
+
+    $content = $this->executeQuery($query);
+
+    $menu = $content['data']['menu'];
+
+    $this->assertEquals($this->menus[2]->uuid(), $menu['id']);
+    $this->assertEquals($this->menus[2]->label(), $menu['name']);
+
+    $this->assertCount(1, $menu['items']);
+
+    $this->assertEquals('Test link on without menu', $menu['items'][0]['title']);
+
+    $this->assertArrayNotHasKey('bingo', $menu['items'][0]['extras']);
+  }
+
+}
diff --git a/tests/src/Functional/Core/MenusTest.php b/tests/src/Functional/Core/MenusTest.php
index 8cd2b5c490b203f728882d389586eb31e6586f52..b51d8e5a40c29ff7b422aaecf51638515d791a45 100644
--- a/tests/src/Functional/Core/MenusTest.php
+++ b/tests/src/Functional/Core/MenusTest.php
@@ -95,6 +95,13 @@ class MenusTest extends GraphQLComposeBrowserTestBase {
       'enabled' => FALSE,
     ]);
 
+    $this->links[5] = MenuLinkContent::create([
+      'title' => 'Test external',
+      'link' => ['uri' => 'https://www.google.com'],
+      'menu_name' => $this->menu->id(),
+      'weight' => 6,
+    ]);
+
     foreach ($this->links as $link) {
       $link->save();
     }
@@ -129,13 +136,16 @@ class MenusTest extends GraphQLComposeBrowserTestBase {
     $this->assertEquals($this->menu->uuid(), $menu['id']);
     $this->assertEquals($this->menu->label(), $menu['name']);
 
-    $this->assertCount(2, $menu['items']);
+    $this->assertCount(3, $menu['items']);
 
     // Sort weight sorting.
     $this->assertEquals('Test link 2', $menu['items'][0]['title']);
+    $this->assertEquals('Test external', $menu['items'][1]['title']);
+    $this->assertEquals('Test link 1', $menu['items'][2]['title']);
 
     // Test internal link.
     $this->assertTrue($menu['items'][0]['internal']);
+    $this->assertFalse($menu['items'][1]['internal']);
   }
 
   /**
@@ -162,7 +172,7 @@ class MenusTest extends GraphQLComposeBrowserTestBase {
 
     $this->assertEmpty($menu['items'][0]['children']);
 
-    $this->assertEquals('Test link child', $menu['items'][1]['children'][0]['title']);
+    $this->assertEquals('Test link child', $menu['items'][2]['children'][0]['title']);
   }
 
 }