MENU.LUA(8)
π menu.lua β the bootloader dynamic menu module, explained by your resident Unix bestie
Okay so this is menu.lua(8), from the System Manager's Manual, and it's literally the whole personality of the bootloader menu system. Buckle up π
β¨ NAME β¨
menu.lua β the bootloader's dynamic menu module. That's it, that's the vibe. π
πΈ DESCRIPTION πΈ
So menu.lua contains the main functionality you need to build a dynamic menu system. It also contains definitions for the built-in menus, and some of those are influenced by loader(8) environment variables. Sneaky but iconic. β¨
Before you hook into anything menu.lua gives you, you gotta include it, like this:
local menu = require("menu")
No cap, that's the whole entry ritual. π«Ά
π¦ MENU DEFINITIONS π¦
Menus are represented in menu.lua as a table, and that table must contain an entries key. Non-negotiable, bestie.
If the value of the entries key is itself a table, then each value in this table defines a single entry in this menu. Peep "MENU ITEM DEFINITIONS" for the structure of each entry. π
entries may also be a function. That function must return a table, and each value of the returned table defines a single entry in this menu. Again, see "MENU ITEM DEFINITIONS". π
π§ MENU ITEM DEFINITIONS π§
Here are the keys you may define for a menu item β the whole glow-up kit:
entry_typeβ the type of this menu entry. See "MENU ITEM TYPES". πcarousel_idβ a unique string id for this carousel. A carousel is a menu entry that rotates through a selection of items. This is used for storage of the carousel's current setting. πvisibleβ a lambda that returnstrueif this menu item should be visible andfalseif it should not be visible. iykyk.itemsβ a table (or a lambda that returns a table) of the possible choices for this carousel.nameβ a string (or a lambda that returns a string) containing the current name of this item.funcβ the function executed when this entry is selected. Every type except forcore.MENU_SEPARATORmay have afunc. π₯submenuβ the submenu menu definition to draw when this entry is selected.aliasβ a table of case-sensitive aliases for this menu entry. All menu entries that can be selected may have any number of alias entries. Yes, case-sensitive, so don't get cute. π
So like, entry_type is the only required key for every entry type. And name is required for all entry types except for core.MENU_SEPARATOR. That's the rule, periodt.
π MENU ITEM TYPES π
The menu item type constants are defined in core.lua(8). These are the available types, and honestly? Each one ate:
core.MENU_RETURNβ return to the parent menu. If the current menu is the default menu,menu.luawill exit the menu and begin the autoboot sequence (if applicable). This type of menu entry may executefunc, when selected, and has aname. πββοΈcore.MENU_ENTRYβ a normal menu entry that executesfuncwhen selected, and has aname. The basic bestie. πcore.MENU_SEPARATORβ a menu entry that serves as a separator. It may have aname. βββcore.MENU_SUBMENUβ a menu entry that openssubmenuwhen selected. It may have aname. πcore.MENU_CAROUSEL_ENTRYβ a menu entry that rotates throughitemslike a carousel.funcis executed when selected, and the callback is passed the choice index,nameof the current choice, and the table of choices. π β¨
π EXPORTED MENUS π
menu.lua exports these menus, and they're all main character energy:
menu.defaultβ the default menu to draw. Set tomenu.welcomeby default.menu.welcomeβ the welcome menu. Contains single and multi user boot options, as well as entries to access other menus. πmenu.boot_optionsβ the "Boot Options" menu.menu.boot_environmentsβ the "Boot Environments" menu. This menu is only visible if the system is booted on a ZFS partition and more than one boot environment was detected at boot. π (conditional queen)
π EXAMPLES π
Replacing the default boot menu with a simple boot menu
So you wanna replace the default boot menu with a simple boot menu? Slay, here's how:
local core = require("core")
local menu = require("menu")
menu.default = {
entries = {
{
entry_type = core.MENU_ENTRY,
name = "Boot",
func = core.boot,
},
{
entry_type = core.MENU_CAROUSEL_ENTRY,
carousel_id = "unique_boot_entry_name",
items = {"NO", "YES"},
name = function(_, choice, _)
return "Option: " .. choice
end,
func = function(_, _, _)
loader.setenv("some_envvar", "some_value")
end,
},
},
}
Look at that carousel go. Obsessed. π
Adding another option to the welcome menu
Wanna add another option to the welcome menu? Say less:
local core = require("core")
local menu = require("menu")
local my_entry = {
entry_type = core.MENU_ENTRY,
name = "Fancy Boot",
func = core.boot,
},
local stock_entries = menu.welcome.entries
function menu.welcome.entries()
local ents = stock_entries()
ents[#ents + 1] = my_entry
return ents
end
(Yes, that trailing comma after the table in the original is exactly how it's written. Copy it character for character, bestie β we don't "fix" things here. β¨)
Creating a vendor submenu or other vendor menu option
To create a vendor submenu or other vendor menu option, override menu.welcome.all_entires.vendor like so β and yes, all_entires is spelled exactly like that in the original, no cap, don't @ me:
local core = require("core")
local menu = require("menu")
-- Fill in with vendor specific entries
local vendor_options = {
entries = {
...
},
}
local welcome_entries = menu.welcome.all_entries
welcome_entries.vendor = {
entry_type = core.MENU_SUBMENU,
name = color.highlight("V") .. "endor Options",
submenu = vendor_options,
alias = {"v", "V"},
visible = function()
return true
end,
}
So in the above example, vendor_options is a local variable that defines the vendor submenu. π (Note the inconsistency between all_entires and all_entries β that's literally in the source page, so we're keeping both exactly as-is. π«Ά)
Adding an additional option to menu.boot_options
To add an additional option, change the menu.boot_options.entries array. The following illustrates this concept β it's giving "silly example" and I respect it:
-- This is a silly example that rotates local_option through the values
-- 0 to 4. local_option would still need to be used elsewhere.
local local_option = 0
-- The `entries` of a menu may either be a table or a function. In this
-- example we're augmenting a menu that just has a static table, but if we
-- wanted to be more robust then we would need to instead check the type
-- of `stock_options` here to determine our next move.
--
-- If `entries` is a table, then the stock menu system won't be changing it
-- so we can just add our menu option as we do below.
--
-- If `entries` is a function, then we would need to provide a new function to
-- replace `entries` that does a core.deepCopyTable() of the result and adds
-- the below item to it. The deep copy is necessary to avoid duplicating our
-- new menu item and allowing the menu to alter its behavior however it pleases.
local stock_options = menu.boot_options.entries
stock_options[#stock_options + 1] = {
entry_type = core.MENU_ENTRY,
name = function()
return color.highlight('L') ..
"ocal Option : " .. local_option
end,
func = function()
local_option = (local_option + 1) % 5
end,
alias= {"l", "L"}
}
β οΈ Important vibe check: ifentriesis a function, you can't just bolt your item on. You'd need to provide a new function to replaceentriesthat does acore.deepCopyTable()of the result and adds the item to it. The deep copy is necessary to avoid duplicating your new menu item and allowing the menu to alter its behavior however it pleases. That's not me being dramatic, that's literally the spec. π
π₯ SEE ALSO π₯
loader.conf(5), core.lua(8), loader(8)
Go read them, bestie, they're literally right there. π
β HISTORY β
The menu.lua file first appeared in FreeBSD 12.0. A legend was born. π
π AUTHORS π
The menu.lua file was originally written by Pedro Souza <pedrosouza@FreeBSD.org>. Later work and this manual page was done by Kyle Evans <kevans@FreeBSD.org>. Shoutout to both, fr fr π«Ά
β¨ and that's the whole page, bestie β¨ β this one came from the Debian edition, dated March 29, 2025, sitting at MENU.LUA(8) in the System Manager's Manual. Go forth and build menus that slay π
πΈ
π¦ where this page comes from
- freebsd 14.5: base 14.5
- freebsd 15.1: base 15.1
- π the page's license: BSD-2-Clause
- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms