Changelog - 2026-10-10
The data provider throws an ApplicationError and reads Content-Range
Behavior Change Breaking ChangeIn one line. On a non-2xx response DefaultRestDataProvider, CountRestDataProvider and send now reject with an ApplicationError built from the server's error envelope, where they used to rethrow the parsed body.
What changed
- A real error. The body is read under the
errorroot key and mapped like IGNISHttpDataSource, so a repository and the data provider throw the same fields. UnlikeHttpDataSource,messageis the server's text and not a log line, because react-admin showserror.message.useNotifyErroralready typed its argument as anApplicationError; it now gets one. - What the fields hold.
statusCodeis the HTTP status, always set.messageandnormalized.textare the server'snormalized.text, else itsmessage, elseHTTP <status>.normalized.codeis the server's code, orcore.system_errorwhen it sent none.normalized.argsis the server'sargsor{}.extrais the server'sextraplusrequestId, andundefinedwhen both are empty. A relaying server's earlierextra.requestIdmoves toextra.upstreamRequestIds, nearest first.causeis the server'sdetails.cause, such as a422's per-field issues.
- What is dropped. Any other key of the body (
type,path,url, the rest ofdetails, ad hoc fields such asfield), and the top-levelrequestId, which is inextra.requestIdnow. - Bodies that are not an envelope. A body that is not JSON, is empty, is an array, or holds a string under
errorno longer leaks aSyntaxErroror throws a bare string. It becomesHTTP <status>with the default code. A body without theerrorkey is read as it stands, so{ message: 'Validation failed' }still gives that message. Content-Range. ForgetListandgetManyReference,records a-b/Nandrecords */NgiveN, and an absent header gives the page's row count, as before. A malformed header (records 0-24/*,137, an empty string) now gives the row count too, where it gaveNaNor the bare number.
Who is affected
- Code that reads
error.statusCode,error.normalized.code,error.normalized.argsorerror.extra. No action needed. - Code that reads
error.requestId. Readerror.extra?.requestId. - Code that reads
type,path,url,detailsor an ad hoc field off a caught error. These are no longer on it.details.causeiserror.cause. - Code that checks for a string, or catches a
SyntaxError, from a failed request. An error body that is not an envelope is anApplicationErrorwithHTTP <status>. - A
checkErrorthat matches onstatus. AnApplicationErrorhasstatusCodeand nostatus, so a server body that carried astatuskey no longer reachesDefaultAuthProvider.checkErroras one. The IGNIS envelope sendsstatusCode, whichcheckErrordoes not read. - Lists whose server sends a malformed
Content-Range.totalis the page's row count instead ofNaN. Fix the header, or useCountRestDataProvider.
Breaking changes
WARNING
Narrow: only code that reads a field the old body carried but the envelope does not, or that depends on the thrown value not being an Error.
Before:
typescript
// The server answered 409 with:
// { error: { statusCode: 409, message: 'Order already paid', requestId: 'req-1', type: 'ConflictError' } }
// ...
try {
await dataProvider.update('orders', { id: 7, data, previousData });
} catch (error) {
// `error` was the parsed `error` object, whatever it held
error.requestId; // 'req-1'
error.type; // 'ConflictError'
error instanceof Error; // false
const copy = { ...error }; // has `message`
}After:
typescript
// The same response
// ...
try {
await dataProvider.update('orders', { id: 7, data, previousData });
} catch (error) {
// `error` is an ApplicationError
error.statusCode; // 409
error.message; // 'Order already paid'
error.extra?.requestId; // 'req-1'
error.type; // undefined: dropped
error instanceof Error; // true
const copy = { ...error }; // no `message`: an Error keeps it as a non-enumerable own property
}Migration guide
- Replace
error.requestIdwitherror.extra?.requestId. - Replace a read of
error.details.causewitherror.cause. The otherdetailskeys,type,pathandurlare not on the error. - If you spread an error to copy it (
{ ...error }), copymessageexplicitly:{ ...error, message: error.message }. - If a
catchbranch testedtypeof error === 'string'orerror instanceof SyntaxErrorfor a failed request, testerror.statusCodeinstead.
See Network and Data provider.