diff --git a/packages/db-mongodb/src/queries/sanitizeQueryValue.ts b/packages/db-mongodb/src/queries/sanitizeQueryValue.ts index ca9bb27e7b6..79fa5ebc3f0 100644 --- a/packages/db-mongodb/src/queries/sanitizeQueryValue.ts +++ b/packages/db-mongodb/src/queries/sanitizeQueryValue.ts @@ -210,7 +210,7 @@ export const sanitizeQueryValue = ({ } if (['relationship', 'upload'].includes(field.type)) { - if (val === 'null') { + if (val === 'null' || (Array.isArray(val) && val.length === 1 && val[0] === 'null')) { formattedValue = null } diff --git a/packages/drizzle/src/queries/sanitizeQueryValue.ts b/packages/drizzle/src/queries/sanitizeQueryValue.ts index 5959a02722c..fc8d2abc7bc 100644 --- a/packages/drizzle/src/queries/sanitizeQueryValue.ts +++ b/packages/drizzle/src/queries/sanitizeQueryValue.ts @@ -141,7 +141,7 @@ export const sanitizeQueryValue = ({ } if (field.type === 'relationship' || field.type === 'upload') { - if (val === 'null') { + if (val === 'null' || (Array.isArray(val) && val.length === 1 && val[0] === 'null')) { formattedValue = null } else if (!(formattedValue === null || typeof formattedValue === 'boolean')) { // convert the value to the idType of the relationship diff --git a/test/relationships/int.spec.ts b/test/relationships/int.spec.ts index 930f5456cb8..ff184d29376 100644 --- a/test/relationships/int.spec.ts +++ b/test/relationships/int.spec.ts @@ -704,6 +704,58 @@ describe('Relationships', () => { expect(query.docs[0].text).toEqual('Tree 3') expect(query.docs[1].text).toEqual('Tree 4') }) + + it('should query using "equals: null" on a hasMany relationship field', async () => { + await payload.delete({ collection: 'directors', where: {} }) + await payload.delete({ collection: 'movies', where: {} }) + + const movie = await payload.create({ + collection: 'movies', + data: { name: 'Some Movie' }, + }) + + await payload.create({ + collection: 'directors', + data: { + name: 'Director With Movies', + movies: [movie.id], + }, + }) + + const directorWithoutMovies = await payload.create({ + collection: 'directors', + data: { + name: 'Director Without Movies', + }, + }) + + // Test programmatic API with null + const programmaticResult = await payload.find({ + collection: 'directors', + depth: 0, + where: { + movies: { equals: null }, + }, + }) + + expect(programmaticResult.totalDocs).toBe(1) + expect(programmaticResult.docs[0]?.id).toBe(directorWithoutMovies.id) + + // Test REST API with ['null'] — the form the admin UI sends for "None" + const restResult = await restClient + .GET(`/directors`, { + query: { + where: { + movies: { equals: ['null'] }, + }, + depth: 0, + }, + }) + .then((res) => res.json() as Promise<{ docs: Director[]; totalDocs: number }>) + + expect(restResult.totalDocs).toBe(1) + expect(restResult.docs[0]?.id).toBe(directorWithoutMovies.id) + }) }) describe('sorting by relationships', () => {