diff --git src/wp-includes/class-wp-hook.php src/wp-includes/class-wp-hook.php
index eb43c10c05..bacc4c4149 100644
--- src/wp-includes/class-wp-hook.php
+++ src/wp-includes/class-wp-hook.php
@@ -267,6 +267,34 @@ final class WP_Hook implements Iterator, ArrayAccess {
 	 * @return mixed The filtered value after all hooked functions are applied to it.
 	 */
 	public function apply_filters( $value, $args ) {
+		return $this->apply_filters_typed( 'mixed', $value, $args );
+	}
+
+	/**
+	 * Calls the callback functions that have been added to a filter hook in a typesafe manner.
+	 *
+	 * @since 5.8.0
+	 *
+	 * @param mixed $value The value to filter.
+	 * @param array $args  Additional parameters to pass to the callback functions.
+	 *                     This array is expected to include $value at index 0.
+	 * @return mixed The filtered value after all hooked functions are applied to it.
+	 */
+	public function apply_filters_typesafe( $value, $args ) {
+		return $this->apply_filters_typed( gettype( $value ), $value, $args );
+	}
+
+	/**
+	 * Calls the callback functions that have been added to a filter hook in a typed manner.
+	 *
+	 * @since 5.8.0
+	 *
+	 * @param mixed $value The value to filter.
+	 * @param array $args  Additional parameters to pass to the callback functions.
+	 *                     This array is expected to include $value at index 0.
+	 * @return mixed The filtered value after all hooked functions are applied to it.
+	 */
+	public function apply_filters_typed( $type, $value, $args ) {
 		if ( ! $this->callbacks ) {
 			return $value;
 		}
@@ -287,12 +315,24 @@ final class WP_Hook implements Iterator, ArrayAccess {
 
 				// Avoid the array_slice() if possible.
 				if ( 0 == $the_['accepted_args'] ) {
-					$value = call_user_func( $the_['function'] );
+					$next_value = call_user_func( $the_['function'] );
 				} elseif ( $the_['accepted_args'] >= $num_args ) {
-					$value = call_user_func_array( $the_['function'], $args );
+					$next_value = call_user_func_array( $the_['function'], $args );
 				} else {
-					$value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int) $the_['accepted_args'] ) );
+					$next_value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int) $the_['accepted_args'] ) );
+				}
+				if ( ! is_type( $type, $next_value ) ) {
+					_doing_it_wrong(
+						$the_['function'],
+						sprintf(
+							__( 'Invalid type returned in filter. Expected %1$s but received %2$s' ),
+							$type,
+							gettype( $next_value )
+						),
+						'5.8'
+					);
 				}
+				$value = $next_value;
 			}
 		} while ( false !== next( $this->iterations[ $nesting_level ] ) );
 
diff --git src/wp-includes/load.php src/wp-includes/load.php
index a68e6db435..8281a48347 100644
--- src/wp-includes/load.php
+++ src/wp-includes/load.php
@@ -1517,6 +1517,45 @@ function is_wp_error( $thing ) {
 	return $is_wp_error;
 }
 
+/**
+ * Checks whether the given variable is a certain type.
+ *
+ * Returns whether `$value` is certain type.
+ *
+ * @since 5.8.0
+ *
+ * @param string $type  The type to check.
+ * @param mixed  $value The variable to check.
+ * @return bool Whether the variable is of the type.
+ */
+function is_type( $type, $value ) {
+	switch( $type ) {
+		case 'boolean':
+			return is_bool( $value );
+		case 'integer':
+			return is_int( $value );
+		case 'double':
+			return is_float( $value );
+		case 'string':
+			return is_string( $value );
+		case 'array':
+			return is_array( $value );
+		case 'object':
+			return is_object( $value );
+		case 'resource':
+		case 'resource (closed)':
+			return is_resource( $value );
+		case 'NULL':
+			return is_null( $value );
+		case 'unknown_type':
+			return false;
+		case 'mixed':
+			return true;
+		default:
+			return is_a( $value, $type ) || is_subclass_of( $value, $type );
+	}
+}
+
 /**
  * Determines whether file modifications are allowed.
  *
diff --git src/wp-includes/plugin.php src/wp-includes/plugin.php
index 7bcfc7aa94..5a0736d6b2 100644
--- src/wp-includes/plugin.php
+++ src/wp-includes/plugin.php
@@ -184,10 +184,37 @@ function has_filter( $tag, $function_to_check = false ) {
  * @param mixed  ...$args Additional parameters to pass to the callback functions.
  * @return mixed The filtered value after all hooked functions are applied to it.
  */
-function apply_filters( $tag, $value ) {
+function apply_filters( $tag, $value, ...$args ) {
+	return apply_filters_typed( 'mixed', $tag, $value, ...$args );
+}
+
+/**
+ * Calls the callback functions that have been added to a filter hook in a typesafe manner.
+ *
+ * @param string $tag     The name of the filter hook.
+ * @param mixed  $value   The value to filter.
+ * @param mixed  ...$args Additional parameters to pass to the callback functions.
+ * @return mixed The filtered value after all hooked functions are applied to it.
+ */
+function apply_filters_typesafe( $tag, $value, ...$args ) {
+	return apply_filters_typed( gettype( $value ), $tag, $value, ...$args );
+}
+
+/**
+ * Calls the callback functions that have been added to a filter hook in a typed manner.
+ *
+ * @param string $type    The type the return value should have.
+ * @param string $tag     The name of the filter hook.
+ * @param mixed  $value   The value to filter.
+ * @param mixed  ...$args Additional parameters to pass to the callback functions.
+ * @return mixed The filtered value after all hooked functions are applied to it.
+ */
+function apply_filters_typed( $type, $tag, $value ) {
 	global $wp_filter, $wp_current_filter;
 
 	$args = func_get_args();
+	// Don't pass the type to the 'all' actions.
+	array_shift( $args );
 
 	// Do 'all' actions first.
 	if ( isset( $wp_filter['all'] ) ) {
@@ -209,7 +236,7 @@ function apply_filters( $tag, $value ) {
 	// Don't pass the tag name to WP_Hook.
 	array_shift( $args );
 
-	$filtered = $wp_filter[ $tag ]->apply_filters( $value, $args );
+	$filtered = $wp_filter[ $tag ]->apply_filters_typed( $type, $value, $args );
 
 	array_pop( $wp_current_filter );
 
