diff --git a/http-body-util/src/combinators/chain.rs b/http-body-util/src/combinators/chain.rs
new file mode 100644
index 0000000..fa17a2d
--- /dev/null
+++ b/http-body-util/src/combinators/chain.rs
@@ -0,0 +1,713 @@
+use http::HeaderMap;
+use http_body::{Body, Frame, SizeHint};
+use pin_project_lite::pin_project;
+use std::{
+ pin::Pin,
+ task::{Context, Poll},
+};
+
+pin_project! {
+ /// A body that links two bodies together, in a chain.
+ ///
+ /// See [`BodyExt::chain()`] for more information.
+ #[project = ChainProj]
+ pub struct Chain {
+ #[pin]
+ inner: Inner,
+ }
+}
+
+pin_project! {
+ #[project = InnerProj]
+ pub enum Inner {
+ First {
+ #[pin]
+ first: A,
+ second: Option,
+ },
+ Second {
+ #[pin]
+ second: B,
+ trailers: Option,
+ },
+ Finished,
+ }
+}
+
+// === impl Chain ===
+
+impl Chain {
+ /// Returns a "chained" body.
+ ///
+ /// The contents of the first provided body will precede the contents of the second body.
+ pub fn new(first: A, second: B) -> Self {
+ Self {
+ inner: Inner::First {
+ first,
+ second: Some(second),
+ },
+ }
+ }
+}
+
+impl Body for Chain
+where
+ A: Body,
+ B: Body,
+ A::Error: Into,
+{
+ type Data = B::Data;
+ type Error = B::Error;
+
+ fn poll_frame(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ ) -> Poll