Skip to content

filesize of a file fetched from a URL comes from Content-Length: 0 when the header is absent #18382

Description

@franknoel

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

  1. Clone the repository. cp .env.example .env, then fill in DATABASE_URL and PAYLOAD_SECRET.
  2. pnpm install
  3. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Bugarea: coreCore Payload functionalitystatus: needs-triagePossible bug which hasn't been reproduced yetv3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions