Works with every framework

Add form handling to your project in under a minute. Copy the code, replace the endpoint ID, and you're live.

HTML (Plain)

Works with any static HTML page. No JavaScript required. Just set the form action to your FormCatch endpoint and you are done.

html--plain-
<form action="https://formcatch.vercel.app/s/YOUR_FORM_ID" method="POST">
  <input type="text" name="name" placeholder="Your name" required />
  <input type="email" name="email" placeholder="Your email" required />
  <textarea name="message" placeholder="Your message"></textarea>

  <!-- Honeypot spam protection (keep this hidden) -->
  <input type="text" name="_honey" style="display:none" />

  <!-- Optional: redirect after submission -->
  <input type="hidden" name="_redirect" value="https://yoursite.com/thanks" />

  <button type="submit">Send</button>
</form>

React / Next.js

Use a standard form with fetch or let the browser handle the POST. Works with Next.js App Router, Pages Router, and plain React.

react---next-js
'use client'
import { useState } from 'react'

export default function ContactForm() {
  const [status, setStatus] = useState('')

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    const form = e.currentTarget
    const data = new FormData(form)

    const res = await fetch('https://formcatch.vercel.app/s/YOUR_FORM_ID', {
      method: 'POST',
      body: data,
    })

    if (res.ok) {
      setStatus('Message sent!')
      form.reset()
    } else {
      setStatus('Something went wrong.')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" placeholder="Name" required />
      <input type="email" name="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" />
      <input type="text" name="_honey" style={{ display: 'none' }} />
      <button type="submit">Send</button>
      {status && <p>{status}</p>}
    </form>
  )
}

Vue / Nuxt

Works with Vue 3 Composition API and Nuxt 3. Use fetch to submit the form data asynchronously.

vue---nuxt
<script setup>
import { ref } from 'vue'

const status = ref('')

async function handleSubmit(e) {
  const form = e.target
  const data = new FormData(form)

  const res = await fetch('https://formcatch.vercel.app/s/YOUR_FORM_ID', {
    method: 'POST',
    body: data,
  })

  if (res.ok) {
    status.value = 'Message sent!'
    form.reset()
  } else {
    status.value = 'Something went wrong.'
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input type="text" name="name" placeholder="Name" required />
    <input type="email" name="email" placeholder="Email" required />
    <textarea name="message" placeholder="Message" />
    <input type="text" name="_honey" style="display:none" />
    <button type="submit">Send</button>
    <p v-if="status">{{ status }}</p>
  </form>
</template>

Gatsby

Gatsby sites are static by default, making FormCatch a perfect fit. Use a simple HTML form or add client-side handling with React.

gatsby
import React, { useState } from "react"

export default function ContactPage() {
  const [status, setStatus] = useState("")

  async function handleSubmit(e) {
    e.preventDefault()
    const data = new FormData(e.target)

    const res = await fetch("https://formcatch.vercel.app/s/YOUR_FORM_ID", {
      method: "POST",
      body: data,
    })

    setStatus(res.ok ? "Thanks! We'll be in touch." : "Error, please retry.")
    if (res.ok) e.target.reset()
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" placeholder="Name" required />
      <input type="email" name="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" />
      <input type="text" name="_honey" style={{ display: "none" }} />
      <button type="submit">Send</button>
      {status && <p>{status}</p>}
    </form>
  )
}

Hugo

Add a contact form to your Hugo site with plain HTML. No JavaScript or build plugins needed. Works with any Hugo theme.

hugo
<!-- layouts/partials/contact-form.html -->
<form action="https://formcatch.vercel.app/s/YOUR_FORM_ID" method="POST">
  <div>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required />
  </div>
  <div>
    <label for="email">Email</label>
    <input type="email" id="email" name="email" required />
  </div>
  <div>
    <label for="message">Message</label>
    <textarea id="message" name="message" rows="5"></textarea>
  </div>

  <!-- Spam protection -->
  <input type="text" name="_honey" style="display:none" />
  <input type="hidden" name="_redirect" value="{{ .Site.BaseURL }}thanks/" />

  <button type="submit">Send Message</button>
</form>

Jekyll

Jekyll generates static HTML, so a standard form tag is all you need. Drop this into any layout or page.

jekyll
---
layout: default
title: Contact
---

<form action="https://formcatch.vercel.app/s/YOUR_FORM_ID" method="POST">
  <label for="name">Name</label>
  <input type="text" id="name" name="name" required />

  <label for="email">Email</label>
  <input type="email" id="email" name="email" required />

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="5"></textarea>

  <!-- Spam protection -->
  <input type="text" name="_honey" style="display:none" />
  <input type="hidden" name="_redirect" value="{{ site.url }}/thanks/" />

  <button type="submit">Send</button>
</form>

Astro

Astro ships zero JavaScript by default. Use a plain HTML form for the simplest approach, or add a client-side script for async submission.

astro
---
// src/pages/contact.astro
import Layout from '../layouts/Layout.astro'
---

<Layout title="Contact">
  <form id="contact-form" action="https://formcatch.vercel.app/s/YOUR_FORM_ID" method="POST">
    <input type="text" name="name" placeholder="Name" required />
    <input type="email" name="email" placeholder="Email" required />
    <textarea name="message" placeholder="Message"></textarea>
    <input type="text" name="_honey" style="display:none" />
    <button type="submit">Send</button>
    <p id="status"></p>
  </form>

  <script>
    const form = document.getElementById('contact-form')
    form.addEventListener('submit', async (e) => {
      e.preventDefault()
      const res = await fetch(form.action, { method: 'POST', body: new FormData(form) })
      document.getElementById('status').textContent = res.ok ? 'Sent!' : 'Error'
      if (res.ok) form.reset()
    })
  </script>
</Layout>

Svelte

Works with Svelte and SvelteKit. Use a reactive variable to show submission status.

svelte
<script>
  let status = ''

  async function handleSubmit(e) {
    const form = e.target
    const data = new FormData(form)

    const res = await fetch('https://formcatch.vercel.app/s/YOUR_FORM_ID', {
      method: 'POST',
      body: data,
    })

    status = res.ok ? 'Message sent!' : 'Something went wrong.'
    if (res.ok) form.reset()
  }
</script>

<form on:submit|preventDefault={handleSubmit}>
  <input type="text" name="name" placeholder="Name" required />
  <input type="email" name="email" placeholder="Email" required />
  <textarea name="message" placeholder="Message"></textarea>
  <input type="text" name="_honey" style="display:none" />
  <button type="submit">Send</button>
  {#if status}
    <p>{status}</p>
  {/if}
</form>

Ready to add forms to your site?

Create your free account, get an endpoint URL, and paste it into your code. That's it.

No credit card required. 3 free endpoints.

Built by the team behind FastDevKit — 50+ free developer tools