Describe the Bug
A JPEG with an EXIF orientation of 5 to 8, as a phone writes for a portrait photo, is uploaded to a collection with upload: true and no resize, format or trim option. Payload stores the file as uploaded, which is right. But the document records the width and height of the stored pixels, not the size a browser shows.
A photo stored 1200×800 with orientation 6 is shown 800×1200 by browsers, by sharp's rotate() and by image optimizers. Its document says width: 1200, height: 800. A page that sizes the image from its document, for example with next/image, shows it stretched.
The cause is in packages/payload/src/uploads/getImageSize.ts. It reads width and height from sharp(file).metadata(), and sharp gives these before the turn. The size after the turn is in metadata().autoOrient. Without sharp, the size comes from the file's header, with the same result.
Payload turns the picture itself only when it re-encodes the file (.rotate() in generateFileData.ts). Then the stored file and the record agree. A file stored as uploaded keeps its orientation tag, and the record keeps the size before the turn.
Expected: the document records the size the picture is shown at, 800×1200 here.
Related:
Link to the code that reproduces this issue
https://github.com/franknoel/payload-exif-orientation-size
Reproduction Steps
- Clone the repository.
cp .env.example .env, then fill in DATABASE_URL and PAYLOAD_SECRET.
pnpm install
pnpm check
The script empties the Media collection, uploads each file of fixtures/ with the local API, and prints the recorded size next to the size a viewer shows. Both fixtures show the same portrait picture:
ok upright.jpg orientation none recorded 800x1200 shown 800x1200
MISMATCH turned.jpg orientation 6 recorded 1200x800 shown 800x1200
The same happens over REST, the way the admin uploads: with pnpm dev running, a POST /api/media with fixtures/turned.jpg answers with width: 1200, height: 800.
Which area(s) are affected?
area: core
Environment Info
Binaries:
Node: 24.21.0
npm: 11.19.0
Yarn: 1.22.22
pnpm: 9.15.9
Relevant Packages:
payload: 3.90.2
next: 16.3.3
@payloadcms/db-mongodb: 3.90.2
@payloadcms/graphql: 3.90.2
@payloadcms/next/utilities: 3.90.2
@payloadcms/richtext-lexical: 3.90.2
@payloadcms/translations: 3.90.2
@payloadcms/ui/shared: 3.90.2
react: 19.2.6
react-dom: 19.2.6
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.6.0
Available memory (MB): 32768
Available CPU cores: 10
Describe the Bug
A JPEG with an EXIF orientation of 5 to 8, as a phone writes for a portrait photo, is uploaded to a collection with
upload: trueand no resize, format or trim option. Payload stores the file as uploaded, which is right. But the document records the width and height of the stored pixels, not the size a browser shows.A photo stored 1200×800 with orientation 6 is shown 800×1200 by browsers, by sharp's
rotate()and by image optimizers. Its document sayswidth: 1200, height: 800. A page that sizes the image from its document, for example withnext/image, shows it stretched.The cause is in
packages/payload/src/uploads/getImageSize.ts. It readswidthandheightfromsharp(file).metadata(), and sharp gives these before the turn. The size after the turn is inmetadata().autoOrient. Without sharp, the size comes from the file's header, with the same result.Payload turns the picture itself only when it re-encodes the file (
.rotate()ingenerateFileData.ts). Then the stored file and the record agree. A file stored as uploaded keeps its orientation tag, and the record keeps the size before the turn.Expected: the document records the size the picture is shown at, 800×1200 here.
Related:
Link to the code that reproduces this issue
https://github.com/franknoel/payload-exif-orientation-size
Reproduction Steps
cp .env.example .env, then fill inDATABASE_URLandPAYLOAD_SECRET.pnpm installpnpm checkThe script empties the Media collection, uploads each file of
fixtures/with the local API, and prints the recorded size next to the size a viewer shows. Both fixtures show the same portrait picture:The same happens over REST, the way the admin uploads: with
pnpm devrunning, aPOST /api/mediawithfixtures/turned.jpganswers withwidth: 1200, height: 800.Which area(s) are affected?
area: core
Environment Info