diff --git a/src/wp-includes/class-wp-meta-query.php b/src/wp-includes/class-wp-meta-query.php
index 5a1b0c41479..b0977aa6f67 100644
--- a/src/wp-includes/class-wp-meta-query.php
+++ b/src/wp-includes/class-wp-meta-query.php
@@ -102,6 +102,7 @@ class WP_Meta_Query {
 	 * @since 5.1.0 Introduced $compare_key clause parameter, which enables LIKE key matches.
 	 * @since 5.3.0 Increased the number of operators available to $compare_key. Introduced $type_key,
 	 *              which enables the $key to be cast to a new data type for comparisons.
+	 * @since 5.9.0 Adds LIKE based STARTSWITH and ENDSWITH operators for value compares
 	 *
 	 * @param array $meta_query {
 	 *     Array of meta query clauses. When first-order clauses or sub-clauses use strings as
@@ -123,6 +124,7 @@ class WP_Meta_Query {
 	 *         @type string $value       Meta value to filter by.
 	 *         @type string $compare     MySQL operator used for comparing the $value. Accepts '=',
 	 *                                   '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE',
+	 *                                   'STARTSWITH', 'NOT STARTSWITH', 'ENDSWITH', 'NOT ENDSWITH',
 	 *                                   'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP',
 	 *                                   'NOT REGEXP', 'RLIKE', 'EXISTS' or 'NOT EXISTS'.
 	 *                                   Default is 'IN' when `$value` is an array, '=' otherwise.
@@ -509,7 +511,11 @@ public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' )
 			'=',
 			'!=',
 			'LIKE',
+			'STARTSWITH',
+			'ENDSWITH',
 			'NOT LIKE',
+			'NOT STARTSWITH',
+			'NOT ENDSWITH',
 			'IN',
 			'NOT IN',
 			'EXISTS',
@@ -713,6 +719,20 @@ public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' )
 					$where      = $wpdb->prepare( '%s', $meta_value );
 					break;
 
+				case 'STARTSWITH':
+				case 'NOT STARTSWITH':
+					$meta_compare = 'STARTSWITH' === $meta_compare ? 'LIKE' : 'NOT LIKE';
+					$meta_value   = $wpdb->esc_like( $meta_value ) . '%';
+					$where        = $wpdb->prepare( '%s', $meta_value );
+					break;
+
+				case 'ENDSWITH':
+				case 'NOT ENDSWITH':
+					$meta_compare = 'ENDSWITH' === $meta_compare ? 'LIKE' : 'NOT LIKE';
+					$meta_value   = '%' . $wpdb->esc_like( $meta_value );
+					$where        = $wpdb->prepare( '%s', $meta_value );
+					break;
+
 				// EXISTS with a value is interpreted as '='.
 				case 'EXISTS':
 					$meta_compare = '=';
diff --git a/tests/phpunit/tests/query/metaQuery.php b/tests/phpunit/tests/query/metaQuery.php
index 5acec55d3f9..03ad7766373 100644
--- a/tests/phpunit/tests/query/metaQuery.php
+++ b/tests/phpunit/tests/query/metaQuery.php
@@ -271,6 +271,138 @@ public function test_meta_query_single_query_compare_not_like() {
 		$this->assertSameSets( $expected, $query->posts );
 	}
 
+	public function test_meta_query_single_query_compare_startswith() {
+		$p1 = self::factory()->post->create();
+		$p2 = self::factory()->post->create();
+		$p3 = self::factory()->post->create();
+		$p4 = self::factory()->post->create();
+		$p5 = self::factory()->post->create();
+		$p6 = self::factory()->post->create();
+
+		add_post_meta( $p1, 'foo', 'barSTARTSWITH' );
+		add_post_meta( $p2, 'foo', 'ENDSWITHbar' );
+		add_post_meta( $p3, 'foo', 'CONTAINSbarCONTAINS' );
+		add_post_meta( $p4, 'foo', 'nomatch' );
+		add_post_meta( $p5, 'foo', 'barbar' );
+
+		$query = new WP_Query(
+			array(
+				'update_post_meta_cache' => false,
+				'update_post_term_cache' => false,
+				'fields'                 => 'ids',
+				'meta_query'             => array(
+					array(
+						'key'     => 'foo',
+						'value'   => 'bar',
+						'compare' => 'STARTSWITH',
+					),
+				),
+			)
+		);
+
+		$expected = array( $p1, $p5 );
+		$this->assertSameSets( $expected, $query->posts );
+	}
+
+	public function test_meta_query_single_query_compare_not_startswith() {
+		$p1 = self::factory()->post->create();
+		$p2 = self::factory()->post->create();
+		$p3 = self::factory()->post->create();
+		$p4 = self::factory()->post->create();
+		$p5 = self::factory()->post->create();
+		$p6 = self::factory()->post->create();
+
+		add_post_meta( $p1, 'foo', 'barSTARTSWITH' );
+		add_post_meta( $p2, 'foo', 'ENDSWITHbar' );
+		add_post_meta( $p3, 'foo', 'CONTAINSbarCONTAINS' );
+		add_post_meta( $p4, 'foo', 'nomatch' );
+		add_post_meta( $p5, 'foo', 'barbar' );
+
+		$query = new WP_Query(
+			array(
+				'update_post_meta_cache' => false,
+				'update_post_term_cache' => false,
+				'fields'                 => 'ids',
+				'meta_query'             => array(
+					array(
+						'key'     => 'foo',
+						'value'   => 'bar',
+						'compare' => 'NOT STARTSWITH',
+					),
+				),
+			)
+		);
+
+		$expected = array( $p2, $p3, $p4 );
+		$this->assertSameSets( $expected, $query->posts );
+	}
+
+	public function test_meta_query_single_query_compare_endswith() {
+		$p1 = self::factory()->post->create();
+		$p2 = self::factory()->post->create();
+		$p3 = self::factory()->post->create();
+		$p4 = self::factory()->post->create();
+		$p5 = self::factory()->post->create();
+		$p6 = self::factory()->post->create();
+
+		add_post_meta( $p1, 'foo', 'barSTARTSWITH' );
+		add_post_meta( $p2, 'foo', 'ENDSWITHbar' );
+		add_post_meta( $p3, 'foo', 'CONTAINSbarCONTAINS' );
+		add_post_meta( $p4, 'foo', 'nomatch' );
+		add_post_meta( $p5, 'foo', 'barbar' );
+
+		$query = new WP_Query(
+			array(
+				'update_post_meta_cache' => false,
+				'update_post_term_cache' => false,
+				'fields'                 => 'ids',
+				'meta_query'             => array(
+					array(
+						'key'     => 'foo',
+						'value'   => 'ar',
+						'compare' => 'ENDSWITH',
+					),
+				),
+			)
+		);
+
+		$expected = array( $p2, $p5 );
+		$this->assertSameSets( $expected, $query->posts );
+	}
+
+	public function test_meta_query_single_query_compare_not_endswith() {
+		$p1 = self::factory()->post->create();
+		$p2 = self::factory()->post->create();
+		$p3 = self::factory()->post->create();
+		$p4 = self::factory()->post->create();
+		$p5 = self::factory()->post->create();
+		$p6 = self::factory()->post->create();
+
+		add_post_meta( $p1, 'foo', 'barSTARTSWITH' );
+		add_post_meta( $p2, 'foo', 'ENDSWITHbar' );
+		add_post_meta( $p3, 'foo', 'CONTAINSbarCONTAINS' );
+		add_post_meta( $p4, 'foo', 'nomatch' );
+		add_post_meta( $p5, 'foo', 'barbar' );
+
+		$query = new WP_Query(
+			array(
+				'update_post_meta_cache' => false,
+				'update_post_term_cache' => false,
+				'fields'                 => 'ids',
+				'meta_query'             => array(
+					array(
+						'key'     => 'foo',
+						'value'   => 'bar',
+						'compare' => 'NOT ENDSWITH',
+					),
+				),
+			)
+		);
+
+		$expected = array( $p1, $p3, $p4 );
+		$this->assertSameSets( $expected, $query->posts );
+	}
+
 	public function test_meta_query_single_query_compare_between_not_between() {
 		$p1 = self::factory()->post->create();
 		$p2 = self::factory()->post->create();
