diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst
new file mode 100644
index 0000000000..f7b5a67e07
--- /dev/null
+++ b/doc/source/command-objects/router.rst
@@ -0,0 +1,20 @@
+======
+router
+======
+
+Network v2
+
+router list
+-----------
+
+List routers
+
+.. program:: router list
+.. code:: bash
+
+    os router list
+        [--long]
+
+.. option:: --long
+
+    List additional fields in output
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index 1c6c2d8ab3..0ec4f71bcb 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -107,6 +107,7 @@ referring to both Compute and Volume quotas.
 * ``request token``: (**Identity**) temporary OAuth-based token
 * ``role``: (**Identity**) a policy object used to determine authorization
 * ``role assignment``: (**Identity**) a relationship between roles, users or groups, and domains or projects
+* ``router``: (**Network**) - a virtual router
 * ``security group``: (**Compute**, **Network**) - groups of network access rules
 * ``security group rule``: (**Compute**, **Network**) - the individual rules that define protocol/IP/port access
 * ``server``: (**Compute**) virtual machine instance
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
new file mode 100644
index 0000000000..cf5dae59f1
--- /dev/null
+++ b/openstackclient/network/v2/router.py
@@ -0,0 +1,93 @@
+#   Licensed under the Apache License, Version 2.0 (the "License"); you may
+#   not use this file except in compliance with the License. You may obtain
+#   a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#   License for the specific language governing permissions and limitations
+#   under the License.
+#
+
+"""Router action implementations"""
+
+import json
+import logging
+
+from cliff import lister
+
+from openstackclient.common import utils
+
+
+def _format_admin_state(state):
+    return 'UP' if state else 'DOWN'
+
+
+def _format_external_gateway_info(info):
+    try:
+        return json.dumps(info)
+    except (TypeError, KeyError):
+        return ''
+
+
+_formatters = {
+    'admin_state_up': _format_admin_state,
+    'external_gateway_info': _format_external_gateway_info,
+}
+
+
+class ListRouter(lister.Lister):
+    """List routers"""
+
+    log = logging.getLogger(__name__ + '.ListRouter')
+
+    def get_parser(self, prog_name):
+        parser = super(ListRouter, self).get_parser(prog_name)
+        parser.add_argument(
+            '--long',
+            action='store_true',
+            default=False,
+            help='List additional fields in output',
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug('take_action(%s)' % parsed_args)
+        client = self.app.client_manager.network
+
+        columns = (
+            'id',
+            'name',
+            'status',
+            'admin_state_up',
+            'distributed',
+            'ha',
+            'tenant_id',
+        )
+        column_headers = (
+            'ID',
+            'Name',
+            'Status',
+            'State',
+            'Distributed',
+            'HA',
+            'Project',
+        )
+        if parsed_args.long:
+            columns = columns + (
+                'routes',
+                'external_gateway_info',
+            )
+            column_headers = column_headers + (
+                'Routes',
+                'External gateway info',
+            )
+
+        data = client.routers()
+        return (column_headers,
+                (utils.get_item_properties(
+                    s, columns,
+                    formatters=_formatters,
+                ) for s in data))
diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py
new file mode 100644
index 0000000000..d91daceb8a
--- /dev/null
+++ b/openstackclient/tests/network/v2/test_router.py
@@ -0,0 +1,105 @@
+#   Licensed under the Apache License, Version 2.0 (the "License"); you may
+#   not use this file except in compliance with the License. You may obtain
+#   a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#   License for the specific language governing permissions and limitations
+#   under the License.
+#
+
+import mock
+
+from openstackclient.network.v2 import router
+from openstackclient.tests.network.v2 import fakes as network_fakes
+
+
+class TestRouter(network_fakes.TestNetworkV2):
+
+    def setUp(self):
+        super(TestRouter, self).setUp()
+
+        # Get a shortcut to the network client
+        self.network = self.app.client_manager.network
+
+
+class TestListRouter(TestRouter):
+
+    # The routers going to be listed up.
+    routers = network_fakes.FakeRouter.create_routers(count=3)
+
+    columns = (
+        'ID',
+        'Name',
+        'Status',
+        'State',
+        'Distributed',
+        'HA',
+        'Project',
+    )
+    columns_long = columns + (
+        'Routes',
+        'External gateway info',
+    )
+
+    data = []
+    for r in routers:
+        data.append((
+            r.id,
+            r.name,
+            r.status,
+            router._format_admin_state(r.admin_state_up),
+            r.distributed,
+            r.ha,
+            r.tenant_id,
+        ))
+    data_long = []
+    for i in range(0, len(routers)):
+        r = routers[i]
+        data_long.append(
+            data[i] + (
+                r.routes,
+                router._format_external_gateway_info(r.external_gateway_info),
+            )
+        )
+
+    def setUp(self):
+        super(TestListRouter, self).setUp()
+
+        # Get the command object to test
+        self.cmd = router.ListRouter(self.app, self.namespace)
+
+        self.network.routers = mock.Mock(return_value=self.routers)
+
+    def test_router_list_no_options(self):
+        arglist = []
+        verifylist = [
+            ('long', False),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        # DisplayCommandBase.take_action() returns two tuples
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.network.routers.assert_called_with()
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.data, list(data))
+
+    def test_router_list_long(self):
+        arglist = [
+            '--long',
+        ]
+        verifylist = [
+            ('long', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        # DisplayCommandBase.take_action() returns two tuples
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.network.routers.assert_called_with()
+        self.assertEqual(self.columns_long, columns)
+        self.assertEqual(self.data_long, list(data))
diff --git a/setup.cfg b/setup.cfg
index 27b00d45b1..01615a3380 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -332,6 +332,7 @@ openstack.network.v2 =
     network_list = openstackclient.network.v2.network:ListNetwork
     network_set = openstackclient.network.v2.network:SetNetwork
     network_show = openstackclient.network.v2.network:ShowNetwork
+    router_list = openstackclient.network.v2.router:ListRouter
 
 openstack.object_store.v1 =
     object_store_account_set = openstackclient.object.v1.account:SetAccount