Relationship with custom text id not working when field is nested in layout components
Describe the Bug
Issue Description: Relationships fail when a custom text id field is nested inside layout components (like row) due to incorrect foreign key type generation.
Working Configuration:
When the custom text id field is at the top level, relationships work correctly. Here's a repo where this issue is reproduced. The main branch works, while the issue branch demonstrates the error.
The repo contains 2 collections: Posts and Comments. Here are the working collection configurations:
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
},
fields: [
{
name: 'id',
required: true,
type: 'text',
},
{
name: 'title',
type: 'text',
required: true,
},
],
};
export const Comments: CollectionConfig = {
slug: 'comments',
fields: [
{
name: 'text',
type: 'textarea',
required: true,
},
{
name: 'post',
type: 'relationship',
relationTo: 'posts',
required: true,
},
],
};
The generated payload-types.ts has the expected type:
export interface Comment {
id: number;
text: string;
post: string | Post; // string - correct
updatedAt: string;
createdAt: string;
}
Non-Working Configuration: When the custom id field is nested inside a row component, the relationship fails:
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
},
fields: [
{
type: 'row',
fields: [
{
name: 'id',
required: true,
type: 'text',
},
{
name: 'title',
type: 'text',
required: true,
},
],
},
],
};
export const Comments: CollectionConfig = {
slug: 'comments',
fields: [
{
name: 'text',
type: 'textarea',
required: true,
},
{
name: 'post',
type: 'relationship',
relationTo: 'posts',
required: true,
},
],
};
The generated payload-types.ts has an incorrect type:
export interface Comment {
id: number;
text: string;
post: number | Post; // number - wrong
updatedAt: string;
createdAt: string;
}
Error:
Error: Failed query: ALTER TABLE "comments" ADD CONSTRAINT "comments_post_id_posts_id_fk" FOREIGN KEY ("post_id") REFERENCES "public"."posts"("id") ON DELETE set null ON UPDATE no action
detail: 'Key columns "post_id" and "id" are of incompatible types: integer and character varying.'
Root Cause: It appears that Payload's schema generation system doesn't properly detect custom text id fields when they're nested inside layout components like row, collapsible, or tabs. This causes the relationship system to default to number type instead of correctly using string to match the target field type.
Link to the code that reproduces this issue
https://github.com/lukabis/payload-custom-id-bug/tree/main
Reproduction Steps
- clone the repo
cp .env.example .envgit checkout issuedocker compose up -d- visit http://localhost:3000/admin
- check the logs
docker compose logs -f payload
Which area(s) are affected? (Select all that apply)
db-postgres, area: core
Environment Info
This project is configured to use pnpm because /home/node/app/package.json has a "packageManager" field
Binaries:
Node: 22.19.0
npm: 10.9.3
Yarn: N/A
pnpm: 10.15.1
Relevant Packages:
payload: 3.54.0
next: 15.5.2
@payloadcms/db-postgres: 3.54.0
@payloadcms/email-nodemailer: 3.54.0
@payloadcms/graphql: 3.54.0
@payloadcms/next/utilities: 3.54.0
@payloadcms/payload-cloud: 3.54.0
@payloadcms/plugin-cloud-storage: 3.54.0
@payloadcms/richtext-lexical: 3.54.0
@payloadcms/storage-s3: 3.54.0
@payloadcms/translations: 3.54.0
@payloadcms/ui/shared: 3.54.0
react: 19.1.1
react-dom: 19.1.1
Operating System:
Platform: linux
Arch: x64
Version: #79~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 15 16:54:53 UTC 2
Available memory (MB): 15715
Available CPU cores: 16
I think this problem is related to this issue.
@danielkoller I can't see the two issues are related.
I can confirm this issue exists and affects all custom text ID fields, not just those in row fields.
Evidence from generated schema:
When using custom text ID fields, Payload correctly generates the main collection table with varchar ID:
export const posts = pgTable('posts', {
id: varchar('id').primaryKey(),
// ...
})
However, relationship tables still use integer for foreign key columns:
export const comments_rels = pgTable('comments_rels', {
id: serial('id').primaryKey(),
postsID: integer('posts_id'), // ❌ Should be varchar
// ...
})
This creates foreign key constraints that try to link incompatible types:
postsIdFk: foreignKey({
columns: [columns['postsID']], // integer type
foreignColumns: [posts.id], // varchar type
name: 'comments_rels_posts_fk',
}).onDelete('cascade'),
Error:
Key columns "posts_id" and "id" are of incompatible types: integer and character varying.
This makes the Custom text ID Fields completely unusable with PostgreSQL when any relationships exist between collections.
Environment:
- Payload: 3.50.0
- Database: PostgreSQL 17-3.5
I created a tiny collection (just id + one text field), and it works fine.
In my real project, collections that contains a relationship array, or join will fail. At certain points Payload appears to treat my id (defined as text) as an integer.
My workaround is use a new filed for the text ids.
I just experienced this exact bug with the group field type. I was using it purely for layout purposes with a custom text id field nested inside.
This is a subtle bug because there's no warning during config validation. My current workaround is to move the custom id field to the top level, which defeats the purpose of layout organization. While I'm not sure if this happens to other databases, it might be related to drizzle or postgres specifically.
Additionally while we fix this issue, it would be helpful to add a warning callout in the docs so developers are aware of this caveat. I could also contribute a PR to add this documentation, however I need to confirm if this only happens to Postgres.