Regression: `findByID` with `disableErrors: true` throws `NotFound` for null/empty ids since 3.91.0
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 75/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- next.js, node.js, typescript
Research direction
Read the findByID implementation changed in commit #18000 and trace where the invalid-ID guard handles disableErrors. Reproduce the null-ID case from the issue, then check null, undefined, and empty-string IDs. Done means each returns null with disableErrors: true, while invalid IDs still raise NotFound when it is false.
Written by the indexing model from the issue text.
Description
Describe the Bug
Since 3.91.0, payload.findByID() throws NotFound when the id is null, undefined or "", even with disableErrors: true. In 3.90.2 the same call returned null.
The docs describe disableErrors as "When set to true, errors will not be thrown. Instead, the findByID operation will return null." That no longer holds for invalid ids.
The change came in with #18000 ("check draft read access against the latest version in findByID", 9106bf8). The release notes don't mention it as a behavior change. That commit replaced this guard:
if (!findOneArgs.where?.and?.[0]?.id) {
throw new NotFound(t)
}
with an explicit id check that runs before the database query:
const isValidID =
(typeof id === 'string' && id.length > 0) || (typeof id === 'number' && Number.isFinite(id))
if (!isValidID) {
throw new NotFound(t)
}
The old guard never fired for a null id. where.and[0].id is the { equals: null } object, which is truthy, so the query ran, found nothing, and disableErrors turned that into null. The new guard does fire, and it runs before disableErrors is consulted.
Impact: a common pattern for resolving optional upload fields in server components started throwing after a minor upgrade:
const media = await payload.findByID({
collection: 'media',
id: block.backgroundImage, // null when the field is empty
disableErrors: true,
})
The NotFound propagates as a Server Components render error, so in production every page with an empty upload field returns a 500.
Suggested fix: honor disableErrors in the new guard:
if (!isValidID) {
if (disableErrors) {
return null
}
throw new NotFound(t)
}
Link to the code that reproduces this issue
https://gist.github.com/mtolmacs/beb3fdc491ee346557a973a6672f1837
Reproduction Steps
- Create a blank app with
npx create-payload-app@latestand setpayloadto3.91.0. - Run this from a server component, route handler or script:
const payload = await getPayload({ config }) const result = await payload.findByID({ collection: 'media', id: null as unknown as number, disableErrors: true, }) console.log(result) - Expected (and what 3.90.2 does): logs
null.
Actual: throwsNotFound: Not Found(status 404).
The same happens with id: undefined and id: ''.
Which area(s) are affected?
area: core
Environment Info
Binaries:
Node: 24.15.0
npm: 11.12.1
Yarn: N/A
pnpm: 11.24.0
Relevant Packages:
payload: 3.91.0
next: 16.3.8
@payloadcms/db-postgres: 3.91.0
@payloadcms/db-sqlite: 3.91.0
@payloadcms/drizzle: 3.91.0
@payloadcms/email-nodemailer: 3.91.0
@payloadcms/email-resend: 3.91.0
@payloadcms/graphql: 3.91.0
@payloadcms/live-preview: 3.91.0
@payloadcms/live-preview-react: 3.91.0
@payloadcms/next/utilities: 3.91.0
@payloadcms/plugin-cloud-storage: 3.91.0
@payloadcms/plugin-import-export: 3.91.0
@payloadcms/plugin-redirects: 3.91.0
@payloadcms/plugin-seo: 3.91.0
@payloadcms/richtext-lexical: 3.91.0
@payloadcms/storage-vercel-blob: 3.91.0
@payloadcms/translations: 3.91.0
@payloadcms/ui/shared: 3.91.0
react: 19.2.4
react-dom: 19.2.4
- Dominant language
- TypeScript
- Stars
- 45.5k
- Forks
- 4.3k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 299
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from payloadcms/payload
-
WhereBuilder throws "Cannot read properties of undefined (reading 'value')" when the list URL filters on a field that is not filterablePossibly taken A pull request linked to this issue is open or already merged. OpenBug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#19002 · 1 comment ·
Maintainers usually reply within 1 day
-
bug: Local API call with req resets req.fallbackLocale to defaultLocalePossibly taken A pull request linked to this issue is open or already merged. Openarea: core Bug created-by: Contributor
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#18768 ·
Maintainers usually reply within 1 day
-
[plugin-multi-tenant] Infinite redirect loop with custom tenantFieldName and isGlobal collectionsPossibly taken A pull request linked to this issue is open or already merged. OpenBug plugin: multi-tenant status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
payloadcms/payload#18690 ·
Maintainers usually reply within 1 day
-
Global update drops overrideAccess, so Local API updateGlobal fails filterOptions validation (3.90 regression)Possibly taken @PINYOPATTANAWASANPORN claimed this 12 days ago. Openarea: core Bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
payloadcms/payload#18674 · 2 comments · 1 reaction ·
Maintainers usually reply within 1 day
-
db-postgres: connect() rejects its `initializing` promise with no argument, producing an unhandledRejection with reason `undefined`Possibly taken @Metehan-Bicer claimed this 15 days ago. Openarea: db-postgres Bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
payloadcms/payload#18656 · 1 comment ·
Maintainers usually reply within 1 day
All issues in payloadcms/payload
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 70/100
microsoft/playwright#43640 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
grasshopper-protocol/grsshppr#172 ·
Maintainers usually reply within 1 day
-
bug help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Tencent/teamai-cli#1397 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
DouyinFE/semi-design#3641 ·
Maintainers usually reply within 3 days
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
Maintainers usually reply within 1 day