diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php
index b732f83ad4e6..7343e5d81ab7 100644
--- a/src/wp-includes/comment.php
+++ b/src/wp-includes/comment.php
@@ -466,6 +466,7 @@ function delete_comment_meta( $comment_id, $meta_key, $meta_value = '' ) {
  * Retrieves comment meta field for a comment.
  *
  * @since 2.9.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
  * @link https://developer.wordpress.org/reference/functions/get_comment_meta/
  *
@@ -475,13 +476,14 @@ function delete_comment_meta( $comment_id, $meta_key, $meta_value = '' ) {
  * @param bool   $single     Optional. Whether to return a single value.
  *                           This parameter has no effect if `$key` is not specified.
  *                           Default false.
+ * @param string $value_type Optional. The expected data type of the value.
  * @return mixed An array of values if `$single` is false.
  *               The value of meta data field if `$single` is true.
  *               False for an invalid `$comment_id` (non-numeric, zero, or negative value).
  *               An empty string if a valid but non-existing comment ID is passed.
  */
-function get_comment_meta( $comment_id, $key = '', $single = false ) {
-	return get_metadata( 'comment', $comment_id, $key, $single );
+function get_comment_meta( $comment_id, $key = '', $single = false, $value_type = '' ) {
+	return get_metadata( 'comment', $comment_id, $key, $single, $value_type );
 }
 
 /**
diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php
index 3267120b6f30..d67a72dea226 100644
--- a/src/wp-includes/functions.php
+++ b/src/wp-includes/functions.php
@@ -8584,3 +8584,93 @@ function is_php_version_compatible( $required ) {
 function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) {
 	return abs( (float) $expected - (float) $actual ) <= $precision;
 }
+
+/**
+ * Get supported data types for meta and option values.
+ *
+ * This is a subset of the PHP data types that can be used for
+ * meta and option values. See the documentation for PHP's 'settype()'.
+ *
+ * @since 6.3.0
+ *
+ * @return array List of supported value types.
+ */
+function wp_get_db_value_types() {
+	return array(
+		'boolean',
+		'bool',
+		'integer',
+		'int',
+		'float',
+		'string',
+		'array',
+		'object',
+	);
+}
+
+/**
+ * Set the type of a meta or option value that was retrieved from the database.
+ *
+ * If `$value_type` is empty, the value is returned unchanged. If `$value_type` is not one
+ * of the expected types, a user error is triggered and the value is returned unchanged.
+ *
+ * @since 6.3.0
+ *
+ * @param mixed  $value      Raw value from the database.
+ * @param string $value_type The expected value type.
+ * @return mixed The value with the requested type.
+ */
+function wp_settype_to_value_from_db( $value, $value_type ) {
+	if ( ! empty( $value_type ) ) {
+		$value_types = wp_get_db_value_types();
+
+		if ( in_array( $value_type, $value_types, true ) ) {
+			settype( $value, $value_type );
+		} else {
+			// Improper use.
+			/* translators: The function name that returns the supported types. */
+			$message = sprintf( __( 'Only types returned by %s are supported.' ), '<code>wp_get_db_value_types()</code>' );
+			_doing_it_wrong( 'wp_settype_to_value_from_db', $message );
+		}
+	}
+
+	return $value;
+}
+
+
+/**
+ * Decode a value after it was retrieved from the database.
+ *
+ * Unserialize the value if needed, and set it to the expected type.
+ * Intended for use with option and meta values.
+ *
+ * @since 6.3.0
+ *
+ * @param mixed  $value      Raw value from the database.
+ * @param string $value_type Optional. The expected value type.
+ * @return mixed The decoded value.
+ */
+function wp_decode_value_from_db( $value, $value_type = '' ) {
+	$original_value = $value;
+
+	if ( empty( $value_type ) ) {
+		$value = maybe_unserialize( $value );
+	} else {
+		if ( 'array' === $value_type || 'object' === $value_type ) {
+			$value = maybe_unserialize( $value );
+		}
+
+		$value = wp_settype_to_value_from_db( $value, $value_type );
+	}
+
+	/**
+	 * Filter the decoded value.
+	 *
+	 * @since 6.3.0
+	 *
+	 * @param mixed  $value          Decoded value.
+	 * @param mixed  $original_value Original value from the database.
+	 * @param string $value_type     The expected value type.
+	 */
+	return apply_filters( 'wp_decode_value_from_db', $value, $original_value, $value_type );
+}
diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php
index 2b5ca02696c2..b1e3b297dd7e 100644
--- a/src/wp-includes/meta.php
+++ b/src/wp-includes/meta.php
@@ -552,26 +552,32 @@ function delete_metadata( $meta_type, $object_id, $meta_key, $meta_value = '', $
  * By default, an empty string is returned if `$single` is true, or an empty array
  * if it's false.
  *
+ * In most cases non-string scalar and null values will be converted and returned
+ * as string equivalents if the `$value_type` paramenter is not set. If it is set
+ * the values will be of the expected type.
+ *
  * @since 2.9.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
  * @see get_metadata_raw()
  * @see get_metadata_default()
  *
- * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
- *                          or any other object type with an associated meta table.
- * @param int    $object_id ID of the object metadata is for.
- * @param string $meta_key  Optional. Metadata key. If not specified, retrieve all metadata for
- *                          the specified object. Default empty string.
- * @param bool   $single    Optional. If true, return only the first value of the specified `$meta_key`.
- *                          This parameter has no effect if `$meta_key` is not specified. Default false.
+ * @param string $meta_type  Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
+ *                           or any other object type with an associated meta table.
+ * @param int    $object_id  ID of the object metadata is for.
+ * @param string $meta_key   Optional. Metadata key. If not specified, retrieve all metadata for
+ *                           the specified object. Default empty string.
+ * @param bool   $single     Optional. If true, return only the first value of the specified `$meta_key`.
+ *                           This parameter has no effect if `$meta_key` is not specified. Default false.
+ * @param string $value_type Optional. The expected data type of the value.
  * @return mixed An array of values if `$single` is false.
  *               The value of the meta field if `$single` is true.
  *               False for an invalid `$object_id` (non-numeric, zero, or negative value),
  *               or if `$meta_type` is not specified.
  *               An empty string if a valid but non-existing object ID is passed.
  */
-function get_metadata( $meta_type, $object_id, $meta_key = '', $single = false ) {
-	$value = get_metadata_raw( $meta_type, $object_id, $meta_key, $single );
+function get_metadata( $meta_type, $object_id, $meta_key = '', $single = false, $value_type = '' ) {
+	$value = get_metadata_raw( $meta_type, $object_id, $meta_key, $single, $value_type );
 	if ( ! is_null( $value ) ) {
 		return $value;
 	}
@@ -583,21 +589,23 @@ function get_metadata( $meta_type, $object_id, $meta_key = '', $single = false )
  * Retrieves raw metadata value for the specified object.
  *
  * @since 5.5.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
- * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
- *                          or any other object type with an associated meta table.
- * @param int    $object_id ID of the object metadata is for.
- * @param string $meta_key  Optional. Metadata key. If not specified, retrieve all metadata for
- *                          the specified object. Default empty string.
- * @param bool   $single    Optional. If true, return only the first value of the specified `$meta_key`.
- *                          This parameter has no effect if `$meta_key` is not specified. Default false.
+ * @param string $meta_type  Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
+ *                           or any other object type with an associated meta table.
+ * @param int    $object_id  ID of the object metadata is for.
+ * @param string $meta_key   Optional. Metadata key. If not specified, retrieve all metadata for
+ *                           the specified object. Default empty string.
+ * @param bool   $single     Optional. If true, return only the first value of the specified `$meta_key`.
+ *                           This parameter has no effect if `$meta_key` is not specified. Default false.
+ * @param string $value_type Optional. The expected data type of the value.
  * @return mixed An array of values if `$single` is false.
  *               The value of the meta field if `$single` is true.
  *               False for an invalid `$object_id` (non-numeric, zero, or negative value),
  *               or if `$meta_type` is not specified.
  *               Null if the value does not exist.
  */
-function get_metadata_raw( $meta_type, $object_id, $meta_key = '', $single = false ) {
+function get_metadata_raw( $meta_type, $object_id, $meta_key = '', $single = false, $value_type = '' ) {
 	if ( ! $meta_type || ! is_numeric( $object_id ) ) {
 		return false;
 	}
@@ -614,6 +622,10 @@ function get_metadata_raw( $meta_type, $object_id, $meta_key = '', $single = fal
 	 * (post, comment, term, user, or any other type with an associated meta table).
 	 * Returning a non-null value will effectively short-circuit the function.
 	 *
+	 * The filter assumes that a numeric array is always returned similarly to the return value
+	 * from {@see 'update_meta_cache()'}. Returning `false` is also acceptable to signify
+	 * incorrect parameters.
+	 *
 	 * Possible filter names include:
 	 *
 	 *  - `get_post_metadata`
@@ -623,21 +635,41 @@ function get_metadata_raw( $meta_type, $object_id, $meta_key = '', $single = fal
 	 *
 	 * @since 3.1.0
 	 * @since 5.5.0 Added the `$meta_type` parameter.
-	 *
-	 * @param mixed  $value     The value to return, either a single metadata value or an array
-	 *                          of values depending on the value of `$single`. Default null.
-	 * @param int    $object_id ID of the object metadata is for.
-	 * @param string $meta_key  Metadata key.
-	 * @param bool   $single    Whether to return only the first value of the specified `$meta_key`.
-	 * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
-	 *                          or any other object type with an associated meta table.
+	 * @since 6.3.0 Added the `$value_type` parameter.
+	 *
+	 * @param mixed  $value      Numeric array of one or more meta values. Default null.
+	 * @param int    $object_id  ID of the object metadata is for.
+	 * @param string $meta_key   Metadata key.
+	 * @param bool   $single     Whether to return only the first value of the specified `$meta_key`.
+	 *                           If `true` only the first element of the filtered array is returned.
+	 * @param string $meta_type  Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
+	 *                           or any other object type with an associated meta table.
+	 * @param string $value_type The expected data type of the value.
 	 */
-	$check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, $single, $meta_type );
-	if ( null !== $check ) {
-		if ( $single && is_array( $check ) ) {
-			return $check[0];
+	$pre_meta = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, $single, $meta_type, $value_type );
+	if ( null !== $pre_meta ) {
+		// `false` is an expected return value in some cases. See above.
+		if ( false === $pre_meta ) {
+			return $pre_meta;
+		}
+
+		if ( $single ) {
+			// A numeric array is expected especiually if the meta value is also an array.
+			if ( is_array( $pre_meta ) && isset( $pre_meta[0] ) ) {
+				$pre_meta = $pre_meta[0];
+			}
+			// Ensure the meta value is of the expected type after the filter.
+			return wp_settype_to_value_from_db( $pre_meta, $value_type );
 		} else {
-			return $check;
+			if ( is_array( $pre_meta ) ) {
+				foreach ( $pre_meta as $key => $meta_value ) {
+					$pre_meta[ $key ] = wp_settype_to_value_from_db( $meta_value, $value_type );
+				}
+			} else {
+				$pre_meta = wp_settype_to_value_from_db( $pre_meta, $value_type );
+			}
+
+			return $pre_meta;
 		}
 	}
 
@@ -645,6 +677,7 @@ function get_metadata_raw( $meta_type, $object_id, $meta_key = '', $single = fal
 
 	if ( ! $meta_cache ) {
 		$meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
+
 		if ( isset( $meta_cache[ $object_id ] ) ) {
 			$meta_cache = $meta_cache[ $object_id ];
 		} else {
@@ -658,9 +691,15 @@ function get_metadata_raw( $meta_type, $object_id, $meta_key = '', $single = fal
 
 	if ( isset( $meta_cache[ $meta_key ] ) ) {
 		if ( $single ) {
-			return maybe_unserialize( $meta_cache[ $meta_key ][0] );
+			return wp_decode_value_from_db( $meta_cache[ $meta_key ][0], $value_type );
 		} else {
-			return array_map( 'maybe_unserialize', $meta_cache[ $meta_key ] );
+			$meta_values = $meta_cache[ $meta_key ];
+
+			foreach ( (array) $meta_values as $key => $meta_value ) {
+				$meta_values[ $key ] = wp_decode_value_from_db( $meta_value, $value_type );
+			}
+
+			return $meta_values;
 		}
 	}
 
@@ -768,12 +807,14 @@ function metadata_exists( $meta_type, $object_id, $meta_key ) {
  * Retrieves metadata by meta ID.
  *
  * @since 3.3.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
  * @global wpdb $wpdb WordPress database abstraction object.
  *
- * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
- *                          or any other object type with an associated meta table.
- * @param int    $meta_id   ID for a specific meta row.
+ * @param string $meta_type  Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
+ *                           or any other object type with an associated meta table.
+ * @param int    $meta_id    ID for a specific meta row.
+ * @param string $value_type Optional. The expected data type of the value.
  * @return stdClass|false {
  *     Metadata object, or boolean `false` if the metadata doesn't exist.
  *
@@ -787,7 +828,7 @@ function metadata_exists( $meta_type, $object_id, $meta_key ) {
  *     @type string $user_id    Optional. The object ID when the meta type is 'user'.
  * }
  */
-function get_metadata_by_mid( $meta_type, $meta_id ) {
+function get_metadata_by_mid( $meta_type, $meta_id, $value_type = '' ) {
 	global $wpdb;
 
 	if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) {
@@ -820,12 +861,18 @@ function get_metadata_by_mid( $meta_type, $meta_id ) {
 	 *
 	 * @since 5.0.0
 	 *
-	 * @param stdClass|null $value   The value to return.
-	 * @param int           $meta_id Meta ID.
+	 * @param stdClass|null $value      The value to return.
+	 * @param int           $meta_id    Meta ID.
+	 * @param string        $value_type The expected data type of the value.
 	 */
-	$check = apply_filters( "get_{$meta_type}_metadata_by_mid", null, $meta_id );
-	if ( null !== $check ) {
-		return $check;
+	$pre_meta = apply_filters( "get_{$meta_type}_metadata_by_mid", null, $meta_id, $value_type );
+	if ( null !== $pre_meta ) {
+		// Ensure the returned meta value is of the expected type.
+		if ( is_object( $pre_meta ) && isset( $pre_meta->meta_value ) ) {
+			$pre_meta->meta_value = wp_settype_to_value_from_db( $pre_meta->meta_value, $value_type );
+		}
+
+		return $pre_meta;
 	}
 
 	$id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id';
@@ -837,7 +884,7 @@ function get_metadata_by_mid( $meta_type, $meta_id ) {
 	}
 
 	if ( isset( $meta->meta_value ) ) {
-		$meta->meta_value = maybe_unserialize( $meta->meta_value );
+		$meta->meta_value = wp_decode_value_from_db( $meta->meta_value, $value_type );
 	}
 
 	return $meta;
diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php
index 486fad241424..7f5311f20054 100644
--- a/src/wp-includes/ms-site.php
+++ b/src/wp-includes/ms-site.php
@@ -1061,20 +1061,22 @@ function delete_site_meta( $site_id, $meta_key, $meta_value = '' ) {
  * Retrieves metadata for a site.
  *
  * @since 5.1.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
- * @param int    $site_id Site ID.
- * @param string $key     Optional. The meta key to retrieve. By default,
- *                        returns data for all keys. Default empty.
- * @param bool   $single  Optional. Whether to return a single value.
- *                        This parameter has no effect if `$key` is not specified.
- *                        Default false.
+ * @param int    $site_id    Site ID.
+ * @param string $key        Optional. The meta key to retrieve. By default,
+ *                           returns data for all keys. Default empty.
+ * @param bool   $single     Optional. Whether to return a single value.
+ *                           This parameter has no effect if `$key` is not specified.
+ *                           Default false.
+ * @param string $value_type Optional. The expected data type of the value.
  * @return mixed An array of values if `$single` is false.
  *               The value of meta data field if `$single` is true.
  *               False for an invalid `$site_id` (non-numeric, zero, or negative value).
  *               An empty string if a valid but non-existing site ID is passed.
  */
-function get_site_meta( $site_id, $key = '', $single = false ) {
-	return get_metadata( 'blog', $site_id, $key, $single );
+function get_site_meta( $site_id, $key = '', $single = false, $value_type = '' ) {
+	return get_metadata( 'blog', $site_id, $key, $single, $value_type );
 }
 
 /**
diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php
index 48e8676ed2ac..8e00a0d93c7d 100644
--- a/src/wp-includes/option.php
+++ b/src/wp-includes/option.php
@@ -18,14 +18,16 @@
  * Not initializing an option and using boolean `false` as a return value
  * is a bad practice as it triggers an additional database query.
  *
- * The type of the returned value can be different from the type that was passed
+ * When the `$value_type` paramenter is not set the type
+ * of the returned value may be different from the type that was passed
  * when saving or updating the option. If the option value was serialized,
  * then it will be unserialized when it is returned. In this case the type will
  * be the same. For example, storing a non-scalar value like an array will
  * return the same array.
  *
  * In most cases non-string scalar and null values will be converted and returned
- * as string equivalents.
+ * as string equivalents if the `$value_type` paramenter is not set. If it is set
+ * the values will be of the expected type.
  *
  * Exceptions:
  *
@@ -35,8 +37,9 @@
  *    {@see 'default_option_$option'}, or {@see 'option_$option'}, the returned
  *    value may not match the expected type.
  * 3. When the option has just been saved in the database, and get_option()
- *    is used right after, non-string scalar and null values are not converted to
- *    string equivalents and the original type is returned.
+ *    is used right after without the `$value_type` paramenter, non-string scalar
+ *    and null values are not converted to string equivalents and are returned with
+ *    their original types.
  *
  * Examples:
  *
@@ -52,9 +55,14 @@
  *   - `'1'`   returns `string(1) "1"`
  *   - `null`  returns `string(0) ""`
  *
+ * When the `$value_type` parameter is used the returned value will be of the expected type.
+ * Retrieving the first option from the above example with
+ * `get_option( 'my_option_name', false, 'boolean' )` will return `boolean false`.
+ * Retrieving the second example will return `boolean true`. Retrieving the third example
+ * with `get_option( 'my_option_name', false, 'integer' )` will return `integer 0`, etc.
  * When adding options with non-scalar values like
  * `add_option( 'my_array', array( false, 'str', null ) )`, the returned value
- * will be identical to the original as it is serialized before saving
+ * will always be identical to the original as it is serialized before saving
  * it in the database:
  *
  *     array(3) {
@@ -64,18 +72,20 @@
  *     }
  *
  * @since 1.5.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
  * @global wpdb $wpdb WordPress database abstraction object.
  *
  * @param string $option        Name of the option to retrieve. Expected to not be SQL-escaped.
  * @param mixed  $default_value Optional. Default value to return if the option does not exist.
+ * @param string $value_type    Optional. The expected data type of the value.
  * @return mixed Value of the option. A value of any type may be returned, including
  *               scalar (string, boolean, float, integer), null, array, object.
  *               Scalar and null values will be returned as strings as long as they originate
  *               from a database stored option value. If there is no option in the database,
  *               boolean `false` is returned.
  */
-function get_option( $option, $default_value = false ) {
+function get_option( $option, $default_value = false, $value_type = '' ) {
 	global $wpdb;
 
 	if ( is_scalar( $option ) ) {
@@ -106,7 +116,7 @@ function get_option( $option, $default_value = false ) {
 				$deprecated_keys[ $option ]
 			)
 		);
-		return get_option( $deprecated_keys[ $option ], $default_value );
+		return get_option( $deprecated_keys[ $option ], $default_value, $value_type );
 	}
 
 	/**
@@ -128,8 +138,9 @@ function get_option( $option, $default_value = false ) {
 	 * @param string $option        Option name.
 	 * @param mixed  $default_value The fallback value to return if the option does not exist.
 	 *                              Default false.
+	 * @param string $value_type    The expected data type of the value.
 	 */
-	$pre = apply_filters( "pre_option_{$option}", false, $option, $default_value );
+	$pre = apply_filters( "pre_option_{$option}", false, $option, $default_value, $value_type );
 
 	/**
 	 * Filters the value of all existing options before it is retrieved.
@@ -146,11 +157,13 @@ function get_option( $option, $default_value = false ) {
 	 * @param string $option        Name of the option.
 	 * @param mixed  $default_value The fallback value to return if the option does not exist.
 	 *                              Default false.
+	 * @param string $value_type    The expected data type of the value.
 	 */
-	$pre = apply_filters( 'pre_option', $pre, $option, $default_value );
+	$pre = apply_filters( 'pre_option', $pre, $option, $default_value, $value_type );
 
 	if ( false !== $pre ) {
-		return $pre;
+		// Ensure the option value is of the expected type after the filters.
+		return wp_settype_to_value_from_db( $pre, $value_type );
 	}
 
 	if ( defined( 'WP_SETUP_CONFIG' ) ) {
@@ -184,8 +197,9 @@ function get_option( $option, $default_value = false ) {
 			 *                               in the database.
 			 * @param string $option         Option name.
 			 * @param bool   $passed_default Was `get_option()` passed a default value?
+			 * @param string $value_type     The expected data type of the value.
 			 */
-			return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default );
+			return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default, $value_type );
 		}
 
 		$alloptions = wp_load_alloptions();
@@ -211,7 +225,7 @@ function get_option( $option, $default_value = false ) {
 					wp_cache_set( 'notoptions', $notoptions, 'options' );
 
 					/** This filter is documented in wp-includes/option.php */
-					return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default );
+					return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default, $value_type );
 				}
 			}
 		}
@@ -224,7 +238,7 @@ function get_option( $option, $default_value = false ) {
 			$value = $row->option_value;
 		} else {
 			/** This filter is documented in wp-includes/option.php */
-			return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default );
+			return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default, $value_type );
 		}
 	}
 
@@ -237,6 +251,8 @@ function get_option( $option, $default_value = false ) {
 		$value = untrailingslashit( $value );
 	}
 
+	$value = wp_decode_value_from_db( $value, $value_type );
+
 	/**
 	 * Filters the value of an existing option.
 	 *
@@ -246,11 +262,12 @@ function get_option( $option, $default_value = false ) {
 	 * @since 3.0.0
 	 * @since 4.4.0 The `$option` parameter was added.
 	 *
-	 * @param mixed  $value  Value of the option. If stored serialized, it will be
-	 *                       unserialized prior to being returned.
-	 * @param string $option Option name.
+	 * @param mixed  $value      Value of the option. If stored serialized, it will be
+	 *                           unserialized prior to being returned.
+	 * @param string $option     Option name.
+	 * @param string $value_type The expected data type of the value.
 	 */
-	return apply_filters( "option_{$option}", maybe_unserialize( $value ), $option );
+	return apply_filters( "option_{$option}", $value, $option, $value_type );
 }
 
 /**
@@ -1415,6 +1432,7 @@ function update_site_option( $option, $value ) {
  * Retrieves a network's option value based on the option name.
  *
  * @since 4.4.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
  * @see get_option()
  *
@@ -1423,9 +1441,10 @@ function update_site_option( $option, $value ) {
  * @param int    $network_id    ID of the network. Can be null to default to the current network ID.
  * @param string $option        Name of the option to retrieve. Expected to not be SQL-escaped.
  * @param mixed  $default_value Optional. Value to return if the option doesn't exist. Default false.
+ * @param string $value_type    Optional. The expected data type of the value.
  * @return mixed Value set for the option.
  */
-function get_network_option( $network_id, $option, $default_value = false ) {
+function get_network_option( $network_id, $option, $default_value = false, $value_type = '' ) {
 	global $wpdb;
 
 	if ( $network_id && ! is_numeric( $network_id ) ) {
@@ -1461,11 +1480,13 @@ function get_network_option( $network_id, $option, $default_value = false ) {
 	 * @param int    $network_id    ID of the network.
 	 * @param mixed  $default_value The fallback value to return if the option does not exist.
 	 *                              Default false.
+	 * @param string $value_type    The expected data type of the value.
 	 */
-	$pre = apply_filters( "pre_site_option_{$option}", false, $option, $network_id, $default_value );
+	$pre = apply_filters( "pre_site_option_{$option}", false, $option, $network_id, $default_value, $value_type );
 
 	if ( false !== $pre ) {
-		return $pre;
+		// Ensure the option value is of the expected type after the filter.
+		return wp_settype_to_value_from_db( $pre, $value_type );
 	}
 
 	// Prevent non-existent options from triggering multiple queries.
@@ -1487,14 +1508,15 @@ function get_network_option( $network_id, $option, $default_value = false ) {
 		 *                              in the database.
 		 * @param string $option        Option name.
 		 * @param int    $network_id    ID of the network.
+		 * @param string $value_type    The expected data type of the value.
 		 */
-		return apply_filters( "default_site_option_{$option}", $default_value, $option, $network_id );
+		return apply_filters( "default_site_option_{$option}", $default_value, $option, $network_id, $value_type );
 	}
 
 	if ( ! is_multisite() ) {
 		/** This filter is documented in wp-includes/option.php */
 		$default_value = apply_filters( 'default_site_option_' . $option, $default_value, $option, $network_id );
-		$value         = get_option( $option, $default_value );
+		$value         = get_option( $option, $default_value, $value_type );
 	} else {
 		$cache_key = "$network_id:$option";
 		$value     = wp_cache_get( $cache_key, 'site-options' );
@@ -1505,7 +1527,7 @@ function get_network_option( $network_id, $option, $default_value = false ) {
 			// Has to be get_row() instead of get_var() because of funkiness with 0, false, null values.
 			if ( is_object( $row ) ) {
 				$value = $row->meta_value;
-				$value = maybe_unserialize( $value );
+				$value = wp_decode_value_from_db( $value, $value_type );
 				wp_cache_set( $cache_key, $value, 'site-options' );
 			} else {
 				if ( ! is_array( $notoptions ) ) {
@@ -1539,8 +1561,9 @@ function get_network_option( $network_id, $option, $default_value = false ) {
 	 * @param mixed  $value      Value of network option.
 	 * @param string $option     Option name.
 	 * @param int    $network_id ID of the network.
+	 * @param string $value_type The expected data type of the value.
 	 */
-	return apply_filters( "site_option_{$option}", $value, $option, $network_id );
+	return apply_filters( "site_option_{$option}", $value, $option, $network_id, $value_type );
 }
 
 /**
diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php
index 48906cd89bc8..60ea87eecddb 100644
--- a/src/wp-includes/post.php
+++ b/src/wp-includes/post.php
@@ -2500,20 +2500,22 @@ function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
  * Retrieves a post meta field for the given post ID.
  *
  * @since 1.5.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
- * @param int    $post_id Post ID.
- * @param string $key     Optional. The meta key to retrieve. By default,
- *                        returns data for all keys. Default empty.
- * @param bool   $single  Optional. Whether to return a single value.
- *                        This parameter has no effect if `$key` is not specified.
- *                        Default false.
+ * @param int    $post_id    Post ID.
+ * @param string $key        Optional. The meta key to retrieve. By default,
+ *                           returns data for all keys. Default empty.
+ * @param bool   $single     Optional. Whether to return a single value.
+ *                           This parameter has no effect if `$key` is not specified.
+ *                           Default false.
+ * @param string $value_type Optional. The expected data type of the value.
  * @return mixed An array of values if `$single` is false.
  *               The value of the meta field if `$single` is true.
  *               False for an invalid `$post_id` (non-numeric, zero, or negative value).
  *               An empty string if a valid but non-existing post ID is passed.
  */
-function get_post_meta( $post_id, $key = '', $single = false ) {
-	return get_metadata( 'post', $post_id, $key, $single );
+function get_post_meta( $post_id, $key = '', $single = false, $value_type = '' ) {
+	return get_metadata( 'post', $post_id, $key, $single, $value_type );
 }
 
 /**
diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php
index 35031e3befec..b080e2c116fc 100644
--- a/src/wp-includes/taxonomy.php
+++ b/src/wp-includes/taxonomy.php
@@ -1371,20 +1371,22 @@ function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
  * Retrieves metadata for a term.
  *
  * @since 4.4.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
- * @param int    $term_id Term ID.
- * @param string $key     Optional. The meta key to retrieve. By default,
- *                        returns data for all keys. Default empty.
- * @param bool   $single  Optional. Whether to return a single value.
- *                        This parameter has no effect if `$key` is not specified.
- *                        Default false.
+ * @param int    $term_id    Term ID.
+ * @param string $key        Optional. The meta key to retrieve. By default,
+ *                           returns data for all keys. Default empty.
+ * @param bool   $single     Optional. Whether to return a single value.
+ *                           This parameter has no effect if `$key` is not specified.
+ *                           Default false.
+ * @param string $value_type Optional. The expected data type of the value.
  * @return mixed An array of values if `$single` is false.
  *               The value of the meta field if `$single` is true.
  *               False for an invalid `$term_id` (non-numeric, zero, or negative value).
  *               An empty string if a valid but non-existing term ID is passed.
  */
-function get_term_meta( $term_id, $key = '', $single = false ) {
-	return get_metadata( 'term', $term_id, $key, $single );
+function get_term_meta( $term_id, $key = '', $single = false, $value_type = '' ) {
+	return get_metadata( 'term', $term_id, $key, $single, $value_type );
 }
 
 /**
diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php
index 8e230708e4ee..2cc6d7884bbe 100644
--- a/src/wp-includes/user.php
+++ b/src/wp-includes/user.php
@@ -1152,22 +1152,24 @@ function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) {
  * Retrieves user meta field for a user.
  *
  * @since 3.0.0
+ * @since 6.3.0 Introduced the `$value_type` parameter.
  *
  * @link https://developer.wordpress.org/reference/functions/get_user_meta/
  *
- * @param int    $user_id User ID.
- * @param string $key     Optional. The meta key to retrieve. By default,
- *                        returns data for all keys.
- * @param bool   $single  Optional. Whether to return a single value.
- *                        This parameter has no effect if `$key` is not specified.
- *                        Default false.
+ * @param int    $user_id    User ID.
+ * @param string $key        Optional. The meta key to retrieve. By default,
+ *                           returns data for all keys.
+ * @param bool   $single     Optional. Whether to return a single value.
+ *                           This parameter has no effect if `$key` is not specified.
+ *                           Default false.
+ * @param string $value_type Optional. The expected data type of the value.
  * @return mixed An array of values if `$single` is false.
  *               The value of meta data field if `$single` is true.
  *               False for an invalid `$user_id` (non-numeric, zero, or negative value).
  *               An empty string if a valid but non-existing user ID is passed.
  */
-function get_user_meta( $user_id, $key = '', $single = false ) {
-	return get_metadata( 'user', $user_id, $key, $single );
+function get_user_meta( $user_id, $key = '', $single = false, $value_type = '' ) {
+	return get_metadata( 'user', $user_id, $key, $single, $value_type );
 }
 
 /**
diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php
index 32fa2bd1e64a..c87e6b88c76a 100644
--- a/tests/phpunit/tests/functions.php
+++ b/tests/phpunit/tests/functions.php
@@ -2150,5 +2150,4 @@ public function test_wp_recursive_ksort() {
 		);
 		$this->assertSameSetsWithIndex( $theme_json, $expected_theme_json );
 	}
-
 }
diff --git a/tests/phpunit/tests/functions/wpDecodeValueFromDb.php b/tests/phpunit/tests/functions/wpDecodeValueFromDb.php
new file mode 100644
index 000000000000..d16ae3a07d8b
--- /dev/null
+++ b/tests/phpunit/tests/functions/wpDecodeValueFromDb.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * Tests for wp_decode_value_from_db().
+ *
+ * @since 6.3.0
+ *
+ * @group functions.php
+ * @group meta
+ * @group option
+ *
+ * @ticket 55942
+ *
+ * @covers ::wp_decode_value_from_db
+ */
+class Tests_Functions_WpDecodeValueFromDb extends WP_UnitTestCase {
+
+	/**
+	 * Tests that wp_decode_value_from_db() returns the correct value.
+	 *
+	 * @dataProvider data_wp_decode_value_from_db
+	 *
+	 * @param mixed  $value      The value to decode.
+	 * @param string $value_type The type of the value.
+	 * @param mixed  $expected   The expected decoded value.
+	 */
+	public function test_wp_decode_value_from_db( $value, $value_type, $expected ) {
+		$this->assertSame( $expected, wp_decode_value_from_db( $value, $value_type ) );
+	}
+
+	/**
+	 * Data provider.
+	 *
+	 * @return array
+	 */
+	public function data_wp_decode_value_from_db() {
+		$boolean = array(
+			'boolean "1"'      => array( '1', 'boolean', true ),
+			'boolean true'     => array( true, 'boolean', true ),
+			'boolean "string"' => array( 'string', 'boolean', true ),
+			'boolean "0"'      => array( '0', 'boolean', false ),
+			'boolean ""'       => array( '', 'boolean', false ),
+			'boolean false'    => array( false, 'boolean', false ),
+		);
+
+		$integer = array(
+			'integer 42'   => array( 42, 'integer', 42 ),
+			'integer "42"' => array( '42', 'integer', 42 ),
+			'integer 0'    => array( 0, 'integer', 0 ),
+			'integer "0"'  => array( '0', 'integer', 0 ),
+			'integer 1'    => array( 1, 'integer', 1 ),
+			'integer "1"'  => array( '1', 'integer', 1 ),
+		);
+
+		$float = array(
+			'float 12.50'   => array( 12.50, 'float', 12.50 ),
+			'float "12.50"' => array( '12.50', 'float', 12.50 ),
+			'float 0'       => array( 0, 'float', 0.0 ),
+			'float 1'       => array( 1, 'float', 1.0 ),
+		);
+
+		$string = array(
+			'string "test"' => array( 'test', 'string', 'test' ),
+			'string 12'     => array( 12, 'string', '12' ),
+			'string true'   => array( true, 'string', '1' ),
+			'string false'  => array( false, 'string', '' ),
+			'string 12.435' => array( 12.435, 'string', '12.435' ),
+		);
+
+		$array = array(
+			'serialized array'        => array( serialize( array( 'test' => 'value' ) ), 'array', array( 'test' => 'value' ) ),
+			'serialized object array' => array( serialize( (object) array( 'test' => 'value' ) ), 'array', array( 'test' => 'value' ) ),
+			'array'                   => array( array( 'test' => 'value' ), 'array', array( 'test' => 'value' ) ),
+			'object array'            => array( (object) array( 'test' => 'value' ), 'array', array( 'test' => 'value' ) ),
+		);
+
+		return array_merge( $boolean, $integer, $float, $string, $array );
+	}
+
+	/**
+	 * Tests that wp_decode_value_from_db() returns the correct value for objects.
+	 */
+	public function test_object_wp_decode_value_from_db() {
+		$obj       = new \stdClass();
+		$obj->test = 'value';
+		$this->assertEquals( $obj, wp_decode_value_from_db( serialize( $obj ), 'object' ) );
+		$this->assertEquals( $obj, wp_decode_value_from_db( serialize( (array) $obj ), 'object' ) );
+		$this->assertEquals( $obj, wp_decode_value_from_db( $obj, 'object' ) );
+		$this->assertEquals( $obj, wp_decode_value_from_db( (array) $obj, 'object' ) );
+	}
+}
