laravel-console-menu: interactive CLI menus inside Artisan commands
🔘 Beautiful PHP CLI menus. Is a php-school/cli-menu wrapper for Laravel/Artisan Console Commands
At a glance
- What is it?
- laravel-console-menu is an MIT licensed package by Nuno Maduro that wraps the php-school/cli-menu library so a Laravel console command can show a coloured, keyboard driven menu with options, free text questions and password prompts, returning the choice to your command. It suits a developer writing a deploy or maintenance command that needs one decision from an operator, and it is a poor fit if you want a full text user interface.
- Who is it for?
- laravel-console-menu suits a Laravel team whose Artisan commands need a single human decision, such as choosing a target environment or confirming a destructive step, because the package is a thin wrapper that keeps the underlying cli-menu API intact while making it reachable from a command class. It does not suit a project that wants a full screen terminal application, and it does not suit a codebase still on PHP 8.0 or older, since the package requires PHP 8.1 or later.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A wrapper, and knowing where the boundary is
Laravel Console Menu was created by, and is maintained by, Nuno Maduro, and it is a php-school/cli-menu wrapper for Laravel Console Commands.
That description tells you almost everything about the project. It is not a terminal user interface framework, it is not a prompt toolkit, and it does not try to be the underlying library's successor. It takes an existing, well established menu library and makes it callable from a Laravel command with one method.
The boundary matters for two reasons. The first is that you inherit the whole of cli-menu's feature set, including things the wrapper does not mention, and the README explicitly sends you to the cli-menu documentation for the full reference. The second is that the wrapper is small enough to read in one sitting, which is a fair thing to expect of a package that is mostly glue between a Laravel command and a menu builder.
The package is MIT licensed with a LICENSE.md file in the repository root, the primary language is PHP, and the repository root holds a src directory for the package code, a docs directory with the example images, a composer.json, and a CONTRIBUTING.md. The README badges show a static analysis workflow running against the master branch and the package published on Packagist.
The audience is Laravel developers who write Artisan commands for internal use, where a command that needs input from a person currently means parsing raw STDIN by hand, and where the alternative is telling the operator to pass a flag and hope they get it right.
One method to open a menu, and the option number comes back
The quick setup is the whole API in one block, and it is worth reading as the model for everything else.
class MenuCommand extends Command
{
public function handle()
{
$option = $this->menu('Pizza menu', [
'Freshly baked muffins',
'Freshly baked croissants',
'Turnovers, crumb cake, cinnamon buns, scones',
])->open();
$this->info("You have chosen the option number #$option");
}
}Three things happen. The menu method is called on the command instance, so it is available to any command class without a trait or a service registration that the README asks for. The title and a list of options are passed in. And open() runs the interactive loop and returns the chosen option.
What it returns depends on what you put in the menu. With the plain list form it is the option number, which is why the example prints the option number. Later, when options carry a value, the return is that value instead, and the same open() call shape works for both.
The alternative to the menu method is the CliMenu class from the underlying library, and the README links to its documentation for the full set of capabilities. That is the honest division of labour here. The wrapper knows about Laravel commands, and everything else is cli-menu.
Installation is one Composer command.
composer require nunomaduro/laravel-console-menuAnd there is one hard requirement, stated as a block quote: PHP 8.1 or later. There is no separate service provider to register and no configuration file to publish in the examples given, which keeps the setup to a single line plus a method call.
Options, questions and the return value that changes with them
The second example adds named options and a free text question, and it changes what open() gives back.
$option = $this->menu('Pizza menu')
->addOption('mozzarella', 'Mozzarella')
->addOption('chicken_parm', 'Chicken Parm')
->addOption('sausage', 'Sausage')
->addQuestion('Make your own', 'Describe your pizza...')
->addOption('burger', 'Prefer burgers')
->setWidth(80)
->open();The API is fluent and each call returns the menu, so the whole thing chains. addOption takes a value and a label, and addQuestion takes a label and a prompt, which is how you mix a fixed choice list with a free text answer in the same menu.
The important behavioural detail is in the return value. With the first form, the plain list, open() returns the index of the chosen item. With named options and a question, it returns the value the operator entered or selected, which is why the second example prints it as the chosen text option. So the same call reads differently depending on the menu you built, and a command that switches between the two forms has to change what it does with the result.
setWidth(80) is worth noticing too. Terminal width is not something you can assume, since the same command runs on a wide developer terminal and a narrow SSH session, and the library's default is not going to be right in both.
The callable item is the escape hatch for anything else
The third example is the one that shows why the package is a wrapper rather than a reimplementation, because it reaches past the option list and into the underlying library.
$menu = $this->menu('Pizza menu')
->addOption('mozzarella', 'Mozzarella')
->addOption('chicken_parm', 'Chicken Parm')
->addOption('sausage', 'Sausage')
->addQuestion('Make your own', 'Describe your pizza...');
$itemCallable = function (CliMenu $cliMenu) use ($menu) {
$cliMenu->askPassword()
->setValidator(function ($password) {
return $password === 'secret';
})
->setPromptText('Secret password?')
->ask();
$menu->setResult('Free spice!');
$cliMenu->close();
};
$menu->addItem('Add extra spice for free (password needed)', $itemCallable);An item can be a callable rather than a label. When the operator picks it, the callable receives the CliMenu object, and from there you can drive anything the library offers, which in this case is a masked password prompt with a validator, a prompt text and an ask call.
The sequence inside the callable is the part to copy. The password prompt runs, the validator decides whether the input is acceptable, and then the menu's own result is set with setResult, and the menu is closed with the CliMenu's close method. The result is that the menu ends with the text the operator never typed, which is how the example produces the joke.
This pattern is the general answer to the question of what happens when the menu you want is not a list. The wrapper does not restrict you to its own API at the point where the wrapper ends, and the escape is a single method call with the library object handed to you.
Appearance is delegated, which keeps the surface small
The appearance section of the README is a list of method calls, and the values they take are worth having in front of you because they are the ones you will actually type.
$this->menu($title, $options)
->setForegroundColour('green')
->setBackgroundColour('black')
->setWidth(200)
->setPadding(10)
->setMargin(5)
->setExitButtonText("Abort") // remove exit button with ->disableDefaultItems()
->setTitleSeparator('*-')
->addLineBreak('<3', 2)
->addStaticItem('AREA 2')
->open();Colours are named rather than numeric. The available colours are black, red, green, yellow, blue, magenta, cyan and white, so an option is a string from a fixed set rather than an escape sequence, which is a small thing that makes a shared helper across a team of commands less error prone.
Two of those calls are worth flagging on their own. setExitButtonText changes the label of the built in exit item, and the comment notes that disableDefaultItems() removes the exit button entirely, which is a meaningful choice for a command that must not be abandoned halfway. And setTitleSeparator with a value like *- changes the rule under the title, which is the kind of small touch that makes a command look like it belongs to the rest of your tooling.
addStaticItem and addLineBreak are how you put content into the menu that is not a choice, so you can group options with headings, which is often what a menu with more than a handful of entries needs.
None of this is the wrapper's own work, and the README is clear that the full documentation lives in the cli-menu repository.
One maintainer, one release line and an upstream you can read
The maintenance picture is simple enough to state.
The package was created by and is maintained by Nuno Maduro, who also maintains Laravel itself, and the homepage linked from the repository metadata is his YouTube channel rather than a documentation site. The documentation is on GitHub, in the wiki of the upstream library, and there is a docs directory in the repository for the example images.
The release list shows a single tag, v3.7.0 published on 2026-02-27, and the last push to the repository was on 2026-04-22. With one maintainer, the practical implication is that the package tracks the upstream library rather than moving on its own schedule, so an upgrade of cli-menu is what will bring new features through, and the wrapper's own release follows when something needs changing in it.
The contributing guidance is short and points at a CONTRIBUTING.md in the repository root, and the README asks people to look at the GitHub releases page for constant updates and detailed information about the changes, and to follow the maintainer on Twitter for announcements.
The project is funded through PayPal and Patreon links in the README, which is worth noting only because it tells you what the sustainability model is: a single maintainer with a sponsorship link, not a company backed component. That is a fine model for a package this small, and it is also the sort of thing a team should know before depending on it in an internal tool that someone else will have to keep working in two years.
Editorial conclusion
laravel-console-menu suits a Laravel team whose Artisan commands need a single human decision, such as choosing a target environment or confirming a destructive step, because the package is a thin wrapper that keeps the underlying cli-menu API intact while making it reachable from a command class. It does not suit a project that wants a full screen terminal application, and it does not suit a codebase still on PHP 8.0 or older, since the package requires PHP 8.1 or later. Before you adopt it, read the cli-menu documentation that the README points to, because the deeper options live there rather than in this repository, and note the maintenance signal, with one listed release, v3.7.0 from 2026-02-27, and the last push to the repository on 2026-04-22.
Frequently asked questions
What is Laravel Console Menu?
It is a package by Nuno Maduro that wraps the php-school/cli-menu library so Laravel Artisan commands can show interactive menus. The README describes it as beautiful PHP CLI menus and a wrapper for Laravel console commands, and it is MIT licensed.
How do I install laravel-console-menu?
Require it with Composer using composer require nunomaduro/laravel-console-menu. The package requires PHP 8.1 or later, and the examples show no service provider registration or published configuration file.
What does the menu method return in laravel-console-menu?
With the plain list form of menu() the open() call returns the number of the chosen option. When the menu is built with addOption and addQuestion, open() returns the selected or entered text instead, so the same call shape yields a different result depending on the menu.
Can laravel-console-menu prompt for a password?
Yes. An item can be added as a callable that receives the CliMenu object, and the README's advanced example uses it to call askPassword with a validator and a prompt text, then set the menu result and close the menu.
Which colours and options does laravel-console-menu support?
The available colours are black, red, green, yellow, blue, magenta, cyan and white, set with setForegroundColour and setBackgroundColour. Other documented calls include setWidth, setPadding, setMargin, setExitButtonText, disableDefaultItems, setTitleSeparator, addLineBreak and addStaticItem.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/nunomaduro-laravel-console-menu)Was this useful?