payloadcms / payloadcms/payload
File reference is not preserved on req when using local API in hook
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
When using the local API from a hook (for example payload.create) in an upload collection the file reference is dropped when persisting req as recommended by the documentation. e.g.:
hooks: {
afterChange: [
async ({ operation, req }) => {
if (operation === 'create') {
await req.payload.create({
collection: 'others',
req, // <- Passing req here to keep context causes the issue
data: {
name: 'Some generated name',
},
});
}
},
({ req }) => {
// Undefined if req is passed above, otherwise the file name is logged
// This causes unexpected behaviour for users who want to use req in afterChange hooks for media collections, as they will not be able to access req.file
// e.g. azure storage adapter silently fails here because req.file is undefined.
console.log(req.file?.name);
}
]
}
This can lead to cases like the azure storage adapter silently failing because there is no longer a file available in the request context when the next hook in the sequence is run, as described by the simple console log above.
I have found some similar issues (either old and closed, or current) which allude to similar issues not directly with the file on the req so I suspect this is still a case of general "leaking" on the context.
- https://github.com/payloadcms/payload/issues/5720
- https://github.com/payloadcms/payload/issues/10250
- https://github.com/payloadcms/payload/issues/15619
- https://github.com/payloadcms/payload/pull/15620
I'm more than happy to take a shot at raising a fix for this one, I can see the "offending" code on local create which sets the file as undefined on req. I just don't know what the intended semantics of this are and whether it should hold onto a file on the req if one already exists?
Link to the code that reproduces this issue
https://github.com/iamscottcab/payload-file-demo
Reproduction Steps
- Clone repo
- Install deps
- Run the app
- Upload something to the media collection
- Note that
undefinedis printed when the secondafterChangeHookis run on theMediacollection. - Comment out
reqin the call topayload.createin the firstafterChangeHook. - Upload a new file to the media collection.
- Note file is preserved and the file name is printed. However we now lose the request context if we need it, for example to use the user in hooks on other collections.
Expected behaviour - The file is persisted through the entire request context despite a call to payload.create on another collection that is not an upload collection.
Which area(s) are affected?
area: core
Environment Info
Binaries:
Node: 22.13.0
npm: N/A
Yarn: N/A
pnpm: N/A
Relevant Packages:
payload: 3.79.1
next: 15.4.11
@payloadcms/db-mongodb: 3.79.1
@payloadcms/graphql: 3.79.1
@payloadcms/next/utilities: 3.79.1
@payloadcms/richtext-lexical: 3.79.1
@payloadcms/translations: 3.79.1
@payloadcms/ui/shared: 3.79.1
react: 19.2.1
react-dom: 19.2.1
Operating System:
Platform: win32
Arch: x64
Version: Windows 10 Pro
Available memory (MB): 16317
Available CPU cores: 12
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the local create code mentioned in the issue, especially where passing req sets req.file to undefined, and reproduce the behavior using the linked payload-file-demo. Done means a nested payload.create call preserves the existing file reference through subsequent upload hooks while retaining the request context.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100