Skip to content

Conversation

@paulpopus
Copy link
Contributor

@paulpopus paulpopus commented Apr 12, 2025

This PR makes it so that modifyResponseHeaders is supported in our adapters when set on the collection config. Previously it would be ignored.

This means that users can now modify or append new headers to what's returned by each service.

import type { CollectionConfig } from 'payload' export const Media: CollectionConfig = { slug: 'media', upload: { modifyResponseHeaders: ({ headers }) => { const newHeaders = new Headers(headers) // Copy existing headers newHeaders.set('X-Frame-Options', 'DENY') // Set new header return newHeaders }, }, }

Also adds support for void return on the modifyResponseHeaders function in the case where the user just wants to use existing headers and doesn't need more control.

eg:

import type { CollectionConfig } from 'payload' export const Media: CollectionConfig = { slug: 'media', upload: { modifyResponseHeaders: ({ headers }) => { headers.set('X-Frame-Options', 'DENY') // You can directly set headers without returning }, }, }

Manual testing checklist (no CI e2es setup for these envs yet):

  • GCS
  • S3
  • Azure
  • UploadThing
  • Vercel Blob
@paulpopus paulpopus changed the title feat(storage*): include modified headers into the response headers of files when using adapters feat(storage-*): include modified headers into the response headers of files when using adapters Apr 12, 2025
@paulpopus paulpopus requested a review from denolfe as a code owner July 9, 2025 11:22
@github-actions
Copy link
Contributor

github-actions bot commented Jul 9, 2025

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 746.95 KB ✅ No change
packages/payload/meta_index.json esbuild/index.js 1.19 MB ⚠️ +26 B (+0.0%)
packages/payload/meta_shared.json esbuild/exports/shared.js 160.41 KB ✅ No change
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 259.22 KB ✅ No change
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 1.11 MB ✅ No change
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 14.10 KB ✅ No change
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████▎ }}}$ 81.1%, 601.90 KB
dist/views/Version ${{\color{Goldenrod}{ █▋ }}}$ 6.6%, 49.35 KB
dist/views/Document ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 14.06 KB
dist/views/Root ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 6.44 KB
dist/views/Versions ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 6.17 KB
dist/views/API ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 5.90 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 5.61 KB
dist/views/Account ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 5.34 KB
dist/elements/Nav ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 4.84 KB
dist/elements/DocumentHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 4.63 KB
dist/views/Login ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 4.39 KB
dist/views/Dashboard ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 3.68 KB
dist/views/ForgotPassword ${{\color{Goldenrod}{ }}}$ 0.4%, 3.09 KB
dist/layouts/Root ${{\color{Goldenrod}{ }}}$ 0.4%, 3.09 KB
dist/utilities/initPage ${{\color{Goldenrod}{ }}}$ 0.4%, 3.01 KB
dist/templates/Default ${{\color{Goldenrod}{ }}}$ 0.4%, 2.83 KB
dist/views/CreateFirstUser ${{\color{Goldenrod}{ }}}$ 0.3%, 2.48 KB
dist/views/ResetPassword ${{\color{Goldenrod}{ }}}$ 0.3%, 2.41 KB
dist/views/CollectionFolders ${{\color{Goldenrod}{ }}}$ 0.3%, 2.38 KB
dist/views/BrowseByFolder ${{\color{Goldenrod}{ }}}$ 0.3%, 2.22 KB
(other) ${{\color{Goldenrod}{ ████▋ }}}$ 18.9%, 140.37 KB

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████▋ }}}$ 70.9%, 840.96 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▊ }}}$ 3.4%, 40.06 KB
dist/collections/operations ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 31.32 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 14.45 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 11.54 KB
dist/globals/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 11.03 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 10.82 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 9.35 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.35 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.27 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.79 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.74 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.58 KB
dist/collections/endpoints ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.87 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.83 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 6.25 KB
dist/utilities/telemetry ${{\color{Goldenrod}{ }}}$ 0.4%, 5.31 KB
dist/auth/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 5.21 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ }}}$ 0.4%, 4.76 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 4.68 KB
(other) ${{\color{Goldenrod}{ ███████▎ }}}$ 29.1%, 344.46 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████ }}}$ 80.3%, 126.19 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▍ }}}$ 5.9%, 9.35 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 2.48 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 1.41 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.28 KB
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 943 B
dist/utilities/fieldSchemaToJSON.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 917 B
dist/folders/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 852 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 713 B
dist/collections/config ${{\color{Goldenrod}{ }}}$ 0.4%, 570 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.4%, 559 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.3%, 423 B
dist/utilities/deepMerge.js ${{\color{Goldenrod}{ }}}$ 0.3%, 413 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.2%, 391 B
dist/utilities/formatLabels.js ${{\color{Goldenrod}{ }}}$ 0.2%, 380 B
dist/utilities/mergeListSearchAndWhere.js ${{\color{Goldenrod}{ }}}$ 0.2%, 344 B
dist/utilities/getBestFitFromSizes.js ${{\color{Goldenrod}{ }}}$ 0.2%, 340 B
(other) ${{\color{Goldenrod}{ ████▉ }}}$ 19.7%, 31.04 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/lexical/plugins ${{\color{Goldenrod}{ ███ }}}$ 12.1%, 30.91 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▍ }}}$ 9.6%, 24.60 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▎ }}}$ 9.2%, 23.47 KB
dist/features/blocks ${{\color{Goldenrod}{ ██▏ }}}$ 8.8%, 22.43 KB
dist/packages/@lexical ${{\color{Goldenrod}{ █▊ }}}$ 7.4%, 18.99 KB
dist/features/link ${{\color{Goldenrod}{ █▊ }}}$ 7.0%, 17.96 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▋ }}}$ 6.8%, 17.44 KB
dist/features/textState ${{\color{Goldenrod}{ █ }}}$ 4.3%, 10.98 KB
dist/features/upload ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 9.60 KB
dist/features/relationship ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 9.41 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 8.08 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 7.34 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 7.04 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 7.01 KB
dist/lexical/config ${{\color{Goldenrod}{ ▌ }}}$ 2.0%, 5.10 KB
dist/features/lists ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 4.95 KB
dist/lexical/theme ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 4.01 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 3.46 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 3.23 KB
dist/features/indent ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 2.50 KB
(other) ${{\color{Goldenrod}{ █████████████████████▉ }}}$ 87.9%, 225.29 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████▉ }}}$ 51.8%, 572.70 KB
dist/elements/FolderView ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 27.72 KB
dist/elements/BulkUpload ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 24.51 KB
dist/elements/WhereBuilder ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 17.94 KB
dist/fields/Relationship ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 15.94 KB
dist/elements/Table ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 15.16 KB
dist/views/Edit ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 14.50 KB
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 14.21 KB
dist/fields/Blocks ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 12.43 KB
dist/fields/Upload ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 11.94 KB
dist/elements/ListControls ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 9.04 KB
dist/elements/PublishButton ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 8.50 KB
dist/elements/LivePreview ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.18 KB
dist/fields/Array ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.92 KB
dist/elements/HTMLDiff ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.81 KB
dist/views/CollectionFolder ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.30 KB
dist/fields/Tabs ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.91 KB
dist/elements/ReactSelect ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.91 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.69 KB
dist/elements/Upload ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.62 KB
(other) ${{\color{Goldenrod}{ ████████████ }}}$ 48.2%, 532.25 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ █████▊ }}}$ 23.1%, 3.12 KB
../../node_modules ${{\color{Goldenrod}{ ████▉ }}}$ 19.6%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██▊ }}}$ 11.3%, 1.52 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ ██▍ }}}$ 9.8%, 1.32 KB
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █▌ }}}$ 6.0%, 814 B
dist/providers/TableColumns ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 757 B
dist/utilities/api.js ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 756 B
dist/elements/Translation ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 493 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 339 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 251 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 168 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 159 B
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 147 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 146 B
dist/utilities/hasSavePermission.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 136 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 129 B
dist/utilities/findLocaleFromCode.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 84 B
dist/utilities/sanitizeID.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 77 B
dist/utilities/isEditing.js ${{\color{Goldenrod}{ }}}$ 0.4%, 59 B
(other) ${{\color{Goldenrod}{ ███████████████████▏ }}}$ 76.9%, 10.38 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.
Copy link
Contributor

@DanRibbens DanRibbens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I likely wouldn't have named this modifyResponseHeaders but I see that this is an existing name in our upload config so we have to go with it.

Looks good!

@paulpopus paulpopus merged commit cb6a73e into main Jul 10, 2025
156 of 158 checks passed
@paulpopus paulpopus deleted the feat/include-modified-headers branch July 10, 2025 15:00
@princebansal
Copy link

@paulpopus This PR has introduced an issue in repo. I specifically faced in azure storage static handler.
When merging the headers, the incoming headers did contains symbol values for some keys which are not plain string.
And when passing the merged headers to Headers() constructor, it fails.

Error log ( I added some console logs in statichandler) and getFile:

[getFileHandler] Starting file request handling [getFileHandler] Collection: media [getFileHandler] Requested filename: 7-51.png [getFileHandler] Checking file access [getFileHandler] Processing custom upload handlers [getFileHandler] Custom handlers: [ [AsyncFunction (anonymous)] ] [getFileHandler] Executing custom handler Static handler request: { filename: '7-51.png', clientUploadContext: undefined, headers: { accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'accept-encoding': 'gzip, deflate, br, zstd', 'accept-language': 'en-US,en;q=0.9', 'cache-control': 'max-age=0', connection: 'keep-alive', cookie: 'alippo-unlocked=true; _ga=GA1.1.661045540.1752758309; _fbp=fb.0.1752758310001.874015798177998662; payload-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4M2Q0OTczYmE1YTgyMWVkM2Y1YjcxYSIsImNvbGxlY3Rpb24iOiJ1c2VycyIsImVtYWlsIjoic2FiZXJhbGlmZXN0eWxlQG15YWxpcHBvLmNvbSIsInNpZCI6IjU3ZDFmYTYyLTg0M2EtNGM2YS04NjMyLTQzN2E3OWFiZDFhNCIsInRlbmFudHMiOlt7InRlbmFudCI6IjY4M2Q0OTczYmE1YTgyMWVkM2Y1YjcxNyIsInJvbGVzIjpbInRlbmFudC1hZG1pbiJdLCJpZCI6IjY4M2Q0OTczZThmNWQ2MDA4MjlkMmFhNSJ9XSwiaWF0IjoxNzUyNzYyMTY1LCJleHAiOjE3ODM4NjYxNjV9.6eo9TRUWsh4moi3vUgp7kzw6yGTt1MvXv2_h05AjZwE; payload-tenant=683d4973ba5a821ed3f5b717; connect.sid=s%3Aiyz7sZOc6VssVhsq3xzyVUBiYfWFOwBt.srTPdxpg7%2BWj7jh0dlSzwMiIE9yeCOEkM3G4KV4AHoQ; _ga_DEEW9TC7WE=GS2.1.s1752761861$o2$g1$t1752762385$j57$l0$h0; __next_hmr_refresh_hash__=eb976e844e4023d14d53b1cd811ff4ad25b9208b7c1eaa02', dnt: '1', host: 'localhost:3000', 'sec-ch-prefers-color-scheme': 'dark', 'sec-ch-ua': '"Chromium";v="137", "Not/A)Brand";v="24"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"macOS"', 'sec-fetch-dest': 'document', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'none', 'sec-fetch-user': '?1', 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36', 'x-forwarded-for': '::1', 'x-forwarded-host': 'localhost:3000', 'x-forwarded-port': '3000', 'x-forwarded-proto': 'http' } } Generated file prefix: Blob path: 7-51.png Range request: { start: 0, end: undefined } Blob downloaded successfully Initial headers: { 'content-length': '728572', 'content-type': 'image/png', 'content-md5': '2Wu7YlXPfaicucBvYjVmMQ==', 'last-modified': 'Wed, 25 Jun 2025 18:01:34 GMT', 'accept-ranges': 'bytes', etag: '"0x8DDB41252E3C077"', server: 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0', 'x-ms-request-id': 'b2f29121-701e-0039-462f-f7ebf1000000', 'x-ms-client-request-id': 'f48302bf-929d-4b7c-b50a-c87db1f676c1', 'x-ms-version': '2024-11-04', 'x-ms-creation-time': 'Wed, 25 Jun 2025 18:01:34 GMT', 'x-ms-lease-status': 'unlocked', 'x-ms-lease-state': 'available', 'x-ms-blob-type': 'BlockBlob', 'x-ms-server-encrypted': 'true', date: 'Thu, 17 Jul 2025 15:32:09 GMT' } Merged headers with incoming headers: { 'content-length': '728572', 'content-type': 'image/png', 'content-md5': '2Wu7YlXPfaicucBvYjVmMQ==', 'last-modified': 'Wed, 25 Jun 2025 18:01:34 GMT', 'accept-ranges': 'bytes', etag: '"0x8DDB41252E3C077"', server: 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0', 'x-ms-request-id': 'b2f29121-701e-0039-462f-f7ebf1000000', 'x-ms-client-request-id': 'f48302bf-929d-4b7c-b50a-c87db1f676c1', 'x-ms-version': '2024-11-04', 'x-ms-creation-time': 'Wed, 25 Jun 2025 18:01:34 GMT', 'x-ms-lease-status': 'unlocked', 'x-ms-lease-state': 'available', 'x-ms-blob-type': 'BlockBlob', 'x-ms-server-encrypted': 'true', date: 'Thu, 17 Jul 2025 15:32:09 GMT', [Symbol(headers list)]: HeadersList { cookies: null, [Symbol(headers map)]: Map(0) {}, [Symbol(headers map sorted)]: null }, [Symbol(guard)]: 'none' } Internal server error: TypeError: Could not convert argument of type symbol to string. at eval (../../packages/Alippo-Payload/packages/storage-azure/src/staticHandler.ts:58:20) at async getFileHandler (../../packages/Alippo-Payload/packages/payload/src/uploads/endpoints/getFile.ts:53:23) at async handleEndpoints (../../packages/Alippo-Payload/packages/payload/src/utilities/handleEndpoints.ts:226:21) at async eval (../../packages/Alippo-Payload/packages/next/src/routes/rest/index.ts:35:21) 56 | } 57 | > 58 | let headers = new Headers(initHeaders) | ^ 59 | 60 | const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match') 61 | const objectEtag = response.headers.get('etag') [21:02:09] ERROR: Could not convert argument of type symbol to string. err: { "type": "TypeError", "message": "Could not convert argument of type symbol to string.", "stack": TypeError: Could not convert argument of type symbol to string. at webidl.converters.DOMString (node:internal/deps/undici/undici:1977:15) at webidl.converters.ByteString (node:internal/deps/undici/undici:1982:35) at Object.record<ByteString, ByteString> (node:internal/deps/undici/undici:1894:30) at webidl.converters.HeadersInit (node:internal/deps/undici/undici:3424:67) at new Headers (node:internal/deps/undici/undici:3278:36) at eval (webpack-internal:///(rsc)/../../packages/Alippo-Payload/packages/storage-azure/src/staticHandler.ts:51:27) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async getFileHandler (webpack-internal:///(rsc)/../../packages/Alippo-Payload/packages/payload/src/uploads/endpoints/getFile.ts:54:30) at async handleEndpoints (webpack-internal:///(rsc)/../../packages/Alippo-Payload/packages/payload/src/utilities/handleEndpoints.ts:179:26) at async eval (webpack-internal:///(rsc)/../../packages/Alippo-Payload/packages/next/src/routes/rest/index.ts:27:26) at async AppRouteRouteModule.do (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:26:34112) at async AppRouteRouteModule.handle (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:26:41338) at async doRender (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/base-server.js:1518:42) at async DevServer.renderToResponseWithComponentsImpl (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/base-server.js:1920:28) at async DevServer.renderPageComponent (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/base-server.js:2408:24) at async DevServer.renderToResponseImpl (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/base-server.js:2445:32) at async DevServer.pipeImpl (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/base-server.js:1008:25) at async NextNodeServer.handleCatchallRenderRequest (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/next-server.js:305:17) at async DevServer.handleRequestImpl (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/base-server.js:900:17) at async /Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/dev/next-dev-server.js:371:20 at async Span.traceAsyncFn (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/trace/trace.js:157:20) at async DevServer.handleRequest (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/dev/next-dev-server.js:368:24) at async invokeRender (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/lib/router-server.js:237:21) at async handleRequest (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/lib/router-server.js:428:24) at async DevServer.requestHandlerImpl [as routerServerHandler] (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/lib/router-server.js:452:13) at async handleInternalReq (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/next-server.js:752:17) at async fetchInternalImage (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/image-optimizer.js:690:9) at async DevServer.imageOptimizer (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/next-server.js:756:81) at async cacheEntry.imageResponseCache.get.routeKind (/Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/next-server.js:194:85) at async /Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/server/response-cache/index.js:84:36 at async /Users/princebansal/Documents/projects/Alippo-AI-Cofounder/node_modules/.pnpm/next@15.3.3_@babel+core@7.26.7_@opentelemetry+api@1.9.0_@playwright+test@1.51.0_react-dom@19._x7q24snkitukddwf4wiygemw3a/node_modules/next/dist/lib/batcher.js:45:32 } [getFileHandler] Custom handler returned Response, returning early GET /cms/api/media/file/7-51.png 500 in 41235ms │ post https://telemetry.payloadcms.com/events 200 in 1218ms (cache skip) │ │ Cache skipped reason: (auto no cache) ⨯ The requested resource isn't a valid image for /cms/api/media/file/7-51.png received text/plain;charset=utf-8 [21:02:11] INFO: 

What worked for me is this:

diff --git a/packages/storage-azure/src/staticHandler.ts b/packages/storage-azure/src/staticHandler.ts index 625b4640d..788fd8e72 100644 --- a/packages/storage-azure/src/staticHandler.ts +++ b/packages/storage-azure/src/staticHandler.ts @@ -30,19 +30,41 @@ export const getHandler = ({ collection, getStorageClient }: Args): StaticHandle const response = blob._response - let initHeaders: Headers = { - ...(response.headers.rawHeaders() as unknown as Headers), + // Get headers as a plain object, filtering out any Symbol properties + const azureHeaders = response.headers.rawHeaders() + const cleanHeaders: Record<string, string> = {} + + // Only copy string properties to avoid Symbol issues + for (const [key, value] of Object.entries(azureHeaders)) { + if (typeof key === 'string' && typeof value === 'string') { + cleanHeaders[key] = value + } } - // Typescript is difficult here with merging these types from Azure + // Merge with incoming headers if provided + let finalHeaders = cleanHeaders if (incomingHeaders) { - initHeaders = { - ...initHeaders, - ...incomingHeaders, + // Convert incoming headers to plain object as well + const incomingHeadersObj: Record<string, string> = {} + if (incomingHeaders instanceof Headers) { + for (const [key, value] of incomingHeaders.entries()) { + incomingHeadersObj[key] = value + } + } else if (typeof incomingHeaders === 'object') { + for (const [key, value] of Object.entries(incomingHeaders)) { + if (typeof key === 'string' && typeof value === 'string') { + incomingHeadersObj[key] = value + } + } + } + + finalHeaders = { + ...cleanHeaders, + ...incomingHeadersObj, } } - let headers = new Headers(initHeaders) + let headers = new Headers(finalHeaders) const etagFromHeaders = req.headers.get('etag') || req.headers.get('if-none-match') const objectEtag = response.headers.get('etag') 

It will be great if you can incorporate this.

@paulpopus
Copy link
Contributor Author

Oh thank you for this @princebansal, can you give me the failing config so I can add a failing test with the fix as well?

If you want the commit credits as well I'd be happy to review a PR on this

@princebansal
Copy link

Gist of azure config in payload.config.ts

... import { azureStorage } from '@payloadcms/storage-azure' export const mediaSlug = 'media' export const mediaWithPrefixSlug = 'media-with-prefix' export const prefix = 'media' ... plugins: [ azureStorage({ collections: { [mediaSlug]: true, [mediaWithPrefixSlug]: { prefix, }, }, allowContainerCreate: process.env.AZURE_STORAGE_ALLOW_CONTAINER_CREATE === 'true', baseURL: process.env.AZURE_STORAGE_ACCOUNT_BASEURL, connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING, containerName: process.env.AZURE_STORAGE_CONTAINER_NAME, }), ] ... 

My collection:

import type { CollectionConfig } from 'payload' import { isAdmin, isAdminOrPublicUpload } from '../access/isAdmin' import { fallbackEndpoint } from './Media/endpoints/fallback' // Fallback identifier used across the application export const FALLBACK_FILENAME = '__FALLBACK__.svg' export const Media: CollectionConfig<'media'> = { slug: 'media', access: { create: isAdminOrPublicUpload, delete: isAdmin, read: ()=> true, update: isAdmin, }, defaultPopulate: { alt: true, darkModeFallback: true, filename: true, height: true, mimeType: true, url: true, width: true, videoThumbnail: true, }, fields: [ { name: 'alt', type: 'text', required: true, admin: { description: 'Alt text for the image' } }, { name: 'filename', type: 'text', admin: { description: 'Media file name. Fallback filenames are protected against changes.' }, hooks: { beforeValidate: [ ({ value, originalDoc }) => { if (originalDoc?.filename === FALLBACK_FILENAME) { return originalDoc.filename; } return value; } ] } }, { name: 'darkModeFallback', type: 'upload', admin: { description: 'Choose an upload to render if the visitor is using dark mode.', }, relationTo: 'media', }, { name: 'annotation', type: 'text', admin: { description: 'Add a caption to the image', }, }, { name: 'videoThumbnail', type: 'upload', relationTo: 'media', admin: { description: 'Choose an upload to render if the visitor is using dark mode.', }, } ], upload: { adminThumbnail: () => false, }, endpoints: [ fallbackEndpoint, ], } 

To reproduce, you can try to access any media(upload type) document from payload get api url by keeping read access as true.
In my case the url was: http://localhost:3000/_next/image?url=/cms/api/media/file/7-51.png&w=3840&q=100
where cms is my custom api route in payload.config.

But I actually did not change anything the config to fix it. I just changed the way the headers are supposed to be merged.

Let me know if this is what you were looking for.
And thanks for proposing the commit credits, but as of now I just need it working 😆 . And as you have worked on the PR earlier, I think you have more context considering more use cases. So please go ahead with the PR. Thanks again :)

@paulpopus
Copy link
Contributor Author

Hey @princebansal I looked deeper into this and I'm not sure that your solution is safe enough but it's hard to tell as I couldn't reproduce the problem at all which means its likely a difference in the setup of the Azure blob storage, I have a basic one so let me know if there's something specific to yours so I can reproduce.

Instead of your current patch can you try this fix and let me know if that solves it?

let initHeaders: Headers = { ...(response.headers.toJson({ preserveCase: true }) as unknown as Headers), }

On line 34 from the original change rawHeaders() to this toJson function and let me know if this fix alone works for you, then I'll PR it

@paulpopus
Copy link
Contributor Author

Hey @princebansal or anyone else encountering this issue, we have a PR that may fix this but without any reproduction we can't properly review and release it.

It would be extremely helpful if a reproduction configuration is provided or at the very least confirmation that this canary version fixes the problem 3.55.0-internal.a030723

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