Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/payload/src/collections/operations/local/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,13 @@ export async function createLocal<

const req = await createLocalReq(options as CreateLocalReqOptions, payload)

req.file = file ?? (await getFileByPath(filePath!))
// Only assign `req.file` when a file or filePath was explicitly passed to this operation.
// Otherwise, this would overwrite (and drop) a file reference that was already set on `req`
// by an earlier upload operation, e.g. when a hook passes the original `req` through to a
// nested Local API call. See https://github.com/payloadcms/payload/issues/15975
if (file || filePath) {
req.file = file ?? (await getFileByPath(filePath!))
}

return createOperation<TSlug, TSelect>({
collection,
Expand Down
9 changes: 8 additions & 1 deletion packages/payload/src/collections/operations/local/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,14 @@ async function updateLocal<
}

const req = await createLocalReq(options as CreateLocalReqOptions, payload)
req.file = file ?? (await getFileByPath(filePath!))

// Only assign `req.file` when a file or filePath was explicitly passed to this operation.
// Otherwise, this would overwrite (and drop) a file reference that was already set on `req`
// by an earlier upload operation, e.g. when a hook passes the original `req` through to a
// nested Local API call. See https://github.com/payloadcms/payload/issues/15975
if (file || filePath) {
req.file = file ?? (await getFileByPath(filePath!))
}

const args = {
id,
Expand Down
31 changes: 31 additions & 0 deletions test/uploads/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,37 @@ describe('Collections - Uploads', () => {
})
})

describe('req.file preservation across nested Local API calls', () => {
it('should preserve req.file when a hook calls the Local API with the original req', async () => {
const filePath = path.resolve(dirname, './image.png')
const file = await getFileByPath(filePath)
file.name = 'preserve-req-file-test.png'

// Simulate `req` as it would exist on an upload request, i.e. already
// carrying a `file` reference before any nested Local API call is made.
const req = { file } as PayloadRequest

// Simulate a hook (e.g. an upload collection's afterChange) that calls
// the Local API on another, non-upload collection, passing the original
// `req` through per the docs' recommendation to preserve request context.
const relationDoc = await payload.create({
collection: relationSlug,
data: {},
req,
})

expect(relationDoc.id).toBeDefined()

// The original file reference on `req` must still be accessible after
// the nested Local API call returns, so that a subsequent hook (or an
// upload storage adapter) can still read it.
expect(req.file).toBeDefined()
expect(req.file?.name).toBe('preserve-req-file-test.png')

await payload.delete({ collection: relationSlug, id: relationDoc.id })
})
})

describe('serverURL handling', () => {
it('should store relative URLs in database even when serverURL is set', async () => {
// Temporarily set serverURL for this test
Expand Down
Loading