I'm the developer of MakeQuickWork, a small set of free browser tools. This is how its Remove EXIF tool works, including two bugs I found while taking the screenshots for this post.
The usual way to strip EXIF in a browser is to draw the image to a canvas and export it again. It works, and it's also a full decode and re-encode: you lose some quality, you lose the colour profile, and the file size jumps around. I wanted the opposite. Same pixels, same bytes for the image data, only the metadata gone.
So the tool doesn't touch the image at all. It rewrites the container. JPEG, PNG and WebP are all formats where metadata lives in separate, clearly labelled blocks, which means you can walk the file, skip the blocks you don't want and copy the rest.
The page is https://makequickwork.com/image/remove-exif/ . It runs in the tab; the photo isn't uploaded.
JPEG: walking the markers
A JPEG is a sequence of segments. Each starts with 0xFF plus a marker byte, and most carry a two-byte big-endian length. The loop starts after SOI (FFD8) and decides per segment:
// Simplified: keep JFIF, ICC and Adobe; drop every other APPn and COM. function keepSegment(marker: number, data: Uint8Array): boolean { if (marker === 0xe0) return startsWith(data, "JFIF\0"); // APP0 JFIF if (marker === 0xe2) return startsWith(data, "ICC_PROFILE\0"); // APP2 colour profile if (marker === 0xee) return startsWith(data, "Adobe"); // APP14, affects colour transform if (marker >= 0xe0 && marker <= 0xef) return false; // APP1 Exif/XMP/extended XMP, APP13 IPTC, ... if (marker === 0xfe) return false; // COM return true; // DQT, DHT, SOFn, DRI, SOS ... } APP1 covers both Exif and XMP (including extended XMP split over several segments), APP13 is where Photoshop puts IPTC, and COM is free text. All of those go. APP14 Adobe stays because decoders use its transform flag to pick the colour conversion, and dropping it can shift colours on CMYK or YCCK files.
The fiddly part is after SOS. The entropy-coded scan data has no length field, so you have to scan for the next real marker. Inside scan data, a literal 0xFF byte is written as FF 00, and restart markers FF D0 to FF D7 can appear between intervals. Neither ends the scan. Anything else does:
// Find the end of entropy-coded data starting at i. while (i < buf.length - 1) { if (buf[i] !== 0xff) { i++; continue; } const next = buf[i + 1]; if (next === 0x00 || (next >= 0xd0 && next <= 0xd7)) { i += 2; continue; } // stuffing or RSTn break; // a real marker: DHT, another SOS (progressive), or EOI } The scan bytes are copied as they are. When the walker reaches EOI (FFD9) it stops and drops anything after it. That matters more than I expected: some phone JPEGs append a second image after EOI, an HDR gain map, and it can carry its own metadata. Removing it means the result shows as a normal non-HDR photo. That's a real trade-off, and the page says so.
Not making photos sideways
Phone cameras usually store the sensor image as-is and set the EXIF Orientation tag so viewers rotate it. Strip the whole EXIF block and a portrait photo shows up lying on its side.
So if the original Orientation wasn't 1, the tool writes a new APP1 with a 28-byte TIFF structure: header, an IFD0 with exactly one entry (tag 0x0112, Orientation), and a zero next-IFD offset. No camera, no date, no GPS. One of the tests rotates a fixture and checks it still displays upright after cleaning.
"Location only" mode
Sometimes you want to keep the camera and settings, for a photography forum say, and only lose the position. That mode keeps the EXIF block and edits it in place:
- Find the GPS IFD pointer (tag
0x8825) in IFD0. - Zero every GPS entry and its out-of-line values, e.g. the 24-byte arrays of three rationals for latitude and longitude.
- Set the GPS IFD's entry count to 0.
Zeroing instead of cutting means no offsets elsewhere in the TIFF have to be recalculated. XMP and IPTC are dropped in this mode too, because both can hold coordinates or place names. Tests check that GPS is gone and that camera and date are still there.
PNG and WebP
PNG is a list of chunks. The tool drops eXIf, tEXt, zTXt, iTXt and tIME, keeps the colour chunks, and gives any rewritten chunk a fresh CRC32.
WebP is RIFF. It removes the EXIF and XMP chunks (yes, with the trailing space), clears the matching flag bits in VP8X, and recomputes the RIFF size.
Two bugs from the screenshots
Showing what's in a photo before removing it is half the point of the tool. Reading is done with exifr; the page highlights location, camera, date taken and a few others, with a "Show all fields" table for the rest.
For the screenshots I made a demo photo, IMG_20261012_143355.jpg, with the camera set to "Google Pixel 8" and the location set to a point in Central Park. The page showed Location: 40.78509, -73.96829 and Camera: Google Pixel 8, both right. The time was 8 hours off.
EXIF dates have no time zone. exifr parses them as local time, which is reasonable, and my code then printed them with toISOString(), which converts to UTC. In my time zone that's 8 hours. The fix prints the local components back:
const p = (n: number) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` + `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; Now it shows Taken: 2026-10-12 14:33:55, matching the file name.
The second bug: after clicking Remove, the list still showed the old location chip, because it was the summary of the original file. Technically true, and confusing. Now the page re-reads the downloaded copy and says what's left. For the demo photo: 123.0 KB → 122.7 KB, "removed EXIF", and "Downloaded copy: no EXIF, GPS or XMP metadata left."
Limits
- JPG, PNG and WebP only. HEIC isn't supported here. The HEIC to JPG tool on the same site writes JPGs with no metadata anyway.
- The HDR gain map goes, as described above.
- The page itself loads about 11 KB of JavaScript; the metadata reader is fetched when you add the first photo. You need a connection to open the page.
The site uses GA4 for anonymous page counts, and there's an automated test that fails if a tool makes a POST or a third-party request other than GA while processing, or if an analytics event contains a file name.
If you know other JPEG segments that should be kept, or files where this approach breaks, I'd like to hear about them.




