---
title: Creating a ROS 2 CLI command and verb
description: In this tutorial we will see how to create a new ROS 2 CLI command and
  verb. A 'Hello World' example will cover entry-points, user input arguments and
  more.
url: https://canonical.com/blog/creating-a-ros-2-cli-command-and-verb?format=md
---

1. [Blog](https://canonical.com/blog)
2. Article

---

[Jeremie Deray](https://canonical.com/blog/author/artivis "More about Jeremie Deray")

7 August 2019

# Creating a ROS 2 CLI command and verb

[CLI](https://canonical.com/blog/tag/cli)
[robotics](https://canonical.com/blog/tag/robotics)
[ROS2](https://canonical.com/blog/tag/ros2)

---

Share the article
> Please note that this blog post has old information that may no longer be correct. We invite you to read the content as a starting point but please search for more updated information in the [ROS documentation](https://docs.ros.org/).

Following our [previous post](https://ubuntu.com/blog/ros-2-command-line-interface) on ROS 2 CLI (Command Line Interface), we will see here how one can extend the set of existing CLI tools by introducing a new command and its related verb(s).

As support for this tutorial, we will create a ‘Hello World’ example so that the new command will be `hello` and the new verb will be `world`. The material is readily available on [github](https://github.com/artivis/ros2hellocli).

Compared to ROS 1, the ROS 2 CLI has been entirely re-designed in Python offering a clean API, a single entry-point (the keyword `ros2`) and more importantly for the topic at hand, a plugin-like interface using [Python entry points.](https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points)

This new interface allows one to easily extend the existing set of commands and verbs using a few boiler-plate classes and the actual implementation of our new tools.

Let’s get to it!

## Setting up the package

First we will create a new ROS 2 python package and the necessary sub-folders:

```
$ cd ~/ros2_ws/src
$ mkdir ros2hellocli && cd ros2hellocli
$ mkdir command verb
```

While the `ros2hellocli` will be the root folder for this project, the `command` folder will contain a command extension point that allows ROS 2 CLI to discover the new command. Similarly, the `verb` folder will contain the verb extension point(s) which will hold the actual implementation of our new functionality.

But first, let us not forget to turn those sub-folders into Python packages:

```
$ touch command/__init__.py verb/__init__.py
```

Now that we have our project structure ready, we will set up the boiler-plate code mentioned earlier, starting with the classical package manifest and `setup.py` files.

```
$ touch package.xml
```

And copy the following,

```
<?xml version="1.0"?>
<package format="2">
  <name>ros2hellocli</name>
  <version>0.0.0</version>
  <description>
    The ROS 2 command line tools example.
  </description>
  <maintainer email="jeremie.deray@example.org">Jeremie Deray</maintainer>
  <license>Apache License 2.0</license>

  <depend>ros2cli</depend>

  <export>
    <build_type>ament_python</build_type>
  </export>
</package>
```

Then,

```
$ touch setup.py
```

And copy the following,

```
from setuptools import find_packages
from setuptools import setup

setup(
  name='ros2hellocli',
  version='0.0.0',
  packages=find_packages(exclude=['test']),
  install_requires=['ros2cli'],
  zip_safe=True,
  author='Jeremie Deray',
  author_email='jeremie.deray@example.org',
  maintainer='Jeremie Deray',
  maintainer_email='jeremie.deray@example.org',
  url='https://github.com/artivis/ros2hellocli',
  download_url='https://github.com/artivis/ros2hellocli/releases',
  keywords=[],
  classifiers=[
      'Environment :: Console',
      'Intended Audience :: Developers',
      'License :: OSI Approved :: Apache Software License',
      'Programming Language :: Python',
  ],
  description='A minimal plugin example for ROS 2 command line tools.',
  long_description="""The package provides the hello command as a plugin example of ROS 2 command line tools.""",
  license='Apache License, Version 2.0',
)
```

As those two files are fairly common in the ROS world, we skip detailing them and refer the reader to ROS documentation for further explanations ([package manifest on ROS wiki](http://wiki.ros.org/Manifest)).

When creating a new CLI tool, remember however to edit the appropriate entries such as `name`/`authors`/`maintainer` etc.
We will also notice that the package depend upon `ros2cli` since it is meant to extend it.

## Creating a ROS 2 CLI command

Now we shall create the new command `hello` and its command entry-point. First we will create a `hello.py` file in the `command` folder,

```
$ touch command/hello.py
```

and populate it as follows,

```
from ros2cli.command import add_subparsers
from ros2cli.command import CommandExtension
from ros2cli.verb import get_verb_extensions

class HelloCommand(CommandExtension):
    """The 'hello' command extension."""

    def add_arguments(self, parser, cli_name):
        self._subparser = parser
        verb_extensions = get_verb_extensions('ros2hellocli.verb')
        add_subparsers(
            parser, cli_name, '_verb', verb_extensions, required=False)

    def main(self, *, parser, args):
        if not hasattr(args, '_verb'):
            self._subparser.print_help()
            return 0

        extension = getattr(args, '_verb')

        return extension.main(args=args)
```

The content of `hello.py` is fairly similar to any other command entry-point.

With the new command being defined, we will now edit the `setup.py` file to advertise this new entry-point so that the CLI framework can find it. Within the `setup()` function in `setup.py`, we append the following lines:

```
  ...
  tests_require=['pytest'],
  entry_points={
        'ros2cli.command': [
            'hello = ros2hellocli.command.hello:HelloCommand',
        ]
    }
)
```

From now on, ROS 2 CLI framework should be able to find the `hello` verb,

```
$ cd ~/ros2_ws
$ colcon build --symlink-install --packages-select ros2hellocli
$ source install/local_setup.bash
$ ros2 [tab][tab]
```

Hitting the `[tab]` key will trigger the CLI auto-completion which will display in the terminal the different options available. Among those options should appear `hello`,

```
$ ros2 [tab][tab]
action            extensions        msg               run             topic
bag      ------>  hello  <------    multicast         security
component         interface         node              service
daemon            launch            param             srv
extension_points  lifecycle         pkg               test
```

Since the CLI framework can find `hello`, we should also be able to call it,

```
$ ros2 hello
usage: ros2 hello [-h]
                  Call `ros2 hello <command> -h` for more detailed usage. ...

The 'hello' command extension

optional arguments:
  -h, --help            show this help message and exit

Commands:

  Call `ros2 hello <command> -h` for more detailed usage.
```

It works!

Fairly simple so far isn’t it? Notice that the output shown in the terminal is the same as calling `ros2 hello --help`.

## Creating a ROS 2 CLI word

Alright, so now that we have successfully created the new command `hello`, we will now create its associated new verb `world`. Notice that the following is transposable to virtually any command.
Just like commands, verbs rely on the same ‘entry-point’ mechanism, we therefore create a `world.py` file in the `verb` folder,

```
$ cd ~/ros2_ws/src/ros2hellocli
$ touch verb/world.py
```

and populate it as follows,

```
from ros2cli.verb import VerbExtension

class WorldVerb(VerbExtension):
    """Prints Hello World on the terminal."""

    def main(self, *, args):
      print('Hello, ROS 2 World!')
```

As previously, we have to advertise this new entry-point in `setup()` as well by appending the following,

```
  ...
  tests_require=['pytest'],
  entry_points={
        'ros2cli.command': [
            'hello = ros2hellocli.command.hello:HelloCommand',
        ],
        'ros2hellocli.verb': [
            'world = ros2hellocli.verb.world:WorldVerb',
        ]
    }
)
```

The ROS 2 CLI framework should be able to find the `world` command,

```
$ cd ~/ros2_ws
$ colcon build --symlink-install --packages-select ros2hellocli
$ source install/local_setup.bash
$ ros2 hello [tab][tab]
world
```

and we should be able to call it,

```
$ ros2 hello world
Hello, ROS 2 World!
```

Et voilà! We successfully created a CLI command/verb duo.

Although this example is working fine, we will improve it a little in order to cover two more aspects of the CLI framework, the first one being handling user input arguments and the second being related to good practice.

We will start by creating an `api` Python package:

```
$ cd ~/ros2_ws/src/ros2hellocli
$ mkdir api
$ touch api/__init__.py
```

It will contain all of the factorized code, everything that one can turn into small and useful Python functions/classes for re-use and prevent code duplication. And that is precisely what we will do with our `print` call. In the `api/__init__.py` file, we will define the following functions,

```
def get_hello_world():
    return 'Hello, ROS 2 World!'

def get_hello_world_leet():
    return 'He110, R0S 2 W04ld!'
```

From there we will modify the `WorldVerb` class so that is calls one of the above function by default and the other if the user passes a given flag option to the CLI. To do so we modify the `verb/world.py` file as follows,

```
from ros2cli.verb import VerbExtension
from ros2hellocli.api import get_hello_world, get_hello_world_leet

class WorldVerb(VerbExtension):
    """Prints Hello World on the terminal."""

    def add_arguments(self, parser, cli_name):
      parser.add_argument(
                  '--leet', '-l', action='store_true',
                  help="Display the message in 'l33t' form.")

    def main(self, *, args):
      message = get_hello_world() if not args.leet else get_hello_world_leet()
      print(message)
```

Let us test this new option,

```
$ cd ~/ros2_ws
$ colcon build --symlink-install --packages-select ros2hellocli
$ ros2 hello world
Hello, ROS 2 World!
$ ros2 hello world --leet
He110, R0S 2 W04ld!
```

Isn’t it great?

So now that we have now covered the basics of adding both a new ROS 2 CLI command and verb, how would you expand the `hello` command with a new `universe` verb?

Notice that you may find many examples in the [ros2cli github repository](https://github.com/ros2/ros2cli) to help you creating powerful CLI tools. If you are not yet familiar with all existing tools, you can have a look at the ROS 2 CLI [cheats sheet](https://github.com/artivis/ros2_cheats_sheet/blob/master/cli/cli_cheats_sheet.pdf) we put together to help you get up to date.

Come [join the discussion](https://discourse.ros.org/t/tutorial-ros-2-cli/10217) and tell us what new CLI tool you have developed!

[Get in touch

Interested in running Ubuntu in your organization?](https://ubuntu.com/about/contact-us/form)

## Sign up for our newsletter

Get the latest Canonical news and updates in your inbox.

Work email:

\*I agree to receive information about Canonical's
products and services.

By submitting this form, I confirm that I have read and agree to [Canonical's Privacy Policy](https://canonical.com/legal/dataprivacy).

Sign up

## Share on

---

## Related posts

[### Extending ROS Noetic Support with ESM-Enabled Content Snaps](https://canonical.com/blog/extending-ros-noetic-support-with-esm-enabled-content-snaps)

Canonical has now extended its ESM (Expanded Security Maintenance) for ROS coverage to ROS Noetic content-sharing snaps. With ESM for ROS now available in both deb and snap...

[Florencia Cabral Berenfus](https://canonical.com/blog/author/flor-cabral)

17 December 2025

[### Discover your fully open source robotics observability at ROSCon 2025](https://canonical.com/blog/roscon-2025)

Another year, another ROSCon! This year we’re heading to Singapore, and Canonical is once again thrilled to sponsor this important community event. Just like last year in...

[Gabriel Aguiar Noury](https://canonical.com/blog/author/g-aguiar-noury)

22 October 2025

[### Canonical is now a platinum member in the Open Source Robotics Alliance](https://canonical.com/blog/osra-membership)

Ubuntu is the home of ROS. The very first ROS distribution, Box Turtle, launched on Ubuntu 8.04 LTS, Hardy Heron, and since then, Ubuntu and ROS have grown hand in hand. With...

[Gabriel Aguiar Noury](https://canonical.com/blog/author/g-aguiar-noury)

20 August 2025

[### ROS Noetic is EOL – take action to maintain fleet security](https://canonical.com/blog/ros-noetic-is-eol-take-action-to-maintain-fleet-security)

As of May 2025, the Robot Operating System (ROS) Noetic Ninjemys officially reached its end of life (EOL). First released in 2020 as the final ROS (1) distribution, ROS Noetic...

[Florencia Cabral Berenfus](https://canonical.com/blog/author/flor-cabral)

8 August 2025
