halo-dev/halo · error · Error
json.errors[0]
Error message
json.errors[0]
What it means
Thrown in the email-code login Thymeleaf template after POSTing to /login/email-code/send. The Spring WebFlux backend returned a non-2xx response whose RFC 7807 Problem Details body contains an `errors` array (Spring's ProblemDetail.errors, populated by MethodArgumentNotValidException / field validation). The first element of that array — typically a string or object serialized to a message — becomes the Error message. It surfaces a per-field validation failure for the submitted email payload.
Source
Thrown at application/src/main/resources/templates/login_email-code.html:68
async function sendRequest() {
const email = document.getElementById("email").value.trim();
if (!email) {
throw new Error(/*[[#{form.emailCode.send.emptyValidation}]]*/ "");
}
const response = await fetch("/login/email-code/send", {
method: "POST",
body: JSON.stringify({ email: email }),
headers: {
"Content-Type": "application/json",
[headerName]: token,
},
});
if (!response.ok) {
const json = await response.json();
if (json.errors && json.errors.length) {
throw new Error(json.errors[0]);
}
if (json.detail) {
throw new Error(json.detail);
}
throw new Error(i18nResources.sendVerificationCodeFailed);
}
return response;
}
const emailCodeSendButton = document.getElementById("emailCodeSendButton");
sendVerificationCode(emailCodeSendButton, sendRequest);
});
</script>
</div>
View on GitHub (pinned to d2f5165f9c)
Solutions
- Inspect the actual response body in the browser Network tab for the /login/email-code/send request to see the real errors[0] text — that text identifies the failing field/rule.
- Verify the email value is non-empty and well-formed before submit; the client only checks emptiness (line 52), not format.
- Confirm the CSRF header name and token are populated (headerName/token come from Thymeleaf ${_csrf.*}); a blank headerName causes Spring to treat the request as unauthenticated and return 401/403.
- If a custom validator or plugin attaches to the email-code endpoint, review its rules against the submitted payload.
- Reproduce with curl: POST /login/email-code/send with the same JSON and CSRF header to read the full ProblemDetail.
Example fix
// before
if (json.errors && json.errors.length) {
throw new Error(json.errors[0]);
}
// after — defensive: errors[] entries may be objects, coerce to a string message
if (json.errors && json.errors.length) {
const first = json.errors[0];
throw new Error(
typeof first === "string" ? first : first?.message || first?.defaultMessage || JSON.stringify(first)
);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Validate payload + CSRF before sending the request
function validateEmailCodeRequest(email, headerName, token) {
const errors = [];
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
errors.push("A valid email address is required");
}
if (!headerName || !token) {
errors.push("Missing CSRF token — reload the login page");
}
return errors;
}
// call before fetch():
const issues = validateEmailCodeRequest(email, headerName, token);
if (issues.length) { showErrors(issues); return; } Type guard
// RFC 7807 ProblemDetail with a non-empty errors array
function hasProblemErrors(body: unknown): body is { errors: unknown[] } {
return (
typeof body === "object" && body !== null &&
Array.isArray((body as any).errors) &&
(body as any).errors.length > 0
);
} Try / catch
// Wrap sendRequest so the thrown message reaches the UI instead of rejecting unhandled
try {
await sendRequest();
} catch (e) {
// e.message is json.errors[0] from the server; display it to the user
showError(e instanceof Error ? e.message : i18nResources.sendVerificationCodeFailed);
} Prevention
- Always send the CSRF header populated by Thymeleaf (${_csrf.headerName}/${_csrf.token}); a blank header guarantees a 4xx.
- Validate email format client-side, not just emptiness, before the POST.
- Log response.status alongside the parsed body during development to distinguish 400-validation from 401/403-auth.
When it happens
Trigger: The browser POSTs {email} with the CSRF header to /login/email-code/send and the server rejects with a body like {"errors":["email must be a valid address"]}. response.ok is false, json.errors is a non-empty array, so json.errors[0] is re-thrown. Common when the email is malformed, the email-code send endpoint enforces format validation, or a plugin/hook adds validators on that route.
Common situations: Email field submitted empty or with an invalid format that bypassed the client-side trim/empty check; a newer backend version tightened validation rules; a CSRF token that expired returns 403 whose body Spring still formats with an errors array; reverse proxy (Nginx) rewrites the path and returns its own JSON error shape that happens to include `errors`.
Related errors
- json.detail
- i18nResources.sendVerificationCodeFailed
- json.errors[0]
- json.errors[0]
- Cannot resolve shared dependency ${root} from ${resolutionBa
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/987701390bc7b0b0.
Report an issue: GitHub.