diff --git a/cyclonedx_py/client.py b/cyclonedx_py/client.py
index 908f5fe8..70a39305 100644
--- a/cyclonedx_py/client.py
+++ b/cyclonedx_py/client.py
@@ -33,6 +33,7 @@
 
 from .parser.conda import CondaListExplicitParser, CondaListJsonParser
 from .parser.environment import EnvironmentParser
+from .parser.pdm import PdmParser
 from .parser.pipenv import PipEnvParser
 from .parser.poetry import PoetryParser
 from .parser.requirements import RequirementsParser
@@ -184,6 +185,12 @@ def get_arg_parser(*, prog: Optional[str] = None) -> argparse.ArgumentParser:
                  'to a `poetry.lock` you wish to use, else we\'ll look for one in the current working directory.',
             dest='input_from_poetry'
         )
+        input_group.add_argument(
+            '--pdm', action='store_true',
+            help='Build a SBOM based on a PDM pdm.lock\'s contents. Use with -i to specify absolute path '
+                 'to a `pdm.lock` you wish to use, else we\'ll look for one in the current working directory.',
+            dest='input_from_pdm'
+        )
         input_group.add_argument(
             '-pip', '--pip', action='store_true',
             help='Build a SBOM based on a PipEnv Pipfile.lock\'s contents. Use with -i to specify absolute path '
@@ -277,6 +284,9 @@ def _get_input_parser(self) -> BaseParser:
                 elif self._arguments.input_from_poetry:
                     self._arguments.input_source = open(os.path.join(current_directory, 'poetry.lock'),
                                                         'rt', encoding="UTF-8", errors='replace')
+                elif self._arguments.input_from_pdm:
+                    self._arguments.input_source = open(os.path.join(current_directory, 'pdm.lock'),
+                                                        'rt', encoding="UTF-8", errors='replace')
                 elif self._arguments.input_from_requirements:
                     self._arguments.input_source = open(os.path.join(current_directory, 'requirements.txt'), 'rb')
                 else:
@@ -324,6 +334,12 @@ def _get_input_parser(self) -> BaseParser:
                 use_purl_bom_ref=self._arguments.use_purl_bom_ref,
                 debug_message=lambda m, *a, **k: self._debug_message(f'PoetryParser {m}', *a, **k)
             )
+        elif self._arguments.input_from_pdm:
+            return PdmParser(
+                pdm_lock_contents=input_data,
+                use_purl_bom_ref=self._arguments.use_purl_bom_ref,
+                debug_message=lambda m, *a, **k: self._debug_message(f'PdmParser {m}', *a, **k)
+            )
         elif self._arguments.input_from_requirements:
             return RequirementsParser(
                 requirements_content=input_data,
diff --git a/cyclonedx_py/parser/pdm.py b/cyclonedx_py/parser/pdm.py
new file mode 100644
index 00000000..e024d48b
--- /dev/null
+++ b/cyclonedx_py/parser/pdm.py
@@ -0,0 +1,84 @@
+# encoding: utf-8
+
+# This file is part of CycloneDX Python Lib
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) OWASP Foundation. All Rights Reserved.
+
+from cyclonedx.exception.model import CycloneDxModelException
+from cyclonedx.model import ExternalReference, ExternalReferenceType, HashType, XsUri
+from cyclonedx.model.component import Component
+from cyclonedx.parser import BaseParser
+
+# See https://github.com/package-url/packageurl-python/issues/65
+from packageurl import PackageURL  # type: ignore
+from toml import loads as load_toml
+
+from ._debug import DebugMessageCallback, quiet
+
+
+class PdmParser(BaseParser):
+
+    def __init__(
+            self, pdm_lock_contents: str, use_purl_bom_ref: bool = False,
+            *,
+            debug_message: DebugMessageCallback = quiet
+    ) -> None:
+        super().__init__()
+        debug_message('init {}', self.__class__.__name__)
+
+        debug_message('loading pdm_lock_contents')
+        pdm_lock = load_toml(pdm_lock_contents)
+
+        debug_message('processing pdm_lock')
+        for package in pdm_lock['package']:
+            debug_message('processing package: {!r}', package)
+            purl = PackageURL(type='pypi', name=package['name'], version=package['version'])
+            bom_ref = purl.to_string() if use_purl_bom_ref else None
+            component = Component(
+                name=package['name'], bom_ref=bom_ref, version=package['version'],
+                purl=purl
+            )
+            debug_message('detecting package_files')
+            package_files = package.get('files', [])
+            debug_message('processing package_files: {!r}', package_files)
+            for file_metadata in package_files:
+                debug_message('processing file_metadata: {!r}', file_metadata)
+                try:
+                    component.external_references.add(ExternalReference(
+                        reference_type=ExternalReferenceType.DISTRIBUTION,
+                        url=XsUri(component.get_pypi_url()),
+                        comment=f'Distribution file: {file_metadata["file"]}',
+                        hashes=[HashType.from_composite_str(file_metadata['hash'])]
+                    ))
+                except CycloneDxModelException as error:
+                    debug_message('Warning: suppressed {!r}', error)
+                    del error
+            self._components.append(component)
+
+
+class PdmFileParser(PdmParser):
+
+    def __init__(
+            self, pdm_lock_filename: str, use_purl_bom_ref: bool = False,
+            *,
+            debug_message: DebugMessageCallback = quiet
+    ) -> None:
+        debug_message('open file: {}', pdm_lock_filename)
+        with open(pdm_lock_filename) as plf:
+            super(PdmFileParser, self).__init__(
+                pdm_lock_contents=plf.read(), use_purl_bom_ref=use_purl_bom_ref,
+                debug_message=debug_message
+            )
