Skip to content

Commit 235e068

Browse files
committed
docs(versions): add warning about unique fields with drafts enabled
Document the known limitation where `unique: true` fields on collections with `versions.drafts` enabled cause silent document creation failures. The Admin UI redirects to `?notFound=<id>` with no error message. This has been an open issue since 2022 (#953, #12628, #3576) affecting users on both MongoDB and PostgreSQL/Drizzle adapters. Added: - Warning banner in Database changes section - New 'Unique fields with Drafts' section with: - Problem explanation - Application-level uniqueness workaround using validate + index - Code example Relates to #953, #12628, #3576
1 parent 727d74e commit 235e068

File tree

1 file changed

+59
-0
lines changed

1 file changed

+59
-0
lines changed

docs/versions/drafts.mdx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ Collections and Globals both support the same options for configuring drafts. Yo
3232

3333
By enabling drafts on a collection or a global, Payload will **automatically inject a new field into your schema** called `_status`. The `_status` field is used internally by Payload to store if a document is set to `draft` or `published`.
3434

35+
<Banner type="warning">
36+
**`unique` fields and Drafts:** Using `unique: true` on fields in collections with drafts enabled can cause silent failures. When a document is created, Payload writes to both the main collection table and the versions table (`_collectionSlug_v`). Because the initial draft is created with empty or default values before the user fills in the form, a database-level unique constraint will fail on the second document creation — since the first empty-value draft already exists. The Admin UI may redirect to a `?notFound=` URL without showing an error. To work around this, replace `unique: true` with `index: true` and enforce uniqueness at the application level using a custom `validate` function. See [Unique Fields with Drafts](#unique-fields-with-drafts) below.
37+
</Banner>
38+
3539
**Admin UI status indication**
3640

3741
Within the Admin UI, if drafts are enabled, a document can be shown with one of three "statuses":
@@ -276,3 +280,58 @@ If a document is published, the Payload Admin UI will be updated to show an "unp
276280
## Reverting to published
277281

278282
If a document is published, and you have made further changes which are saved as a draft, Payload will show a "revert to published" button at the top of the sidebar which will allow you to reject your draft changes and "revert" back to the published state of the document. Your drafts will still be saved, but a new version will be created that will reflect the last published state of the document.
283+
284+
## Unique fields with Drafts
285+
286+
When using `unique: true` on fields in a collection that has `versions.drafts` enabled, you may encounter silent document creation failures. This is a known limitation ([#953](https://github.com/payloadcms/payload/issues/953), [#12628](https://github.com/payloadcms/payload/issues/12628)).
287+
288+
### The problem
289+
290+
When Payload creates a new document in a drafts-enabled collection, it writes to both the main collection table and the versions table. The database-level `UNIQUE` constraint on the main table can conflict with the draft creation process — particularly when:
291+
292+
- Multiple documents are created before unique field values are filled in (autosave creates rows with empty values)
293+
- The versions system attempts to write to both tables within the same operation
294+
295+
The result is a silent failure where the Admin UI redirects to `?notFound=<id>` instead of showing the newly created document, with no visible error message.
296+
297+
### Workaround
298+
299+
Replace `unique: true` with `index: true` and enforce uniqueness at the application level using a custom `validate` function:
300+
301+
```ts
302+
const MyCollection: CollectionConfig = {
303+
slug: 'my-collection',
304+
versions: {
305+
drafts: true,
306+
},
307+
fields: [
308+
{
309+
name: 'slug',
310+
type: 'text',
311+
required: true,
312+
// Use index instead of unique to avoid DB-level constraint conflicts with drafts
313+
index: true,
314+
validate: async (value, { req, id }) => {
315+
if (!value) return true
316+
const existing = await req.payload.find({
317+
collection: 'my-collection',
318+
where: {
319+
slug: { equals: value },
320+
id: { not_equals: id || 0 },
321+
},
322+
limit: 1,
323+
})
324+
if (existing.totalDocs > 0) {
325+
return 'This value is already in use'
326+
}
327+
return true
328+
},
329+
},
330+
],
331+
}
332+
```
333+
334+
This approach:
335+
- Preserves the index for query performance
336+
- Validates uniqueness before save, providing a clear error message in the Admin UI
337+
- Avoids the database-level constraint that conflicts with the drafts version system

0 commit comments

Comments
 (0)