About this EXIF viewer
A small, dependency-free tool for one job: reading a photo's Exif metadata and producing a copy with none of it, without sending the photo anywhere. No account, no upload, nothing kept after you close the tab.
Why it exists
Most people never see a photo's metadata because nothing in a normal photo app surfaces it prominently, yet it travels with the file by default — including GPS coordinates, if location services were on when the shot was taken. Checking before sharing needs an actual reader, not an assumption that "the platform probably handles it." This tool exists so that check is one drop-and-look away, and so cleaning the result is the same action, not a separate download.
How the parser is implemented
The metadata reader in exif.js is a hand-written byte parser with no library behind it — this codebase's rule is to write the thirty lines instead of pulling in a dependency, and a Exif/TIFF reader for the common tags is genuinely that small. It works directly against an ArrayBuffer using DataView:
- Confirm it's a JPEG by checking the first two bytes for the SOI marker
0xFFD8, then walk the file's marker segments. - Find the Exif APP1 segment — marker
0xFFE1whose payload starts with the literal bytesExif\0\0— and stop walking once found (or once a SOS/EOI marker ends the header region with nothing found). - Read the TIFF header just past that: a 2-byte byte-order mark (
IIfor little-endian,MMfor big-endian), the TIFF magic number 42, and an offset to the first Image File Directory. - Walk each IFD — a 2-byte entry count, then 12-byte entries (tag, type, count, value-or-offset) — for IFD0's camera tags, the Exif SubIFD's exposure/ISO/date tags (via pointer tag
0x8769), and the GPS IFD's coordinates (via pointer tag0x8825). - Convert GPS degrees/minutes/seconds rationals to a single signed decimal-degree value, applying the hemisphere reference (N/S, E/W) as a sign.
Anything outside that scope — proprietary maker notes, IPTC, XMP, thumbnail sub-images embedded inside the Exif block — is deliberately not decoded. An unrecognised tag type or an offset that doesn't fit the buffer is skipped rather than guessed at, and the whole parse is wrapped so a truncated or malformed file returns an empty result instead of throwing.
The cleanup step is separate and much simpler: the picked file is decoded as an image and redrawn onto a <canvas> at its original size, then exported with canvas.toBlob(). A canvas has no concept of a metadata block, so the export is clean regardless of what the source file carried.
How the parser was tested
The pure parser module was run under Node with a small assertion script before this page shipped, entirely independent of a browser or a real camera file. Since there is no library to reach for, the tests construct minimal JPEG buffers byte-by-byte — a real APP1/Exif/TIFF structure, just a hand-built one instead of a photograph:
| Case | Fixture | Result |
|---|---|---|
| Little-endian byte order | hand-built TIFF with "II" mark | byteOrder === 'II', decodes correctly |
| Big-endian byte order | hand-built TIFF with "MM" mark | byteOrder === 'MM', decodes correctly |
| Camera make string | ASCII tag, both byte orders | reads back exactly, NUL-trimmed |
| Shooting settings | ExposureTime, FNumber, ISO, FocalLength, DateTimeOriginal | 1/500s, f/2.8, ISO 200, 50mm, correct date |
| GPS coordinate decode | 51°30'0"N, 0°7'0"W rationals | 51.5, −0.116667 decimal degrees (sign from ref) |
| Non-JPEG buffer | PNG signature bytes | empty-but-valid result, no throw |
| JPEG with no APP1 | bare SOI + EOI | isJPEG true, hasExif false, no throw |
| Empty buffer | zero-length input | empty-but-valid result, no throw |
All 35 assertions passed. What Node cannot exercise is real camera-file variety: different manufacturers write Exif slightly differently, some embed a thumbnail image inside the Exif block, some use maker-note extensions this tool doesn't parse, and file corruption in the wild looks nothing like a hand-built fixture. The parser is defensive by construction — unknown tag types and out-of-range offsets are skipped, not guessed at — but a genuinely unusual real-world file is the one thing this test suite cannot fully stand in for.
How the site is built
- Static HTML, one small stylesheet, two small scripts. No framework, no build step, no bundler.
- Nothing leaves your device. The picked photo is scanned and redrawn entirely inside your browser tab; there is no upload endpoint anywhere in this tool for it to reach.
- No CDN, no web font, no remote image. The favicon and header mark are inline SVG. Once the page has loaded, reading or cleaning a photo needs no further network activity at all.
- System font stack. Your operating system's UI font, so nothing has to download and nothing shifts as it loads.
- Dark mode follows your system setting via
prefers-color-scheme.
Scope and honesty about limits
This tool reads the common, documented Exif tags from JPEG files and offers a canvas re-encode that removes all metadata regardless of format. It does not decode proprietary maker-note extensions, IPTC or XMP metadata blocks, or embedded thumbnail sub-images — and it does not batch-process multiple files at once. The canvas re-encode still guarantees a clean export even for metadata this tool's reader doesn't specifically decode, since a canvas simply has nowhere to keep any of it.
If you find a real photo whose metadata this tool misreads or fails to flag, that is worth reporting — see contact.