diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7631a57b..312f4533 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,6 +29,8 @@ jobs:
             features: rustc-rayon
           - rust: stable
             features: serde
+          - rust: stable
+            features: borsh
           - rust: stable
             features: std
           - rust: beta
@@ -119,8 +121,10 @@ jobs:
         with:
           tool: cargo-hack
       - run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z direct-minimal-versions
-      - name: Build
-        run: cargo build --verbose --all-features
+      - name: Build (nightly)
+        run: cargo +nightly build --verbose --all-features
+      - name: Build (MSRV)
+        run: cargo build --verbose --features arbitrary,quickcheck,serde,rayon
 
   # One job that "summarizes" the success state of this pipeline. This can then be added to branch
   # protection, rather than having to add each job separately.
diff --git a/Cargo.toml b/Cargo.toml
index 95216563..13a54dea 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ equivalent = { version = "1.0", default-features = false }
 arbitrary = { version = "1.0", optional = true, default-features = false }
 quickcheck = { version = "1.0", optional = true, default-features = false }
 serde = { version = "1.0", optional = true, default-features = false }
+borsh = { version = "1.2", optional = true, default-features = false }
 rayon = { version = "1.5.3", optional = true }
 
 # Internal feature, only used when building as part of rustc,
@@ -54,7 +55,7 @@ no-dev-version = true
 tag-name = "{{version}}"
 
 [package.metadata.docs.rs]
-features = ["arbitrary", "quickcheck", "serde", "rayon"]
+features = ["arbitrary", "quickcheck", "serde", "borsh", "rayon"]
 rustdoc-args = ["--cfg", "docsrs"]
 
 [workspace]
diff --git a/src/borsh.rs b/src/borsh.rs
new file mode 100644
index 00000000..7b4afdc4
--- /dev/null
+++ b/src/borsh.rs
@@ -0,0 +1,123 @@
+#![cfg_attr(docsrs, doc(cfg(feature = "borsh")))]
+
+use alloc::vec::Vec;
+use core::hash::BuildHasher;
+use core::hash::Hash;
+use core::iter::ExactSizeIterator;
+use core::mem::size_of;
+
+use borsh::error::ERROR_ZST_FORBIDDEN;
+use borsh::io::{Error, ErrorKind, Read, Result, Write};
+use borsh::{BorshDeserialize, BorshSerialize};
+
+use crate::map::IndexMap;
+use crate::set::IndexSet;
+
+impl<K, V, S> BorshSerialize for IndexMap<K, V, S>
+where
+    K: BorshSerialize,
+    V: BorshSerialize,
+{
+    #[inline]
+    fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
+        check_zst::<K>()?;
+
+        let iterator = self.iter();
+
+        u32::try_from(iterator.len())
+            .map_err(|_| ErrorKind::InvalidData)?
+            .serialize(writer)?;
+
+        for (key, value) in iterator {
+            key.serialize(writer)?;
+            value.serialize(writer)?;
+        }
+
+        Ok(())
+    }
+}
+
+impl<K, V, S> BorshDeserialize for IndexMap<K, V, S>
+where
+    K: BorshDeserialize + Eq + Hash,
+    V: BorshDeserialize,
+    S: BuildHasher + Default,
+{
+    #[inline]
+    fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
+        check_zst::<K>()?;
+        let vec = <Vec<(K, V)>>::deserialize_reader(reader)?;
+        Ok(vec.into_iter().collect::<IndexMap<K, V, S>>())
+    }
+}
+
+impl<T, S> BorshSerialize for IndexSet<T, S>
+where
+    T: BorshSerialize,
+{
+    #[inline]
+    fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
+        check_zst::<T>()?;
+
+        let iterator = self.iter();
+
+        u32::try_from(iterator.len())
+            .map_err(|_| ErrorKind::InvalidData)?
+            .serialize(writer)?;
+
+        for item in iterator {
+            item.serialize(writer)?;
+        }
+
+        Ok(())
+    }
+}
+
+impl<T, S> BorshDeserialize for IndexSet<T, S>
+where
+    T: BorshDeserialize + Eq + Hash,
+    S: BuildHasher + Default,
+{
+    #[inline]
+    fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
+        check_zst::<T>()?;
+        let vec = <Vec<T>>::deserialize_reader(reader)?;
+        Ok(vec.into_iter().collect::<IndexSet<T, S>>())
+    }
+}
+
+fn check_zst<T>() -> Result<()> {
+    if size_of::<T>() == 0 {
+        return Err(Error::new(ErrorKind::InvalidData, ERROR_ZST_FORBIDDEN));
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod borsh_tests {
+    use super::*;
+
+    #[test]
+    fn map_borsh_roundtrip() {
+        let original_map: IndexMap<i32, i32> = {
+            let mut map = IndexMap::new();
+            map.insert(1, 2);
+            map.insert(3, 4);
+            map.insert(5, 6);
+            map
+        };
+        let serialized_map = borsh::to_vec(&original_map).unwrap();
+        let deserialized_map: IndexMap<i32, i32> =
+            BorshDeserialize::try_from_slice(&serialized_map).unwrap();
+        assert_eq!(original_map, deserialized_map);
+    }
+
+    #[test]
+    fn set_borsh_roundtrip() {
+        let original_map: IndexSet<i32> = [1, 2, 3, 4, 5, 6].into_iter().collect();
+        let serialized_map = borsh::to_vec(&original_map).unwrap();
+        let deserialized_map: IndexSet<i32> =
+            BorshDeserialize::try_from_slice(&serialized_map).unwrap();
+        assert_eq!(original_map, deserialized_map);
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index b88c1bce..d6d3ede9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -35,6 +35,8 @@
 //!   to [`IndexMap`] and [`IndexSet`]. Alternative implementations for
 //!   (de)serializing [`IndexMap`] as an ordered sequence are available in the
 //!   [`map::serde_seq`] module.
+//! * `borsh`: Adds implementations for [`BorshSerialize`] and [`BorshDeserialize`]
+//!   to [`IndexMap`] and [`IndexSet`].
 //! * `arbitrary`: Adds implementations for the [`arbitrary::Arbitrary`] trait
 //!   to [`IndexMap`] and [`IndexSet`].
 //! * `quickcheck`: Adds implementations for the [`quickcheck::Arbitrary`] trait
@@ -46,6 +48,8 @@
 //! [`no_std`]: #no-standard-library-targets
 //! [`Serialize`]: `::serde::Serialize`
 //! [`Deserialize`]: `::serde::Deserialize`
+//! [`BorshSerialize`]: `::borsh::BorshSerialize`
+//! [`BorshDeserialize`]: `::borsh::BorshDeserialize`
 //! [`arbitrary::Arbitrary`]: `::arbitrary::Arbitrary`
 //! [`quickcheck::Arbitrary`]: `::quickcheck::Arbitrary`
 //!
@@ -110,6 +114,8 @@ use alloc::vec::{self, Vec};
 mod arbitrary;
 #[macro_use]
 mod macros;
+#[cfg(feature = "borsh")]
+mod borsh;
 mod mutable_keys;
 #[cfg(feature = "serde")]
 mod serde;