diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b68390723c6794e3ddd91776b02b935150def92b_LmdpdGxhYi1jaS55bWw=..443952591e0c07435b5f9b18308c552702e6f94d_LmdpdGxhYi1jaS55bWw= 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,7 +9,7 @@
 
 mypy:
   variables:
-    INSTALL_SECTION: "[test,schema-validation]"
+    INSTALL_SECTION: "[test,schema-validation,qrcode]"
 
 test:
   variables:
@@ -13,7 +13,7 @@
 
 test:
   variables:
-    INSTALL_SECTION: "[test,schema-validation]"
+    INSTALL_SECTION: "[test,schema-validation,qrcode]"
 
 test-no-schema-validation:
   extends: test
diff --git a/__init__.py b/__init__.py
index b68390723c6794e3ddd91776b02b935150def92b_X19pbml0X18ucHk=..443952591e0c07435b5f9b18308c552702e6f94d_X19pbml0X18ucHk= 100644
--- a/__init__.py
+++ b/__init__.py
@@ -54,3 +54,4 @@
 )
 from .xref import Xref, JsonLD_ID
 from .image import ImageFile, ImageDataURL
+from .qr_code import QRCodeFile, QRCodeDataURL, stringToQRCode, QRToImage
diff --git a/pyproject.toml b/pyproject.toml
index b68390723c6794e3ddd91776b02b935150def92b_cHlwcm9qZWN0LnRvbWw=..443952591e0c07435b5f9b18308c552702e6f94d_cHlwcm9qZWN0LnRvbWw= 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,6 +19,7 @@
 
 [project.optional-dependencies]
 schema-validation = ["fastjsonschema"]
+qrcode = ["qrcode"]
 doc = ["sphinx"]
 test = []
 
diff --git a/qr_code.py b/qr_code.py
new file mode 100644
index 0000000000000000000000000000000000000000..443952591e0c07435b5f9b18308c552702e6f94d_cXJfY29kZS5weQ==
--- /dev/null
+++ b/qr_code.py
@@ -0,0 +1,90 @@
+##############################################################################
+#
+#    Converter Odoo module
+#    Copyright © 2020, 2025 XCG Consulting <https://xcg-consulting.fr>
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from collections.abc import Mapping
+from typing import Any
+
+from odoo import models  # type: ignore[import-untyped]
+
+from .base import Converter
+
+_qrcode: None | Exception = None
+try:
+    import qrcode  # type: ignore[import-untyped]
+except ImportError as e:
+    _qrcode = e
+
+
+def QRToImage(qr):
+    """Generate a image dict from a QR Code"""
+    return {"body": qr.data.decode("ascii"), "mime-type": "image/png"}
+
+
+def stringToQRCode(data: str):
+    """Generate a QR code from a string and return it as a base64-encoded image."""
+    if not data:
+        return False
+
+    if _qrcode is not None:
+        raise _qrcode
+
+    qr = qrcode.QRCode(
+        version=1,
+        error_correction=qrcode.constants.ERROR_CORRECT_L,
+        box_size=10,
+        border=4,
+    )
+    qr.add_data(data)
+    qr.make(fit=True)
+    return qr
+
+
+class QRCodeFile(Converter):
+    def __init__(self, fieldname):
+        self.fieldname = fieldname
+
+    def odoo_to_message(
+        self, instance: models.Model, ctx: Mapping | None = None
+    ) -> Any:
+        value = getattr(instance, self.fieldname)
+
+        if not value:
+            return {}
+
+        return QRToImage(stringToQRCode(value))
+
+
+class QRCodeDataURL(Converter):
+    def __init__(self, fieldname):
+        self.fieldname = fieldname
+
+    def odoo_to_message(
+        self, instance: models.Model, ctx: Mapping | None = None
+    ) -> Any:
+        value = getattr(instance, self.fieldname)
+
+        if not value:
+            return ""
+
+        image = QRToImage(stringToQRCode(value))
+        return "data:{};base64,{}".format(
+            image["mime-type"],
+            image["body"],
+        )