Skip to content

Collection and global slugs are not validated for uniqueness #18584

Open

Description

@VeiaG

Describe the Bug

Payload does not check that slugs are unique across collections and globals. It also does not check globals against each other. The only slug check is for collection vs collection (DuplicateCollection in packages/payload/src/config/sanitize.ts). A DuplicateGlobal error is exported from payload but never thrown.

When a slug is shared, each database adapter fails in its own way. Some of those failures are silent and corrupt data:

Config MongoDB SQLite
collection + global, same slug crashes on init: OverwriteModelError: Cannot overwrite `settings` model once compiled. silent data corruption. Both use one settings table that only has the global's columns. Updating the global overwrites a collection document, and collection-only fields are dropped without an error
two globals, same slug crashes on init: Discriminator with name "settings" already exists silent data loss. The table gets the second global's columns while the API follows the first global's fields, so neither global's fields are saved

Even when the database errors, the message points at mongoose or drizzle, not at the config. This matters most for plugins: plugins add both collections and globals, so a clash between a plugin and the project, or between two plugins, gives either a confusing error or, on SQL adapters, no error at all.

When the database layer is worked around with dbName, the admin panel still breaks. On MongoDB the config boots, but the field schema maps (getSchemaMap / getClientSchemaMap in @payloadcms/ui) are cached by slug alone. The collection and the global therefore swap field configs: the labels and descriptions of richText fields and custom-component fields come from the other entity, and fields that exist in only one of them disappear from the form. On SQLite, getIsLocked queries the global's relation column for the collection document, and the document view renders empty.

The language-keyed variant of the same cache problem is reported in #18583.

Suggested fix: validate collection and global slugs together in sanitizeConfig, using one shared set. Throw a clear error such as Slug "settings" is used by both collection "settings" and global "settings" (or DuplicateGlobal for two globals) before any adapter initializes.

Link to the code that reproduces this issue

https://github.com/VeiaG/payload-3-reproductions/tree/repro-collection-global-slug-collision

Reproduction Steps

The repo has a settings collection (title, collectionOnly) and a settings global (title, globalOnly). payload.db contains the dev user and one collection document, "Collection doc".

SQLite: silent corruption

  1. pnpm install, then pnpm dev. Log in (auto-login).
  2. Open Collections → Settings → "Collection doc".
  3. Open Globals → Settings, set Title to Global title, and save.
  4. Open the collection document again. Its title is now Global title. The REST response for the global update even returns the collection document's id.
  5. Set Collection Only on any collection document and save. The value is not stored and no error appears. The shared settings table has only id, title, global_only, updated_at, created_at.

MongoDB: startup crash

  1. pnpm dev:mongo (in-memory MongoDB).
  2. Payload fails on init with OverwriteModelError: Cannot overwrite `settings` model once compiled.

Two globals with the same slug (not in the repo, replace the collection with a second global):

globals: [
  SettingsGlobal,
  { slug: 'settings', fields: [{ name: 'secondGlobalField', type: 'text' }] },
],
  • MongoDB: Discriminator with name "settings" already exists on init.
  • SQLite: boots without errors. POST /api/globals/settings with { "title": "t", "secondGlobalField": "s" } returns success, but neither value is saved.

Expected: sanitizeConfig throws a clear error naming the duplicate slug, the same way it already does for duplicate collection slugs.

Which area(s) are affected?

area: core, db: mongodb, db: sqlite

Environment Info

Binaries:
  Node: 25.8.1
Relevant Packages:
  payload: 3.90.2
  next: 15.4.11
  @payloadcms/db-mongodb: 3.90.2
  @payloadcms/db-sqlite: 3.90.2
  @payloadcms/drizzle: 3.90.2
  @payloadcms/next/utilities: 3.90.2
  @payloadcms/ui/shared: 3.90.2
  react: 19.2.0
  react-dom: 19.2.0
Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 11 Pro

Activity

  1. added
    status: needs-triagePossible bug which hasn't been reproduced yet
    validate-reproductionAuto-added tag on create to tell bot to check recreation URL, removed after check.
    on Oct 7, 2026
  2. added
    area: coreCore Payload functionality
    db: mongodb@payloadcms/db-mongodb
    db: sqlite@payloadcms/db-sqlite
    and removed
    validate-reproductionAuto-added tag on create to tell bot to check recreation URL, removed after check.
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions