Skip to content

AJAX submissions

By default, Formward responds to a form POST with a 302 redirect (the standard browser behaviour). For single-page apps or custom UIs where you want to stay on the page and handle the response yourself, send the request with an Accept: application/json header and Formward returns 200 { ok: true, id } instead. An X-Requested-With header works too, but Accept is the one to use.

Detection

Formward detects an AJAX request by inspecting the incoming headers. Either of these triggers JSON mode, and Accept is the recommended one:

  • Accept header contains application/json
  • X-Requested-With header is present (any value). It is allowed in the cross-origin preflight, so it works from the browser, butAccept is a CORS-safelisted header and never needs a preflight entry.

fetch() example

async function submitForm(form) {
  const data = new FormData(form);

  const res = await fetch("https://forms.formward.eu/f/<formId>", {
    method: "POST",
    headers: {
      Accept: "application/json",
    },
    body: new URLSearchParams(data),
  });

  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    // See status codes in Responses & redirects
    throw new Error(body.error ?? `HTTP ${res.status}`);
  }

  const { ok, id } = await res.json();
  // ok: true, id: "<submissionId>"
  console.log("Submission recorded:", id);
}

Successful response

HTTP/1.1 200 OK
Content-Type: application/json

{ "ok": true, "id": "clxyz123..." }

The id is the submission ID assigned by Formward. You can use it to cross-reference entries in the dashboard.

Error responses

Error responses are also JSON when the request was sent in AJAX mode. The body shape is { ok: false, error: string }, with an additional upgrade: true field on plan-limit errors. See Responses & redirects for the full status code table.

Sending JSON vs form-encoded

The ingestion server accepts application/x-www-form-urlencoded bodies (standard form POST), application/json bodies and multipart/form-data (for file uploads). When submitting via fetch the simplest option is new URLSearchParams(data) as the body: it produces a form-encoded body without needing to set a Content-Type header manually. To send JSON, set Content-Type: application/json and pass JSON.stringify(data); the body must be a JSON object.

CORS

Cross-origin requests are gated by the allowed origins list on your form. If the Origin header of the request is not on that list (and the list is non-empty), the server returns 403. Add your site's origin in the form settings before going live.