Title: wp_save_image
Published: April 25, 2014
Last modified: February 24, 2026

---

# wp_save_image( int $post_id ): stdClass

## In this article

 * [Parameters](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#parameters)
 * [Return](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#return)
 * [Source](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#source)
 * [Hooks](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#hooks)
 * [Related](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#related)
 * [Changelog](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#changelog)

[ Back to top](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#wp--skip-link--target)

Saves image to post, along with enqueued changes in `$_REQUEST['history']`.

## 󠀁[Parameters](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#parameters)󠁿

 `$post_id`intrequired

Attachment post ID.

## 󠀁[Return](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#return)󠁿

 stdClass

## 󠀁[Source](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#source)󠁿

    ```php
    function wp_save_image( $post_id ) {
    	$_wp_additional_image_sizes = wp_get_additional_image_sizes();

    	$return  = new stdClass();
    	$success = false;
    	$delete  = false;
    	$scaled  = false;
    	$nocrop  = false;
    	$post    = get_post( $post_id );

    	$img = wp_get_image_editor( _load_image_to_edit_path( $post_id, 'full' ) );

    	if ( is_wp_error( $img ) ) {
    		$return->error = esc_js( __( 'Unable to create new image.' ) );
    		return $return;
    	}

    	$full_width  = ! empty( $_REQUEST['fwidth'] ) ? (int) $_REQUEST['fwidth'] : 0;
    	$full_height = ! empty( $_REQUEST['fheight'] ) ? (int) $_REQUEST['fheight'] : 0;
    	$target      = ! empty( $_REQUEST['target'] ) ? preg_replace( '/[^a-z0-9_-]+/i', '', $_REQUEST['target'] ) : '';
    	$scale       = ! empty( $_REQUEST['do'] ) && 'scale' === $_REQUEST['do'];

    	/** This filter is documented in wp-admin/includes/image-edit.php */
    	$edit_thumbnails_separately = (bool) apply_filters( 'image_edit_thumbnails_separately', false );

    	if ( $scale ) {
    		$size            = $img->get_size();
    		$original_width  = $size['width'];
    		$original_height = $size['height'];

    		if ( $full_width > $original_width || $full_height > $original_height ) {
    			$return->error = esc_js( __( 'Images cannot be scaled to a size larger than the original.' ) );
    			return $return;
    		}

    		if ( $full_width > 0 && $full_height > 0 ) {
    			// Check if it has roughly the same w / h ratio.
    			$diff = round( $original_width / $original_height, 2 ) - round( $full_width / $full_height, 2 );
    			if ( -0.1 < $diff && $diff < 0.1 ) {
    				// Scale the full size image.
    				if ( $img->resize( $full_width, $full_height ) ) {
    					$scaled = true;
    				}
    			}

    			if ( ! $scaled ) {
    				$return->error = esc_js( __( 'Error while saving the scaled image. Please reload the page and try again.' ) );
    				return $return;
    			}
    		}
    	} elseif ( ! empty( $_REQUEST['history'] ) ) {
    		$changes = json_decode( wp_unslash( $_REQUEST['history'] ) );
    		if ( $changes ) {
    			$img = image_edit_apply_changes( $img, $changes );
    		}
    	} else {
    		$return->error = esc_js( __( 'Nothing to save, the image has not changed.' ) );
    		return $return;
    	}

    	$meta         = wp_get_attachment_metadata( $post_id );
    	$backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );

    	if ( ! is_array( $meta ) ) {
    		$return->error = esc_js( __( 'Image data does not exist. Please re-upload the image.' ) );
    		return $return;
    	}

    	if ( ! is_array( $backup_sizes ) ) {
    		$backup_sizes = array();
    	}

    	// Generate new filename.
    	$path = get_attached_file( $post_id );

    	$basename = pathinfo( $path, PATHINFO_BASENAME );
    	$dirname  = pathinfo( $path, PATHINFO_DIRNAME );
    	$ext      = pathinfo( $path, PATHINFO_EXTENSION );
    	$filename = pathinfo( $path, PATHINFO_FILENAME );
    	$suffix   = time() . rand( 100, 999 );

    	if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE
    		&& isset( $backup_sizes['full-orig'] ) && $backup_sizes['full-orig']['file'] !== $basename
    	) {

    		if ( $edit_thumbnails_separately && 'thumbnail' === $target ) {
    			$new_path = "{$dirname}/{$filename}-temp.{$ext}";
    		} else {
    			$new_path = $path;
    		}
    	} else {
    		while ( true ) {
    			$filename     = preg_replace( '/-e([0-9]+)$/', '', $filename );
    			$filename    .= "-e{$suffix}";
    			$new_filename = "{$filename}.{$ext}";
    			$new_path     = "{$dirname}/$new_filename";

    			if ( file_exists( $new_path ) ) {
    				++$suffix;
    			} else {
    				break;
    			}
    		}
    	}

    	$saved_image = wp_save_image_file( $new_path, $img, $post->post_mime_type, $post_id );
    	// Save the full-size file, also needed to create sub-sizes.
    	if ( ! $saved_image ) {
    		$return->error = esc_js( __( 'Unable to save the image.' ) );
    		return $return;
    	}

    	if ( 'nothumb' === $target || 'all' === $target || 'full' === $target || $scaled ) {
    		$tag = false;

    		if ( isset( $backup_sizes['full-orig'] ) ) {
    			if ( ( ! defined( 'IMAGE_EDIT_OVERWRITE' ) || ! IMAGE_EDIT_OVERWRITE )
    				&& $backup_sizes['full-orig']['file'] !== $basename
    			) {
    				$tag = "full-$suffix";
    			}
    		} else {
    			$tag = 'full-orig';
    		}

    		if ( $tag ) {
    			$backup_sizes[ $tag ] = array(
    				'width'    => $meta['width'],
    				'height'   => $meta['height'],
    				'filesize' => $meta['filesize'],
    				'file'     => $basename,
    			);
    		}

    		$success = ( $path === $new_path ) || update_attached_file( $post_id, $new_path );

    		$meta['file'] = _wp_relative_upload_path( $new_path );

    		$size             = $img->get_size();
    		$meta['width']    = $size['width'];
    		$meta['height']   = $size['height'];
    		$meta['filesize'] = $saved_image['filesize'];

    		if ( $success && ( 'nothumb' === $target || 'all' === $target ) ) {
    			$sizes = get_intermediate_image_sizes();

    			if ( $edit_thumbnails_separately && 'nothumb' === $target ) {
    				$sizes = array_diff( $sizes, array( 'thumbnail' ) );
    			}
    		}

    		$return->fw = $meta['width'];
    		$return->fh = $meta['height'];
    	} elseif ( $edit_thumbnails_separately && 'thumbnail' === $target ) {
    		$sizes   = array( 'thumbnail' );
    		$success = true;
    		$delete  = true;
    		$nocrop  = true;
    	}

    	/*
    	 * We need to remove any existing resized image files because
    	 * a new crop or rotate could generate different sizes (and hence, filenames),
    	 * keeping the new resized images from overwriting the existing image files.
    	 * https://core.trac.wordpress.org/ticket/32171
    	 */
    	if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE && ! empty( $meta['sizes'] ) ) {
    		foreach ( $meta['sizes'] as $size ) {
    			if ( ! empty( $size['file'] ) && preg_match( '/-e[0-9]{13}-/', $size['file'] ) ) {
    				$delete_file = path_join( $dirname, $size['file'] );
    				wp_delete_file( $delete_file );
    			}
    		}
    	}

    	if ( isset( $sizes ) ) {
    		$_sizes = array();

    		foreach ( $sizes as $size ) {
    			$tag = false;

    			if ( isset( $meta['sizes'][ $size ] ) ) {
    				if ( isset( $backup_sizes[ "$size-orig" ] ) ) {
    					if ( ( ! defined( 'IMAGE_EDIT_OVERWRITE' ) || ! IMAGE_EDIT_OVERWRITE )
    						&& $backup_sizes[ "$size-orig" ]['file'] !== $meta['sizes'][ $size ]['file']
    					) {
    						$tag = "$size-$suffix";
    					}
    				} else {
    					$tag = "$size-orig";
    				}

    				if ( $tag ) {
    					$backup_sizes[ $tag ] = $meta['sizes'][ $size ];
    				}
    			}

    			if ( isset( $_wp_additional_image_sizes[ $size ] ) ) {
    				$width  = (int) $_wp_additional_image_sizes[ $size ]['width'];
    				$height = (int) $_wp_additional_image_sizes[ $size ]['height'];
    				$crop   = ( $nocrop ) ? false : $_wp_additional_image_sizes[ $size ]['crop'];
    			} else {
    				$height = get_option( "{$size}_size_h" );
    				$width  = get_option( "{$size}_size_w" );
    				$crop   = ( $nocrop ) ? false : get_option( "{$size}_crop" );
    			}

    			$_sizes[ $size ] = array(
    				'width'  => $width,
    				'height' => $height,
    				'crop'   => $crop,
    			);
    		}

    		$meta['sizes'] = array_merge( $meta['sizes'], $img->multi_resize( $_sizes ) );
    	}

    	unset( $img );

    	if ( $success ) {
    		wp_update_attachment_metadata( $post_id, $meta );
    		update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes );

    		if ( 'thumbnail' === $target || 'all' === $target || 'full' === $target ) {
    			// Check if it's an image edit from attachment edit screen.
    			if ( ! empty( $_REQUEST['context'] ) && 'edit-attachment' === $_REQUEST['context'] ) {
    				$thumb_url = wp_get_attachment_image_src( $post_id, array( 900, 600 ), true );

    				$return->thumbnail = $thumb_url[0];
    			} else {
    				$file_url = wp_get_attachment_url( $post_id );

    				if ( ! empty( $meta['sizes']['thumbnail'] ) ) {
    					$thumb             = $meta['sizes']['thumbnail'];
    					$return->thumbnail = path_join( dirname( $file_url ), $thumb['file'] );
    				} else {
    					$return->thumbnail = "$file_url?w=128&h=128";
    				}
    			}
    		}
    	} else {
    		$delete = true;
    	}

    	if ( $delete ) {
    		wp_delete_file( $new_path );
    	}

    	$return->msg = esc_js( __( 'Image saved' ) );

    	return $return;
    }
    ```

[View all references](https://developer.wordpress.org/reference/files/wp-admin/includes/image-edit.php/)
[View on Trac](https://core.trac.wordpress.org/browser/tags/6.9.4/src/wp-admin/includes/image-edit.php#L913)
[View on GitHub](https://github.com/WordPress/wordpress-develop/blob/6.9.4/src/wp-admin/includes/image-edit.php#L913-L1164)

## 󠀁[Hooks](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#hooks)󠁿

 [apply_filters( ‘image_edit_thumbnails_separately’, bool $show )](https://developer.wordpress.org/reference/hooks/image_edit_thumbnails_separately/)

Shows the settings in the Image Editor that allow selecting to edit only the thumbnail
of an image.

## 󠀁[Related](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#related)󠁿

| Uses | Description | 
| [wp_get_additional_image_sizes()](https://developer.wordpress.org/reference/functions/wp_get_additional_image_sizes/)`wp-includes/media.php` |

Retrieves additional image sizes.

  | 
| [wp_delete_file()](https://developer.wordpress.org/reference/functions/wp_delete_file/)`wp-includes/functions.php` |

Deletes a file.

  | 
| [image_edit_apply_changes()](https://developer.wordpress.org/reference/functions/image_edit_apply_changes/)`wp-admin/includes/image-edit.php` |

Performs group of changes on Editor specified.

  | 
| [wp_save_image_file()](https://developer.wordpress.org/reference/functions/wp_save_image_file/)`wp-admin/includes/image-edit.php` |

Saves image to file.

  | 
| [_load_image_to_edit_path()](https://developer.wordpress.org/reference/functions/_load_image_to_edit_path/)`wp-admin/includes/image.php` |

Retrieves the path or URL of an attachment’s attached file.

  | 
| [esc_js()](https://developer.wordpress.org/reference/functions/esc_js/)`wp-includes/formatting.php` |

Escapes single quotes, `"`, , `&amp;`, and fixes line endings.

  | 
| [path_join()](https://developer.wordpress.org/reference/functions/path_join/)`wp-includes/functions.php` |

Joins two filesystem paths together.

  | 
| [wp_get_image_editor()](https://developer.wordpress.org/reference/functions/wp_get_image_editor/)`wp-includes/media.php` |

Returns a [WP_Image_Editor](https://developer.wordpress.org/reference/classes/wp_image_editor/) instance and loads file into it.

  | 
| [get_intermediate_image_sizes()](https://developer.wordpress.org/reference/functions/get_intermediate_image_sizes/)`wp-includes/media.php` |

Gets the available intermediate image size names.

  | 
| [wp_get_attachment_image_src()](https://developer.wordpress.org/reference/functions/wp_get_attachment_image_src/)`wp-includes/media.php` |

Retrieves an image to represent an attachment.

  | 
| [wp_get_attachment_metadata()](https://developer.wordpress.org/reference/functions/wp_get_attachment_metadata/)`wp-includes/post.php` |

Retrieves attachment metadata for attachment ID.

  | 
| [wp_update_attachment_metadata()](https://developer.wordpress.org/reference/functions/wp_update_attachment_metadata/)`wp-includes/post.php` |

Updates metadata for an attachment.

  | 
| [wp_get_attachment_url()](https://developer.wordpress.org/reference/functions/wp_get_attachment_url/)`wp-includes/post.php` |

Retrieves the URL for an attachment.

  | 
| [update_post_meta()](https://developer.wordpress.org/reference/functions/update_post_meta/)`wp-includes/post.php` |

Updates a post meta field based on the given post ID.

  | 
| [get_attached_file()](https://developer.wordpress.org/reference/functions/get_attached_file/)`wp-includes/post.php` |

Retrieves attached file path based on attachment ID.

  | 
| [update_attached_file()](https://developer.wordpress.org/reference/functions/update_attached_file/)`wp-includes/post.php` |

Updates attachment file path based on attachment ID.

  | 
| [_wp_relative_upload_path()](https://developer.wordpress.org/reference/functions/_wp_relative_upload_path/)`wp-includes/post.php` |

Returns relative path to an uploaded file.

  | 
| [__()](https://developer.wordpress.org/reference/functions/__/)`wp-includes/l10n.php` |

Retrieves the translation of $text.

  | 
| [wp_unslash()](https://developer.wordpress.org/reference/functions/wp_unslash/)`wp-includes/formatting.php` |

Removes slashes from a string or recursively removes slashes from strings within an array.

  | 
| [apply_filters()](https://developer.wordpress.org/reference/functions/apply_filters/)`wp-includes/plugin.php` |

Calls the callback functions that have been added to a filter hook.

  | 
| [get_option()](https://developer.wordpress.org/reference/functions/get_option/)`wp-includes/option.php` |

Retrieves an option value based on an option name.

  | 
| [get_post_meta()](https://developer.wordpress.org/reference/functions/get_post_meta/)`wp-includes/post.php` |

Retrieves a post meta field for the given post ID.

  | 
| [get_post()](https://developer.wordpress.org/reference/functions/get_post/)`wp-includes/post.php` |

Retrieves post data given a post ID or post object.

  | 
| [is_wp_error()](https://developer.wordpress.org/reference/functions/is_wp_error/)`wp-includes/load.php` |

Checks whether the given variable is a WordPress Error.

  |

[Show 19 more](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#)
[Show less](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#)

| Used by | Description | 
| [wp_ajax_image_editor()](https://developer.wordpress.org/reference/functions/wp_ajax_image_editor/)`wp-admin/includes/ajax-actions.php` |

Handles image editing via AJAX.

  |

## 󠀁[Changelog](https://developer.wordpress.org/reference/functions/wp_save_image/?output_format=md#changelog)󠁿

| Version | Description | 
| [2.9.0](https://developer.wordpress.org/reference/since/2.9.0/) | Introduced. |

## User Contributed Notes

You must [log in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fwp_save_image%2F)
before being able to contribute a note or feedback.