diff --git a/arduino/cores/packagemanager/loader.go b/arduino/cores/packagemanager/loader.go
index ac1b82e7118..3b94fc3a76d 100644
--- a/arduino/cores/packagemanager/loader.go
+++ b/arduino/cores/packagemanager/loader.go
@@ -19,6 +19,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/arduino/arduino-cli/arduino/cores"
 	"github.com/arduino/arduino-cli/configuration"
@@ -250,8 +251,7 @@ func (pm *PackageManager) loadPlatform(targetPackage *cores.Package, platformPat
 			pm.Log.Infof("Package is built-in")
 		}
 		if err := pm.loadPlatformRelease(release, platformPath); err != nil {
-			return status.Newf(codes.FailedPrecondition, "loading platform release: %s", err)
-
+			return status.Newf(codes.FailedPrecondition, "loading platform release %s: %s", release, err)
 		}
 		pm.Log.WithField("platform", release).Infof("Loaded platform")
 
@@ -279,7 +279,7 @@ func (pm *PackageManager) loadPlatform(targetPackage *cores.Package, platformPat
 			}
 			release := platform.GetOrCreateRelease(version)
 			if err := pm.loadPlatformRelease(release, versionDir); err != nil {
-				return status.Newf(codes.FailedPrecondition, "loading platform release: %s", err)
+				return status.Newf(codes.FailedPrecondition, "loading platform release %s: %s", release, err)
 			}
 			pm.Log.WithField("platform", release).Infof("Loaded platform")
 		}
@@ -341,7 +341,7 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p
 	}
 
 	if err := pm.loadBoards(platform); err != nil {
-		return err
+		return fmt.Errorf("loading boards: %s", err)
 	}
 
 	return nil
@@ -374,14 +374,47 @@ func (pm *PackageManager) loadBoards(platform *cores.PlatformRelease) error {
 
 	propertiesByBoard := boardsProperties.FirstLevelOf()
 
-	platform.Menus = propertiesByBoard["menu"]
+	if menus, ok := propertiesByBoard["menu"]; ok {
+		platform.Menus = menus
+	} else {
+		platform.Menus = properties.NewMap()
+	}
 
-	delete(propertiesByBoard, "menu") // TODO: check this one
+	// This is not a board id so we remove it to correctly
+	// set all other boards properties
+	delete(propertiesByBoard, "menu")
 
+	skippedBoards := []string{}
 	for boardID, boardProperties := range propertiesByBoard {
-		boardProperties.Set("_id", boardID) // TODO: What is that for??
-		board := platform.GetOrCreateBoard(boardID)
+		var board *cores.Board
+		for key := range boardProperties.AsMap() {
+			if !strings.HasPrefix(key, "menu.") {
+				continue
+			}
+			// Menu keys are formed like this:
+			//     menu.cache.off=false
+			//     menu.cache.on=true
+			// so we assume that the a second element in the slice exists
+			menuName := strings.Split(key, ".")[1]
+			if !platform.Menus.ContainsKey(menuName) {
+				fqbn := fmt.Sprintf("%s:%s:%s", platform.Platform.Package.Name, platform.Platform.Architecture, boardID)
+				skippedBoards = append(skippedBoards, fqbn)
+				goto next_board
+			}
+		}
+		// The board's ID must be available in a board's properties since it can
+		// be used in all configuration files for several reasons, like setting compilation
+		// flags depending on the board id.
+		// For more info:
+		// https://arduino.github.io/arduino-cli/dev/platform-specification/#global-predefined-properties
+		boardProperties.Set("_id", boardID)
+		board = platform.GetOrCreateBoard(boardID)
 		board.Properties.Merge(boardProperties)
+	next_board:
+	}
+
+	if len(skippedBoards) > 0 {
+		return fmt.Errorf("skipping loading of boards %s: malformed custom board options", strings.Join(skippedBoards, ", "))
 	}
 
 	return nil
diff --git a/arduino/cores/packagemanager/package_manager_test.go b/arduino/cores/packagemanager/package_manager_test.go
index 0c12edb506f..ff51923c35a 100644
--- a/arduino/cores/packagemanager/package_manager_test.go
+++ b/arduino/cores/packagemanager/package_manager_test.go
@@ -228,8 +228,10 @@ func TestFindToolsRequiredForBoard(t *testing.T) {
 	loadIndex("https://dl.espressif.com/dl/package_esp32_index.json")
 	loadIndex("http://arduino.esp8266.com/stable/package_esp8266com_index.json")
 	loadIndex("https://adafruit.github.io/arduino-board-index/package_adafruit_index.json")
-	errs := pm.LoadHardware()
-	require.Len(t, errs, 0)
+	// We ignore the errors returned since they might not be necessarily blocking
+	// but just warnings for the user, like in the case a board is not loaded
+	// because of malformed menus
+	pm.LoadHardware()
 	esp32, err := pm.FindBoardWithFQBN("esp32:esp32:esp32")
 	require.NoError(t, err)
 	esptool231 := pm.FindToolDependency(&cores.ToolDependency{
diff --git a/legacy/builder/test/user_hardware/my_avr_platform/avr/boards.txt b/legacy/builder/test/user_hardware/my_avr_platform/avr/boards.txt
index 35bd354871d..5b50b4dfaf1 100644
--- a/legacy/builder/test/user_hardware/my_avr_platform/avr/boards.txt
+++ b/legacy/builder/test/user_hardware/my_avr_platform/avr/boards.txt
@@ -1,3 +1,5 @@
+menu.cpu=Processor
+
 custom_yun.name=Arduino Yún
 custom_yun.upload.via_ssh=true
 
diff --git a/test/test_core.py b/test/test_core.py
index 8b255fbe1ec..e01517589ec 100644
--- a/test/test_core.py
+++ b/test/test_core.py
@@ -14,6 +14,7 @@
 # a commercial license, send an email to license@arduino.cc.
 import os
 import datetime
+import shutil
 import time
 import platform
 import pytest
@@ -675,3 +676,39 @@ def test_core_list_platform_without_platform_txt(run_command, data_dir):
     core = cores[0]
     assert core["id"] == "some-packager:some-arch"
     assert core["name"] == "some-packager-some-arch"
+
+
+def test_core_with_wrong_custom_board_options_is_loaded(run_command, data_dir):
+    test_platform_name = "platform_with_wrong_custom_board_options"
+    platform_install_dir = Path(data_dir, "hardware", "arduino-beta-dev", test_platform_name)
+    platform_install_dir.mkdir(parents=True)
+
+    # Install platform in Sketchbook hardware dir
+    shutil.copytree(
+        Path(__file__).parent / "testdata" / test_platform_name, platform_install_dir, dirs_exist_ok=True,
+    )
+
+    assert run_command("update")
+
+    res = run_command("core list --format json")
+    assert res.ok
+
+    cores = json.loads(res.stdout)
+    mapped = {core["id"]: core for core in cores}
+    assert len(mapped) == 1
+    # Verifies platform is loaded except excluding board with wrong options
+    assert "arduino-beta-dev:platform_with_wrong_custom_board_options" in mapped
+    boards = {b["fqbn"]: b for b in mapped["arduino-beta-dev:platform_with_wrong_custom_board_options"]["boards"]}
+    assert len(boards) == 1
+    # Verify board with malformed options is not loaded
+    assert "arduino-beta-dev:platform_with_wrong_custom_board_options:nessuno" not in boards
+    # Verify other board is loaded
+    assert "arduino-beta-dev:platform_with_wrong_custom_board_options:altra" in boards
+    # Verify warning is shown to user
+    assert (
+        "Error initializing instance: "
+        + "loading platform release arduino-beta-dev:platform_with_wrong_custom_board_options@4.2.0: "
+        + "loading boards: "
+        + "skipping loading of boards arduino-beta-dev:platform_with_wrong_custom_board_options:nessuno: "
+        + "malformed custom board options"
+    ) in res.stderr
diff --git a/test/testdata/platform_with_wrong_custom_board_options/boards.txt b/test/testdata/platform_with_wrong_custom_board_options/boards.txt
new file mode 100644
index 00000000000..7eb84395f77
--- /dev/null
+++ b/test/testdata/platform_with_wrong_custom_board_options/boards.txt
@@ -0,0 +1,63 @@
+menu.cpu=Processor
+
+nessuno.name=Arduino Nessuno
+nessuno.vid.0=0x2341
+nessuno.pid.0=0x0043
+nessuno.vid.1=0x2341
+nessuno.pid.1=0x0001
+nessuno.vid.2=0x2A03
+nessuno.pid.2=0x0043
+nessuno.vid.3=0x2341
+nessuno.pid.3=0x0243
+nessuno.upload.tool=avrdude
+nessuno.upload.protocol=arduino
+nessuno.upload.maximum_size=32256
+nessuno.upload.maximum_data_size=2048
+nessuno.upload.speed=115200
+nessuno.bootloader.tool=avrdude
+nessuno.bootloader.low_fuses=0xFF
+nessuno.bootloader.high_fuses=0xDE
+nessuno.bootloader.extended_fuses=0xFD
+nessuno.bootloader.unlock_bits=0x3F
+nessuno.bootloader.lock_bits=0x0F
+nessuno.bootloader.file=optiboot/optiboot_atmega328.hex
+nessuno.build.mcu=atmega328p
+nessuno.build.f_cpu=16000000L
+nessuno.build.board=AVR_NESSUNO
+nessuno.build.core=arduino
+nessuno.build.variant=standard
+
+nessuno.menu.cache.on=Enabled
+nessuno.menu.cache.on.build.cache_flags=-DENABLE_CACHE
+nessuno.menu.cache.off=Disabled
+nessuno.menu.cache.off.build.cache_flags=
+
+altra.name=Arduino Altra
+altra.vid.0=0x2341
+altra.pid.0=0x0043
+altra.vid.1=0x2341
+altra.pid.1=0x0001
+altra.vid.2=0x2A03
+altra.pid.2=0x0043
+altra.vid.3=0x2341
+altra.pid.3=0x0243
+altra.upload.tool=avrdude
+altra.upload.protocol=arduino
+altra.upload.maximum_size=32256
+altra.upload.maximum_data_size=2048
+altra.upload.speed=115200
+altra.bootloader.tool=avrdude
+altra.bootloader.low_fuses=0xFF
+altra.bootloader.high_fuses=0xDE
+altra.bootloader.extended_fuses=0xFD
+altra.bootloader.unlock_bits=0x3F
+altra.bootloader.lock_bits=0x0F
+altra.bootloader.file=optiboot/optiboot_atmega328.hex
+altra.build.mcu=atmega328p
+altra.build.f_cpu=16000000L
+altra.build.board=AVR_ALTRA
+altra.build.core=arduino
+altra.build.variant=standard
+altra.menu.cpu.atmega328=ATmega328P
+altra.menu.cpu.atmega168=ATmega168
+altra.menufoo=bar
diff --git a/test/testdata/platform_with_wrong_custom_board_options/platform.txt b/test/testdata/platform_with_wrong_custom_board_options/platform.txt
new file mode 100644
index 00000000000..682f130ed49
--- /dev/null
+++ b/test/testdata/platform_with_wrong_custom_board_options/platform.txt
@@ -0,0 +1,2 @@
+name=Arduino Test Boards
+version=4.2.0
\ No newline at end of file