[v4 beta] Storage adapter API regression: signed upload context removed without an equivalent replacement
Maintainers usually reply within 1 day
@nathanlentz is already working on this.
Since Oct 16, 2026.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 8/100
- Issue type
- Feature
- Clarity
- Needs clarification
- Activity status
- Active
- Tech stack
- typescript
Research direction
Start from the upload receipt code path: createClientUploadReceipt() and the receipt-validation change in commit db8423cc, plus the uploadReference refactor from PR #17312 and the path check from PR #18548. Trace how the submitted reference gets replaced with _objectKey, prefix and signedReceipt before the storage handler runs. This is a design question about adapter extension points, not a bounded fix, so confirm scope with maintainers before writing code. Done would mean an agreed adapter API that carries verified context and allows custom physical layouts while keeping core file tracking.
Written by the indexing model from the issue text.
Description
The below is AI generated, but there's been a fair bit of revision to edit pare it back a bit.
Summary
The v4 storage adapter API removes capabilities that were available in v3:
- Carrying adapter-owned, verified data through signed upload receipts.
- Using that data to implement a storage layout different from Payload’s default.
We cannot find an equivalent extension point that retains Payload’s standard original-file tracking, transformations, history and cleanup behaviour.
We maintain an internal adapter for our preferred storage provider. The concern is the loss of adapter API flexibility, rather than a request for one particular storage layout.
Versions
| Package | Previous | Current |
|---|---|---|
payload |
3.90.1 |
4.0.0-beta.1 |
@payloadcms/plugin-cloud-storage |
3.90.1 |
4.0.0-beta.1 |
Background: this started with a recent v3 change
Previously, uploads used a flat storage layout by default.
A recent v3 update introduced per-upload UUID directories alongside signed upload receipts. We noticed this after upgrading, when new files started appearing inside these directories.
The v3 adapter API still gave us the tools to restore our preferred layout while keeping signed upload verification.
The v4 changes remove those tools and introduce stricter path assumptions in the standard upload flow.
What changed in the API
1. Receipts no longer accept adapter-owned context
In v3, createClientUploadReceipt() accepted context. Payload verified the receipt and passed its authenticated context to the storage handler.
An adapter could use this to carry a verified provider object ID or other provider-specific data from upload completion into the document save.
The v3 receipt implementation supported this. The v4 receipt implementation instead accepts a fixed set of storage-identity fields, without adapter-owned context.
2. Core removes supplementary reference fields
The v4 upload-instructions refactor (#17312) replaced clientUploadContext with uploadReference.
With requiresUploadReceipt: true, the subsequent receipt-validation change replaces the submitted reference with:
{
_objectKey: receipt._objectKey,
prefix: receipt.filePrefix,
signedReceipt,
}
Returning additional data from the client upload handler therefore does not provide an alternative:
return {
...originalUploadReference,
providerObjectId,
signedProviderContext,
}
Neither additional field reaches the server-side storage handler.
We are not asking Payload to trust client-supplied IDs. The adapter must verify them against the original upload authorisation before treating them as trusted data.
3. The standard receipt flow requires a particular path structure
The upload file-versioning implementation (#18548) added a check that an ordinary new upload’s original storage path ends in:
<filePrefix>/<objectKey>/<name-original.ext>
A path without the _objectKey directory is rejected before the storage handler runs.
The same PR also added UUID object-key generation for managed server-side uploads.
Example impact: flat storage
Our adapter previously used:
uploads/images/example.jpg
The standard v4 flow requires a structure such as:
uploads/images/<objectKey>/example-original.jpg
The extra directory is the concern here, not the -original suffix or UUID format.
Flat storage is one example of the flexibility lost. The broader issue is that adapters can no longer use signed provider context to implement their own verification and storage decisions through the standard flow.
Why opting out is not equivalent
Setting requiresUploadReceipt: false preserves supplementary reference fields and avoids the directory check.
However, in equivalent no-transform tests, Payload stopped populating original. This does not preserve the same automatic original-file tracking and cleanup behaviour.
Using private _payload… request-context fields to restore that behaviour would also create an unsupported dependency on core internals.
Expected behaviour
The adapter API should provide a supported way to:
- Carry authenticated, adapter-owned upload context.
- Resolve the provider object and its physical storage path.
- Retain core receipt checks, original-file tracking, transformations, history and rollback support.
Restoring signed receipt context—or providing an equivalent verification/finalisation hook—would address the first part.
Adapters should also be able to choose their physical storage layout without opting out of core file-management support.
We understand the need for distinct upload identities and safe handling of retained files. The regression is the loss of extension points that previously let adapters meet those requirements without adopting Payload’s default directory structure.
- Dominant language
- TypeScript
- Stars
- 45.6k
- Forks
- 4.4k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 280
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing 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.
More from payloadcms/payload
-
Form Builder Plugin: an internal link in an email message stops all of the form's emailsPossibly taken @DoctorGuidance claimed this 2 days ago. OpenBug plugin: form-builder status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
payloadcms/payload#19253 ·
Maintainers usually reply within 1 day
-
Required Lexical field accepts two empty paragraphsPossibly taken @lazerg claimed this 3 days ago. OpenBug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
payloadcms/payload#19222 ·
Maintainers usually reply within 1 day
-
area: ui Bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
payloadcms/payload#19198 ·
Maintainers usually reply within 1 day
-
Grid view: show the file placeholder when a thumbnail image fails to loadPossibly taken @lazerg claimed this 5 days ago. OpenBug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
payloadcms/payload#19181 ·
Maintainers usually reply within 1 day
-
WhereBuilder throws "Cannot read properties of undefined (reading 'value')" when the list URL filters on a field that is not filterablePossibly taken A pull request linked to this issue is open or already merged. OpenBug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#19002 · 1 comment ·
Maintainers usually reply within 1 day
All issues in payloadcms/payload
Similar issues
-
area: cli bug flake ready-for-agent
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
gazetta-studio/gazetta-studio#1772 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
microsoft/playwright#43875 · 1 comment ·
Maintainers usually reply within 1 day
-
documentation good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
feature
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
calagopus/panel#285 · 1 comment ·
Maintainers usually reply within 1 day