diff --git a/doc/source/cli/command-objects/project.rst b/doc/source/cli/command-objects/project.rst
index cb0941ca12..6891a79a48 100644
--- a/doc/source/cli/command-objects/project.rst
+++ b/doc/source/cli/command-objects/project.rst
@@ -19,6 +19,7 @@ Create new project
         [--enable | --disable]
         [--property <key=value>]
         [--or-show]
+        [--tag <tag>]
         <name>
 
 .. option:: --domain <domain>
@@ -56,6 +57,13 @@ Create new project
 
     If the project already exists return the existing project data and do not fail.
 
+.. option:: --tag
+
+    Add a tag to the project
+    (repeat option to set multiple tags)
+
+    .. versionadded:: 3
+
 .. _project_create-name:
 .. describe:: <name>
 
@@ -98,6 +106,8 @@ List projects
         [--my-projects]
         [--long]
         [--sort <key>[:<direction>,<key>:<direction>,..]]
+        [--tags <tag>[,<tag>,...]] [--tags-any <tag>[,<tag>,...]]
+        [--not-tags <tag>[,<tag>,...]] [--not-tags-any <tag>[,<tag>,...]]
 
 .. option:: --domain <domain>
 
@@ -127,6 +137,30 @@ List projects
     multiple keys and directions can be specified --sort
     <key>[:<direction>,<key>:<direction>,..]
 
+.. option:: --tags <tag>[,<tag>,...]
+
+    List projects which have all given tag(s)
+
+    .. versionadded:: 3
+
+.. option:: --tags-any <tag>[,<tag>,...]
+
+    List projects which have any given tag(s)
+
+    .. versionadded:: 3
+
+.. option:: --not-tags <tag>[,<tag>,...]
+
+    Exclude projects which have all given tag(s)
+
+    .. versionadded:: 3
+
+.. option:: --not-tags-any <tag>[,<tag>,...]
+
+    Exclude projects which have any given tag(s)
+
+    .. versionadded:: 3
+
 project set
 -----------
 
@@ -141,6 +175,7 @@ Set project properties
         [--description <description>]
         [--enable | --disable]
         [--property <key=value>]
+        [--tag <tag> | --clear-tags | --remove-tags <tag>]
         <project>
 
 .. option:: --name <name>
diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py
index 60efbac4a4..e819a0a89b 100644
--- a/openstackclient/identity/v3/project.py
+++ b/openstackclient/identity/v3/project.py
@@ -26,7 +26,7 @@ import six
 
 from openstackclient.i18n import _
 from openstackclient.identity import common
-
+from openstackclient.identity.v3 import tag
 
 LOG = logging.getLogger(__name__)
 
@@ -79,6 +79,7 @@ class CreateProject(command.ShowOne):
             action='store_true',
             help=_('Return existing project'),
         )
+        tag.add_tag_option_to_parser_for_create(parser, _('project'))
         return parser
 
     def take_action(self, parsed_args):
@@ -102,6 +103,7 @@ class CreateProject(command.ShowOne):
         kwargs = {}
         if parsed_args.property:
             kwargs = parsed_args.property.copy()
+        kwargs['tags'] = list(set(parsed_args.tags))
 
         try:
             project = identity_client.projects.create(
@@ -207,6 +209,7 @@ class ListProject(command.Lister):
                    '(default: asc), repeat this option to specify multiple '
                    'keys and directions.'),
         )
+        tag.add_tag_filtering_option_to_parser(parser, _('projects'))
         return parser
 
     def take_action(self, parsed_args):
@@ -234,6 +237,8 @@ class ListProject(command.Lister):
 
             kwargs['user'] = user_id
 
+        tag.get_tag_filtering_args(parsed_args, kwargs)
+
         if parsed_args.my_projects:
             # NOTE(adriant): my-projects supersedes all the other filters.
             kwargs = {'user': self.app.client_manager.auth_ref.user_id}
@@ -303,6 +308,7 @@ class SetProject(command.Command):
             help=_('Set a property on <project> '
                    '(repeat option to set multiple properties)'),
         )
+        tag.add_tag_option_to_parser_for_set(parser, _('project'))
         return parser
 
     def take_action(self, parsed_args):
@@ -323,6 +329,7 @@ class SetProject(command.Command):
             kwargs['enabled'] = False
         if parsed_args.property:
             kwargs.update(parsed_args.property)
+        tag.update_tags_in_args(parsed_args, project, kwargs)
 
         identity_client.projects.update(project.id, **kwargs)
 
diff --git a/openstackclient/identity/v3/tag.py b/openstackclient/identity/v3/tag.py
new file mode 100644
index 0000000000..abf022d486
--- /dev/null
+++ b/openstackclient/identity/v3/tag.py
@@ -0,0 +1,116 @@
+#   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 argparse
+
+from openstackclient.i18n import _
+
+
+class _CommaListAction(argparse.Action):
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        setattr(namespace, self.dest, values.split(','))
+
+
+def add_tag_filtering_option_to_parser(parser, collection_name):
+    parser.add_argument(
+        '--tags',
+        metavar='<tag>[,<tag>,...]',
+        action=_CommaListAction,
+        help=_('List %s which have all given tag(s) '
+               '(Comma-separated list of tags)') % collection_name
+    )
+    parser.add_argument(
+        '--tags-any',
+        metavar='<tag>[,<tag>,...]',
+        action=_CommaListAction,
+        help=_('List %s which have any given tag(s) '
+               '(Comma-separated list of tags)') % collection_name
+    )
+    parser.add_argument(
+        '--not-tags',
+        metavar='<tag>[,<tag>,...]',
+        action=_CommaListAction,
+        help=_('Exclude %s which have all given tag(s) '
+               '(Comma-separated list of tags)') % collection_name
+    )
+    parser.add_argument(
+        '--not-tags-any',
+        metavar='<tag>[,<tag>,...]',
+        action=_CommaListAction,
+        help=_('Exclude %s which have any given tag(s) '
+               '(Comma-separated list of tags)') % collection_name
+    )
+
+
+def get_tag_filtering_args(parsed_args, args):
+    if parsed_args.tags:
+        args['tags'] = ','.join(parsed_args.tags)
+    if parsed_args.tags_any:
+        args['tags-any'] = ','.join(parsed_args.tags_any)
+    if parsed_args.not_tags:
+        args['not-tags'] = ','.join(parsed_args.not_tags)
+    if parsed_args.not_tags_any:
+        args['not-tags-any'] = ','.join(parsed_args.not_tags_any)
+
+
+def add_tag_option_to_parser_for_create(parser, resource_name):
+    tag_group = parser.add_mutually_exclusive_group()
+    tag_group.add_argument(
+        '--tag',
+        action='append',
+        dest='tags',
+        metavar='<tag>',
+        default=[],
+        help=_('Tag to be added to the %s '
+               '(repeat option to set multiple tags)') % resource_name
+    )
+
+
+def add_tag_option_to_parser_for_set(parser, resource_name):
+    parser.add_argument(
+        '--tag',
+        action='append',
+        dest='tags',
+        metavar='<tag>',
+        default=[],
+        help=_('Tag to be added to the %s '
+               '(repeat option to set multiple tags)') % resource_name
+    )
+    parser.add_argument(
+        '--clear-tags',
+        action='store_true',
+        help=_('Clear tags associated with the %s. Specify '
+               'both --tag and --clear-tags to overwrite '
+               'current tags') % resource_name
+    )
+    parser.add_argument(
+        '--remove-tag',
+        metavar='<tag>',
+        default=[],
+        help=_('Tag to be deleted from the %s '
+               '(repeat option to delete multiple tags)') % resource_name
+    )
+
+
+def update_tags_in_args(parsed_args, obj, args):
+    if parsed_args.clear_tags:
+        args['tags'] = []
+        obj.tags = []
+    if parsed_args.remove_tag:
+        if parsed_args.remove_tag in obj.tags:
+            obj.tags.remove(parsed_args.remove_tag)
+        args['tags'] = list(set(obj.tags))
+        return
+    if parsed_args.tags:
+        args['tags'] = list(set(obj.tags).union(
+            set(parsed_args.tags)))
diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py
index 3e2caf01d5..3770e29fe9 100644
--- a/openstackclient/tests/unit/identity/v3/fakes.py
+++ b/openstackclient/tests/unit/identity/v3/fakes.py
@@ -34,6 +34,7 @@ DOMAIN = {
     'name': domain_name,
     'description': domain_description,
     'enabled': True,
+    'tags': [],
     'links': base_url + 'domains/' + domain_id,
 }
 
@@ -115,6 +116,7 @@ PROJECT = {
     'description': project_description,
     'enabled': True,
     'domain_id': domain_id,
+    'tags': [],
     'links': base_url + 'projects/' + project_id,
 }
 
@@ -124,6 +126,7 @@ PROJECT_2 = {
     'description': project_description + 'plus four more',
     'enabled': True,
     'domain_id': domain_id,
+    'tags': [],
     'links': base_url + 'projects/' + project_id,
 }
 
@@ -145,6 +148,7 @@ PROJECT_WITH_PARENT = {
     'enabled': True,
     'domain_id': domain_id,
     'parent_id': project_id,
+    'tags': [],
     'links': base_url + 'projects/' + (project_id + '-with-parent'),
 }
 
@@ -155,6 +159,7 @@ PROJECT_WITH_GRANDPARENT = {
     'enabled': True,
     'domain_id': domain_id,
     'parent_id': PROJECT_WITH_PARENT['id'],
+    'tags': [],
     'links': base_url + 'projects/' + (project_id + '-with-grandparent'),
 }
 
@@ -619,6 +624,7 @@ class FakeProject(object):
             'is_domain': False,
             'domain_id': 'domain-id-' + uuid.uuid4().hex,
             'parent_id': 'parent-id-' + uuid.uuid4().hex,
+            'tags': [],
             'links': 'links-' + uuid.uuid4().hex,
         }
         project_info.update(attrs)
@@ -666,6 +672,7 @@ class FakeDomain(object):
             'name': 'domain-name-' + uuid.uuid4().hex,
             'description': 'domain-description-' + uuid.uuid4().hex,
             'enabled': True,
+            'tags': [],
             'links': 'links-' + uuid.uuid4().hex,
         }
         domain_info.update(attrs)
diff --git a/openstackclient/tests/unit/identity/v3/test_domain.py b/openstackclient/tests/unit/identity/v3/test_domain.py
index 36f13d3326..014986e573 100644
--- a/openstackclient/tests/unit/identity/v3/test_domain.py
+++ b/openstackclient/tests/unit/identity/v3/test_domain.py
@@ -31,6 +31,7 @@ class TestDomainCreate(TestDomain):
         'enabled',
         'id',
         'name',
+        'tags'
     )
 
     def setUp(self):
@@ -43,6 +44,7 @@ class TestDomainCreate(TestDomain):
             True,
             self.domain.id,
             self.domain.name,
+            self.domain.tags
         )
 
         # Get the command object to test
@@ -390,12 +392,13 @@ class TestDomainShow(TestDomain):
             self.domain.id,
         )
 
-        collist = ('description', 'enabled', 'id', 'name')
+        collist = ('description', 'enabled', 'id', 'name', 'tags')
         self.assertEqual(collist, columns)
         datalist = (
             self.domain.description,
             True,
             self.domain.id,
             self.domain.name,
+            self.domain.tags
         )
         self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py
index 16ac3116f9..266da22775 100644
--- a/openstackclient/tests/unit/identity/v3/test_project.py
+++ b/openstackclient/tests/unit/identity/v3/test_project.py
@@ -50,6 +50,7 @@ class TestProjectCreate(TestProject):
         'is_domain',
         'name',
         'parent_id',
+        'tags'
     )
 
     def setUp(self):
@@ -67,6 +68,7 @@ class TestProjectCreate(TestProject):
             False,
             self.project.name,
             self.project.parent_id,
+            self.project.tags
         )
         # Get the command object to test
         self.cmd = project.CreateProject(self.app, None)
@@ -80,6 +82,7 @@ class TestProjectCreate(TestProject):
             ('enable', False),
             ('disable', False),
             ('name', self.project.name),
+            ('tags', [])
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 
@@ -95,6 +98,7 @@ class TestProjectCreate(TestProject):
             'description': None,
             'enabled': True,
             'parent': None,
+            'tags': []
         }
         # ProjectManager.create(name=, domain=, description=,
         #                       enabled=, **kwargs)
@@ -110,6 +114,7 @@ class TestProjectCreate(TestProject):
             'is_domain',
             'name',
             'parent_id',
+            'tags'
         )
         self.assertEqual(collist, columns)
         datalist = (
@@ -120,6 +125,7 @@ class TestProjectCreate(TestProject):
             False,
             self.project.name,
             self.project.parent_id,
+            self.project.tags
         )
         self.assertEqual(datalist, data)
 
@@ -134,6 +140,7 @@ class TestProjectCreate(TestProject):
             ('disable', False),
             ('name', self.project.name),
             ('parent', None),
+            ('tags', [])
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 
@@ -149,6 +156,7 @@ class TestProjectCreate(TestProject):
             'description': 'new desc',
             'enabled': True,
             'parent': None,
+            'tags': []
         }
         # ProjectManager.create(name=, domain=, description=,
         #                       enabled=, **kwargs)
@@ -170,6 +178,7 @@ class TestProjectCreate(TestProject):
             ('disable', False),
             ('name', self.project.name),
             ('parent', None),
+            ('tags', [])
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 
@@ -185,6 +194,7 @@ class TestProjectCreate(TestProject):
             'description': None,
             'enabled': True,
             'parent': None,
+            'tags': []
         }
         # ProjectManager.create(name=, domain=, description=,
         #                       enabled=, **kwargs)
@@ -206,6 +216,7 @@ class TestProjectCreate(TestProject):
             ('disable', False),
             ('name', self.project.name),
             ('parent', None),
+            ('tags', [])
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
         mocker = mock.Mock()
@@ -221,6 +232,7 @@ class TestProjectCreate(TestProject):
             'description': None,
             'enabled': True,
             'parent': None,
+            'tags': []
         }
         self.projects_mock.create.assert_called_with(
             **kwargs
@@ -238,6 +250,7 @@ class TestProjectCreate(TestProject):
             ('disable', False),
             ('name', self.project.name),
             ('parent', None),
+            ('tags', [])
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 
@@ -253,6 +266,7 @@ class TestProjectCreate(TestProject):
             'description': None,
             'enabled': True,
             'parent': None,
+            'tags': []
         }
         # ProjectManager.create(name=, domain=, description=,
         #                       enabled=, **kwargs)
@@ -288,6 +302,7 @@ class TestProjectCreate(TestProject):
             'description': None,
             'enabled': False,
             'parent': None,
+            'tags': []
         }
         # ProjectManager.create(name=, domain=,
         #                       description=, enabled=, **kwargs)
@@ -324,6 +339,7 @@ class TestProjectCreate(TestProject):
             'parent': None,
             'fee': 'fi',
             'fo': 'fum',
+            'tags': []
         }
         # ProjectManager.create(name=, domain=, description=,
         #                       enabled=, **kwargs)
@@ -352,6 +368,7 @@ class TestProjectCreate(TestProject):
             ('enable', False),
             ('disable', False),
             ('name', self.project.name),
+            ('tags', [])
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 
@@ -363,6 +380,7 @@ class TestProjectCreate(TestProject):
             'parent': self.parent.id,
             'description': None,
             'enabled': True,
+            'tags': []
         }
 
         self.projects_mock.create.assert_called_with(
@@ -377,6 +395,7 @@ class TestProjectCreate(TestProject):
             'is_domain',
             'name',
             'parent_id',
+            'tags'
         )
         self.assertEqual(columns, collist)
         datalist = (
@@ -387,6 +406,7 @@ class TestProjectCreate(TestProject):
             self.project.is_domain,
             self.project.name,
             self.parent.id,
+            self.project.tags
         )
         self.assertEqual(data, datalist)
 
@@ -417,6 +437,43 @@ class TestProjectCreate(TestProject):
             parsed_args,
         )
 
+    def test_project_create_with_tags(self):
+        arglist = [
+            '--domain', self.project.domain_id,
+            '--tag', 'foo',
+            self.project.name,
+        ]
+        verifylist = [
+            ('domain', self.project.domain_id),
+            ('enable', False),
+            ('disable', False),
+            ('name', self.project.name),
+            ('parent', None),
+            ('tags', ['foo'])
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        # In base command class ShowOne in cliff, abstract method take_action()
+        # returns a two-part tuple with a tuple of column names and a tuple of
+        # data to be shown.
+        columns, data = self.cmd.take_action(parsed_args)
+
+        # Set expected values
+        kwargs = {
+            'name': self.project.name,
+            'domain': self.project.domain_id,
+            'description': None,
+            'enabled': True,
+            'parent': None,
+            'tags': ['foo']
+        }
+        self.projects_mock.create.assert_called_with(
+            **kwargs
+        )
+
+        self.assertEqual(self.columns, columns)
+        self.assertEqual(self.datalist, data)
+
 
 class TestProjectDelete(TestProject):
 
@@ -816,6 +873,38 @@ class TestProjectSet(TestProject):
         )
         self.assertIsNone(result)
 
+    def test_project_set_tags(self):
+        arglist = [
+            '--name', 'qwerty',
+            '--domain', self.project.domain_id,
+            '--tag', 'foo',
+            self.project.name,
+        ]
+        verifylist = [
+            ('name', 'qwerty'),
+            ('domain', self.project.domain_id),
+            ('enable', False),
+            ('disable', False),
+            ('project', self.project.name),
+            ('tags', ['foo'])
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        result = self.cmd.take_action(parsed_args)
+
+        # Set expected values
+        kwargs = {
+            'name': 'qwerty',
+            'tags': ['foo']
+        }
+        # ProjectManager.update(project, name=, domain=, description=,
+        #                       enabled=, **kwargs)
+        self.projects_mock.update.assert_called_with(
+            self.project.id,
+            **kwargs
+        )
+        self.assertIsNone(result)
+
 
 class TestProjectShow(TestProject):
 
@@ -867,6 +956,7 @@ class TestProjectShow(TestProject):
             'is_domain',
             'name',
             'parent_id',
+            'tags'
         )
         self.assertEqual(collist, columns)
         datalist = (
@@ -877,6 +967,7 @@ class TestProjectShow(TestProject):
             False,
             self.project.name,
             self.project.parent_id,
+            self.project.tags
         )
         self.assertEqual(datalist, data)
 
@@ -926,6 +1017,7 @@ class TestProjectShow(TestProject):
             'name',
             'parent_id',
             'parents',
+            'tags'
         )
         self.assertEqual(columns, collist)
         datalist = (
@@ -936,7 +1028,8 @@ class TestProjectShow(TestProject):
             self.project.is_domain,
             self.project.name,
             self.project.parent_id,
-            [{'project': {'id': self.project.parent_id}}]
+            [{'project': {'id': self.project.parent_id}}],
+            self.project.tags
         )
         self.assertEqual(data, datalist)
 
@@ -985,6 +1078,7 @@ class TestProjectShow(TestProject):
             'name',
             'parent_id',
             'subtree',
+            'tags'
         )
         self.assertEqual(columns, collist)
         datalist = (
@@ -995,7 +1089,8 @@ class TestProjectShow(TestProject):
             self.project.is_domain,
             self.project.name,
             self.project.parent_id,
-            [{'project': {'id': 'children-id'}}]
+            [{'project': {'id': 'children-id'}}],
+            self.project.tags
         )
         self.assertEqual(data, datalist)
 
@@ -1047,6 +1142,7 @@ class TestProjectShow(TestProject):
             'parent_id',
             'parents',
             'subtree',
+            'tags'
         )
         self.assertEqual(columns, collist)
         datalist = (
@@ -1058,7 +1154,8 @@ class TestProjectShow(TestProject):
             self.project.name,
             self.project.parent_id,
             [{'project': {'id': self.project.parent_id}}],
-            [{'project': {'id': 'children-id'}}]
+            [{'project': {'id': 'children-id'}}],
+            self.project.tags
         )
         self.assertEqual(data, datalist)
 
diff --git a/releasenotes/notes/bp-project-tags-b544aef9672d415b.yaml b/releasenotes/notes/bp-project-tags-b544aef9672d415b.yaml
new file mode 100644
index 0000000000..0da35ac37d
--- /dev/null
+++ b/releasenotes/notes/bp-project-tags-b544aef9672d415b.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Add ``--tag`` option to ``project create`` command,  ``--tag``, ``--clear-tags``, and
+    ``--remove-tag`` options to ``project set`` command. Add ``--tags``, ``--tags-any``, 
+    ``--not-tags``, and ``--not-tags-any`` options to ``project list`` command to filter
+    list results by different projects based on their tags.
+    [`blueprint project-tags <https://blueprints.launchpad.net/keystone/+spec/project-tags>`_]