Repository navigation
Collection and global slugs are not validated for uniqueness #18584
Description
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
pnpm install, thenpnpm dev. Log in (auto-login).- Open Collections → Settings → "Collection doc".
- Open Globals → Settings, set Title to
Global title, and save. - Open the collection document again. Its title is now
Global title. The REST response for the global update even returns the collection document'sid. - Set Collection Only on any collection document and save. The value is not stored and no error appears. The shared
settingstable has onlyid, title, global_only, updated_at, created_at.
MongoDB: startup crash
pnpm dev:mongo(in-memory MongoDB).- 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 existson init. - SQLite: boots without errors.
POST /api/globals/settingswith{ "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
fix(core): validate collection and global slug uniqueness in sanitize…