diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000000000000000000000000000000000000..ae76261eadc6e26b37902c123a3146f0cba94aef
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,3 @@
+BasedOnStyle: Google
+SortIncludes: false
+Standard: Cpp03
diff --git a/.github/workflows/jrl-cmakemodules.yml b/.github/workflows/jrl-cmakemodules.yml
index 0de0d5ec9df241637d1649606131f99d492d80ac..4ae45554ce5e3c24bab9a59ba1e6b093a7b02da0 100644
--- a/.github/workflows/jrl-cmakemodules.yml
+++ b/.github/workflows/jrl-cmakemodules.yml
@@ -1,5 +1,8 @@
 name: JRL-cmakemodules
 on: [push,pull_request]
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
 
 jobs:
   with-submodules:
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index c3c182b817a53bdf4107f9923df5f0e70e3b3a07..f48a606c5c034c0d9f9f4abaf80ad42870252e10 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -1,6 +1,9 @@
 name: Check build on linux
 
 on: ["push", "pull_request"]
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
 
 jobs:
   test:
diff --git a/.github/workflows/macos-linux-conda.yml b/.github/workflows/macos-linux-conda.yml
index f40513a54acae8d30824212f6d83ff5899e5e0bb..edc1784413efb81fcd2f91bd24206acf329e5810 100644
--- a/.github/workflows/macos-linux-conda.yml
+++ b/.github/workflows/macos-linux-conda.yml
@@ -1,6 +1,9 @@
 name: Conda-CI
 
 on: [push,pull_request]
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
 
 jobs:
   eigenpy-conda:
diff --git a/.github/workflows/reloc.yml b/.github/workflows/reloc.yml
index ae271202955ecf63b93e7201fada793e071c1f21..6f317f7b51194d3dbde1980d92a62046482923f4 100644
--- a/.github/workflows/reloc.yml
+++ b/.github/workflows/reloc.yml
@@ -1,6 +1,9 @@
 name: Ensure relocatable
 
 on: [push,pull_request]
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
 
 jobs:
   relocatable:
diff --git a/.github/workflows/ros_ci.yml b/.github/workflows/ros_ci.yml
index cdcaacf19880220d27ec0070a2f5bd1cb87ddd9a..330f8379a0374d123b0a85f74c2752d33f53e9fa 100644
--- a/.github/workflows/ros_ci.yml
+++ b/.github/workflows/ros_ci.yml
@@ -5,6 +5,9 @@ name: ROS-CI
 
 # This determines when this workflow is run
 on: [push, pull_request] # on all pushes and PRs
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
 
 jobs:
   CI:
diff --git a/.github/workflows/windows-conda.yml b/.github/workflows/windows-conda.yml
index 3ab602b3fd56241d6f42f3b45367010dad571e49..74d923fd74bf33f73345c07c5468bcdc956d6c43 100644
--- a/.github/workflows/windows-conda.yml
+++ b/.github/workflows/windows-conda.yml
@@ -1,5 +1,8 @@
 name: Build Eigenpy on Windows via Conda
 on: [push,pull_request]
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
 
 jobs:
   build:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index af4445e4a90140152f3ba1b547e6f72df9a57c0d..2de93919c7b1e412d3caf4948e200a6a13dda762 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### Added
 - Support for `Eigen::SparseMatrix` types ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426))
+- Support for `boost::variant` types with `BoostVariantConvertor` ([#430](https://github.com/stack-of-tasks/eigenpy/pull/430))
 
 ### Fixed
 - Fix the issue of missing exposition of Eigen types with __int64 scalar type ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426))
diff --git a/include/eigenpy/boost-variant.hpp b/include/eigenpy/boost-variant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f3735a953fe4f718041b457921c74a816a2bda40
--- /dev/null
+++ b/include/eigenpy/boost-variant.hpp
@@ -0,0 +1,136 @@
+//
+// Copyright (c) 2024 INRIA
+//
+
+#ifndef __eigenpy_utils_boost_variant_hpp__
+#define __eigenpy_utils_boost_variant_hpp__
+
+#include <boost/python.hpp>
+#include <boost/variant.hpp>
+#include <boost/mpl/for_each.hpp>
+
+namespace eigenpy {
+
+namespace details {
+
+/// Convert boost::variant<class...> alternative to a Python object.
+/// This converter copy the alternative.
+template <typename Variant>
+struct BoostVariantValueToObject : boost::static_visitor<PyObject*> {
+  typedef Variant variant_type;
+
+  static result_type convert(const variant_type& gm) {
+    return apply_visitor(BoostVariantValueToObject(), gm);
+  }
+
+  template <typename T>
+  result_type operator()(T& t) const {
+    return boost::python::incref(boost::python::object(t).ptr());
+  }
+};
+
+/// Convert boost::variant<class...> alternative reference to a Python object.
+/// This converter return the alternative reference.
+/// The code that create the reference holder is taken from
+/// \see boost::python::to_python_indirect.
+template <typename Variant>
+struct BoostVariantRefToObject : boost::static_visitor<PyObject*> {
+  typedef Variant variant_type;
+
+  static result_type convert(const variant_type& gm) {
+    return apply_visitor(BoostVariantRefToObject(), gm);
+  }
+
+  template <typename T>
+  result_type operator()(T& t) const {
+    return boost::python::detail::make_reference_holder::execute(&t);
+  }
+};
+
+/// Converter used in \see ReturnInternalBoostVariant.
+/// This is inspired by \see boost::python::reference_existing_object.
+/// It will call \see BoostVariantRefToObject to extract the alternative
+/// reference.
+template <typename Variant>
+struct BoostVariantConverter {
+  typedef Variant variant_type;
+
+  template <class T>
+  struct apply {
+    struct type {
+      PyObject* operator()(const variant_type& gm) const {
+        return BoostVariantRefToObject<variant_type>::convert(gm);
+      }
+
+#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
+      PyTypeObject const* get_pytype() const {
+        return boost::python::converter::registered_pytype<
+            variant_type>::get_pytype();
+      }
+#endif
+    };
+  };
+};
+
+/// Declare a variant alternative implicitly convertible to the variant
+template <typename Variant>
+struct BoostVariantImplicitlyConvertible {
+  typedef Variant variant_type;
+
+  template <class T>
+  void operator()(T) {
+    boost::python::implicitly_convertible<T, variant_type>();
+  }
+};
+
+}  // namespace details
+
+/// Variant of \see boost::python::return_internal_reference that
+/// extract boost::variant<class...> alternative reference before
+/// converting it into a PyObject
+template <typename Variant>
+struct ReturnInternalBoostVariant : boost::python::return_internal_reference<> {
+  typedef Variant variant_type;
+
+  typedef details::BoostVariantConverter<variant_type> result_converter;
+};
+
+/// Define a defaults converter to convert a boost::variant alternative to a
+/// Python object by copy and to convert implicitly an alternative to a
+/// boost::variant.
+///
+/// Example:
+///
+///   typedef boost::variant<Struct1, Struct2> MyVariant;
+///   struct VariantHolder {
+///     MyVariant variant;
+///   };
+///   ...
+///   void expose() {
+///     boost::python::class_<Struct1>("Struct1", bp::init<>());
+///     boost::python::class_<Struct2>("Struct1", bp::init<>())
+///     typedef eigenpy::BoostVariantConvertor<MyVariant> Convertor;
+///     Convertor::registration();
+///
+///     boost::python::class_<VariantHolder>("VariantHolder", bp::init<>())
+///       .add_property("variant",
+///         bp::make_getter(&VariantHolder::variant,
+///                         Convertor::return_internal_reference()),
+///         bp::make_setter(&VariantHolder::variant));
+///   }
+template <typename Variant>
+struct BoostVariantConvertor {
+  typedef Variant variant_type;
+  typedef ReturnInternalBoostVariant<variant_type> return_internal_reference;
+
+  static void registration() {
+    typedef details::BoostVariantValueToObject<variant_type> variant_to_value;
+    boost::python::to_python_converter<variant_type, variant_to_value>();
+    boost::mpl::for_each<typename variant_type::types>(
+        details::BoostVariantImplicitlyConvertible<variant_type>());
+  }
+};
+
+}  // namespace eigenpy
+
+#endif  // ifndef __eigenpy_utils_boost_variant_hpp__
diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
index dab2bfba7596b7ceff0927e21e622b62ba1e8e22..c65c6cf1995d436de6a614ca141551e3f851d4f8 100644
--- a/unittest/CMakeLists.txt
+++ b/unittest/CMakeLists.txt
@@ -45,6 +45,7 @@ add_lib_unit_test(std_vector)
 add_lib_unit_test(std_array)
 add_lib_unit_test(std_pair)
 add_lib_unit_test(user_struct)
+add_lib_unit_test(boost_variant)
 
 function(config_bind_optional tagname opttype)
   set(MODNAME bind_optional_${tagname})
@@ -132,6 +133,10 @@ add_python_unit_test("py-user-struct" "unittest/python/test_user_struct.py"
                      "python;unittest")
 set_tests_properties("py-user-struct" PROPERTIES DEPENDS ${PYWRAP})
 
+add_python_unit_test("py-boost-variant" "unittest/python/test_boost_variant.py"
+                     "python;unittest")
+set_tests_properties("py-boost-variant" PROPERTIES DEPENDS ${PYWRAP})
+
 add_python_unit_test("py-bind-virtual" "unittest/python/test_bind_virtual.py"
                      "python;unittest")
 set_tests_properties("py-bind-virtual" PROPERTIES DEPENDS ${PYWRAP})
diff --git a/unittest/boost_variant.cpp b/unittest/boost_variant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..096b69a489f532a006c31b745bcb75fe10eaa043
--- /dev/null
+++ b/unittest/boost_variant.cpp
@@ -0,0 +1,41 @@
+/// @file
+/// @copyright Copyright 2024 CNRS INRIA
+
+#include <eigenpy/eigenpy.hpp>
+#include <eigenpy/boost-variant.hpp>
+
+namespace bp = boost::python;
+
+struct V1 {
+  int v;
+};
+struct V2 {
+  char v;
+};
+typedef boost::variant<V1, V2> MyVariant;
+
+MyVariant make_variant() { return V1(); }
+
+struct VariantHolder {
+  MyVariant variant;
+};
+
+BOOST_PYTHON_MODULE(boost_variant) {
+  using namespace eigenpy;
+
+  enableEigenPy();
+
+  bp::class_<V1>("V1", bp::init<>()).def_readwrite("v", &V1::v);
+  bp::class_<V2>("V2", bp::init<>()).def_readwrite("v", &V2::v);
+
+  typedef eigenpy::BoostVariantConvertor<MyVariant> Convertor;
+  Convertor::registration();
+
+  bp::def("make_variant", make_variant);
+
+  boost::python::class_<VariantHolder>("VariantHolder", bp::init<>())
+      .add_property("variant",
+                    bp::make_getter(&VariantHolder::variant,
+                                    Convertor::return_internal_reference()),
+                    bp::make_setter(&VariantHolder::variant));
+}
diff --git a/unittest/python/test_boost_variant.py b/unittest/python/test_boost_variant.py
new file mode 100644
index 0000000000000000000000000000000000000000..023b940bdc91150e97f80d9d96f142cca73fff54
--- /dev/null
+++ b/unittest/python/test_boost_variant.py
@@ -0,0 +1,36 @@
+from boost_variant import V1, V2, VariantHolder, make_variant
+
+variant = make_variant()
+assert isinstance(variant, V1)
+
+v1 = V1()
+v1.v = 10
+
+v2 = V2()
+v2.v = "c"
+
+variant_holder = VariantHolder()
+
+# Test copy from variant alternative V1 to non initialized variant
+variant_holder.variant = v1
+assert isinstance(variant_holder.variant, V1)
+assert variant_holder.variant.v == v1.v
+
+# variant_holder.variant is a copy of v1
+variant_holder.variant.v = 11
+assert v1.v != variant_holder.variant.v
+
+# Test variant_holder.variant return by reference
+# v1 reference variant_holder.variant
+v1 = variant_holder.variant
+variant_holder.variant.v = 100
+assert variant_holder.variant.v == 100
+assert v1.v == 100
+v1.v = 1000
+assert variant_holder.variant.v == 1000
+assert v1.v == 1000
+
+# Test with the second alternative type
+variant_holder.variant = v2
+assert isinstance(variant_holder.variant, V2)
+assert variant_holder.variant.v == v2.v