diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
index d610504692..a2b1565f6e 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
@@ -371,6 +371,7 @@ abstract class WP_REST_Controller {
 	 * Adds the values from additional fields to a data object.
 	 *
 	 * @since 4.7.0
+	 * @since 5.1.0 includes $skip_field value to return in order to omit the field from the response
 	 *
 	 * @param array           $object  Data object.
 	 * @param WP_REST_Request $request Full details about the request.
@@ -392,7 +393,34 @@ abstract class WP_REST_Controller {
 				continue;
 			}
 
-			$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
+			/**
+			 * Sentinel value which the callback can return in order
+			 * to indicate that this field should not be added
+			 *
+			 * We're relying on the fact that object identity returns
+			 * whether two objects are the same instance of the same
+			 * class. That means it will be impossible to confuse a
+			 * value created inside the callback with this value.
+			 *
+			 * @example
+			 * function my_get_callback( $object, $field_name, $request, $object_type, $skip_field ) {
+			 *   // only attach video meta to video attachments
+			 *   if ( 0 !== strpos( $object->post_mime_type, 'video/' ) {
+			 *     return $skip_field;
+			 *   }
+			 *
+			 *   return calculate_expensive_metadata( $object->ID );
+			 * }
+			 *
+			 */
+			$skip_field  = new stdClass;
+			$field_value = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type(), $skip_field );
+
+			if ( $field_value === $skip_field ) {
+				continue;
+			}
+
+			$object[ $field_name ] = $field_value;
 		}
 
 		return $object;
