payloadcms / payloadcms/payload

GraphQL schema builder dedupes inline blocks by slug across collections, ignoring `interfaceName`

Open
#16,769 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area: core area: graphql stale status: needs-triage v3
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
  1. Boot Payload with both collections registered.
  2. 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]
  1. 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
  2. Run payload generate:types and 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.ts with 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 interfaceName got picked as the canonical one (or omit interfaceName entirely, letting both auto-generate to the same name).
  • Setting interfaceName differently 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.