Server responses
Your endpoint should return JSON. formsub reads that JSON, decides success vs error, and updates the UI (or calls your callback).
What to return
Success
json
{
"status": "success",
"code": 200,
"message": "Thanks — we got your message."
}With the default handler, the form is cleared shortly after a success.
Error, with field messages
json
{
"status": "error",
"code": 400,
"message": "Please fix the highlighted fields.",
"errors": [
{ "name": "email", "error": "Email is already registered" }
]
}Each errors item should include:
nameorfield— matches the HTMLnameerrorormessage— text shown next to the input
Items may also be plain strings.
How success and error are detected
Success when any of these match:
status === "success"orstatus === 200code === 200orcode === "success"orcode === "ok"/"OK"
Error when any of these match:
status === "error"orstatus === 400code === 400orcode === "error"/"ERROR"
Prefer status
Sending both status and code is fine. A single "status": "success" is enough.
If the body is not JSON, or the network fails, formsub still calls your callback / default handler with a synthesized error-shaped object.
Express (Node)
js
import express from 'express';
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.post('/api/contact', (req, res) => {
const { email, name } = req.body;
if (!email) {
return res.status(400).json({
status: 'error',
code: 400,
message: 'Validation failed',
errors: [{ name: 'email', error: 'Email is required' }],
});
}
// save message...
res.json({
status: 'success',
code: 200,
message: 'Message sent!',
});
});
app.listen(3000);For file uploads, add middleware such as multer. FormData from the browser is multipart/form-data, not JSON.
Node (native HTTP)
js
import { createServer } from 'node:http';
import { parse } from 'node:querystring';
createServer((req, res) => {
if (req.method !== 'POST' || req.url !== '/api/contact') {
res.writeHead(404);
res.end();
return;
}
let body = '';
req.on('data', (chunk) => { body += chunk; });
req.on('end', () => {
const data = parse(body);
const json = (status, payload) => {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(payload));
};
if (!data.email) {
return json(400, {
status: 'error',
code: 400,
message: 'Validation failed',
errors: [{ name: 'email', error: 'Email is required' }],
});
}
json(200, {
status: 'success',
code: 200,
message: 'Message sent!',
});
});
}).listen(3000);PHP
php
<?php
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['status' => 'error', 'message' => 'Method not allowed']);
exit;
}
$email = $_POST['email'] ?? '';
$name = $_POST['name'] ?? '';
if ($email === '') {
http_response_code(400);
echo json_encode([
'status' => 'error',
'code' => 400,
'message' => 'Validation failed',
'errors' => [['name' => 'email', 'error' => 'Email is required']],
]);
exit;
}
// save message...
echo json_encode([
'status' => 'success',
'code' => 200,
'message' => 'Message sent!',
]);With multipart uploads, text fields are in $_POST and files in $_FILES.