fix: reuploading file on Save draft resets main doc _status to draft#10
fix: reuploading file on Save draft resets main doc _status to draft#10deepshekhardas wants to merge 7 commits into
Conversation
The MCP endpoint was accepting only Bearer token but Payload convention uses 'payload-mcp-api-keys API-Key <key>' format. Now accepts both: - 'payload-mcp-api-keys API-Key <key>' (Payload convention) - 'Bearer <key>' (backwards compatible) This makes the plugin consistent with other Payload API-key surfaces. Fixes: payloadcms#16572
When using localizeStatus, the last version wasn't retrieved properly when publishing directly. This is because the version_updatedAt and version_createdAt weren't being set properly when publishing. The fix adds a check for localized status (_status) in locales to ensure main row data update happens even when only localized fields have changed. Fixes: payloadcms#16395
drizzle-kit/api was being required at module load time, causing it to be included in production bundle for OpenNext Cloudflare and other edge runtimes. This moves the require() inside the function so it's only loaded when actually needed (during migrations/schema push). Also fixes the same issue in postgres adapter. Fixes: payloadcms#16470
When a POST request uses ?select[…] to project mimeType/filename out of the response doc, the cloud-storage plugin silently skips uploading to S3. Fix by falling back to req.file properties when data.filename/data.mimeType are undefined due to select projection. Also handle sizes fallback using payloadUploadSizes when data.sizes is missing. Closes payloadcms#16670
When blocks containing relationship fields are reordered, old _rels rows at previous path positions were never deleted, causing stale FK references. Fix by adding prefix-based deletion for blocks fields: 1. Add 'prefix' property to RelationshipToDelete type 2. In transformBlocks, signal that all old rels under the blocks field should be purged by adding a prefix entry to relationshipsToDelete 3. Update deleteExistingRowsByPath to support prefix deletions using LIKE queries (e.g., path LIKE 'layout.%') This also addresses the related issue payloadcms#15976 (rels not cleaned on version restore) since the same root cause applies. Closes payloadcms#16647
On Next.js 15.5+, @next/env no longer exposes a default export - only
named exports remain. The existing default import causes undefined to
be destructured, throwing 'Cannot destructure property loadEnvConfig'.
Fix by using namespace import with fallback:
- import * as nextEnvImport from '@next/env'
- const { loadEnvConfig } = nextEnvImport.default ?? nextEnvImport
The ?? fallback maintains backwards-compat with Next.js < 15.5 while
supporting 15.5+.
Closes payloadcms#16674
For an upload-enabled collection with drafts enabled, replacing a published file and saving as a draft incorrectly overwrites the main collection document's _status from 'published' to 'draft'. The bug was in collections/operations/utilities/update.ts where data._status = 'draft' was set unconditionally when isSavingDraft was true, even when updating an existing published document. Fix by only setting _status = 'draft' for new documents (when id is undefined). For existing documents, the main doc should remain published while a draft version is created. Closes payloadcms#16633
There was a problem hiding this comment.
5 issues found across 10 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/drizzle/src/postgres/requireDrizzleKit.ts">
<violation number="1" location="packages/drizzle/src/postgres/requireDrizzleKit.ts:4">
P1: `require('module')` is used before any `require` exists, which will throw in ESM runtime.</violation>
</file>
<file name="packages/drizzle/src/sqlite/requireDrizzleKit.ts">
<violation number="1" location="packages/drizzle/src/sqlite/requireDrizzleKit.ts:4">
P1: `require('module')` is called before `require` is defined. In this ESM module, `require` is not globally available, so this will throw `ReferenceError: require is not defined` at runtime.</violation>
</file>
<file name="packages/plugin-cloud-storage/src/utilities/getIncomingFiles.ts">
<violation number="1" location="packages/plugin-cloud-storage/src/utilities/getIncomingFiles.ts:39">
P2: The new `sizes` fallback uses `payloadUploadSizes` (buffers) where image size metadata is expected, so the fallback path never adds resized files.</violation>
</file>
<file name="packages/drizzle/src/upsertRow/deleteExistingRowsByPath.ts">
<violation number="1" location="packages/drizzle/src/upsertRow/deleteExistingRowsByPath.ts:83">
P1: Escape LIKE wildcard characters in dynamic prefixes before building the delete pattern; otherwise `%`/`_` in a path can over-match and delete unintended rows.</violation>
</file>
<file name="packages/drizzle/src/transform/write/blocks.ts">
<violation number="1" location="packages/drizzle/src/transform/write/blocks.ts:150">
P2: Prefix deletion here relies on an unescaped SQL LIKE pattern, so `_`/`%` in block field names can over-delete unrelated relationship paths.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const require = createRequire(import.meta.url) | ||
|
|
||
| export const requireDrizzleKit: RequireDrizzleKit = () => { | ||
| const { createRequire } = require('module') |
There was a problem hiding this comment.
P1: require('module') is used before any require exists, which will throw in ESM runtime.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/drizzle/src/postgres/requireDrizzleKit.ts, line 4:
<comment>`require('module')` is used before any `require` exists, which will throw in ESM runtime.</comment>
<file context>
@@ -1,10 +1,8 @@
-const require = createRequire(import.meta.url)
-
export const requireDrizzleKit: RequireDrizzleKit = () => {
+ const { createRequire } = require('module')
+ const require = createRequire(import.meta.url)
const {
</file context>
| const require = createRequire(import.meta.url) | ||
|
|
||
| export const requireDrizzleKit: RequireDrizzleKit = () => { | ||
| const { createRequire } = require('module') |
There was a problem hiding this comment.
P1: require('module') is called before require is defined. In this ESM module, require is not globally available, so this will throw ReferenceError: require is not defined at runtime.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/drizzle/src/sqlite/requireDrizzleKit.ts, line 4:
<comment>`require('module')` is called before `require` is defined. In this ESM module, `require` is not globally available, so this will throw `ReferenceError: require is not defined` at runtime.</comment>
<file context>
@@ -1,10 +1,8 @@
-const require = createRequire(import.meta.url)
-
export const requireDrizzleKit: RequireDrizzleKit = () => {
+ const { createRequire } = require('module')
+ const require = createRequire(import.meta.url)
const {
</file context>
| for (const prefix of localizedPrefixPathsToDelete) { | ||
| const whereConstraints = [ | ||
| eq(table[parentColumnName], parentID), | ||
| like(table[pathColumnName], `${prefix}%`), |
There was a problem hiding this comment.
P1: Escape LIKE wildcard characters in dynamic prefixes before building the delete pattern; otherwise %/_ in a path can over-match and delete unintended rows.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/drizzle/src/upsertRow/deleteExistingRowsByPath.ts, line 83:
<comment>Escape LIKE wildcard characters in dynamic prefixes before building the delete pattern; otherwise `%`/`_` in a path can over-match and delete unintended rows.</comment>
<file context>
@@ -66,4 +75,34 @@ export const deleteExistingRowsByPath = async ({
+ for (const prefix of localizedPrefixPathsToDelete) {
+ const whereConstraints = [
+ eq(table[parentColumnName], parentID),
+ like(table[pathColumnName], `${prefix}%`),
+ ]
+
</file context>
| if (data?.sizes) { | ||
| Object.entries(data.sizes).forEach(([key, resizedFileData]) => { | ||
| if (payloadUploadSizes?.[key] && resizedFileData.mimeType) { | ||
| const sizes = data?.sizes ?? payloadUploadSizes |
There was a problem hiding this comment.
P2: The new sizes fallback uses payloadUploadSizes (buffers) where image size metadata is expected, so the fallback path never adds resized files.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin-cloud-storage/src/utilities/getIncomingFiles.ts, line 39:
<comment>The new `sizes` fallback uses `payloadUploadSizes` (buffers) where image size metadata is expected, so the fallback path never adds resized files.</comment>
<file context>
@@ -21,21 +21,25 @@ export function getIncomingFiles({
- if (data?.sizes) {
- Object.entries(data.sizes).forEach(([key, resizedFileData]) => {
- if (payloadUploadSizes?.[key] && resizedFileData.mimeType) {
+ const sizes = data?.sizes ?? payloadUploadSizes
+ if (sizes) {
+ Object.entries(sizes).forEach(([key, resizedFileData]) => {
</file context>
|
|
||
| relationshipsToDelete.push({ | ||
| path: `${path || ''}${field.name}.`, | ||
| prefix: true, |
There was a problem hiding this comment.
P2: Prefix deletion here relies on an unescaped SQL LIKE pattern, so _/% in block field names can over-delete unrelated relationship paths.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/drizzle/src/transform/write/blocks.ts, line 150:
<comment>Prefix deletion here relies on an unescaped SQL LIKE pattern, so `_`/`%` in block field names can over-delete unrelated relationship paths.</comment>
<file context>
@@ -144,4 +144,9 @@ export const transformBlocks = ({
+
+ relationshipsToDelete.push({
+ path: `${path || ''}${field.name}.`,
+ prefix: true,
+ })
}
</file context>
Fixes payloadcms#16633
For an upload-enabled collection with drafts enabled, replacing a published file and saving as a draft incorrectly overwrites the main collection document's _status from 'published' to 'draft'.
The bug was in collections/operations/utilities/update.ts where data._status = 'draft' was set unconditionally when isSavingDraft was true, even when updating an existing published document.
Fix by only setting _status = 'draft' for new documents (when id is undefined). For existing documents, the main doc should remain published while a draft version is created.
Summary by cubic
Prevents published docs from reverting to draft when replacing a file and saving as draft. Also includes stability fixes across
drizzle,plugin-cloud-storage,plugin-mcp, and env loading.Bug Fixes
_status = 'draft'for new docs; existing published docs stay published._statuschanges.plugin-cloud-storage: fall back toreq.filefor filename/mimeType and usepayloadUploadSizeswhendata.sizesis missing to prevent skipped uploads after field projection.plugin-mcp: accept both "payload-mcp-api-keys API-Key " and "Bearer " headers.@next/envvia namespaced fallback.Dependencies
drizzle-kitindrizzleadapters to avoid bundling in production/edge.Written for commit c719c36. Summary will update on new commits. Review in cubic