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 c251b57dad..0641e8f818 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
@@ -2406,6 +2406,27 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 			$schema['links'] = $schema_links;
 		}
 
+		// Take a snapshot of which fields are in the schema pre-filtering.
+		$schema_fields = array_keys( $schema['properties'] );
+
+		/**
+		 * Filter the post's schema.
+		 *
+		 * The dynamic part of the filter `$this->post_type` refers to the post
+		 * type slug for the controller.
+		 *
+		 * @since 5.4.0
+		 *
+		 * @param array $schema Item schema data.
+		 */
+		$schema = apply_filters( "rest_{$this->post_type}_item_schema", $schema );
+
+		// Emit a _doing_it_wrong warning if user tries to add new properties using this filter.
+		$new_fields = array_diff( array_keys( $schema['properties'] ), $schema_fields );
+		if ( count( $new_fields ) > 0 ) {
+			_doing_it_wrong( __METHOD__, __( 'Please use register_rest_field to add new schema properties.' ), '5.4.0' );
+		}
+
 		$this->schema = $schema;
 
 		return $this->add_additional_fields_schema( $this->schema );
diff --git tests/phpunit/tests/rest-api/rest-posts-controller.php tests/phpunit/tests/rest-api/rest-posts-controller.php
index 3735d1146a..e9bc42d3bf 100644
--- tests/phpunit/tests/rest-api/rest-posts-controller.php
+++ tests/phpunit/tests/rest-api/rest-posts-controller.php
@@ -4841,6 +4841,29 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 		$this->assertNull( get_post_type_object( 'test' )->get_rest_controller() );
 	}
 
+	/**
+	* @ticket 47779
+	 */
+	public function test_rest_post_type_item_schema_filter_change_property() {
+		add_filter( 'rest_post_item_schema', array( $this, 'filter_post_item_schema' ) );
+
+		// Re-initialize the controller to cache-bust schemas from prior test runs.
+		$GLOBALS['wp_rest_server']->override_by_default = true;
+		$controller                                     = new WP_REST_Posts_Controller( 'post' );
+		$controller->register_routes();
+		$GLOBALS['wp_rest_server']->override_by_default = false;
+
+		$request    = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts' );
+		$response   = rest_get_server()->dispatch( $request );
+		$data       = $response->get_data();
+		$properties = $data['schema']['properties']['content']['properties'];
+
+		remove_filter( 'rest_post_item_schema', array( $this, 'filter_post_item_schema' ) );
+
+		$this->assertArrayHasKey( 'new_prop', $properties );
+		$this->assertEquals( array( 'new_context' ), $properties['new_prop']['context'] );
+	}
+
 	public function tearDown() {
 		_unregister_post_type( 'private-post' );
 		_unregister_post_type( 'youseeme' );
@@ -4868,4 +4891,13 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 			'post-my-test-template.php' => 'My Test Template',
 		);
 	}
+
+	public function filter_post_item_schema( $schema ) {
+		$schema['properties']['content']['properties']['new_prop'] = array(
+			'description' => __( 'A new prop added with a the rest_post_item_schema filter.' ),
+			'type'        => 'string',
+			'context'     => array( 'new_context' ),
+		);
+		return $schema;
+	}
 }
