Skip to content

Contact Form API

The contact form endpoint handles submissions from the ContactForm component.

POST /api/contact

multipart/form-data (FormData)

FieldTypeRequiredValidation
namestringYes2-100 characters
emailstringYesValid email format
messagestringYes10-5000 characters
honeypotstringNoMust be empty (spam check)
const form = document.querySelector('form');
const formData = new FormData(form);
const response = await fetch('/api/contact', {
method: 'POST',
body: formData,
});
const result = await response.json();
{
"success": true
}
{
"success": false,
"errors": {
"email": ["Please enter a valid email address"],
"message": ["Message must be at least 10 characters"]
}
}
{
"success": false,
"error": "Internal server error"
}

The endpoint is defined in src/pages/api/contact.ts:

import type { APIRoute } from 'astro';
import { z } from 'zod';
const contactSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
message: z.string().min(10).max(5000),
honeypot: z.string().max(0),
});
export const POST: APIRoute = async ({ request }) => {
const formData = await request.formData();
const data = Object.fromEntries(formData);
const result = contactSchema.safeParse(data);
if (!result.success) {
return new Response(
JSON.stringify({
success: false,
errors: result.error.flatten().fieldErrors,
}),
{ status: 400 }
);
}
// Process the submission (email, database, etc.)
await processContactSubmission(result.data);
return new Response(
JSON.stringify({ success: true }),
{ status: 200 }
);
};
import { sendEmail } from '@/lib/email';
await sendEmail({
to: 'hello@yoursite.com',
subject: `Contact from ${data.name}`,
body: data.message,
replyTo: data.email,
});

Override the endpoint in environment:

Terminal window
CONTACT_FORM_ENDPOINT=https://api.yoursite.com/contact

The honeypot field provides basic spam protection:

<!-- Hidden field - bots fill it, humans don't -->
<input type="text" name="honeypot" class="hidden" tabindex="-1" />

Any submission with a non-empty honeypot is rejected.