A phone photograph arrives at 4032 by 3024 pixels and something like 4 MB. The column it is going to sit in is 760 pixels wide. The browser downloads all 12.2 million pixels, decodes them into roughly 46 MB of memory, and then discards more than nine tenths of them to draw the picture at the size your theme asked for.
On a WordPress site that is otherwise healthy, that one habit is the most common reason pages feel slow. Not the theme. Not the plugin count. Photographs uploaded at the size the camera produced them, sitting in a column a fifth as wide. If you are working through the diagnostic order for a slow site, this is the first thing to rule out, because it is cheap to fix and it is usually the answer.
The fix is one step, and it belongs before the upload rather than after. Resize the file on your own machine, then upload the smaller file. Everything WordPress does next, the sub-sizes, the srcset, the scaled copy it quietly makes for you, then works from a sensible original instead of a huge one.
Resize first, upload second
The tool below does that job. Drop one image or forty onto the target, set a target width, press Resize, and download the results. It runs entirely in your browser: every file is decoded, redrawn on a canvas and re-encoded on your own machine. Nothing is uploaded, nothing is queued, and no account is involved.
The defaults are the ones most content images want: 1600 pixels wide, quality 82, never enlarge, keep the original format. If you stop reading here, that alone fixes the problem for the majority of uploads. The rest of the article is about which number you should actually be using, what WordPress does to your file after you hand it over, and the handful of cases where the tool needs help.
Image resizer and compressor
Drop in photos, set a width and a quality, get smaller files back. Nothing is uploaded: every image is decoded, resized and re-encoded inside this browser tab.
What 4000 pixels actually cost
Pixel count drives everything else, and it rises with the square of the width. Halving the width does not halve the picture, it quarters it. Going from 4032 pixels wide to 1600 leaves you with 15.7 percent of the pixels, and going to 1200 leaves under 9 percent.
File size follows, though not in a straight line, because a JPEG stores detail and a busy photograph compresses worse than a plain one. As a working figure, a 12 megapixel phone photograph that leaves the camera at 3 to 5 MB lands between 200 and 400 KB at 1600 pixels wide and quality 82, and between 100 and 200 KB at 1200. Ten of those on a page is the difference between two megabytes and twenty.
Memory is the part nobody measures. A decoded bitmap costs four bytes per pixel no matter how well the file compressed, so the 4032 pixel photograph occupies about 46 MB while the browser is painting it, and the 1600 pixel version about 7 MB. That cost is paid on the visitor’s device, and on a mid range phone with several such images in one article it is the difference between scrolling and stuttering.
The number that matters is the display width
There is no universal correct size. There is only the width your theme draws the image at, and you can read that number in about fifteen seconds. Open the page in a desktop browser with the window at full width, right click the image, choose Inspect. Chrome shows a tooltip when you hover the img element in the Elements panel: the layout size it is rendered at, and the intrinsic size of the file that was downloaded. If the second number is several times the first, you have found your problem.
The console version is faster once you know it. Select the image in the Elements panel so it becomes $0, then ask it directly:
$0.clientWidth // 760 the width your theme renders it at
$0.naturalWidth // 4032 the width of the file the visitor downloaded
$0.currentSrc // which srcset candidate the browser actually chose
Take the layout width, double it so the picture still looks sharp on a high density screen, and that is your ceiling. A 760 pixel column wants a file about 1520 pixels wide, which is why 1600 is such a comfortable round number to standardise on. Measure on the widest layout you support, because the responsive sizes only shrink from there. WordPress still builds a srcset from the sub-sizes it generates, so the width you upload sets the ceiling, not the delivery.
The safety net WordPress already has
Core has scaled big uploads by itself since 5.3, and it is worth knowing exactly how far that protection goes. In wp-admin/includes/image.php, wp_create_image_subsizes() reads a threshold through the big_image_size_threshold filter, default 2560. If either side of your upload is larger than that, WordPress writes a second file with -scaled appended to the name, with the long edge fitted to 2560, and that copy becomes the full size everywhere on the front end.
Two details decide how much this actually helps. The sub-sizes are generated from the untouched original rather than from the scaled copy, which is the right call for quality. And the untouched original is kept, permanently: its file name is stored in the attachment metadata as original_image, and it only leaves the disk when you delete the attachment. Your uploads folder therefore holds both files, and the 4 MB you never needed is still there, still in every backup, still being copied to staging.
So the net limits the damage on the page and not in the folder. It also sits far above any real display width, which makes 2560 a fire break rather than a resize strategy. You can move it in your theme or a small plugin:
add_filter( 'big_image_size_threshold', function () {
return 1800; // scaled copies get a 1800 px long edge instead of 2560
} );
That only changes the size of the copy. The original still stays, and it still gets written to disk first. Resizing before the upload is the only version of this that keeps the folder small as well as the page fast. For the full map of which files one upload turns into, and which of them your theme actually uses, see every file WordPress creates from a single upload.
Ceilings worth standardising on
Measuring is better than guessing, but you will not measure every time. These ceilings assume a fairly standard content column and a doubling for high density screens, and they are ceilings for the file you upload, not sizes you register in WordPress.
| Where the image appears | Upload ceiling | Why that number |
|---|---|---|
| Full width hero or banner | 1800 to 2000 px | Covers a wide layout at 2x and still stays under the 2560 threshold |
| Image inside a content column | 1200 to 1600 px | A 600 to 800 px column, doubled |
| Half width or two column block | 800 px | A 400 px slot, doubled |
| Card, list row, related post | 400 to 600 px | Nothing on the page ever draws it larger |
| Logo, avatar, inline icon | 200 to 400 px | Or an SVG, which has no fixed pixel size at all |
Portraits are the exception that trips people up. A target width of 1200 applied to a 3000 by 4000 pixel portrait still gives you a 1600 pixel tall image that pushes the next paragraph off the screen. That is what the Max height field is for: it is applied as a second constraint on the same scale factor, so it can win. A 200 by 150 image with a target width of 800 and a max height of 300 comes out at 400 by 300, because the height constraint bites first.
Why one big jump looks soft
Resizing down is not cropping. Every destination pixel has to be invented from a neighbourhood of source pixels, and the quality of the result depends entirely on how many of them the sampler bothers to read. When the reduction is large and the sampler reads a small fixed neighbourhood, most of the source pixels are never looked at. Fine detail, hair, foliage, brickwork, the text in a screenshot, either turns to noise or vanishes, and hard edges pick up a jagged shimmer.
Halving is the friendly case: each destination pixel is the average of exactly four source pixels, so nothing is ignored. That is why the tool halves rather than jumping. It plans the steps first, halving the width while the next halving would still land above your target, then does one final draw to the exact size. From 4032 to 800 that is 2016, then 1008, then 800: three draws instead of one. Every intermediate canvas is released as soon as the next one has been drawn, so the batch does not accumulate.
A reduction of less than half is drawn in one go, because there is nothing to gain from a step, and enlarging is never stepped at all. If you have images that already look soft on the site and you want to work out which of the several possible causes is yours, the blurry images walkthrough separates a bad downscale from a theme upscaling a thumbnail, which look similar and need opposite fixes.
Quality 82, and why 95 is a waste
The slider defaults to 82 for a reason. WordPress uses the same number: protected $default_quality = 82 in wp-includes/class-wp-image-editor.php, applied to every sub-size core writes unless a filter changes it. So a JPEG you upload at quality 95 is already being re-encoded at 82 for the sizes your theme actually serves. Paying for 95 on the upload buys you nothing on the page.
Above roughly 85 the curve turns cruel. The extra bytes go into detail that survives no display and no downscale, and moving from 82 to 95 commonly doubles a photographic JPEG. Below about 70 you start to see it: blocking in smooth gradients, skies and skin worst of all. If a file is still too big at 82, the honest move is to resize further rather than to compress harder. Fewer pixels at 82 beats the same pixels at 55, every time.
Format matters as much as quality. PNG is lossless, so choosing it disables the slider and shows n/a, and a photograph saved as PNG is several times larger than the JPEG of the same picture. WebP at matching visual quality is usually 25 to 35 percent smaller than JPEG. The format comparison covers which to pick for photographs, flat graphics and transparency.
The cases that catch people out
- Files can come back bigger. Re-encoding a PNG as a PNG often grows it, and a small JPEG written as a PNG almost always does. The row says “N% larger” in amber instead of a green saving, “about the same” when it rounds to zero, and the total line owns up to it too. A tool that only ever reports savings is lying about at least one of your files.
- Never enlarge is on by default. An 800 pixel photograph in a batch with a 1600 target comes back at 800 pixels, unchanged in size, so a mixed batch cannot be silently upscaled into mush. Untick it and the same file is drawn up to the target, which produces a bigger file and no extra detail.
- Metadata is gone whatever the checkbox says. A canvas re-encode drops EXIF, GPS coordinates and the colour profile, so unticking Strip metadata changes the note to say metadata still cannot be kept rather than pretending to preserve it. Two consequences: the location of your house does not travel with the photo, which is a gift, and the EXIF orientation flag goes too, so a shot that relied on it appears in its stored rotation. Check portrait photographs after your first batch.
- Keep original follows the source type. Anything that is not JPEG, PNG or WebP, a GIF or a BMP or an AVIF, is written as JPEG. If your browser cannot encode WebP, that option is disabled and relabelled rather than silently handing you a PNG wearing a WebP name.
- JPEG has no transparency. A transparent PNG written as JPEG is flattened onto white before the final draw. If your logo needs to sit on a coloured background, keep it as PNG or WebP.
- Things that are not images are skipped, not swallowed. A file whose type is not an image gets a row saying so and no result, a file that claims to be an image and will not decode gets its own message, and a batch of nothing but those leaves the Resize button disabled.
- Stale numbers are called out. Touching any control while results are on screen appends “Settings changed, press Resize again” to the total line, so the figures you are reading always match the settings that produced them. A target width that is empty, zero or above 12000 refuses the run and leaves the existing results alone.
Under the surface the tool works one image at a time and decodes each source fresh on every run, so a batch of forty large photographs never holds forty decoded bitmaps in memory at once. Object URLs are tracked and revoked: re-running a file releases the previous result first, and Clear releases the lot. Download all fires one link at a time, 350 milliseconds apart, with no zip library involved.
What it deliberately does not do
No cropping, no rotation, no correction of the EXIF orientation flag it has just discarded, no AVIF output, no zip download, and no memory of your settings between visits. It resizes and re-encodes, and that is the whole job. If you need to change what is in the frame rather than how many pixels are in it, that is an editor’s work, not a resizer’s.
It also does nothing for the images already sitting in your media library, which is the awkward half of this problem for anyone who has been running a site for a few years. Those files are on the server, referenced by post content and by attachment metadata, and shrinking them means rewriting the files in place and regenerating the sub-sizes without breaking the links. Bulk resizing an existing library is a different operation with different risks, and it deserves a backup first.
The rule that comes out of all this is short. Find the width your theme draws the image at, double it, treat that as a ceiling, and resize before the upload rather than trusting a threshold that was set to 2560 for safety and not for speed. One number, decided once, applied to every photograph you publish afterwards.
What makes it worth the small effort is that this is one of the few web performance decisions with no trade-off hiding behind it. A caching layer changes when things break. A new plugin adds code you now maintain. Uploading a 1600 pixel photograph instead of a 4032 pixel one costs you nothing at all: the picture looks identical on the page, the visitor downloads roughly a tenth of the bytes, their phone spends a sixth of the memory, and your uploads folder stops growing by four megabytes every time somebody adds a photo to a post.