From Form to Function: Building a Contact Form with Astro API Endpoints

January 10, 2026
Series: Astro
Tags: astro , cloudflare , webdev

One of Astro’s greatest advantages over other static-site generators is the flexibility. Officially, generators like Jekyll or Hugo do not support features like contact forms; you need a separate API or edge function for that. Astro, however, supports custom API endpoints that run on the edge.

Prerequisites

You’ll need:

  • An Astro project (v5+)
  • The Cloudflare adapter installed
npm create astro@latest
Create a new project if you don
npx astro add cloudflare
Add the Cloudflare adapter for edge functions.

Astro endpoints

By default, Astro pre-renders all pages into static HTML. For server-side logic like form handling, you need to mark specific routes as dynamic.

Astro provides two approaches:

Static mode (default)

Most pages are pre-rendered. Add export const prerender = false to specific files that need server-side logic.

export const prerender = false;
Opt-out of pre-rendering for dynamic routes.

Server mode

All pages render at request time. Add export const prerender = true to static pages.

astro.config.mjs
import cloudflare from '@astrojs/cloudflare';
import { defineConfig } from "astro/config";

export default defineConfig({
  output: "server",
  adapter: cloudflare(),
});
Only use this if most of your site is dynamic.

For a contact form, stick with static mode. Only the API endpoint needs to be dynamic.

Building the contact form

1. Create the contact page

src/pages/contact.astro
---
const contactFormAction = "/api/contact";

// This page can be pre-rendered (static HTML)
export const prerender = true;
---

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Contact Us</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>
      body {
        font-family: sans-serif;
        padding: 2rem;
        max-width: 600px;
        margin: auto;
      }

      form {
        display: flex;
        flex-direction: column;
        gap: 1rem;
      }

      input, textarea, button {
        padding: 0.5rem;
        font-size: 1rem;
      }

      button {
        background-color: #333;
        color: #fff;
        border: none;
        cursor: pointer;
      }

      button:hover {
        background-color: #555;
      }
    </style>
  </head>
  <body>
    <h1>Contact Us</h1>
    <form method="POST" action={contactFormAction}>
      <label>
        Name:
        <input type="text" name="name" required />
      </label>
      <label>
        Email:
        <input type="email" name="email" required />
      </label>
      <label>
        Message:
        <textarea name="message" rows="5" required></textarea>
      </label>
      <button type="submit">Send Message</button>
    </form>
  </body>
</html>
A simple HTML form that POSTs to our API endpoint.

2. Create the API endpoint

src/pages/api/contact.ts
// This endpoint must run on the server
export const prerender = false;

import type { APIRoute } from "astro";

export const POST: APIRoute = async ({ request }) => {
  const formData = await request.formData();
  const name = formData.get("name");
  const email = formData.get("email");
  const message = formData.get("message");

  // Validate inputs
  if (!name || !email || !message) {
    return new Response("Missing required fields", { status: 400 });
  }

  // Here you could:
  // - Send an email (via Resend, SendGrid, etc.)
  // - Store in a database
  // - Forward to a webhook (Slack, Discord)
  // - Write to Cloudflare KV
  
  console.log("Contact form submission:", { name, email, message });

  // Redirect to thank-you page
  return new Response(null, {
    status: 302,
    headers: {
      Location: "/contact-success",
    },
  });
};
This runs on the server and handles form submissions.

3. Create a success page

src/pages/contact-success.astro
---
export const prerender = true;
---

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Message Sent</title>
  </head>
  <body>
    <h1>Thank you!</h1>
    <p>Your message has been sent. We'll get back to you soon.</p>
    <a href="/">← Back to home</a>
  </body>
</html>

Project structure

src/
├── pages/
│   ├── contact.astro        # Static form page
│   ├── contact-success.astro # Static thank-you page
│   └── api/
│       └── contact.ts       # Dynamic API endpoint

Testing locally

npm run dev

Navigate to http://localhost:4321/contact, fill out the form, and submit. Check your terminal for the logged output.

Deploying to Cloudflare

npm run build
Build the project.
npx wrangler pages deploy ./dist --project-name=your-project-name
Deploy to Cloudflare Pages.

Debugging

  • Server logs: Use npx wrangler tail to monitor logs in real-time
  • Network tab: Check browser dev tools for the POST request
  • Console: Server-side console.log appears in the terminal, not the browser

Next steps

To make the form actually send emails, you could integrate:

You could also add client-side validation, CAPTCHA protection, or rate limiting for production use.