WordPress Images

How to Edit Images in WordPress With the Built In Editor

WordPress has a built in image editor most people never open. Here is what every control does, exactly which files a save writes to disk, where the backup of your original lives, and why the Apply changes to radio buttons vanished.

How to Edit Images in WordPress With the Built In Editor

WordPress ships an image editor in core. It sits behind a button on every image attachment, it crops, scales, rotates and flips, and most site owners have never opened it once. Its oldest hook, the deprecated image_edit_before_change filter, is marked since 2.9.0, so the thing has been sitting there for well over a decade.

The people who do open it get stuck on the same screen: a set of radio buttons under the heading Apply changes to, offering All image sizes, Thumbnail, and All sizes except thumbnail, with nothing on the page explaining the difference. On a current WordPress install those radios are not on screen at all, and there is a specific reason for that.

The mechanism underneath matters more than the buttons. Every save writes new files instead of replacing old ones, the attachment metadata is repointed at the new files, and the original stays exactly where it was. Once you know that, the editor stops being mysterious and becomes a tool with a narrow, genuinely useful job.

Where the editor actually lives

There is one editor, reachable from two places. In Media, open an image and click Edit Image under the preview. In the media modal inside a post, select an image and click Edit Image in the attachment details panel. Both buttons run the same JavaScript call, imageEdit.open(), passing the attachment ID and a nonce created as image_editor-{attachment ID}. The button is only printed when wp_image_editor_supports() reports that the active backend can handle that attachment’s mime type, which is why a few uploads have no Edit Image button at all.

The screen itself is rendered by wp_image_editor() in wp-admin/includes/image-edit.php. The preview you drag your crop box on is not the original file. It is a resized stream produced by the imgedit-preview admin-ajax action, and core scales it so the longest edge is at most 600 pixels. The ratio is computed as 600 / max( width, height ) and parked in a hidden field called imgedit-sizer, then your selection is multiplied back up by that ratio when you save. That is why a crop which looked exact on screen can land a pixel or two off on a very large photograph.

The controls, one by one

Crop ships disabled and stays that way until you drag a selection on the preview. Once you have one, three groups of number fields become useful: Aspect ratio holds the shape of the selection (1 and 1 for a square, 16 and 9 for widescreen), Selection sets the exact pixel width and height, and Starting Coordinates sets the top left corner. Holding shift while resizing the box preserves the ratio. Core’s own help text sets the floor: “The minimum selection size is the thumbnail size as set in the Media settings”, which is 150 by 150 on a default install.

Scale is proportional and downward only. It is posted separately from everything else, as do=scale rather than through the history queue, which is why the built in help says “For best results, scaling should be done before you crop, flip, or rotate.” Ask for anything larger than the current image and you get “Images cannot be scaled to a size larger than the original.” Core also compares the old and new aspect ratios rounded to two decimal places, and if they differ by 0.1 or more the resize is never attempted and you get “Error while saving the scaled image” instead. You cannot quietly stretch a photograph out of shape here.

Image Rotation opens a small menu holding Rotate 90° left, Rotate 90° right, Rotate 180°, Flip vertical and Flip horizontal. Before printing the rotate buttons, core asks wp_image_editor_supports() whether the backend can rotate that mime type. If it cannot, the two rotate buttons render empty and disabled, the 180° option is not printed at all, and a note appears reading “Image rotation is not supported by your web host.” The classic cause is a GD build without imagerotate(), which is a host level problem rather than a WordPress setting. Both flips still work.

Undo and Redo move through the pending change list, not the file. Nothing has been written to disk at that point, so stepping back costs nothing.

Table of the built in WordPress image editor controls, showing how crop, scale, rotate, flip and undo are posted to the server and what limit core enforces on each.

Nothing happens until you press Save Edits

Every crop, rotate and flip is appended to a JSON string in a hidden field called imgedit-history. The entries are compact: r carries a rotation angle, f a flip axis, c a crop selection. Press Save Edits and that string is posted to the server, where image_edit_apply_changes() expands it back into named operations.

That function then does something people rarely notice: it collapses consecutive operations of the same type before touching a single pixel. Consecutive rotations have their angles added together, so four clicks on Rotate 90° right reduce to one 360 degree operation. Consecutive flips are combined with an exclusive or on the axis value, so two flips on the same axis cancel out. Crops are never merged, because each one is measured against the result of the crop before it. The practical effect is that clicking around in the editor does not degrade the image. Only the reduced list is applied, once, in one pass, to one freshly loaded copy of the file.

Developers get one hook in the middle of this, wp_image_editor_before_change, which receives the WP_Image_Editor instance and the reduced change list just before the operations run.

Apply changes to, the control nobody understands

If you have used WordPress for years you will remember a panel called Thumbnail Settings sitting beside the preview, with a small square preview of the current thumbnail and three radio buttons under it. Since WordPress 6.3 that panel is hidden by default, gated behind a filter that returns false unless you say otherwise. It also requires the attachment to have a thumbnail and a populated sizes array, so an upload that never got sub sizes will not show it either way.

// In a small plugin, or your child theme's functions.php.
// Brings back the "Apply changes to" radio buttons in the
// built-in image editor. Default since WordPress 6.3 is false.
add_filter( 'image_edit_thumbnails_separately', '__return_true' );

The three options post a target value of all, thumbnail or nothumb, and wp_save_image() branches on it. Here is exactly what each one writes.

All image sizes saves the edited full size image to a new file, repoints the attachment at it with update_attached_file(), then regenerates every size returned by get_intermediate_image_sizes() from that new full size file. Thumbnail, medium, medium_large, large, the 1536x1536 and 2048x2048 sizes core registers itself, plus everything your theme and plugins added with add_image_size(). This is the option you want almost always.

All sizes except thumbnail does the same thing, then removes one entry from the list with array_diff( $sizes, array( 'thumbnail' ) ) before regenerating. The full image and every other size are rewritten. The existing 150 by 150 thumbnail file is left alone on disk and left alone in the metadata.

Thumbnail is the odd one, and it is the reason the whole control exists. Core still applies your crop to the full size image and still writes it to a file, because multi_resize() needs a source to work from. It then generates only the thumbnail, sets an internal delete flag, and calls wp_delete_file() on that full size file on the way out. When the request finishes, the only thing that changed anywhere on your site is the one 150 by 150 file and its entry in the attachment metadata.

Table comparing the three Apply changes to targets, showing that all rewrites every file, nothumb skips the thumbnail, and thumbnail rewrites only the 150 by 150 file.

There is one more twist inside that branch. When the target is the thumbnail, core sets an internal $nocrop flag, and the crop argument for the size it is about to generate becomes false. Normally the thumbnail_crop option defaults to 1, meaning the thumbnail is hard cropped to a square out of the centre of the image. With $nocrop on, your selection is the crop and core simply resizes it down to fit. That is the point of the feature: a centre square taken automatically out of a wide photograph very often cuts a person’s head in half, and this is the one place in core where you can choose that square yourself without touching the image everyone else sees. Understanding how one upload turns into a folder full of derivatives makes the rest of this screen much easier to read, and the way registered sizes work is worth reading alongside it.

What lands on disk after two edits

WordPress does not overwrite. When you save, wp_save_image() builds a suffix from time() concatenated with rand( 100, 999 ), which gives a 13 digit string, and appends it to the file name as -e{suffix}. A regular expression, /-e([0-9]+)$/, strips any previous -e suffix first, so names do not stack into nonsense after repeated edits, and if that name is somehow already taken the suffix is incremented until it is free.

Take harbour.jpg at 1200 by 800 pixels, comfortably under the big_image_size_threshold default of 2560 so core does not make a -scaled copy at upload time. Rotate it 180 degrees and save, then flip it horizontally and save again. The uploads month folder now holds this:

harbour.jpg                          the original, never touched
harbour-150x150.jpg                  original sub sizes, now unreferenced
harbour-300x200.jpg
harbour-768x512.jpg
harbour-1024x683.jpg
harbour-e1755512345678.jpg           first save, new full size
harbour-e1755512345678-150x150.jpg   first save, new sub sizes
harbour-e1755512345678-300x200.jpg
harbour-e1755512345678-768x512.jpg
harbour-e1755512345678-1024x683.jpg
harbour-e1755598765432.jpg           second save, the live full size
harbour-e1755598765432-150x150.jpg   second save, the live sub sizes
harbour-e1755598765432-300x200.jpg
harbour-e1755598765432-768x512.jpg
harbour-e1755598765432-1024x683.jpg
Figure showing that a 1200 by 800 upload edited twice leaves fifteen files in the uploads folder while only five of them are still referenced by the attachment.

Only the last generation is referenced. update_attached_file() rewrites the _wp_attached_file post meta and wp_update_attachment_metadata() rewrites the sizes array, so the front end serves the newest files while the older ones sit there costing disk space and backup time. Two edits on one photograph leave fifteen files where five are needed. Multiply that by a few hundred product images and the uploads folder gets heavy in a way nothing in the admin will tell you about. This is one of the quiet contributors to media bloat, and it is worth knowing before you start clearing out an overgrown media library, because those older generations are not orphans in the usual sense. They are the restore path.

To see how much of this you already have, list the edited files from the command line. Both of these only read:

# Count image files created by the built-in editor.
find wp-content/uploads -type f -name "*-e[0-9][0-9][0-9]*" | wc -l

# Look at the largest ones first.
find wp-content/uploads -type f -name "*-e[0-9][0-9][0-9]*" 
  -printf "%st%pn" | sort -rn | head -20

One constant changes this behaviour. Define IMAGE_EDIT_OVERWRITE as true in wp-config.php and core writes over the edited file in place instead of minting a new name, deleting the previous edited sub size files as it goes. Read the condition carefully though: that branch also requires an existing full-orig backup entry whose file name differs from the current one, so the very first edit still produces a suffixed file. The constant does not give you zero extra files. It gives you exactly one extra generation per attachment, forever, which is usually a good trade.

What lands on disk is easier to believe when you can watch it happen. The simulator below has the same four tools as the editor in your media library and the same Apply changes to setting.

Make an edit and the file list underneath updates the way WordPress would: the original untouched, a new file with a timestamp in its name, a fresh copy of every sub size, and the backup entry in the database that points at the old ones. Do it twice and count the files. That number is why a media library grows faster than the library it represents.

Media editor simulator

The image editor in the media library looks like it changes your picture. It does not: every save writes a new set of files with a time stamp in the name and leaves the old set on the disk. Drop a picture, edit it the way you would in WordPress, and watch the uploads folder fill up. The file is read in this browser tab only, nothing is uploaded and nothing on your site is touched.

The picture in the editor

No picture yet.

No changes waiting.

Drag on the picture to draw a selection, or type one into the two selection fields: a typed selection is placed in the middle.

Drop an image here
or press Enter to pick one. The picture stays on your disk.
Apply changes to
No picture yet

Loading the example picture.

Saved edits0
Files on disk0
In use0
Kept as backup0
Estimated space0
File in the uploads folderPixelsEstimatedState
_wp_attachment_backup_sizes in the database

Finding what the edits left behind

  
wp_ is the table prefix, change it if yours differs.

The new name is the old one with -e and thirteen digits: the Unix time of the save and three random digits. A second edit replaces that part of the name, it does not stack. Every size is cut from the new full size file, so the sizes carry the same stamp.

Cropping in an image programme before the upload gives you one clean set of files. The same crop in the media editor gives you a second set and keeps the first, because the old files are the backup that "Restore original image" needs.

Setting IMAGE_EDIT_OVERWRITE to true in wp-config.php makes WordPress write over the edited file instead of adding another one on every save. The first backup set is still kept, so one spare set per picture remains.

The sizes used here are the WordPress defaults: thumbnail 150 by 150 cropped, medium 300, medium_large 768, large 1024, plus 1536 and 2048. A size is only written when the picture is larger than it. The estimate uses the bytes per pixel of the full size result, so it is a guide, not a measurement.

WunderPaint
The Dynamic Design and Automation Studio
WunderPaint is a layered image editor for your WordPress media library. WunderPaint Studio is the same thing in any browser, free and without an account.

Restore original image, and why the button is missing

On the first save, core records what it is replacing in a post meta key called _wp_attachment_backup_sizes. The full size entry is keyed full-orig and holds the width, height, file size and file name of the image being replaced. Each sub size goes in under a key like thumbnail-orig or medium-orig, and the stored value is the whole entry copied straight out of the attachment metadata: file name, dimensions, mime type and all. Later edits add generational keys built from the same 13 digit suffix, for example full-1755512345678. You can read the record without touching it:

# Read only. Replace 123 with the attachment ID.
wp post meta get 123 _wp_attachment_backup_sizes

The Restore original image panel only appears when two conditions hold: a full-orig entry exists in that array, and the file name currently recorded in the attachment metadata is different from the one stored there. Restoring runs wp_restore_image(), which points the attachment back at the original file name and puts the -orig entries back into the sizes array. It deletes nothing, and core says so on the screen: “Previously edited copies of the image will not be deleted.” After a restore the panel disappears again, because the attached file and the backup record now match.

The button goes missing for four reasons, in rough order of how often they happen. The attachment has never been edited, so there is no backup meta at all. The meta was dropped during a migration, an import, or by a tool that rewrote attachment metadata wholesale, which is a real hazard when you regenerate thumbnails in bulk with something that rebuilds the meta array from scratch. The original file was deleted from disk by a cleanup script that saw an unreferenced image and removed it, in which case the meta still promises a restore that would produce a broken image. Or IMAGE_EDIT_OVERWRITE is defined and you have already restored once, because on that path core deletes the backup meta entirely after a successful restore.

Scaling here is not the same as resizing before upload

This is the mistake that costs people the most quality. The Scale control looks like the obvious way to turn a 6000 pixel camera file into something web sized. It is not, for three reasons.

First, the editor is not working on your camera file. It loads its source through _load_image_to_edit_path( $post_id, 'full' ), which resolves to get_attached_file(), which is whatever _wp_attached_file currently points at. On any upload whose longest edge exceeds the big_image_size_threshold default of 2560 pixels, that is already the -scaled file core generated at upload time, not the untouched original. The real original is parked under the original_image key in the attachment metadata and reachable with wp_get_original_image_path(), and the editor never opens it. Scaling in the editor is therefore a second resize of an image that has already been resized and re-encoded once.

Second, every save is a fresh lossy encode, and the quality is not yours to choose per image. WP_Image_Editor::get_default_quality() returns 86 for image/webp and falls back to the class default of 82 for JPEG and everything else. The only ways to change that are the wp_editor_set_quality filter and, for JPEG, jpeg_quality, and both apply site wide. A photograph that has been through the editor twice has been encoded at quality 82 twice, on top of whatever your camera or export tool already did.

Third, the whole image has to be decompressed into PHP memory before anything can touch it. A 6000 by 4000 pixel JPEG is 24 million pixels, and GD holds a truecolour image at four bytes per pixel, so that is roughly 96 MB of raw bitmap before a single operation runs. This is where a lot of white screens on Save Edits come from. Resizing on your own machine before upload avoids all three problems at once.

Cropping to a ratio the theme will crop again

A common plan is to open a photograph, type 16 and 9 into the aspect ratio boxes, drag a clean selection, and save with the expectation that the header or card slot will now look right. It often does not, and the reason is in the save path rather than in your crop.

Your crop produces a new full size image at 16:9. Core then regenerates every registered size from it by calling multi_resize(), and for each size it takes the crop flag from either the registered size definition or the {$size}_crop option. Any size registered with a hard crop, meaning add_image_size( 'card', 600, 400, true ), does not respect your 16:9 shape at all. It takes a 600 by 400 rectangle out of the centre of the image you just cropped. You cropped, then the theme cropped again, and the second crop is the one on the page. The only sizes that keep your ratio are the ones registered with the crop argument left false, which resize proportionally to fit inside a bounding box.

Featured images make this worse, because the featured image is only a pointer to an attachment and the theme decides which registered size to request. Cropping the attachment tells the theme nothing. If your header keeps cutting the top off people’s heads, the fix is usually in how the featured image size is registered and requested, not in the editor.

What the built in editor cannot do

Being clear about the ceiling saves a lot of searching. There are no layers, no masks and no groups. There is no text tool, so you cannot put a caption or a price badge onto an image. There is no exposure, contrast, saturation, curves or white balance control of any kind, because WP_Image_Editor exposes resize, crop, rotate and flip and nothing else. There is no drawing and no background removal.

It also cannot change the file format. The save call passes $post->post_mime_type straight through to wp_save_image_file(), so a JPEG that goes into the editor comes out a JPEG. Converting a library to WebP or AVIF is a different job with different tools and the image_editor_output_format filter behind it. Quality is global, as described above, so there is no per size and no per image quality setting. And there is no batch: one attachment, one screen, one save.

What it does do, it does properly. A crooked scan straightened, a phone photograph rotated the right way up, a stray edge trimmed off a screenshot, a slightly oversized graphic scaled down: all of that is one minute of work, on the server, with a restore path.

When a plugin is the honest answer

The point where core stops being enough is easy to recognise. You need text on the image. You need the same treatment applied to two hundred product photographs rather than one. You need to composite a logo, remove a background, or adjust colour. You want to see which of your uploads are actually used anywhere before you delete a generation of edited files. None of that is a shortcoming of the built in editor, it is simply outside what four geometric operations can express, and a plugin with a real canvas and a real media manager is the correct answer rather than a workaround. WunderPaint is one such plugin: it adds layers, text and adjustments to the editing screen and pairs them with a media library manager for the housekeeping side.

Symptom and cause

The Apply changes to radio buttons are not on the screen. The Thumbnail Settings panel has been hidden by default since WordPress 6.3, because image_edit_thumbnails_separately returns false unless something turns it on. Add the filter above and the panel comes back, provided the attachment actually has a thumbnail and sub sizes.

The rotation buttons are empty and disabled. wp_image_editor_supports() reported that the active backend cannot rotate this mime type, usually a GD build without imagerotate(). Making the Imagick extension available, so core picks that backend instead, restores rotation. Flip is not affected.

Save Edits spins and then fails, or the page goes white. The full size image could not be decoded within the memory available to PHP. Very large uploads need more headroom than the admin default, and the file the editor opens may be a 2560 pixel -scaled version that is still expensive to hold as a raw bitmap.

The image on the front end did not change. The new files have new names, so this is not a browser cache issue at the file level. It is usually a page cache or a CDN serving the old HTML, or a hard coded image URL in a page builder module that stored the file path rather than the attachment ID.

Restore original image is not offered. There is no full-orig entry in _wp_attachment_backup_sizes, or the file name in the attachment metadata already matches the one recorded there. A migration, an import or a metadata rebuild can remove that record permanently.

The uploads folder is much larger than the media library suggests. Every edit adds a full generation of files and keeps the previous one. Count the files matching -e followed by digits to see the scale of it.

A trim tool, not a darkroom

The built in editor makes far more sense once you stop reading it as a small version of Photoshop and start reading it as what it is: a safe way to change the geometry of a file that other content already points at. Everything odd about it follows from that goal. It writes new files so that nothing in flight breaks. It keeps a backup record so a mistake is recoverable. It regenerates every registered size so the derivatives never disagree with the full size image. It refuses to scale up because doing so would invent pixels it cannot get back.

The Apply changes to control is the same story from another angle. Core knows the thumbnail is hard cropped by default and that a machine chosen centre square is often wrong, so it offers one narrow escape hatch: change the small square and leave everything else alone. The fact that it now sits behind a filter tells you how few people needed it and how many were confused by it, which is a fair summary of the whole screen.

Use it for what it is good at, which is straightening, rotating, trimming and the occasional careful thumbnail. Make your scaling and format decisions before the file ever reaches the upload form. And once a quarter, look at how many -e files have piled up in the uploads folder, because that is the price of the safety net, and it is only worth paying if you know it is there.

How to Edit Images in WordPress With the Built In Editor

Table of Contents

Learn it by building something

Every week one thing you can make the same afternoon, from dynamic templates to 3D type. Written down step by step.

One mail a week, and then it ends.
Unsubscribe in one click.

Design Fundamentals

Contrast Is a Number, Not an Opinion

Contrast is the one accessibility metric that is pure arithmetic: two colours in, one number out, and the number either clears the threshold or it does not. Check a pair in the browser, then learn what the ratio measures, why relative luminance is not the average of the channels, where the large text exception really starts, and what to do when the brand colour fails.

Developer Tools

htaccess Explainer: Reading the File Nobody Reads

Every WordPress site on Apache has an .htaccess, and Apache reads it on every request for every directory in the path. Here is what the WordPress block actually does, why [L] does not mean last, the difference between Redirect and RewriteRule, and which pasted security snippets have done nothing since Apache 2.4.

Troubleshooting

Why Is Your WordPress Site Slow? A Diagnostic Order, Not a Checklist

WordPress performance advice is usually handed over as a flat, alphabetized checklist. This walks through the same fixes in the order they actually pay off, starting with the thing most sites get wrong first: images.

Photo Editing

Cut out hair from a photo without shaving the edges off

A background remover that works by colour asks every pixel a yes or no question, and the honest answer for a pixel that a strand of hair passes through is neither. Matting gives that pixel a fraction instead. Two small networks, both running in your browser, and the grey alpha view that tells you in a second whether the cutout will work.

Design Fundamentals

Ten Typography Mistakes That Give an Amateur Design Away

Bad type is rarely the wrong font. It is almost always spacing, size and a handful of habits nobody ever told you to drop.

Developer Tools

Dummy Text Generator: Lorem Ipsum That Behaves Like Real Copy

Lorem ipsum has a longest word of thirteen letters and no umlauts at all, so a layout tested with it is tested with the easiest text it will ever hold. What placeholder text should actually prove, and a generator that produces it.

Download the free WunderPaint Plugin for WordPress

The WunderPaint workspace with the layers panel, adjustment sliders, text style presets and the asset library along the bottom

The Image Editor & Design Studio

Everything described here can be done in the browser, on your own site. The live demo runs the full editor with nothing to install.

Free

Chaos Art

Autonomous painters make one-of-a-kind abstract art in 3D space - gestures, art movements, painterly media, and embeds that paint a new original for every visitor.

Pro

Particle Strokes

Paint with swarms of light: twenty-two movements, a stamp you draw yourself, and curves that give a stroke a shape - the swarm keeps painting for a few seconds after you let go.

Pro

City Diorama

Any place on earth as a miniature you could hold: real streets, water and building footprints raised into a 3D diorama - or wrapped around a sphere as your own tiny planet.

Free

Papercut Art

Layered paper pictures with real depth - a photo sliced along its actual depth into up to twenty layers, parametric landscapes, animals and clouds you can shape, letters with real counters, and a look that runs on three dials.

Pro

3D Earth Studio

A hyperrealistic globe - day and night with city lights, live clouds, atmosphere halo, country borders and highlights, click-to-place markers with flight-route arcs, satellite orbits, seamless rotation video and a live website embed.

Free

Mystic Studio

Turn a birth date into wall art - a real natal chart with houses and aspects, the moon of that night, zodiac and Chinese zodiac posters, numerology cards and a synastry wheel for two, in eight artful themes.

Free

Marble Bath

Marble paper on a virtual water bath - drop, rake and comb real Ebru patterns with gestures, flowers and classic recipes, razor-sharp at any size and re-editable as a layer.