SDK does not throw errors instead returns undefined
Describe the Bug
SDK package version 3.62.1
When a Payload API request fails (e.g., 400 Bad Request), the PayloadSDK returns undefined instead of throwing an error. This makes it impossible to properly handle API errors and provides a poor developer experience.
Expected Behavior
When an API request fails, the SDK should throw an error that can be caught and handled appropriately. This would allow:
- Proper error handling in try/catch blocks
- React Query's
onErrorcallback to fire - Users to see appropriate error messages instead of success messages
Actual Behavior
The SDK returns undefined when a request fails, causing:
- No way to easily access error details from the API response
- Silent failures that are difficult to debug
import { type Config } from '@/payload-types'
import { PayloadSDK } from '@payloadcms/sdk'
export const sdk = new PayloadSDK<Config>({
baseURL: process.env.NEXT_PUBLIC_SERVER_URL + '/api',
baseInit: {
credentials: 'include',
},
fetch: typeof window !== 'undefined' ? window.fetch.bind(window) : globalThis.fetch,
})
export default sdk
{
"errors": [
{
"name": "ValidationError",
"data": {
"collection": "waitlist-email",
"errors": [
{
"message": "Value must be unique",
"path": "email"
}
]
},
"message": "The following field is invalid: email"
}
]
}
Link to the code that reproduces this issue
see below
Reproduction Steps
- Set up a Payload SDK instance with a custom fetch
- Attempt to create a document that violates validation (e.g., duplicate unique field)
- Observe that the SDK returns
undefinedinstead of throwing an error
import { PayloadSDK } from '@payloadcms/sdk'
const sdk = new PayloadSDK<Config>({
baseURL: process.env.NEXT_PUBLIC_SERVER_URL + '/api',
baseInit: {
credentials: 'include',
},
fetch: typeof window !== 'undefined' ? window.fetch.bind(window) : globalThis.fetch,
})
// This returns undefined when the API returns 400 Bad Request
const response = await sdk.create({
collection: 'waitlist-email',
data: {
email: '[email protected]', // Assuming this email already exists
},
})
console.log(response) // undefined
API Response
The API correctly returns a 400 Bad Request with error details:
{
"errors": [
{
"name": "ValidationError",
"data": {
"collection": "waitlist-email",
"errors": [
{
"message": "Value must be unique",
"path": "email"
}
]
},
"message": "The following field is invalid: email"
}
]
}
However, this error information is lost because the SDK returns undefined instead of throwing an error with this data.
Which area(s) are affected? (Select all that apply)
plugin: other
Environment Info
Binaries:
Node: 22.14.0
npm: 10.9.2
Yarn: N/A
pnpm: 10.6.5
Relevant Packages:
payload: 3.62.1
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:40 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6041
Available memory (MB): 24576
Available CPU cores: 12
Please add a reproduction in order for us to be able to investigate.
Depending on the quality of reproduction steps, this issue may be closed if no reproduction is provided.
Why was this issue marked with the invalid-reproduction label?
To be able to investigate, we need access to a reproduction to identify what triggered the issue. We prefer a link to a public GitHub repository created with create-payload-app@latest -t blank or a forked/branched version of this repository with tests added (more info in the reproduction-guide).
To make sure the issue is resolved as quickly as possible, please make sure that the reproduction is as minimal as possible. This means that you should remove unnecessary code, files, and dependencies that do not contribute to the issue. Ensure your reproduction does not depend on secrets, 3rd party registries, private dependencies, or any other data that cannot be made public. Avoid a reproduction including a whole monorepo (unless relevant to the issue). The easier it is to reproduce the issue, the quicker we can help.
Please test your reproduction against the latest version of Payload to make sure your issue has not already been fixed.
I added a link, why was it still marked?
Ensure the link is pointing to a codebase that is accessible (e.g. not a private repository). "example.com", "n/a", "will add later", etc. are not acceptable links -- we need to see a public codebase. See the above section for accepted links.
Useful Resources
Underlying cause is because the various methods (find, global, etc) effectively
-
fetchfrom the backend - return the
response.json()promise
They never check response.ok. If you want it to throw instead and want a quick and dirty workaround, you could patch the package to modify the request method that they all call to throw an error response if not ok, and use the cause for the status.
https://github.com/payloadcms/payload/blob/87137febd4311f3cded321542cd1c8a163147254/packages/sdk/src/index.ts#L275-L314
I think the real fix is to change the API response to include this information. For example, the api package (which generates libraries from swagger defs), returns:
{
data: isJson ? await response.json() : await response.text(),
status: response.status,
headers: response.headers,
res: response
}
And throws a custom error on 4XX-5XX responses.
But that would be a breaking change.
Thanks ! Managed to do a little work around using this for now.
import { type Config } from '@/payload-types'
import { PayloadSDK } from '@payloadcms/sdk'
const originalFetch = typeof window !== 'undefined' ? window.fetch.bind(window) : globalThis.fetch
const wrappedFetch: typeof fetch = async (...args) => {
const response = await originalFetch(...args)
if (!response.ok) {
const errorData = await response.json().catch(() => response)
throw errorData
}
return response
}
export const sdk = new PayloadSDK<Config>({
baseURL: process.env.NEXT_PUBLIC_SERVER_URL + '/api',
baseInit: {
credentials: 'include',
},
fetch: wrappedFetch,
})
export default sdk