FORMHUSH / Next.js

Filter Next.js contact form spam

Connect a Next.js contact form to FormHush using a client component or native HTML. Include the honeypot and handle accepted and rejected requests.

FormHush can receive submissions from a Next.js page without a Next.js API route. You can use a native HTML form or handle the response in a client component.

Add a client component

Save the example as a component and keep "use client" before the imports. Event handlers and local state need a client boundary in the App Router. See the Next.js client directive reference.

Before connecting

The site is a preview. Accounts and forms are provisioned by an operator; the dashboard does not create them. Obtain a provisioned form ID and have the operator configure allowed origins, notification destinations and any thank-you URL before using the integration.

The examples use https://api.formhush.com/f/example-contact. Replace the illustrative example-contact ID with your provisioned ID; it is not a live demo form. Keep account keys out of public markup. Browser requests must come from an exact allowed origin, including the scheme and any port.

"use client";

import { useState, type FormEvent } from "react";

export default function ContactForm() {
  const [status, setStatus] = useState("");
  const [pending, setPending] = useState(false);

  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (pending) return;
    const form = event.currentTarget;
    const data = new FormData(form);
    setPending(true);
    setStatus("Sending...");
    try {
      const response = await fetch("https://api.formhush.com/f/example-contact", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          name: data.get("name"),
          email: data.get("email"),
          message: data.get("message"),
          website: data.get("website")
        })
      });
      if (!response.ok) {
        setStatus(response.status === 429
          ? "Submission limit reached. Please try again later."
          : "Message not accepted. Check your details and try again.");
        return;
      }
      form.reset();
      setStatus("Message received.");
    } catch {
      setStatus("Could not confirm receipt. Your text is still here; check before retrying.");
    } finally {
      setPending(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>Name <input name="name" maxLength={120} autoComplete="name" /></label>
      <label>Email <input name="email" type="email" maxLength={254} autoComplete="email" /></label>
      <label>Message <textarea name="message" maxLength={8000} required /></label>
      <input name="website" type="text" hidden tabIndex={-1} autoComplete="off" />
      <button type="submit" disabled={pending}>Send message</button>
      <p role="status">{status}</p>
    </form>
  );
}

Native form and server alternatives

A server component can render the form from the HTML guide without a client submit handler. Use the lowercase native form element with a POST method for that example.

If you add a Server Action or route to forward requests, preserve the four supported fields and implement abuse protection there too. A shared server proxy can group requests under one IP rate limit. Do not put dashboard account keys in the browser; public submissions do not require them.

Fields and thank-you behaviour

message is required, up to 8,000 characters. name is optional, up to 120 characters; email is optional, up to 254 characters and must be valid when supplied. The website string is mandatory and must stay empty for a legitimate visitor. A filled honeypot is stored as spam, not silently discarded.

Only JSON and URL-encoded POST bodies are accepted, with a 16,384-byte limit. Additional fields are not stored. FormHush does not use _gotcha, and a submitted _redirect does not control navigation.

For a native URL-encoded form, the operator sets the form’s HTTPS thank_you_url during provisioning. An accepted request then returns HTTP 303 to that URL. Without it, the endpoint returns HTTP 201 JSON. JSON requests always receive JSON; a JavaScript form handles its own success state or navigation after checking the response.

Classification, delivery and retention

Rules check the honeypot and known spam patterns first. When configured, Jev classifies the remaining message text after email addresses and phone numbers are masked. That masking is not complete anonymisation. Ambiguous decisions stay in Needs review without automatic forwarding.

Real leads and urgent messages are queued for configured destinations. When AI is unavailable, remaining accepted messages become Unsorted and are also queued; deterministic spam rules still apply. Delivery adapters support email, Slack, Telegram and webhooks, but need operator configuration. The email provider is not yet selected. Retries do not guarantee that a third party receives a message.

Submissions and related notification jobs are deleted after 30 days by scheduled cleanup. Copies sent to your channels follow those services’ retention policies. Processing can involve the United States; see the privacy policy for providers and details.

Test the complete flow

  1. Test from an allowed origin using a provisioned form. Submit a clear enquiry and check its stored result in the dashboard.
  2. Check delivery separately, using a configured test destination. An accepted response alone does not prove notification delivery.
  3. Send a nonempty message with website filled to verify that it is stored as spam without notification. An empty message is a validation error, not a spam verdict.
  4. Confirm a missing honeypot or empty message returns HTTP 400, an unsupported body format returns HTTP 415, and a disallowed browser origin returns HTTP 403.
  5. Verify the thank-you page or inline success message and preserve entered text on errors. Rate and monthly limits return HTTP 429; respect Retry-After before retrying. Check for a stored record after a network failure to avoid duplicate submissions.

Plans and next steps

  • Free: 0 USD per month for 100 submissions.
  • Solo: 9 USD per month for 1,000 submissions.
  • Studio: 29 USD per month for 5,000 submissions.

These are the planned offers; this preview does not collect payments. All valid stored submissions, including spam, count toward the account quota, shared across its forms. Quotas reset each UTC calendar month. Over-limit requests are rejected with HTTP 429, with no automatic overage charges.

To discuss provisioning, contact hello@formhush.com. See the privacy policy for retention and providers, or browse all integration guides.