Describe the Bug
When Payload fetches a file itself, the document's filesize is the value of the response's Content-Length header, not the size of the file. Payload fetches a file for a create from a URL ("Paste URL"), and for a duplicate or an edit on a storage adapter.
With one SVG of 52,895 B:
- The source sends its length: the document says 52,895 B.
- The source sends the body in chunks, with no length: the document says 0 B.
- The source sends the body compressed, with the length of the compressed body: the document says 5,132 B.
The stored file is right each time, 52,895 B.
The cause is in packages/payload/src/uploads/getExternalFile.ts. The whole body is in memory, and the size still comes from the header:
const data = await res.arrayBuffer()
return {
name: filename,
data: Buffer.from(data),
mimetype: res.headers.get('content-type') || undefined,
size: Number(res.headers.get('content-length')) || 0,
}
generateFileData then records file.size for every file that does not go through sharp: an SVG, a PDF, and a JPEG or PNG with no resize, format or trim option.
On main, the same line is in downloadFileToBuffer.ts.
Expected: size is the length of the body that was read, data.byteLength.
We met this on a site with @payloadcms/storage-vercel-blob. The Blob store sends an SVG compressed, with no length. A duplicate of a 66,253 B SVG, and a create from its URL, were both recorded at 0 B.
Related: #8108, "S3 cloud storage overwrites filesize to zero when editing an image", was closed with no cause named. It may be this line.
Link to the code that reproduces this issue
https://github.com/franknoel/payload-external-file-size
Reproduction Steps
- Clone the repository.
cp .env.example .env, then fill in DATABASE_URL and PAYLOAD_SECRET.
pnpm install
pnpm check
The Media collection allows localhost:4135 in pasteURL.allowList. The script serves fixtures/drawing.svg three ways on that port, creates a document from each URL with the local API, and prints the recorded file size next to the size of the stored file:
ok with-length.svg recorded 52895 B stored 52895 B
MISMATCH no-length.svg recorded 0 B stored 52895 B
MISMATCH compressed.svg recorded 5132 B stored 52895 B
The same happens over REST: with pnpm dev running and the file server up, a POST /api/media with { "alt": "rest", "filename": "rest.svg", "url": "http://localhost:4135/no-length.svg" } answers with filesize: 0.
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
When Payload fetches a file itself, the document's
filesizeis the value of the response'sContent-Lengthheader, not the size of the file. Payload fetches a file for a create from a URL ("Paste URL"), and for a duplicate or an edit on a storage adapter.With one SVG of 52,895 B:
The stored file is right each time, 52,895 B.
The cause is in
packages/payload/src/uploads/getExternalFile.ts. The whole body is in memory, and the size still comes from the header:generateFileDatathen recordsfile.sizefor every file that does not go through sharp: an SVG, a PDF, and a JPEG or PNG with no resize, format or trim option.On
main, the same line is indownloadFileToBuffer.ts.Expected:
sizeis the length of the body that was read,data.byteLength.We met this on a site with
@payloadcms/storage-vercel-blob. The Blob store sends an SVG compressed, with no length. A duplicate of a 66,253 B SVG, and a create from its URL, were both recorded at 0 B.Related: #8108, "S3 cloud storage overwrites filesize to zero when editing an image", was closed with no cause named. It may be this line.
Link to the code that reproduces this issue
https://github.com/franknoel/payload-external-file-size
Reproduction Steps
cp .env.example .env, then fill inDATABASE_URLandPAYLOAD_SECRET.pnpm installpnpm checkThe Media collection allows
localhost:4135inpasteURL.allowList. The script servesfixtures/drawing.svgthree ways on that port, creates a document from each URL with the local API, and prints the recorded file size next to the size of the stored file:The same happens over REST: with
pnpm devrunning and the file server up, aPOST /api/mediawith{ "alt": "rest", "filename": "rest.svg", "url": "http://localhost:4135/no-length.svg" }answers withfilesize: 0.Which area(s) are affected?
area: core
Environment Info