diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
index c1be06a2a0..571d207803 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
@@ -16,6 +16,30 @@
  */
 class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 
+	public function register_routes() {
+		parent::register_routes();
+		register_rest_route(
+			$this->namespace,
+			'/' . $this->rest_base . '/(?P<id>[\d]+)/post-process',
+			array(
+				'methods'             => WP_REST_Server::CREATABLE,
+				'callback'            => array( $this, 'post_process_item' ),
+				'permission_callback' => array( $this, 'post_process_item_permissions_check' ),
+				'args'                => array(
+					'id'     => array(
+						'description' => __( 'Unique identifier for the object.' ),
+						'type'        => 'integer',
+					),
+					'action' => array(
+						'type'     => 'string',
+						'enum'     => array( 'create-image-subsizes' ),
+						'required' => true,
+					),
+				),
+			)
+		);
+	}
+
 	/**
 	 * Determines the allowed query_vars for a get_items() response and
 	 * prepares for WP_Query.
@@ -100,6 +124,70 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 			return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
 		}
 
+		$insert = $this->insert_attachment( $request );
+
+		if ( is_wp_error( $insert ) ) {
+			return $insert;
+		}
+
+		// Extract by name.
+		$attachment_id = $insert['attachment_id'];
+		$file          = $insert['file'];
+
+		if ( isset( $request['alt_text'] ) ) {
+			update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
+		}
+
+		$attachment    = get_post( $attachment_id );
+		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
+
+		if ( is_wp_error( $fields_update ) ) {
+			return $fields_update;
+		}
+
+		$request->set_param( 'context', 'edit' );
+
+		/**
+		 * Fires after a single attachment is completely created or updated via the REST API.
+		 *
+		 * @since 5.0.0
+		 *
+		 * @param WP_Post         $attachment Inserted or updated attachment object.
+		 * @param WP_REST_Request $request    Request object.
+		 * @param bool            $creating   True when creating an attachment, false when updating.
+		 */
+		do_action( 'rest_after_insert_attachment', $attachment, $request, true );
+
+		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
+			// Set a custom header with the attachment_id.
+			// Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
+			header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
+		}
+
+		// Include admin function to get access to wp_generate_attachment_metadata().
+		require_once ABSPATH . 'wp-admin/includes/media.php';
+
+		// Post-process the upload (create image sub-sizes, make PDF thumbnalis, etc.) and insert attachment meta.
+		// At this point the server may run out of resources and post-processing of uploaded images may fail.
+		wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
+
+		$response = $this->prepare_item_for_response( $attachment, $request );
+		$response = rest_ensure_response( $response );
+		$response->set_status( 201 );
+		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $attachment_id ) ) );
+
+		return $response;
+	}
+
+	/**
+	 * Inserts the attachment post in the database. Does not update the attachment meta.
+	 *
+	 * @since 5.3.0
+	 *
+	 * @param WP_REST_Request $request
+	 * @return array|WP_Error
+	 */
+	protected function insert_attachment( $request ) {
 		// Get the file via $_FILES or raw data.
 		$files   = $request->get_file_params();
 		$headers = $request->get_headers();
@@ -138,7 +226,8 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 			}
 		}
 
-		$attachment                 = $this->prepare_item_for_database( $request );
+		$attachment = $this->prepare_item_for_database( $request );
+
 		$attachment->post_mime_type = $type;
 		$attachment->guid           = $url;
 
@@ -155,6 +244,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 			} else {
 				$id->add_data( array( 'status' => 400 ) );
 			}
+
 			return $id;
 		}
 
@@ -172,40 +262,10 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 		 */
 		do_action( 'rest_insert_attachment', $attachment, $request, true );
 
-		// Include admin function to get access to wp_generate_attachment_metadata().
-		require_once ABSPATH . 'wp-admin/includes/media.php';
-
-		wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
-
-		if ( isset( $request['alt_text'] ) ) {
-			update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
-		}
-
-		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
-
-		if ( is_wp_error( $fields_update ) ) {
-			return $fields_update;
-		}
-
-		$request->set_param( 'context', 'edit' );
-
-		/**
-		 * Fires after a single attachment is completely created or updated via the REST API.
-		 *
-		 * @since 5.0.0
-		 *
-		 * @param WP_Post         $attachment Inserted or updated attachment object.
-		 * @param WP_REST_Request $request    Request object.
-		 * @param bool            $creating   True when creating an attachment, false when updating.
-		 */
-		do_action( 'rest_after_insert_attachment', $attachment, $request, true );
-
-		$response = $this->prepare_item_for_response( $attachment, $request );
-		$response = rest_ensure_response( $response );
-		$response->set_status( 201 );
-		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );
-
-		return $response;
+		return array(
+			'attachment_id' => $id,
+			'file'          => $file,
+		);
 	}
 
 	/**
@@ -253,6 +313,39 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 		return $response;
 	}
 
+	/**
+	 * Performs post processing on an attachment.
+	 *
+	 * @since 5.3.0
+	 *
+	 * @param WP_REST_Request $request
+	 * @return WP_REST_Response|WP_Error
+	 */
+	public function post_process_item( $request ) {
+		switch ( $request['action'] ) {
+			case 'create-image-subsizes':
+				require_once ABSPATH . 'wp-admin/includes/image.php';
+				wp_update_image_subsizes( $request['id'] );
+				break;
+		}
+
+		$request['context'] = 'edit';
+
+		return $this->prepare_item_for_response( get_post( $request['id'] ), $request );
+	}
+
+	/**
+	 * Checks if a given request can perform post processing on an attachment.
+	 *
+	 * @sicne 5.3.0
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
+	 */
+	public function post_process_item_permissions_check( $request ) {
+		return $this->update_item_permissions_check( $request );
+	}
+
 	/**
 	 * Prepares a single attachment for create or update.
 	 *
@@ -380,6 +473,11 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 			$data['source_url'] = wp_get_attachment_url( $post->ID );
 		}
 
+		if ( in_array( 'missing_image_sizes', $fields, true ) ) {
+			require_once ABSPATH . 'wp-admin/includes/image.php';
+			$data['missing_image_sizes'] = array_keys( wp_get_missing_image_subsizes( $post->ID ) );
+		}
+
 		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 
 		$data = $this->filter_response_by_context( $data, $context );
@@ -514,6 +612,14 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 			'readonly'    => true,
 		);
 
+		$schema['properties']['missing_image_sizes'] = array(
+			'description' => __( 'List of the missing image sizes of the attachment.' ),
+			'type'        => 'array',
+			'items'       => array( 'type' => 'string' ),
+			'context'     => array( 'edit' ),
+			'readonly'    => true,
+		);
+
 		unset( $schema['properties']['password'] );
 
 		$this->schema = $schema;
diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php
index 556cc6c8be..dc0b61835d 100644
--- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php
+++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php
@@ -1327,7 +1327,7 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control
 		$response   = rest_get_server()->dispatch( $request );
 		$data       = $response->get_data();
 		$properties = $data['schema']['properties'];
-		$this->assertEquals( 26, count( $properties ) );
+		$this->assertEquals( 27, count( $properties ) );
 		$this->assertArrayHasKey( 'author', $properties );
 		$this->assertArrayHasKey( 'alt_text', $properties );
 		$this->assertArrayHasKey( 'caption', $properties );
@@ -1360,6 +1360,7 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control
 		$this->assertArrayHasKey( 'raw', $properties['title']['properties'] );
 		$this->assertArrayHasKey( 'rendered', $properties['title']['properties'] );
 		$this->assertArrayHasKey( 'type', $properties );
+		$this->assertArrayHasKey( 'missing_image_sizes', $properties );
 	}
 
 	public function test_get_additional_field_registration() {
diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php
index 5c780011d3..51abf95fc1 100644
--- a/tests/phpunit/tests/rest-api/rest-schema-setup.php
+++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php
@@ -99,6 +99,7 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase {
 			'/wp/v2/pages/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)',
 			'/wp/v2/media',
 			'/wp/v2/media/(?P<id>[\\d]+)',
+			'/wp/v2/media/(?P<id>[\\d]+)/post-process',
 			'/wp/v2/blocks',
 			'/wp/v2/blocks/(?P<id>[\d]+)',
 			'/wp/v2/blocks/(?P<id>[\d]+)/autosaves',
diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js
index 3083545431..b8217e729b 100644
--- a/tests/qunit/fixtures/wp-api-generated.js
+++ b/tests/qunit/fixtures/wp-api-generated.js
@@ -2304,6 +2304,33 @@ mockedApiResponse.Schema = {
                 }
             ]
         },
+        "/wp/v2/media/(?P<id>[\\d+])/post-process": {
+            "namespace": "wp/v2",
+            "methods": [
+                "POST"
+            ],
+            "endpoints": [
+                {
+                    "methods": [
+                        "POST"
+                    ],
+                    "args": {
+                        "id": {
+                            "required": false,
+                            "description": "Unique identifier for the object.",
+                            "type": "integer"
+                        },
+                        "action": {
+                            "required": true,
+                            "enum": [
+                                "create-image-subsizes"
+                            ],
+                            "type": "string"
+                        }
+                    }
+                }
+            ]
+        },
         "/wp/v2/blocks": {
             "namespace": "wp/v2",
             "methods": [
