diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8478647c5481af1d530cea679f61017ddb6fad6..897bc5fe115f2e43aa9e7f4be762064b4680a2f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -64,6 +64,10 @@ Coq 8.11 is no longer supported in this version of Iris.
   - Use `□`/`-∗` instead of `<pers>`/`→`.
   - Strengthen to ensure that functions for recursive calls are non-expansive.
 * Add `big_andM` (big conjunction on finite maps) with lemmas similar to `big_andL`.
+* Add transitive embedding that constructs an embedding of `PROP1` into `PROP3`
+  by combining the embeddings of `PROP1` into `PROP2` and `PROP2` into `PROP3`.
+  This construct is *not* declared as an instance to avoid TC search divergence.
+  (by Hai Dang, BedRock Systems)
 
 **Changes in `proofmode`:**
 
diff --git a/iris/bi/embedding.v b/iris/bi/embedding.v
index 509f35cc1d8ee423091bb1de0fa5eb84a2be3a73..8a7e39e5827220c22bfe6129b0543d2c13547893 100644
--- a/iris/bi/embedding.v
+++ b/iris/bi/embedding.v
@@ -317,3 +317,62 @@ search for the other premises fail. See the proof of [monPred_objectively_plain]
 for an example where it would fail with a regular [Instance].*)
 Global Hint Extern 4 (Plain _) =>
   notypeclasses refine (embed_plain _ _) : typeclass_instances.
+
+(** Transitive embedding: this constructs an embedding of [PROP1] into [PROP3]
+by combining the embeddings of [PROP1] into [PROP2] and [PROP2] into [PROP3].
+Note that declaring these instances globally can make TC search ambiguous or
+diverging. These are only defined so that a user can conveniently use them to
+manually combine embeddings. *)
+Section embed_embed.
+  Context `{BiEmbed PROP1 PROP2, BiEmbed PROP2 PROP3}.
+
+  Local Instance embed_embed : Embed PROP1 PROP3 := λ P, ⎡ ⎡ P ⎤ ⎤%I.
+
+  Lemma embed_embedding_mixin : BiEmbedMixin PROP1 PROP3 embed_embed.
+  Proof.
+    split; unfold embed, embed_embed.
+    - solve_proper.
+    - solve_proper.
+    - intros P. by rewrite !embed_emp_valid.
+    - intros PROP' ? P Q. by rewrite !embed_interal_inj.
+    - by rewrite -!embed_emp_2.
+    - intros P Q. by rewrite -!embed_impl.
+    - intros A Φ. by rewrite -!embed_forall.
+    - intros A Φ. by rewrite -!embed_exist.
+    - intros P Q. by rewrite -!embed_sep.
+    - intros P Q. by rewrite -!embed_wand.
+    - intros P. by rewrite -!embed_persistently.
+  Qed.
+  Local Instance embed_bi_embed : BiEmbed PROP1 PROP3 :=
+    {| bi_embed_mixin := embed_embedding_mixin |}.
+
+  Lemma embed_embed_alt (P : PROP1) : ⎡ P ⎤ ⊣⊢@{PROP3} ⎡ ⎡ P ⎤ ⎤.
+  Proof. done. Qed.
+
+  Lemma embed_embed_emp :
+    BiEmbedEmp PROP1 PROP2 → BiEmbedEmp PROP2 PROP3 →
+    BiEmbedEmp PROP1 PROP3.
+  Proof. rewrite /BiEmbedEmp !embed_embed_alt. by intros -> ->. Qed.
+  Lemma embed_embed_later :
+    BiEmbedLater PROP1 PROP2 → BiEmbedLater PROP2 PROP3 →
+    BiEmbedLater PROP1 PROP3.
+  Proof. intros ?? P. by rewrite !embed_embed_alt !embed_later. Qed.
+  Lemma embed_embed_internal_eq
+      `{!BiInternalEq PROP1, !BiInternalEq PROP2, !BiInternalEq PROP3} :
+    BiEmbedInternalEq PROP1 PROP2 → BiEmbedInternalEq PROP2 PROP3 →
+    BiEmbedInternalEq PROP1 PROP3.
+  Proof. intros ?? A x y. by rewrite !embed_embed_alt !embed_internal_eq. Qed.
+  Lemma embed_embed_bupd `{!BiBUpd PROP1, !BiBUpd PROP2, !BiBUpd PROP3} :
+    BiEmbedBUpd PROP1 PROP2 → BiEmbedBUpd PROP2 PROP3 →
+    BiEmbedBUpd PROP1 PROP3.
+  Proof. intros ?? P. by rewrite !embed_embed_alt !embed_bupd. Qed.
+  Lemma embed_embed_fupd `{!BiFUpd PROP1, !BiFUpd PROP2, !BiFUpd PROP3} :
+    BiEmbedFUpd PROP1 PROP2 → BiEmbedFUpd PROP2 PROP3 →
+    BiEmbedFUpd PROP1 PROP3.
+  Proof. intros ?? E1 E2 P. by rewrite !embed_embed_alt !embed_fupd. Qed.
+  Lemma embed_embed_plainly
+      `{!BiPlainly PROP1, !BiPlainly PROP2, !BiPlainly PROP3} :
+    BiEmbedPlainly PROP1 PROP2 → BiEmbedPlainly PROP2 PROP3 →
+    BiEmbedPlainly PROP1 PROP3.
+  Proof. intros ?? P. by rewrite !embed_embed_alt !embed_plainly. Qed.
+End embed_embed.