diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst
index a342115d38..f76f79ff53 100644
--- a/doc/source/command-objects/project.rst
+++ b/doc/source/command-objects/project.rst
@@ -156,6 +156,8 @@ Set project properties
     Set a property on :ref:`\<project\> <project_set-project>`
     (repeat option to set multiple properties)
 
+    *Identity version 2 only*
+
 .. _project_set-project:
 .. describe:: <project>
 
@@ -195,3 +197,25 @@ Display project details
 .. describe:: <project>
 
     Project to display (name or ID)
+
+project unset
+-------------
+
+Unset project properties
+
+*Identity version 2 only*
+
+.. program:: project unset
+.. code:: bash
+
+    os project unset
+        --property <key> [--property <key> ...]
+        <project>
+
+.. option:: --property <key>
+
+    Property key to remove from project (repeat option to remove multiple properties)
+
+.. describe:: <project>
+
+    Project to modify (name or ID)
diff --git a/functional/tests/identity/v2/test_project.py b/functional/tests/identity/v2/test_project.py
index 88b282ef3e..3a5e8e81a9 100644
--- a/functional/tests/identity/v2/test_project.py
+++ b/functional/tests/identity/v2/test_project.py
@@ -68,12 +68,12 @@ class ProjectTests(test_identity.IdentityTests):
         )
         items = self.parse_show(raw_output)
         fields = list(self.PROJECT_FIELDS)
-        fields.extend(['k0'])
+        fields.extend(['properties'])
         self.assert_show_fields(items, fields)
         project = self.parse_show_as_object(raw_output)
         self.assertEqual(new_project_name, project['name'])
         self.assertEqual('False', project['enabled'])
-        self.assertEqual('v0', project['k0'])
+        self.assertEqual("k0='v0'", project['properties'])
 
     def test_project_show(self):
         project_name = self._create_dummy_project()
@@ -81,4 +81,6 @@ class ProjectTests(test_identity.IdentityTests):
             'project show %s' % project_name
         )
         items = self.parse_show(raw_output)
-        self.assert_show_fields(items, self.PROJECT_FIELDS)
+        fields = list(self.PROJECT_FIELDS)
+        fields.extend(['properties'])
+        self.assert_show_fields(items, fields)
diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py
index 065f0adfbb..4330c79ca0 100644
--- a/openstackclient/identity/v2_0/project.py
+++ b/openstackclient/identity/v2_0/project.py
@@ -282,4 +282,60 @@ class ShowProject(show.ShowOne):
 
         # TODO(stevemar): Remove the line below when we support multitenancy
         info.pop('parent_id', None)
+
+        # NOTE(stevemar): Property handling isn't really supported in Keystone
+        # and needs a lot of extra handling. Let's reserve the properties that
+        # the API has and handle the extra top level properties.
+        reserved = ('name', 'id', 'enabled', 'description')
+        properties = {}
+        for k, v in info.items():
+            if k not in reserved:
+                # If a key is not in `reserved` it's a property, pop it
+                info.pop(k)
+                # If a property has been "unset" it's `None`, so don't show it
+                if v is not None:
+                    properties[k] = v
+
+        info['properties'] = utils.format_dict(properties)
         return zip(*sorted(six.iteritems(info)))
+
+
+class UnsetProject(command.Command):
+    """Unset project properties"""
+
+    log = logging.getLogger(__name__ + '.UnsetProject')
+
+    def get_parser(self, prog_name):
+        parser = super(UnsetProject, self).get_parser(prog_name)
+        parser.add_argument(
+            'project',
+            metavar='<project>',
+            help=_('Project to modify (name or ID)'),
+        )
+        parser.add_argument(
+            '--property',
+            metavar='<key>',
+            action='append',
+            default=[],
+            help=_('Unset a project property '
+                   '(repeat option to unset multiple properties)'),
+            required=True,
+        )
+        return parser
+
+    @utils.log_method(log)
+    def take_action(self, parsed_args):
+        identity_client = self.app.client_manager.identity
+        project = utils.find_resource(
+            identity_client.tenants,
+            parsed_args.project,
+        )
+        if not parsed_args.property:
+            self.app.log.error("No changes requested\n")
+        else:
+            kwargs = project._info
+            for key in parsed_args.property:
+                if key in kwargs:
+                    kwargs[key] = None
+            identity_client.tenants.update(project.id, **kwargs)
+        return
diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py
index 16ab195736..e2100cd2d1 100644
--- a/openstackclient/tests/identity/v2_0/test_project.py
+++ b/openstackclient/tests/identity/v2_0/test_project.py
@@ -592,12 +592,58 @@ class TestProjectShow(TestProject):
             identity_fakes.project_id,
         )
 
-        collist = ('description', 'enabled', 'id', 'name')
+        collist = ('description', 'enabled', 'id', 'name', 'properties')
         self.assertEqual(collist, columns)
         datalist = (
             identity_fakes.project_description,
             True,
             identity_fakes.project_id,
             identity_fakes.project_name,
+            '',
         )
         self.assertEqual(datalist, data)
+
+
+class TestProjectUnset(TestProject):
+
+    def setUp(self):
+        super(TestProjectUnset, self).setUp()
+
+        project_dict = {'fee': 'fi', 'fo': 'fum'}
+        project_dict.update(identity_fakes.PROJECT)
+        self.projects_mock.get.return_value = fakes.FakeResource(
+            None,
+            copy.deepcopy(project_dict),
+            loaded=True,
+        )
+
+        # Get the command object to test
+        self.cmd = project.UnsetProject(self.app, None)
+
+    def test_project_unset_key(self):
+        arglist = [
+            '--property', 'fee',
+            '--property', 'fo',
+            identity_fakes.project_name,
+        ]
+        verifylist = [
+            ('property', ['fee', 'fo']),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        self.cmd.run(parsed_args)
+        # Set expected values
+        kwargs = {
+            'description': identity_fakes.project_description,
+            'enabled': True,
+            'fee': None,
+            'fo': None,
+            'id': identity_fakes.project_id,
+            'name': identity_fakes.project_name,
+        }
+
+        self.projects_mock.update.assert_called_with(
+            identity_fakes.project_id,
+            **kwargs
+        )
diff --git a/setup.cfg b/setup.cfg
index 7f54fb1fb0..986a077151 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -163,6 +163,7 @@ openstack.identity.v2 =
     project_list = openstackclient.identity.v2_0.project:ListProject
     project_set = openstackclient.identity.v2_0.project:SetProject
     project_show = openstackclient.identity.v2_0.project:ShowProject
+    project_unset = openstackclient.identity.v2_0.project:UnsetProject
 
     role_add = openstackclient.identity.v2_0.role:AddRole
     role_create = openstackclient.identity.v2_0.role:CreateRole