diff --git a/README.md b/README.md
index 888191b80b343952f0e1a560a493aa47f6d9c6f7..e065b76e4835020775989ccb1683039376091362 100644
--- a/README.md
+++ b/README.md
@@ -301,6 +301,25 @@ In addition to logs in the console, this job produces the following reports, kep
 | `$PYTHON_PROJECT_DIR/reports/py-ruff.gitlab.json` | [GitLab](https://docs.astral.sh/ruff/settings/#output-format) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscodequality) |
 | `$PYTHON_PROJECT_DIR/reports/py-ruff.native.json` | [JSON](https://docs.astral.sh/ruff/settings/#output-format) | [SonarQube integration](https://docs.sonarqube.org/latest/analysis/external-issues/)<br/>_This report is generated only if SonarQube template is detected_ |
 
+#### `py-mypy` job
+
+This job is **disabled by default** and performs code analysis based on [mypy](https://mypy.readthedocs.io/en/stable/).
+It is activated by setting `$MYPY_ENABLED` to `true`.
+
+It is bound to the `build` stage, and uses the following variables:
+
+| Input / Variable         | Description                        | Default value     |
+| ------------------------ | ---------------------------------- | ----------------- |
+| `mypy-args` / `MYPY_ARGS` | Additional [mypy CLI options](https://mypy.readthedocs.io/en/stable/command_line.html) | _none_           |
+| `mypy-files` / `MYPY_FILES` | Files or directories to analyse   | _none_ (by default analyses all found python source files) |
+
+In addition to a textual report in the console, this job produces the following reports, kept for one day:
+
+| Report         | Format                                                                       | Usage             |
+| -------------- | ---------------------------------------------------------------------------- | ----------------- |
+| `$PYTHON_PROJECT_DIR/reports/py-mypy.codeclimate.json` | [Code Climate](https://github.com/soul-catcher/mypy-gitlab-code-quality) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscodequality) |
+| `$PYTHON_PROJECT_DIR/reports/py-mypy.console.txt` | [mypy console output](https://mypy.readthedocs.io/) | [SonarQube integration](https://docs.sonarqube.org/latest/analysis/external-issues/) |
+
 ### SonarQube analysis
 
 If you're using the SonarQube template to analyse your Python code, here is a sample `sonar-project.properties` file:
@@ -326,6 +345,8 @@ sonar.python.pylint.reportPaths=reports/py-lint.parseable.txt
 sonar.python.bandit.reportPaths=reports/py-bandit.bandit.csv
 # Ruff: JSON format (if enabled)
 sonar.python.ruff.reportPaths=reports/py-ruff.native.json
+# mypy: JSON format (if enabled)
+sonar.python.mypy.reportPaths=reports/py-mypy.console.txt
 ```
 
 More info:
diff --git a/kicker.json b/kicker.json
index b6c9c789bde24db52281b08e48a7d8062309d0d1..518615487367fc95c63fd484be71782f4a9dd4b3 100644
--- a/kicker.json
+++ b/kicker.json
@@ -271,6 +271,24 @@
           "advanced": true
         }
       ]
+    },
+    {
+      "id": "mypy",
+      "name": "mypy",
+      "description": "Code analysis based on [mypy](https://mypy.readthedocs.io/).",
+      "enable_with": "MYPY_ENABLED",
+      "variables": [
+        {
+          "name": "MYPY_ARGS",
+          "description": "Additional [mypy CLI options](https://mypy.readthedocs.io/en/stable/command_line.html)",
+          "advanced": true
+        },
+        {
+          "name": "MYPY_FILES",
+          "description": "Files or directories to analyse",
+          "advanced": true
+        }
+      ]
     }
   ],
   "variants": [
diff --git a/templates/gitlab-ci-python.yml b/templates/gitlab-ci-python.yml
index 38d089ccc5ccfc1953b687700fef4fe8ec03a8d2..1cc04b9454666d812aa68152ca54f5a4d9558a4e 100644
--- a/templates/gitlab-ci-python.yml
+++ b/templates/gitlab-ci-python.yml
@@ -165,6 +165,16 @@ spec:
     ruff-ext-exclude:
       description: Define [extend-exclude](https://docs.astral.sh/ruff/settings/#extend-exclude) files
       default: ""
+    mypy-enabled:
+      description: Enable mypy
+      type: boolean
+      default: false
+    mypy-args:
+      description: Additional [mypy CLI options](https://mypy.readthedocs.io/en/stable/command_line.html)
+      default: ""
+    mypy-files:
+      description: Files or directories to analyse
+      default: ''
 ---
 # default workflow rules: Merge Request pipelines
 workflow:
@@ -275,6 +285,9 @@ variables:
   RUFF_ENABLED: $[[ inputs.ruff-enabled ]]
   RUFF_ARGS: $[[ inputs.ruff-args ]]
   RUFF_EXT_EXCLUDE: $[[ inputs.ruff-ext-exclude ]]
+  MYPY_ENABLED: $[[ inputs.mypy-enabled ]]
+  MYPY_ARGS: $[[ inputs.mypy-args ]]
+  MYPY_FILES: $[[ inputs.mypy-files ]]
 
 
 .python-scripts: &python-scripts |
@@ -939,6 +952,32 @@ py-ruff:
       when: never
     - !reference [.test-policy, rules]
 
+py-mypy:
+  extends: .python-base
+  stage: build
+  variables:
+    MYPY_CACHE_DIR: "$CI_PROJECT_DIR/.cache/mypy"
+  script:
+    - mkdir -p -m 777 reports
+    - install_requirements
+    - _pip install mypy mypy-to-codeclimate
+    - _run mypy ${MYPY_ARGS} ${MYPY_FILES:-$(find -type f -name "*.py" -not -path "./.cache/*")} | tee reports/py-mypy.console.txt || true
+    # mypy-to-codeclimate will fail if any error was found
+    - _run mypy-to-codeclimate reports/py-mypy.console.txt reports/py-mypy.codeclimate.json
+  artifacts:
+    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
+    expire_in: 1 day
+    when: always
+    reports:
+      codequality: $PYTHON_PROJECT_DIR/reports/py-mypy.codeclimate.json
+    paths:
+      - "$PYTHON_PROJECT_DIR/reports/py-mypy.*"
+  rules:
+    # exclude if $MYPY_ENABLED not set
+    - if: '$MYPY_ENABLED != "true"'
+      when: never
+    - !reference [.test-policy, rules]
+
 ###############################################################################################
 #                                      test stage                                             #
 ###############################################################################################