diff --git a/sqlx-core/src/from_row.rs b/sqlx-core/src/from_row.rs
index 9c647d370a..8dc5b8c7fa 100644
--- a/sqlx-core/src/from_row.rs
+++ b/sqlx-core/src/from_row.rs
@@ -1,4 +1,9 @@
-use crate::{error::Error, row::Row};
+use std::marker::PhantomData;
+
+use crate::{
+    error::{Error, UnexpectedNullError},
+    row::Row,
+};
 
 /// A record that can be built from a row returned by the database.
 ///
@@ -487,3 +492,47 @@ impl_from_row_for_tuple!(
     (14) -> T15;
     (15) -> T16;
 );
+
+pub struct Wrapper<T>(pub PhantomData<T>);
+
+pub trait FromOptRow<'r, R, T> {
+    fn __from_row(&self, row: &'r R) -> Result<T, Error>;
+}
+
+impl<'r, R, T> FromOptRow<'r, R, Option<T>> for Wrapper<Option<T>>
+where
+    R: Row,
+    T: FromRow<'r, R>,
+{
+    fn __from_row(&self, row: &'r R) -> Result<Option<T>, Error> {
+        let value = T::from_row(row).map(Some);
+        if let Err(Error::ColumnDecode { source, .. }) = value.as_ref() {
+            if let Some(UnexpectedNullError) = source.downcast_ref() {
+                return Ok(None);
+            }
+        }
+        value
+    }
+}
+
+impl<'r, R, T> FromOptRow<'r, R, T> for &Wrapper<T>
+where
+    R: Row,
+    T: FromRow<'r, R>,
+{
+    fn __from_row(&self, row: &'r R) -> Result<T, Error> {
+        T::from_row(row)
+    }
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __from_opt_row {
+    ($t:ty, $row:expr) => {{
+        use std::marker::PhantomData;
+        use $crate::from_row::{FromOptRow, Wrapper};
+        let wrapper = Wrapper(PhantomData::<$t>);
+        let value = (&wrapper).__from_row($row);
+        value
+    }};
+}
diff --git a/sqlx-macros-core/src/derives/row.rs b/sqlx-macros-core/src/derives/row.rs
index 5f7b6dca70..d46c400778 100644
--- a/sqlx-macros-core/src/derives/row.rs
+++ b/sqlx-macros-core/src/derives/row.rs
@@ -108,14 +108,12 @@ fn expand_derive_from_row_struct(
                 }
                 // Flatten
                 (true, None, false) => {
-                    predicates.push(parse_quote!(#ty: ::sqlx::FromRow<#lifetime, R>));
-                    parse_quote!(<#ty as ::sqlx::FromRow<#lifetime, R>>::from_row(__row))
+                    parse_quote!(::sqlx::__from_opt_row!(#ty, __row))
                 }
                 // Flatten + Try from
                 (true, Some(try_from), false) => {
-                    predicates.push(parse_quote!(#try_from: ::sqlx::FromRow<#lifetime, R>));
                     parse_quote!(
-                        <#try_from as ::sqlx::FromRow<#lifetime, R>>::from_row(__row)
+                        ::sqlx::__from_opt_row!(#try_from, __row)
                             .and_then(|v| {
                                 <#ty as ::std::convert::TryFrom::<#try_from>>::try_from(v)
                                     .map_err(|e| {
diff --git a/src/lib.rs b/src/lib.rs
index 870fa703c5..0fbf7df945 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,7 +9,8 @@ pub use sqlx_core::connection::{ConnectOptions, Connection};
 pub use sqlx_core::database::{self, Database};
 pub use sqlx_core::describe::Describe;
 pub use sqlx_core::executor::{Execute, Executor};
-pub use sqlx_core::from_row::FromRow;
+pub use sqlx_core::from_row::{FromRow, Wrapper, FromOptRow};
+pub use sqlx_core::__from_opt_row;
 pub use sqlx_core::pool::{self, Pool};
 #[doc(hidden)]
 pub use sqlx_core::query::query_with_result as __query_with_result;