diff --git a/docs/tutorial/commands/index.md b/docs/tutorial/commands/index.md
index d2693849d7..f531c7cfb3 100644
--- a/docs/tutorial/commands/index.md
+++ b/docs/tutorial/commands/index.md
@@ -257,6 +257,39 @@ Commands:
+
+## Sorting of the commands
+
+Note that by design, **Typer** shows the commands in the order they've been declared.
+
+So, if we take our original example, with `create` and `delete` commands, and reverse the order in the Python file:
+
+```Python hl_lines="7 12"
+{!../docs_src/commands/index/tutorial004.py!}
+```
+
+Then we will see the `delete` command first in the help output:
+
+
+
+```console
+// Check the help
+$ python main.py --help
+
+Usage: main.py [OPTIONS] COMMAND [ARGS]...
+
+Options:
+ --install-completion Install completion for the current shell.
+ --show-completion Show completion for the current shell, to copy it or customize the installation.
+ --help Show this message and exit.
+
+Commands:
+ delete
+ create
+```
+
+
+
## Click Group
If you come from Click, a `typer.Typer` app with subcommands is more or less the equivalent of a Click Group.
diff --git a/docs_src/commands/index/tutorial004.py b/docs_src/commands/index/tutorial004.py
new file mode 100644
index 0000000000..83419b695f
--- /dev/null
+++ b/docs_src/commands/index/tutorial004.py
@@ -0,0 +1,17 @@
+import typer
+
+app = typer.Typer()
+
+
+@app.command()
+def delete():
+ print("Deleting user: Hiro Hamada")
+
+
+@app.command()
+def create():
+ print("Creating user: Hiro Hamada")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/tests/assets/cli/multiapp-docs-title.md b/tests/assets/cli/multiapp-docs-title.md
index e688a9ba91..ffde843736 100644
--- a/tests/assets/cli/multiapp-docs-title.md
+++ b/tests/assets/cli/multiapp-docs-title.md
@@ -18,41 +18,41 @@ The end
**Commands**:
-* `sub`
* `top`: Top command
+* `sub`
-## `multiapp sub`
+## `multiapp top`
+
+Top command
**Usage**:
```console
-$ multiapp sub [OPTIONS] COMMAND [ARGS]...
+$ multiapp top [OPTIONS]
```
**Options**:
* `--help`: Show this message and exit.
-**Commands**:
-
-* `bye`: Say bye
-* `hello`: Say Hello
-* `hi`: Say Hi
-
-### `multiapp sub bye`
-
-Say bye
+## `multiapp sub`
**Usage**:
```console
-$ multiapp sub bye [OPTIONS]
+$ multiapp sub [OPTIONS] COMMAND [ARGS]...
```
**Options**:
* `--help`: Show this message and exit.
+**Commands**:
+
+* `hello`: Say Hello
+* `hi`: Say Hi
+* `bye`: Say bye
+
### `multiapp sub hello`
Say Hello
@@ -87,14 +87,14 @@ $ multiapp sub hi [OPTIONS] [USER]
* `--help`: Show this message and exit.
-## `multiapp top`
+### `multiapp sub bye`
-Top command
+Say bye
**Usage**:
```console
-$ multiapp top [OPTIONS]
+$ multiapp sub bye [OPTIONS]
```
**Options**:
diff --git a/tests/assets/cli/multiapp-docs.md b/tests/assets/cli/multiapp-docs.md
index ed4592f5c8..67d02568db 100644
--- a/tests/assets/cli/multiapp-docs.md
+++ b/tests/assets/cli/multiapp-docs.md
@@ -18,41 +18,41 @@ The end
**Commands**:
-* `sub`
* `top`: Top command
+* `sub`
-## `multiapp sub`
+## `multiapp top`
+
+Top command
**Usage**:
```console
-$ multiapp sub [OPTIONS] COMMAND [ARGS]...
+$ multiapp top [OPTIONS]
```
**Options**:
* `--help`: Show this message and exit.
-**Commands**:
-
-* `bye`: Say bye
-* `hello`: Say Hello
-* `hi`: Say Hi
-
-### `multiapp sub bye`
-
-Say bye
+## `multiapp sub`
**Usage**:
```console
-$ multiapp sub bye [OPTIONS]
+$ multiapp sub [OPTIONS] COMMAND [ARGS]...
```
**Options**:
* `--help`: Show this message and exit.
+**Commands**:
+
+* `hello`: Say Hello
+* `hi`: Say Hi
+* `bye`: Say bye
+
### `multiapp sub hello`
Say Hello
@@ -87,14 +87,14 @@ $ multiapp sub hi [OPTIONS] [USER]
* `--help`: Show this message and exit.
-## `multiapp top`
+### `multiapp sub bye`
-Top command
+Say bye
**Usage**:
```console
-$ multiapp top [OPTIONS]
+$ multiapp sub bye [OPTIONS]
```
**Options**:
diff --git a/tests/test_tutorial/test_commands/test_index/test_tutorial004.py b/tests/test_tutorial/test_commands/test_index/test_tutorial004.py
new file mode 100644
index 0000000000..ae0139e93a
--- /dev/null
+++ b/tests/test_tutorial/test_commands/test_index/test_tutorial004.py
@@ -0,0 +1,45 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.commands.index import tutorial004 as mod
+
+app = mod.app
+
+runner = CliRunner()
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "[OPTIONS] COMMAND [ARGS]..." in result.output
+ print(result.output)
+ assert "Commands" in result.output
+ assert "create" in result.output
+ assert "delete" in result.output
+ # Test that the 'delete' command precedes the 'create' command in the help output
+ create_char = result.output.index("create")
+ delete_char = result.output.index("delete")
+ assert delete_char < create_char
+
+
+def test_create():
+ result = runner.invoke(app, ["create"])
+ assert result.exit_code == 0
+ assert "Creating user: Hiro Hamada" in result.output
+
+
+def test_delete():
+ result = runner.invoke(app, ["delete"])
+ assert result.exit_code == 0
+ assert "Deleting user: Hiro Hamada" in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ capture_output=True,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/typer/core.py b/typer/core.py
index c5cdbfee8d..00e21da869 100644
--- a/typer/core.py
+++ b/typer/core.py
@@ -744,3 +744,9 @@ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> Non
ctx=ctx,
markup_mode=self.rich_markup_mode,
)
+
+ def list_commands(self, ctx: click.Context) -> List[str]:
+ """Returns a list of subcommand names.
+ Note that in Click's Group class, these are sorted.
+ In Typer, we wish to maintain the original order of creation (cf Issue #933)"""
+ return [n for n, c in self.commands.items()]