diff --git src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
index e53ff4b910..7d34469652 100644
--- src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
+++ src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
@@ -271,6 +271,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 
 		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
 
+		if ( ! empty( $request['tax_relation'] ) ) {
+			$query_args['tax_query'] = array( 'relation' => $request['tax_relation'] );
+		}
+
 		foreach ( $taxonomies as $taxonomy ) {
 			$base        = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
 			$tax_exclude = $base . '_exclude';
@@ -2506,6 +2510,14 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 
 		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
 
+		if ( ! empty( $taxonomies ) ) {
+			$query_params['tax_relation'] = array(
+				'description' => __( 'Limit result set based on relationship between multiple taxonomies.' ),
+				'type'        => 'string',
+				'enum'        => array( 'AND', 'OR' ),
+			);
+		}
+
 		foreach ( $taxonomies as $taxonomy ) {
 			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
 
diff --git tests/phpunit/tests/rest-api/rest-posts-controller.php tests/phpunit/tests/rest-api/rest-posts-controller.php
index ba174c448e..b3dac9e77b 100644
--- tests/phpunit/tests/rest-api/rest-posts-controller.php
+++ tests/phpunit/tests/rest-api/rest-posts-controller.php
@@ -175,6 +175,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 				'sticky',
 				'tags',
 				'tags_exclude',
+				'tax_relation',
 			),
 			$keys
 		);
@@ -959,6 +960,29 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 		$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
 	}
 
+	public function test_get_items_tags_or_categories_query() {
+		$id1      = self::$post_id;
+		$id2      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$id3      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$id4      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$tag      = wp_insert_term( 'My Tag', 'post_tag' );
+		$category = wp_insert_term( 'My Category', 'category' );
+
+		wp_set_object_terms( $id1, array( $tag['term_id'] ), 'post_tag' );
+		wp_set_object_terms( $id2, array( $category['term_id'] ), 'category' );
+
+		$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
+		$request->set_param( 'tax_relation', 'OR' );
+		$request->set_param( 'tags', array( $tag['term_id'] ) );
+		$request->set_param( 'categories', array( $category['term_id'] ) );
+
+		$response = rest_get_server()->dispatch( $request );
+		$data     = $response->get_data();
+		$this->assertCount( 2, $data );
+		$this->assertEquals( $id2, $data[0]['id'] );
+		$this->assertEquals( $id1, $data[1]['id'] );
+	}
+
 	public function test_get_items_tags_and_categories_exclude_query() {
 		$id1      = self::$post_id;
 		$id2      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
@@ -985,6 +1009,32 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 		$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
 	}
 
+	public function test_get_items_tags_or_categories_exclude_query() {
+		$id1      = self::$post_id;
+		$id2      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$id3      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$id4      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$tag      = wp_insert_term( 'My Tag', 'post_tag' );
+		$category = wp_insert_term( 'My Category', 'category' );
+
+		wp_set_object_terms( $id1, array( $tag['term_id'] ), 'post_tag' );
+		wp_set_object_terms( $id2, array( $tag['term_id'] ), 'post_tag' );
+		wp_set_object_terms( $id2, array( $category['term_id'] ), 'category' );
+		wp_set_object_terms( $id3, array( $category['term_id'] ), 'category' );
+
+		$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
+		$request->set_param( 'tags', array( $tag['term_id'] ) );
+		$request->set_param( 'categories_exclude', array( $category['term_id'] ) );
+		$request->set_param( 'tax_relation', 'OR' );
+
+		$response = rest_get_server()->dispatch( $request );
+		$data     = $response->get_data();
+		$this->assertCount( 3, $data );
+		$this->assertEquals( $id2, $data[0]['id'] );
+		$this->assertEquals( $id4, $data[1]['id'] );
+		$this->assertEquals( $id1, $data[2]['id'] );
+	}
+
 	public function test_get_items_sticky() {
 		$id1 = self::$post_id;
 		$id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
diff --git tests/qunit/fixtures/wp-api-generated.js tests/qunit/fixtures/wp-api-generated.js
index 47f925fe44..4301f30182 100644
--- tests/qunit/fixtures/wp-api-generated.js
+++ tests/qunit/fixtures/wp-api-generated.js
@@ -322,6 +322,15 @@ mockedApiResponse.Schema = {
                                 "type": "string"
                             }
                         },
+                        "tax_relation": {
+                            "required": false,
+                            "enum": [
+                                "AND",
+                                "OR"
+                            ],
+                            "description": "Limit result set based on relationship between multiple taxonomies.",
+                            "type": "string"
+                        },
                         "categories": {
                             "required": false,
                             "default": [],
