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

  1. 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.
  2. Verify the email value is non-empty and well-formed before submit; the client only checks emptiness (line 52), not format.
  3. 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.
  4. If a custom validator or plugin attaches to the email-code endpoint, review its rules against the submitted payload.
  5. 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

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


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/987701390bc7b0b0. Report an issue: GitHub.