payloadcms/payload · error · UnauthorizedError
Unauthorized, you must be logged in to make this request.
Error message
Unauthorized, you must be logged in to make this request.
What it means
Thrown as UnauthorizedError (HTTP 401) by buildFormState's catch block when a downstream function threw a plain Error whose message is exactly 'Unauthorized'. buildFormState wraps form-state construction; if any inner step (e.g. a server function like renderTab) throws the generic 'Unauthorized' string, this wrapper promotes it to a proper 401 UnauthorizedError.
Source
Thrown at packages/ui/src/utilities/buildFormState.ts:81
> = async (args) => {
const { req } = args
try {
await canAccessAdmin({ req })
const res = await buildFormState(args)
return res
} catch (err) {
req.payload.logger.error({ err, msg: `There was an error building form state` })
if (err.message === 'Could not find field schema for given path') {
return {
message: err.message,
}
}
if (err.message === 'Unauthorized') {
throw new UnauthorizedError()
}
return formatErrors(err)
}
}
export const buildFormState = async (
args: BuildFormStateArgs,
): Promise<BuildFormStateSuccessResult> => {
const {
id: idFromArgs,
checkForStaleData,
collectionSlug,
data: incomingData,
docPermissions,
docPreferences,
documentFormState,
formState,View on GitHub (pinned to 00c58b35c0)
Solutions
- Ensure the admin session is valid before the form-state build is triggered.
- Make sure nested server functions invoked during form-state build receive the authenticated req.user.
- Treat the 401 in the client as a signal to redirect to login and re-fetch form state.
Example fix
// before — inner function throws bare string
if (!req.user) throw new Error('Unauthorized')
// after — throw the typed error so callers can instanceof-check
import { UnauthorizedError } from 'payload'
if (!req.user) throw new UnauthorizedError()
// buildFormState's string-match catch becomes redundant and can be removed. Defensive patterns
Strategy: try-catch
Validate before calling
function isAuthenticated(user: unknown): user is { id: string } {
return Boolean(user)
}
if (!isAuthenticated(req.user)) {
// avoid the nested 'Unauthorized' string throw entirely
redirect('/login')
} Type guard
import { UnauthorizedError } from 'payload'
function isUnauthorizedError(err: unknown): err is UnauthorizedError {
return err instanceof UnauthorizedError
} Try / catch
try {
await buildFormState({ collectionSlug, schemaPath })
} catch (err) {
if (isUnauthorizedError(err)) {
redirectToLogin()
return
}
throw err
} Prevention
- Throw UnauthorizedError (not a bare 'Unauthorized' string) in nested functions so callers can type-narrow.
- Ensure req.user is propagated to every nested server function during form-state build.
- Treat a 401 from buildFormState as a session-expiry signal and re-authenticate.
When it happens
Trigger: During form-state building, a nested operation throws new Error('Unauthorized') (the bare-string pattern from errors 391/393), and buildFormState's catch detects that exact message and re-throws UnauthorizedError.
Common situations: Session expired mid form-state build; a nested server function (renderTab/renderField) hit its !req.user guard; auth not propagated into a sub-call during form-state construction.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unauthorized
- Unauthorized
- Field config not found for ${schemaPath}
- Unauthorized, you must be logged in to make this request.
- Either collectionSlug, globalSlug, or widgetSlug must be pro
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/f41a5a1a6fcc0ebf.
Report an issue: GitHub.