diff --git a/src/Add-ons/Addon.cxx b/src/Add-ons/Addon.cxx
index ee6686db0..fe7411e81 100644
--- a/src/Add-ons/Addon.cxx
+++ b/src/Add-ons/Addon.cxx
@@ -29,7 +29,9 @@
 #include <simgear/nasal/cppbind/NasalHash.hxx>
 #include <simgear/nasal/naref.h>
 #include <simgear/props/props.hxx>
+#include <simgear/props/props_io.hxx>
 
+#include <Main/fg_props.hxx>
 #include <Main/globals.hxx>
 #include <Scripting/NasalSys.hxx>
 
@@ -266,6 +268,22 @@ int Addon::getLoadSequenceNumber() const
 void Addon::setLoadSequenceNumber(int num)
 { _loadSequenceNumber = num; }
 
+std::vector<SGPropertyNode_ptr> Addon::getMenubarNodes() const
+{ return _menubarNodes; }
+
+void Addon::setMenubarNodes(const std::vector<SGPropertyNode_ptr>& menubarNodes)
+{ _menubarNodes = menubarNodes; }
+
+void Addon::addToFGMenubar() const
+{
+  SGPropertyNode* menuRootNode = fgGetNode("/sim/menubar/default", true);
+
+  for (const auto& node: getMenubarNodes()) {
+    SGPropertyNode* childNode = menuRootNode->addChild("menu");
+    ::copyProperties(node.ptr(), childNode);
+  }
+}
+
 std::string Addon::str() const
 {
   std::ostringstream oss;
@@ -311,9 +329,81 @@ Addon Addon::fromAddonDir(const SGPath& addonPath)
   addon.setSupportUrl(std::move(metadata.supportUrl));
   addon.setCodeRepositoryUrl(std::move(metadata.codeRepositoryUrl));
 
+  SGPath menuFile = addonPath / "addon-menubar-items.xml";
+
+  if (menuFile.exists()) {
+    addon.setMenubarNodes(readMenubarItems(menuFile));
+  }
+
   return addon;
 }
 
+// Static method
+std::vector<SGPropertyNode_ptr>
+Addon::readMenubarItems(const SGPath& menuFile)
+{
+  SGPropertyNode rootNode;
+
+  try {
+    readProperties(menuFile, &rootNode);
+  } catch (const sg_exception &e) {
+    throw errors::error_loading_menubar_items_file(
+      "unable to load add-on menu bar items from file '" +
+      menuFile.utf8Str() + "': " + e.getFormattedMessage());
+  }
+
+  // Check the 'meta' section
+  SGPropertyNode *metaNode = rootNode.getChild("meta");
+  if (metaNode == nullptr) {
+    throw errors::error_loading_menubar_items_file(
+      "no /meta node found in add-on menu bar items file '" +
+      menuFile.utf8Str() + "'");
+  }
+
+  // Check the file type
+  SGPropertyNode *fileTypeNode = metaNode->getChild("file-type");
+  if (fileTypeNode == nullptr) {
+    throw errors::error_loading_menubar_items_file(
+      "no /meta/file-type node found in add-on menu bar items file '" +
+      menuFile.utf8Str() + "'");
+  }
+
+  string fileType = fileTypeNode->getStringValue();
+  if (fileType != "FlightGear add-on menu bar items") {
+    throw errors::error_loading_menubar_items_file(
+      "Invalid /meta/file-type value for add-on menu bar items file '" +
+      menuFile.utf8Str() + "': '" + fileType + "' "
+      "(expected 'FlightGear add-on menu bar items')");
+  }
+
+  // Check the format version
+  SGPropertyNode *fmtVersionNode = metaNode->getChild("format-version");
+  if (fmtVersionNode == nullptr) {
+    throw errors::error_loading_menubar_items_file(
+      "no /meta/format-version node found in add-on menu bar items file '" +
+      menuFile.utf8Str() + "'");
+  }
+
+  int formatVersion = fmtVersionNode->getIntValue();
+  if (formatVersion != 1) {
+    throw errors::error_loading_menubar_items_file(
+      "unknown format version in add-on menu bar items file '" +
+      menuFile.utf8Str() + "': " + std::to_string(formatVersion));
+  }
+
+  SG_LOG(SG_GENERAL, SG_DEBUG,
+         "Loaded add-on menu bar items from '" << menuFile.utf8Str() + "'");
+
+  SGPropertyNode *menubarItemsNode = rootNode.getChild("menubar-items");
+  std::vector<SGPropertyNode_ptr> res;
+
+  if (menubarItemsNode != nullptr) {
+    res = menubarItemsNode->getChildren("menu");
+  }
+
+  return res;
+}
+
 // Static method
 void Addon::setupGhost(nasal::Hash& addonsModule)
 {
diff --git a/src/Add-ons/Addon.hxx b/src/Add-ons/Addon.hxx
index 9cb3c5ad6..062216d43 100644
--- a/src/Add-ons/Addon.hxx
+++ b/src/Add-ons/Addon.hxx
@@ -173,6 +173,12 @@ public:
   // Get all non-empty URLs pertaining to this add-on
   std::multimap<UrlType, QualifiedUrl> getUrls() const;
 
+  // Getter and setter for the menu bar item nodes of the add-on
+  std::vector<SGPropertyNode_ptr> getMenubarNodes() const;
+  void setMenubarNodes(const std::vector<SGPropertyNode_ptr>& menubarNodes);
+  // Add the menus defined in addon-menubar-items.xml to /sim/menubar/default
+  void addToFGMenubar() const;
+
   // Simple string representation
   std::string str() const;
 
@@ -186,6 +192,10 @@ private:
   static SGPath getMetadataFile(const SGPath& addonPath);
   SGPath getMetadataFile() const;
 
+  // Read all menus from addon-menubar-items.xml (under the add-on base path)
+  static std::vector<SGPropertyNode_ptr>
+  readMenubarItems(const SGPath& menuFile);
+
   // The add-on identifier, in reverse DNS style. The AddonManager refuses to
   // register two add-ons with the same id in a given FlightGear session.
   std::string _id;
@@ -226,6 +236,8 @@ private:
   std::string _triggerProperty;
   // Semantics explained above
   int _loadSequenceNumber = -1;
+
+  std::vector<SGPropertyNode_ptr> _menubarNodes;
 };
 
 std::ostream& operator<<(std::ostream& os, const Addon& addon);
diff --git a/src/Add-ons/AddonManager.cxx b/src/Add-ons/AddonManager.cxx
index 74ab00e49..9faae1ca2 100644
--- a/src/Add-ons/AddonManager.cxx
+++ b/src/Add-ons/AddonManager.cxx
@@ -255,6 +255,14 @@ SGPropertyNode_ptr AddonManager::addonNode(const string& addonId) const
   return getAddon(addonId)->getAddonNode();
 }
 
+void
+AddonManager::addAddonMenusToFGMenubar() const
+{
+  for (const auto& addon: _registeredAddons) {
+    addon->addToFGMenubar();
+  }
+}
+
 } // of namespace addons
 
 } // of namespace flightgear
diff --git a/src/Add-ons/AddonManager.hxx b/src/Add-ons/AddonManager.hxx
index 942888b69..53a614c91 100644
--- a/src/Add-ons/AddonManager.hxx
+++ b/src/Add-ons/AddonManager.hxx
@@ -86,6 +86,10 @@ public:
   // Base node pertaining to the add-on in the Global Property Tree
   SGPropertyNode_ptr addonNode(const std::string& addonId) const;
 
+  // Add the 'menu' nodes defined by each registered add-on to
+  // /sim/menubar/default
+  void addAddonMenusToFGMenubar() const;
+
 private:
   // Constructor called from createInstance() only
   explicit AddonManager() = default;
diff --git a/src/Add-ons/addon_fwd.hxx b/src/Add-ons/addon_fwd.hxx
index 390953282..cb5ef9889 100644
--- a/src/Add-ons/addon_fwd.hxx
+++ b/src/Add-ons/addon_fwd.hxx
@@ -56,6 +56,7 @@ class error;
 class error_loading_config_file;
 class no_metadata_file_found;
 class error_loading_metadata_file;
+class error_loading_menubar_items_file;
 class duplicate_registration_attempt;
 class fg_version_too_old;
 class fg_version_too_recent;
diff --git a/src/Add-ons/exceptions.hxx b/src/Add-ons/exceptions.hxx
index 08ede87d6..0fb162cf1 100644
--- a/src/Add-ons/exceptions.hxx
+++ b/src/Add-ons/exceptions.hxx
@@ -50,6 +50,9 @@ class no_metadata_file_found : public error
 class error_loading_metadata_file : public error
 { using error::error; };
 
+class error_loading_menubar_items_file : public error
+{ using error::error; };
+
 class duplicate_registration_attempt : public error
 { using error::error; };
 
diff --git a/src/Main/main.cxx b/src/Main/main.cxx
index 74a61afc6..40578b8d4 100644
--- a/src/Main/main.cxx
+++ b/src/Main/main.cxx
@@ -585,6 +585,9 @@ int fgMainInit( int argc, char **argv )
     SG_LOG(SG_GENERAL, SG_INFO,
            "EmbeddedResourceManager: selected locale '" << locale << "'");
 
+    // Copy the property nodes for the menus added by registered add-ons
+    addons::AddonManager::instance()->addAddonMenusToFGMenubar();
+
     // Initialize the Window/Graphics environment.
     fgOSInit(&argc, argv);
     _bootstrap_OSInit++;