payloadcms / payloadcms/payload
GraphQL schema builder dedupes inline blocks by slug across collections, ignoring `interfaceName`
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
Related to #15897 : both are "blocks identified by the wrong key when deduping," but that issue is in TypeScript typegen within a single collection, while this is in the GraphQL schema builder across collections.
When two different collections each define an inline block sharing the same slug (e.g. 'text') but with different interfaceNames and different sub-fields, Payload's GraphQL schema builder registers only the first block encountered and reuses its GraphQL object type for every blocks field union that references that slug; even in the other collection, whose block has different fields.
payload generate:types handles the same config correctly and emits both interfaces. The bug is isolated to the GraphQL schema builder.
The net effect: GraphQL queries against the second collection's union appear to "succeed" but return the wrong shape, and querying fields that are only defined on the second collection's block fails with Cannot query field "X" on type "".
Minimal repro:
// collections/A.ts
export const A: CollectionConfig = {
slug: 'a',
fields: [
{
name: 'items',
type: 'blocks',
blocks: [
{
slug: 'text',
interfaceName: 'ABlockText',
fields: [
{ name: 'label', type: 'text' },
{ name: 'showOnA', type: 'checkbox' }, // only on A
],
},
],
},
// collections/B.ts
export const B: CollectionConfig = {
slug: 'b',
fields: [
{
name: 'items',
type: 'blocks',
blocks: [
{
slug: 'text',
interfaceName: 'BBlockText',
fields: [
{ name: 'label', type: 'text' },
{ name: 'showOnB', type: 'checkbox' }, // only on B
],
},
],
},
],
}
Link to the code that reproduces this issue
https://github.com/ynmstudio/payload/tree/main/test/_community
Reproduction Steps
- Boot Payload with both collections registered.
- Introspect the schema:
curl -s -X POST http://localhost:3000/api/graphql -H "Content-Type: application/json" -d '{"query":"{ __type(name: \"ABlockText\") { name fields { name } } }"}'=> { "data": { "__type": null } }curl -s -X POST http://localhost:3000/api/graphql -H "Content-Type: application/json" -d '{"query":"{ __type(name: \"BBlockText\") { name fields { name } } }"}'=> returns BBlockText with fields [label, showOnB]
- Inspect collection A's union:
curl -s -X POST http://localhost:3000/api/graphql -H "Content-Type: application/json" -d '{"query":"{ __type(name: \"A_Items\") { kind possibleTypes { name } } }"}'=> possibleTypes contains "BBlockText" — A's interfaceName is dropped entirely - Run
payload generate:typesand observe that both ABlockText and BBlockText interfaces are emitted correctly with their respective fields; confirming the bug is GraphQL-only.
Which area(s) are affected?
area: graphql, area: core
Environment Info
- Payload: 3.84.0
- @payloadcms/db-postgres: 3.84.0
- @payloadcms/next: 3.84.0
- Node: 20.20.2
- DB adapter config: postgresAdapter({ idType: 'uuid', blocksAsJSON: true }) (unclear whether blocksAsJSON is relevant — happy to retest without it)
- issue is reproducable on 4.0.0-beta.0 (confirmed on monorepo main)
Additional context
- TS typegen behaves correctly; both interfaces are present in
payload-types.tswith the right fields. The mismatch between TS and GraphQL is itself a smell. - Workaround until fixed: mirror any block field needed in one collection into the other collection's block too, and query against whichever block's
interfaceNamegot picked as the canonical one (or omit interfaceName entirely, letting both auto-generate to the same name). - Setting
interfaceNamedifferently per block is the natural way to disambiguate; the GraphQL builder appears to key its block-type registry on slug only and skip subsequent registrations.
Contributor 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.
Research direction
Start with the GraphQL schema builder and the block-type registry, using the reproduction in test/_community with collections A and B. Re-run the listed introspection queries and compare them with payload generate:types. Done means both interfaceNames appear as distinct GraphQL types, each union references the correct block shape, and type generation remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100