Skip to content

Instantly share code, notes, and snippets.

@cbratschi
Created October 21, 2026 16:39
Show Gist options
  • Select an option

  • Save cbratschi/c97633bfa3acc0b6cc6cadac2ccfd020 to your computer and use it in GitHub Desktop.

Select an option

Save cbratschi/c97633bfa3acc0b6cc6cadac2ccfd020 to your computer and use it in GitHub Desktop.
Payload 3.91.0 MongoDB: conflicting indexes on separate globals (standalone reproduction)

Payload MongoDB global index-name conflict

Standalone reproduction with stock Payload 3.91.0, its MongoDB adapter 3.91.0, and Mongoose 8.24.1. No application plugins or Next.js server are needed.

Use Node.js 26 and an existing local MongoDB replica set. The tested server is MongoDB 8.0.28. Save all three files in one directory, then run:

npm install
MONGODB_URI='mongodb://127.0.0.1:27017/?replicaSet=rs0&directConnection=true' npm run reproduce

Adjust the connection URI for your local development replica set. Do not use a production database. Each execution explicitly selects a fresh payload_global_index_repro_<UUID> database; it does not read or drop any existing application database. It leaves its synthetic database for inspection.

The script configures two globals with different slugs and versions.drafts. It awaits every Mongoose model's normal initialization to expose the rejected background index build. The index named _status_1 has different partialFilterExpression.globalType values for the two discriminators, so the second definition fails with MongoDB code 86 (IndexKeySpecsConflict). Which global wins may vary.

The script exits successfully when that expected failure is reproduced. It also writes and rereads both globals to distinguish incomplete indexing from content loss or an unconditional startup failure. After an upstream fix, the version-sensitive conflict assertion should fail and be replaced with assertions that all intended indexes exist.

{
"name": "payload-global-index-conflict-reproduction",
"private": true,
"version": "1.0.0",
"scripts": {
"reproduce": "node reproduce.cjs"
},
"engines": {
"node": ">=26"
},
"dependencies": {
"@payloadcms/db-mongodb": "3.91.0",
"payload": "3.91.0"
},
"overrides": {
"mongoose": "8.24.1"
}
}
const assert = require('node:assert/strict');
const { randomBytes, randomUUID } = require('node:crypto');
const { mongooseAdapter } = require('@payloadcms/db-mongodb');
const { buildConfig, getPayload } = require('payload');
/**
* Reproduce with stock Payload only, using a new synthetic database per run.
*
* @returns Nothing after the expected index conflict and persisted readback pass.
*/
async function main() {
assert.ok(process.env.MONGODB_URI, 'Set MONGODB_URI to a local development replica set.');
const databaseName = `payload_global_index_repro_${randomUUID().replaceAll('-', '')}`;
const config = await buildConfig({
secret: randomBytes(48).toString('hex'),
db: mongooseAdapter({
url: process.env.MONGODB_URI,
connectOptions: { dbName: databaseName, serverSelectionTimeoutMS: 10000 },
}),
collections: [],
globals: ['alpha', 'beta'].map((slug) => ({
slug,
fields: [{ name: 'title', type: 'text' }],
versions: { drafts: true },
})),
typescript: { autoGenerate: false },
});
const payload = await getPayload({ config, key: databaseName });
try {
const models = Object.values(payload.db.connection.models);
const initialized = await Promise.allSettled(models.map((model) => model.init()));
const errors = initialized.flatMap((result, index) => result.status === 'rejected'
? [{ model: models[index].modelName, code: result.reason.code, message: result.reason.message }]
: []);
console.log(JSON.stringify({
databaseName,
errors,
indexes: await payload.db.globals.collection.indexes(),
}, null, 2));
assert.ok(errors.length > 0, 'Expected the current index-name collision.');
assert.ok(errors.every((error) => error.code === 86), 'Expected IndexKeySpecsConflict (86).');
for (const slug of ['alpha', 'beta']) {
await payload.updateGlobal({
slug,
overrideAccess: true,
data: { title: `Saved ${slug}`, _status: 'published' },
});
const current = await payload.findGlobal({ slug, overrideAccess: true });
assert.equal(current.title, `Saved ${slug}`);
}
console.log('Conflict reproduced; both global writes and persisted readbacks still passed.');
} finally {
await payload.destroy();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment