Describe the Bug
With a Postgres adapter and defaultIDType: 'number' (serial — the default), saving a versioned/draft document crashes when the document contains a select field with hasMany: true nested two or more array levels deep (array → array → select hasMany).
The insert into the version table for the select values fails because Payload puts the parent array row's text uuid into the version child's parent_id column, which is typed integer:
error: invalid input syntax for type integer: "6a425fc0f04da02c1c6e5399"
code: 22P02
where: unnamed portal parameter $2 = '...' // $2 = parent_id
query: insert into "_things_v_version_outer_inner_days"
("order", "parent_id", "value", "id")
values ($1, $2, $3, default), ...
The string 6a425fc0f04da02c1c6e5399 is the inner array row's id (Payload's text uuid for array rows). It is being inserted into parent_id, an integer column.
Depth matters (verified):
select hasMany at the document root → works (links to _things_v.id, an integer).
select hasMany inside a single array level → works (links to the array row's new serial version id).
select hasMany inside an array nested in another array (depth ≥ 2) → crashes.
Link to the code that reproduces this issue
Reproduced locally via the Local API. Minimal config:
// collection
{
slug: 'things',
versions: { drafts: true },
fields: [
{
name: 'outer',
type: 'array',
fields: [
{
name: 'inner',
type: 'array',
fields: [
{
name: 'days',
type: 'select',
hasMany: true,
options: ['monday', 'tuesday', 'wednesday'],
},
],
},
],
},
],
}
// db: postgresAdapter / vercelPostgresAdapter, defaultIDType defaults to 'number' (serial)
Reproduction Steps
const doc = await payload.create({ collection: 'things', data: {} })
await payload.update({
collection: 'things',
id: doc.id,
draft: true,
data: { outer: [{ inner: [{ days: ['monday'] }] }] },
})
// throws: invalid input syntax for type integer: "<inner array row uuid>"
(Also reproduces through the admin UI: edit a drafts-enabled doc, add an outer row → an inner row → pick a value in the hasMany select → save/autosave.)
Expected vs Actual
- Expected: the version snapshot inserts the select values, linking each child to its parent array row by the parent's new serial version-row id (as it already does correctly at depth 0 and depth 1).
- Actual: at depth ≥ 2 the version select-table
parent_id (typed integer) is populated with the parent array row's text uuid (the value stored in the parent version row's _uuid), producing invalid input syntax for type integer and aborting the transaction. No save succeeds while any such nested select value is set.
Root cause (analysis)
In Postgres version tables, array rows get a fresh serial id and keep the original text row id in _uuid. The version child table for a nested select hasMany is created with parent_id integer (FK → the parent array version row's serial id). When building the version row, Payload appears to use the parent array row's original text id (_uuid) as parent_id instead of the remapped serial id. This only surfaces when the parent array is itself nested inside another array — at shallower levels the parent id used is already an integer, so the type mismatch never appears.
Which area(s) are affected?
Database (@payloadcms/db-vercel-postgres / Postgres adapters), Versions / Drafts.
Environment
|
|
| Payload |
3.85.0 |
| DB adapter |
@payloadcms/db-vercel-postgres 3.85.0 (drizzle-orm 0.45.2) |
| PostgreSQL |
17 |
| defaultIDType |
number (serial, default) |
| Node |
24.15.0 |
| Next.js |
16.2.6 |
| OS |
macOS |
Workaround
Avoid select hasMany nested two array levels deep in a versioned collection. Store the multi-select as a json field (string[]), or use a sub-array of { value: select } instead.
Describe the Bug
With a Postgres adapter and
defaultIDType: 'number'(serial — the default), saving a versioned/draft document crashes when the document contains aselectfield withhasMany: truenested two or more array levels deep (array→array→select hasMany).The insert into the version table for the select values fails because Payload puts the parent array row's text uuid into the version child's
parent_idcolumn, which is typedinteger:The string
6a425fc0f04da02c1c6e5399is the inner array row's id (Payload's text uuid for array rows). It is being inserted intoparent_id, anintegercolumn.Depth matters (verified):
select hasManyat the document root → works (links to_things_v.id, an integer).select hasManyinside a single array level → works (links to the array row's new serial version id).select hasManyinside an array nested in another array (depth ≥ 2) → crashes.Link to the code that reproduces this issue
Reproduced locally via the Local API. Minimal config:
Reproduction Steps
(Also reproduces through the admin UI: edit a drafts-enabled doc, add an outer row → an inner row → pick a value in the
hasManyselect → save/autosave.)Expected vs Actual
parent_id(typedinteger) is populated with the parent array row's text uuid (the value stored in the parent version row's_uuid), producinginvalid input syntax for type integerand aborting the transaction. No save succeeds while any such nested select value is set.Root cause (analysis)
In Postgres version tables, array rows get a fresh serial
idand keep the original text row id in_uuid. The version child table for a nestedselect hasManyis created withparent_id integer(FK → the parent array version row's serialid). When building the version row, Payload appears to use the parent array row's original text id (_uuid) asparent_idinstead of the remapped serial id. This only surfaces when the parent array is itself nested inside another array — at shallower levels the parent id used is already an integer, so the type mismatch never appears.Which area(s) are affected?
Database (
@payloadcms/db-vercel-postgres/ Postgres adapters), Versions / Drafts.Environment
Workaround
Avoid
select hasManynested two array levels deep in a versioned collection. Store the multi-select as ajsonfield (string[]), or use a sub-array of{ value: select }instead.