payloadcms/payload · error · APIError

No User

Error message

No User

What it means

Thrown at the top of the logout operation when `req.user` is falsy. Payload resolves the authenticated user from the JWT/cookie middleware before the operation runs, so reaching this throw means no valid credential reached the handler. It surfaces as an APIError with HTTP 400 (BAD_REQUEST). It exists because logout is meaningless without a session to destroy.

Source

Thrown at packages/payload/src/auth/operations/logout.ts:28

import { killTransaction } from '../../utilities/killTransaction.js'

export type Arguments = {
  allSessions?: boolean
  collection: Collection
  req: PayloadRequest
}

export const logoutOperation = async (incomingArgs: Arguments): Promise<boolean> => {
  let args = incomingArgs
  const {
    allSessions,
    collection: { config: collectionConfig },
    req: { user },
    req,
  } = incomingArgs

  if (!user) {
    throw new APIError('No User', httpStatus.BAD_REQUEST)
  }
  if (user.collection !== collectionConfig.slug) {
    throw new APIError('Incorrect collection', httpStatus.FORBIDDEN)
  }

  const shouldCommit = await initTransaction(req)

  try {
    if (collectionConfig.hooks?.afterLogout?.length) {
      for (const hook of collectionConfig.hooks.afterLogout) {
        args =
          (await hook({
            collection: args.collection?.config,
            context: req.context,
            req,
          })) || args
      }
    }

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Confirm the request reaches Payload with a valid `payload-token` cookie or `Authorization: JWT <token>` header.
  2. If using the Local API, pass the authenticated request object: `await payload.logout({ collection, req: authenticatedReq })` rather than a fresh `createLocalReq({})`.
  3. Check that `config.csrf` / cookie `sameSite` and the reverse proxy are not dropping the cookie.
  4. Verify the JWT has not already expired before issuing the logout call.

Example fix

// before
await payload.logout({ collection: 'users', req: createLocalReq({}, payload) })
// after
const req = await createLocalReq({ user: loggedInUser }, payload)
await payload.logout({ collection: 'users', req })
Defensive patterns

Strategy: validation

Validate before calling

// Before logout, ensure a user is attached to the request
if (!req.user) {
  // nothing to log out; clear client state locally
  return clearLocalSession()
}
await payload.logout({ collection: req.user.collection, req })

Type guard

// Narrow the request to an authenticated one before calling logout
function isAuthenticatedReq(req: PayloadRequest): req is PayloadRequest & { user: User } {
  return !!req.user && typeof req.user.collection === 'string'
}

Try / catch

try {
  await payload.logout({ collection, req })
} catch (e) {
  if (e instanceof APIError && e.status === 400 && e.message === 'No User') {
    // no active session — clear client token
  } else throw e
}

Prevention

When it happens

Trigger: A client calls the logout endpoint (e.g. `POST /api/<collection>/logout`) with no `payload-token` cookie/`Authorization` header, or with an already-expired token. It also occurs when calling `payload.logout()` via the Local API with a `req` that has no `user` attached (e.g. a `createLocalReq({})` without injecting credentials).

Common situations: Frontend logs the token out then immediately retries logout; the auth middleware is misconfigured or skipped for that route; a proxy/gateway strips the cookie header; a custom Local API script forgets to pass the authenticated `req`.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/22664ec517d31868. Report an issue: GitHub.