diff --git a/doc/source/cli/command-objects/volume-group-type.rst b/doc/source/cli/command-objects/volume-group-type.rst
new file mode 100644
index 0000000000..edb88dc7b0
--- /dev/null
+++ b/doc/source/cli/command-objects/volume-group-type.rst
@@ -0,0 +1,8 @@
+=================
+volume group type
+=================
+
+Block Storage v3
+
+.. autoprogram-cliff:: openstack.volume.v3
+   :command: volume group type *
diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst
index b91a896f41..ada38e3e57 100644
--- a/doc/source/cli/commands.rst
+++ b/doc/source/cli/commands.rst
@@ -160,6 +160,7 @@ referring to both Compute and Volume quotas.
 * ``volume backup record``: (**Volume**) volume record that can be imported or exported
 * ``volume backend``: (**Volume**) volume backend storage
 * ``volume group``: (**Volume**) group of volumes
+* ``volume group type``: (**Volume**) deployment-specific types of volumes groups available
 * ``volume host``: (**Volume**) the physical computer for volumes
 * ``volume message``: (**Volume**) volume API internal messages detailing volume failure messages
 * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes
diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv
index 031aa43900..5de8ea5cdd 100644
--- a/doc/source/cli/data/cinder.csv
+++ b/doc/source/cli/data/cinder.csv
@@ -57,14 +57,14 @@ group-snapshot-create,,Creates a group snapshot. (Supported by API versions 3.14
 group-snapshot-delete,,Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest)
 group-snapshot-list,,Lists all group snapshots. (Supported by API versions 3.14 - 3.latest)
 group-snapshot-show,,Shows group snapshot details. (Supported by API versions 3.14 - 3.latest)
-group-specs-list,,Lists current group types and specs. (Supported by API versions 3.11 - 3.latest)
-group-type-create,,Creates a group type. (Supported by API versions 3.11 - 3.latest)
-group-type-default,,List the default group type. (Supported by API versions 3.11 - 3.latest)
-group-type-delete,,Deletes group type or types. (Supported by API versions 3.11 - 3.latest)
-group-type-key,,Sets or unsets group_spec for a group type. (Supported by API versions 3.11 - 3.latest)
-group-type-list,,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest)
-group-type-show,,Show group type details. (Supported by API versions 3.11 - 3.latest)
-group-type-update,,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest)
+group-specs-list,volume group type list,Lists current group types and specs. (Supported by API versions 3.11 - 3.latest)
+group-type-create,volume group type create,Creates a group type. (Supported by API versions 3.11 - 3.latest)
+group-type-default,volume group type list --default,List the default group type. (Supported by API versions 3.11 - 3.latest)
+group-type-delete,volume group type delete,Deletes group type or types. (Supported by API versions 3.11 - 3.latest)
+group-type-key,volume group type set,Sets or unsets group_spec for a group type. (Supported by API versions 3.11 - 3.latest)
+group-type-list,volume group type set,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest)
+group-type-show,volume group type show,Show group type details. (Supported by API versions 3.11 - 3.latest)
+group-type-update,volume group type set,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest)
 group-update,volume group set,Updates a group. (Supported by API versions 3.13 - 3.latest)
 image-metadata,volume set --image-property,Sets or deletes volume image metadata.
 image-metadata-show,volume show,Shows volume image metadata.
diff --git a/openstackclient/tests/unit/volume/v3/fakes.py b/openstackclient/tests/unit/volume/v3/fakes.py
index b0c96290f8..c300ca3855 100644
--- a/openstackclient/tests/unit/volume/v3/fakes.py
+++ b/openstackclient/tests/unit/volume/v3/fakes.py
@@ -129,10 +129,11 @@ class FakeVolumeGroupType:
     """Fake one or more volume group types."""
 
     @staticmethod
-    def create_one_volume_group_type(attrs=None):
+    def create_one_volume_group_type(attrs=None, methods=None):
         """Create a fake group type.
 
         :param attrs: A dictionary with all attributes of group type
+        :param methods: A dictionary with all methods
         :return: A FakeResource object with id, name, description, etc.
         """
         attrs = attrs or {}
@@ -152,6 +153,7 @@ class FakeVolumeGroupType:
         group_type = fakes.FakeResource(
             None,
             group_type_info,
+            methods=methods,
             loaded=True)
         return group_type
 
diff --git a/openstackclient/tests/unit/volume/v3/test_volume_group_type.py b/openstackclient/tests/unit/volume/v3/test_volume_group_type.py
new file mode 100644
index 0000000000..7e758a2c1f
--- /dev/null
+++ b/openstackclient/tests/unit/volume/v3/test_volume_group_type.py
@@ -0,0 +1,475 @@
+# 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.
+
+from unittest import mock
+
+from cinderclient import api_versions
+from osc_lib.cli import format_columns
+from osc_lib import exceptions
+
+from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes
+from openstackclient.volume.v3 import volume_group_type
+
+
+class TestVolumeGroupType(volume_fakes.TestVolume):
+
+    def setUp(self):
+        super().setUp()
+
+        self.volume_group_types_mock = \
+            self.app.client_manager.volume.group_types
+        self.volume_group_types_mock.reset_mock()
+
+
+class TestVolumeGroupTypeCreate(TestVolumeGroupType):
+
+    maxDiff = 2000
+
+    fake_volume_group_type = \
+        volume_fakes.FakeVolumeGroupType.create_one_volume_group_type()
+
+    columns = (
+        'ID',
+        'Name',
+        'Description',
+        'Is Public',
+        'Properties',
+    )
+    data = (
+        fake_volume_group_type.id,
+        fake_volume_group_type.name,
+        fake_volume_group_type.description,
+        fake_volume_group_type.is_public,
+        format_columns.DictColumn(fake_volume_group_type.group_specs),
+    )
+
+    def setUp(self):
+        super().setUp()
+
+        self.volume_group_types_mock.create.return_value = \
+            self.fake_volume_group_type
+
+        self.cmd = volume_group_type.CreateVolumeGroupType(self.app, None)
+
+    def test_volume_group_type_create(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.11')
+
+        arglist = [
+            self.fake_volume_group_type.name,
+        ]
+        verifylist = [
+            ('name', self.fake_volume_group_type.name),
+            ('description', None),
+            ('is_public', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.volume_group_types_mock.create.assert_called_once_with(
+            self.fake_volume_group_type.name,
+            None,
+            True)
+        self.assertEqual(self.columns, columns)
+        self.assertCountEqual(self.data, data)
+
+    def test_volume_group_type_create_with_options(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.11')
+
+        arglist = [
+            self.fake_volume_group_type.name,
+            '--description', 'foo',
+            '--private',
+        ]
+        verifylist = [
+            ('name', self.fake_volume_group_type.name),
+            ('description', 'foo'),
+            ('is_public', False),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.volume_group_types_mock.create.assert_called_once_with(
+            self.fake_volume_group_type.name,
+            'foo',
+            False)
+        self.assertEqual(self.columns, columns)
+        self.assertCountEqual(self.data, data)
+
+    def test_volume_group_type_create_pre_v311(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.10')
+
+        arglist = [
+            self.fake_volume_group_type.name,
+        ]
+        verifylist = [
+            ('name', self.fake_volume_group_type.name),
+            ('description', None),
+            ('is_public', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        exc = self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+        self.assertIn(
+            '--os-volume-api-version 3.11 or greater is required',
+            str(exc))
+
+
+class TestVolumeGroupTypeDelete(TestVolumeGroupType):
+
+    fake_volume_group_type = \
+        volume_fakes.FakeVolumeGroupType.create_one_volume_group_type()
+
+    def setUp(self):
+        super().setUp()
+
+        self.volume_group_types_mock.get.return_value = \
+            self.fake_volume_group_type
+        self.volume_group_types_mock.delete.return_value = None
+
+        self.cmd = volume_group_type.DeleteVolumeGroupType(self.app, None)
+
+    def test_volume_group_type_delete(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.11')
+
+        arglist = [
+            self.fake_volume_group_type.id,
+        ]
+        verifylist = [
+            ('group_type', self.fake_volume_group_type.id),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        result = self.cmd.take_action(parsed_args)
+
+        self.volume_group_types_mock.delete.assert_called_once_with(
+            self.fake_volume_group_type.id,
+        )
+        self.assertIsNone(result)
+
+    def test_volume_group_type_delete_pre_v311(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.10')
+
+        arglist = [
+            self.fake_volume_group_type.id,
+        ]
+        verifylist = [
+            ('group_type', self.fake_volume_group_type.id),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        exc = self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+        self.assertIn(
+            '--os-volume-api-version 3.11 or greater is required',
+            str(exc))
+
+
+class TestVolumeGroupTypeSet(TestVolumeGroupType):
+
+    fake_volume_group_type = \
+        volume_fakes.FakeVolumeGroupType.create_one_volume_group_type(
+            methods={
+                'get_keys': {'foo': 'bar'},
+                'set_keys': None,
+                'unset_keys': None,
+            })
+
+    columns = (
+        'ID',
+        'Name',
+        'Description',
+        'Is Public',
+        'Properties',
+    )
+    data = (
+        fake_volume_group_type.id,
+        fake_volume_group_type.name,
+        fake_volume_group_type.description,
+        fake_volume_group_type.is_public,
+        format_columns.DictColumn(fake_volume_group_type.group_specs),
+    )
+
+    def setUp(self):
+        super().setUp()
+
+        self.volume_group_types_mock.get.return_value = \
+            self.fake_volume_group_type
+        self.volume_group_types_mock.update.return_value = \
+            self.fake_volume_group_type
+
+        self.cmd = volume_group_type.SetVolumeGroupType(self.app, None)
+
+    def test_volume_group_type_set(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.11')
+
+        self.fake_volume_group_type.set_keys.return_value = None
+
+        arglist = [
+            self.fake_volume_group_type.id,
+            '--name', 'foo',
+            '--description', 'hello, world',
+            '--public',
+            '--property', 'fizz=buzz',
+        ]
+        verifylist = [
+            ('group_type', self.fake_volume_group_type.id),
+            ('name', 'foo'),
+            ('description', 'hello, world'),
+            ('is_public', True),
+            ('no_property', False),
+            ('properties', {'fizz': 'buzz'}),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.volume_group_types_mock.update.assert_called_once_with(
+            self.fake_volume_group_type.id,
+            name='foo',
+            description='hello, world',
+            is_public=True,
+        )
+        self.fake_volume_group_type.set_keys.assert_called_once_with(
+            {'fizz': 'buzz'},
+        )
+        self.assertEqual(self.columns, columns)
+        self.assertCountEqual(self.data, data)
+
+    def test_volume_group_type_with_no_property_option(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.11')
+
+        arglist = [
+            self.fake_volume_group_type.id,
+            '--no-property',
+            '--property', 'fizz=buzz',
+        ]
+        verifylist = [
+            ('group_type', self.fake_volume_group_type.id),
+            ('name', None),
+            ('description', None),
+            ('is_public', None),
+            ('no_property', True),
+            ('properties', {'fizz': 'buzz'}),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.volume_group_types_mock.get.assert_called_once_with(
+            self.fake_volume_group_type.id)
+        self.fake_volume_group_type.get_keys.assert_called_once_with()
+        self.fake_volume_group_type.unset_keys.assert_called_once_with(
+            {'foo': 'bar'}.keys())
+        self.assertEqual(self.columns, columns)
+        self.assertCountEqual(self.data, data)
+
+    def test_volume_group_type_set_pre_v311(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.10')
+
+        arglist = [
+            self.fake_volume_group_type.id,
+            '--name', 'foo',
+            '--description', 'hello, world',
+        ]
+        verifylist = [
+            ('group_type', self.fake_volume_group_type.id),
+            ('name', 'foo'),
+            ('description', 'hello, world'),
+            ('is_public', None),
+            ('no_property', False),
+            ('properties', None),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        exc = self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+        self.assertIn(
+            '--os-volume-api-version 3.11 or greater is required',
+            str(exc))
+
+
+class TestVolumeGroupTypeUnset(TestVolumeGroupType):
+
+    fake_volume_group_type = \
+        volume_fakes.FakeVolumeGroupType.create_one_volume_group_type(
+            methods={'unset_keys': None})
+
+    columns = (
+        'ID',
+        'Name',
+        'Description',
+        'Is Public',
+        'Properties',
+    )
+    data = (
+        fake_volume_group_type.id,
+        fake_volume_group_type.name,
+        fake_volume_group_type.description,
+        fake_volume_group_type.is_public,
+        format_columns.DictColumn(fake_volume_group_type.group_specs),
+    )
+
+    def setUp(self):
+        super().setUp()
+
+        self.volume_group_types_mock.get.return_value = \
+            self.fake_volume_group_type
+
+        self.cmd = volume_group_type.UnsetVolumeGroupType(self.app, None)
+
+    def test_volume_group_type_unset(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.11')
+
+        arglist = [
+            self.fake_volume_group_type.id,
+            '--property', 'fizz',
+        ]
+        verifylist = [
+            ('group_type', self.fake_volume_group_type.id),
+            ('properties', ['fizz']),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.volume_group_types_mock.get.assert_has_calls([
+            mock.call(self.fake_volume_group_type.id),
+            mock.call(self.fake_volume_group_type.id),
+        ])
+        self.fake_volume_group_type.unset_keys.assert_called_once_with(
+            ['fizz'])
+        self.assertEqual(self.columns, columns)
+        self.assertCountEqual(self.data, data)
+
+    def test_volume_group_type_unset_pre_v311(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.10')
+
+        arglist = [
+            self.fake_volume_group_type.id,
+            '--property', 'fizz',
+        ]
+        verifylist = [
+            ('group_type', self.fake_volume_group_type.id),
+            ('properties', ['fizz']),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        exc = self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+        self.assertIn(
+            '--os-volume-api-version 3.11 or greater is required',
+            str(exc))
+
+
+class TestVolumeGroupTypeList(TestVolumeGroupType):
+
+    fake_volume_group_types = \
+        volume_fakes.FakeVolumeGroupType.create_volume_group_types()
+
+    columns = (
+        'ID',
+        'Name',
+        'Is Public',
+        'Properties',
+    )
+    data = [
+        (
+            fake_volume_group_type.id,
+            fake_volume_group_type.name,
+            fake_volume_group_type.is_public,
+            fake_volume_group_type.group_specs,
+        ) for fake_volume_group_type in fake_volume_group_types
+    ]
+
+    def setUp(self):
+        super().setUp()
+
+        self.volume_group_types_mock.list.return_value = \
+            self.fake_volume_group_types
+        self.volume_group_types_mock.default.return_value = \
+            self.fake_volume_group_types[0]
+
+        self.cmd = volume_group_type.ListVolumeGroupType(self.app, None)
+
+    def test_volume_group_type_list(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.11')
+
+        arglist = [
+        ]
+        verifylist = [
+            ('show_default', False),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.volume_group_types_mock.list.assert_called_once_with()
+        self.assertEqual(self.columns, columns)
+        self.assertCountEqual(tuple(self.data), data)
+
+    def test_volume_group_type_list_with_default_option(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.11')
+
+        arglist = [
+            '--default',
+        ]
+        verifylist = [
+            ('show_default', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.volume_group_types_mock.default.assert_called_once_with()
+        self.assertEqual(self.columns, columns)
+        self.assertCountEqual(tuple([self.data[0]]), data)
+
+    def test_volume_group_type_list_pre_v311(self):
+        self.app.client_manager.volume.api_version = \
+            api_versions.APIVersion('3.10')
+
+        arglist = [
+        ]
+        verifylist = [
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        exc = self.assertRaises(
+            exceptions.CommandError,
+            self.cmd.take_action,
+            parsed_args)
+        self.assertIn(
+            '--os-volume-api-version 3.11 or greater is required',
+            str(exc))
diff --git a/openstackclient/volume/v3/volume_group_type.py b/openstackclient/volume/v3/volume_group_type.py
new file mode 100644
index 0000000000..860fa544a5
--- /dev/null
+++ b/openstackclient/volume/v3/volume_group_type.py
@@ -0,0 +1,410 @@
+# 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 logging
+
+from cinderclient import api_versions
+from osc_lib.cli import format_columns
+from osc_lib.cli import parseractions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
+LOG = logging.getLogger(__name__)
+
+
+def _format_group_type(group):
+    columns = (
+        'id',
+        'name',
+        'description',
+        'is_public',
+        'group_specs',
+    )
+    column_headers = (
+        'ID',
+        'Name',
+        'Description',
+        'Is Public',
+        'Properties',
+    )
+
+    # TODO(stephenfin): Consider using a formatter for volume_types since it's
+    # a list
+    return (
+        column_headers,
+        utils.get_item_properties(
+            group,
+            columns,
+            formatters={
+                'group_specs': format_columns.DictColumn,
+            },
+        ),
+    )
+
+
+class CreateVolumeGroupType(command.ShowOne):
+    """Create a volume group type.
+
+    This command requires ``--os-volume-api-version`` 3.11 or greater.
+    """
+
+    def get_parser(self, prog_name):
+        parser = super().get_parser(prog_name)
+        parser.add_argument(
+            'name',
+            metavar='<name>',
+            help=_('Name of new volume group type.'),
+        )
+        parser.add_argument(
+            '--description',
+            metavar='<description>',
+            help=_('Description of the volume group type.')
+        )
+        type_group = parser.add_mutually_exclusive_group()
+        type_group.add_argument(
+            '--public',
+            dest='is_public',
+            action='store_true',
+            default=True,
+            help=_(
+                'Volume group type is available to other projects (default)'
+            ),
+        )
+        type_group.add_argument(
+            '--private',
+            dest='is_public',
+            action='store_false',
+            help=_('Volume group type is not available to other projects')
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        volume_client = self.app.client_manager.volume
+
+        if volume_client.api_version < api_versions.APIVersion('3.11'):
+            msg = _(
+                "--os-volume-api-version 3.11 or greater is required to "
+                "support the 'volume group type create' command"
+            )
+            raise exceptions.CommandError(msg)
+
+        group_type = volume_client.group_types.create(
+            parsed_args.name,
+            parsed_args.description,
+            parsed_args.is_public)
+
+        return _format_group_type(group_type)
+
+
+class DeleteVolumeGroupType(command.Command):
+    """Delete a volume group type.
+
+    This command requires ``--os-volume-api-version`` 3.11 or greater.
+    """
+
+    def get_parser(self, prog_name):
+        parser = super().get_parser(prog_name)
+        parser.add_argument(
+            'group_type',
+            metavar='<group_type>',
+            help=_('Name or ID of volume group type to delete'),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        volume_client = self.app.client_manager.volume
+
+        if volume_client.api_version < api_versions.APIVersion('3.11'):
+            msg = _(
+                "--os-volume-api-version 3.11 or greater is required to "
+                "support the 'volume group type delete' command"
+            )
+            raise exceptions.CommandError(msg)
+
+        group_type = utils.find_resource(
+            volume_client.group_types,
+            parsed_args.group_type,
+        )
+
+        volume_client.group_types.delete(group_type.id)
+
+
+class SetVolumeGroupType(command.ShowOne):
+    """Update a volume group type.
+
+    This command requires ``--os-volume-api-version`` 3.11 or greater.
+    """
+
+    def get_parser(self, prog_name):
+        parser = super().get_parser(prog_name)
+        parser.add_argument(
+            'group_type',
+            metavar='<group_type>',
+            help=_('Name or ID of volume group type.'),
+        )
+        parser.add_argument(
+            '--name',
+            metavar='<name>',
+            help=_('New name for volume group type.'),
+        )
+        parser.add_argument(
+            '--description',
+            metavar='<description>',
+            help=_('New description for volume group type.'),
+        )
+        type_group = parser.add_mutually_exclusive_group()
+        type_group.add_argument(
+            '--public',
+            dest='is_public',
+            action='store_true',
+            default=None,
+            help=_('Make volume group type available to other projects.'),
+        )
+        type_group.add_argument(
+            '--private',
+            dest='is_public',
+            action='store_false',
+            help=_('Make volume group type unavailable to other projects.')
+        )
+        parser.add_argument(
+            '--no-property',
+            action='store_true',
+            help=_(
+                'Remove all properties from this volume group type '
+                '(specify both --no-property and --property '
+                'to remove the current properties before setting '
+                'new properties)'
+            ),
+        )
+        parser.add_argument(
+            '--property',
+            metavar='<key=value>',
+            action=parseractions.KeyValueAction,
+            dest='properties',
+            help=_(
+                'Property to add or modify for this volume group type '
+                '(repeat option to set multiple properties)'
+            ),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        volume_client = self.app.client_manager.volume
+
+        if volume_client.api_version < api_versions.APIVersion('3.11'):
+            msg = _(
+                "--os-volume-api-version 3.11 or greater is required to "
+                "support the 'volume group type set' command"
+            )
+            raise exceptions.CommandError(msg)
+
+        group_type = utils.find_resource(
+            volume_client.group_types,
+            parsed_args.group_type,
+        )
+
+        kwargs = {}
+        errors = 0
+
+        if parsed_args.name is not None:
+            kwargs['name'] = parsed_args.name
+
+        if parsed_args.description is not None:
+            kwargs['description'] = parsed_args.description
+
+        if parsed_args.is_public is not None:
+            kwargs['is_public'] = parsed_args.is_public
+
+        if kwargs:
+            try:
+                group_type = volume_client.group_types.update(
+                    group_type.id, **kwargs)
+            except Exception as e:
+                LOG.error(_("Failed to update group type: %s"), e)
+                errors += 1
+
+        if parsed_args.no_property:
+            try:
+                keys = group_type.get_keys().keys()
+                group_type.unset_keys(keys)
+            except Exception as e:
+                LOG.error(_("Failed to clear group type properties: %s"), e)
+                errors += 1
+
+        if parsed_args.properties:
+            try:
+                group_type.set_keys(parsed_args.properties)
+            except Exception as e:
+                LOG.error(_("Failed to set group type properties: %s"), e)
+                errors += 1
+
+        if errors > 0:
+            msg = _(
+                "Command Failed: One or more of the operations failed"
+            )
+            raise exceptions.CommandError()
+
+        return _format_group_type(group_type)
+
+
+class UnsetVolumeGroupType(command.ShowOne):
+    """Unset properties of a volume group type.
+
+    This command requires ``--os-volume-api-version`` 3.11 or greater.
+    """
+
+    def get_parser(self, prog_name):
+        parser = super().get_parser(prog_name)
+        parser.add_argument(
+            'group_type',
+            metavar='<group_type>',
+            help=_('Name or ID of volume group type.'),
+        )
+        parser.add_argument(
+            '--property',
+            metavar='<key>',
+            action='append',
+            dest='properties',
+            help=_(
+                'Property to remove from this volume group type '
+                '(repeat option to unset multiple properties)'
+            ),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        volume_client = self.app.client_manager.volume
+
+        if volume_client.api_version < api_versions.APIVersion('3.11'):
+            msg = _(
+                "--os-volume-api-version 3.11 or greater is required to "
+                "support the 'volume group type unset' command"
+            )
+            raise exceptions.CommandError(msg)
+
+        group_type = utils.find_resource(
+            volume_client.group_types,
+            parsed_args.group_type,
+        )
+
+        group_type.unset_keys(parsed_args.properties)
+
+        group_type = utils.find_resource(
+            volume_client.group_types,
+            parsed_args.group_type,
+        )
+
+        return _format_group_type(group_type)
+
+
+class ListVolumeGroupType(command.Lister):
+    """Lists all volume group types.
+
+    This command requires ``--os-volume-api-version`` 3.11 or greater.
+    """
+
+    def get_parser(self, prog_name):
+        parser = super().get_parser(prog_name)
+        parser.add_argument(
+            '--default',
+            action='store_true',
+            dest='show_default',
+            default=False,
+            help=_('List the default volume group type.'),
+        )
+        # TODO(stephenfin): Add once we have an equivalent command for
+        # 'cinder list-filters'
+        # parser.add_argument(
+        #     '--filter',
+        #     metavar='<key=value>',
+        #     action=parseractions.KeyValueAction,
+        #     dest='filters',
+        #     help=_(
+        #         "Filter key and value pairs. Use 'foo' to "
+        #         "check enabled filters from server. Use 'key~=value' for "
+        #         "inexact filtering if the key supports "
+        #         "(supported by --os-volume-api-version 3.33 or above)"
+        #     ),
+        # )
+        return parser
+
+    def take_action(self, parsed_args):
+        volume_client = self.app.client_manager.volume
+
+        if volume_client.api_version < api_versions.APIVersion('3.11'):
+            msg = _(
+                "--os-volume-api-version 3.11 or greater is required to "
+                "support the 'volume group type list' command"
+            )
+            raise exceptions.CommandError(msg)
+
+        if parsed_args.show_default:
+            group_types = [volume_client.group_types.default()]
+        else:
+            group_types = volume_client.group_types.list()
+
+        column_headers = (
+            'ID',
+            'Name',
+            'Is Public',
+            'Properties',
+        )
+        columns = (
+            'id',
+            'name',
+            'is_public',
+            'group_specs',
+        )
+
+        return (
+            column_headers,
+            (
+                utils.get_item_properties(a, columns)
+                for a in group_types
+            ),
+        )
+
+
+class ShowVolumeGroupType(command.ShowOne):
+    """Show detailed information for a volume group type.
+
+    This command requires ``--os-volume-api-version`` 3.11 or greater.
+    """
+
+    def get_parser(self, prog_name):
+        parser = super().get_parser(prog_name)
+        parser.add_argument(
+            'group_type',
+            metavar='<group_type>',
+            help=_('Name or ID of volume group type.'),
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        volume_client = self.app.client_manager.volume
+
+        if volume_client.api_version < api_versions.APIVersion('3.11'):
+            msg = _(
+                "--os-volume-api-version 3.11 or greater is required to "
+                "support the 'volume group type show' command"
+            )
+            raise exceptions.CommandError(msg)
+
+        group_type = utils.find_resource(
+            volume_client.group_types,
+            parsed_args.group,
+        )
+
+        return _format_group_type(group_type)
diff --git a/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml b/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml
new file mode 100644
index 0000000000..02ffd38dda
--- /dev/null
+++ b/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Add ``volume group type create``, ``volume group type delete``,
+    ``volume group type list``, ``volume group type set/unset`` and
+    ``volume group type show`` commands to create, delete, list, update,
+    and show volume group types, respectively.
diff --git a/setup.cfg b/setup.cfg
index 1d031a8fc4..2a01ae7b0d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -719,8 +719,15 @@ openstack.volume.v3 =
     volume_group_list = openstackclient.volume.v3.volume_group:ListVolumeGroup
     volume_group_failover = openstackclient.volume.v3.volume_group:FailoverVolumeGroup
     volume_group_set = openstackclient.volume.v3.volume_group:SetVolumeGroup
+    volume_group_unset = openstackclient.volume.v3.volume_group:UnsetVolumeGroup
     volume_group_show = openstackclient.volume.v3.volume_group:ShowVolumeGroup
 
+    volume_group_type_create = openstackclient.volume.v3.volume_group_type:CreateVolumeGroupType
+    volume_group_type_delete = openstackclient.volume.v3.volume_group_type:DeleteVolumeGroupType
+    volume_group_type_list = openstackclient.volume.v3.volume_group_type:ListVolumeGroupType
+    volume_group_type_set = openstackclient.volume.v3.volume_group_type:SetVolumeGroupType
+    volume_group_type_show = openstackclient.volume.v3.volume_group_type:ShowVolumeGroupType
+
     volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost
 
     volume_message_delete = openstackclient.volume.v3.volume_message:DeleteMessage