Skip to content

If you want to add captcha to react form safely, the short answer is: render the challenge on the client, capture a pass token, and verify that token on your server before accepting the submission. That keeps bots from posting directly to your endpoint while preserving a normal form flow for real users.

For React apps, the trick is not just “put a widget on the page.” It’s deciding when to show it, how to store the token, how to validate it server-side, and how to avoid making the form feel heavier than it needs to be. A good implementation is usually invisible until needed.

What a solid React CAPTCHA flow looks like

A modern React form integration should follow a simple trust boundary:

  1. The browser renders the form and a CAPTCHA challenge.
  2. The user completes the challenge and receives a short-lived pass_token.
  3. Your React app submits the form fields plus that token to your backend.
  4. Your backend verifies the token with the CAPTCHA provider before processing the form.

That last step is the one people sometimes skip, and it’s the most important. If you only check the token in the browser, an attacker can bypass your UI and post directly to your API.

Here’s the flow in plain terms:

  • Client: gathers form values and CAPTCHA token
  • Server: validates token with private credentials
  • App logic: accepts or rejects the submission based on validation result

abstract flow diagram showing browser, captcha challenge, backend validation, an

With CaptchaLa, the validation endpoint is server-side and uses X-App-Key plus X-App-Secret. The validation request goes to POST https://apiv1.captcha.la/v1/validate with a body like { pass_token, client_ip }. That makes it straightforward to keep the secret out of your React bundle.

How to add captcha to react form step by step

Below is a practical way to wire it up in a React app. The exact component names will depend on the SDK or loader you use, but the architecture stays the same.

1) Load the CAPTCHA script

CaptchaLa provides a loader at:

https://cdn.captcha-cdn.net/captchala-loader.js

You can load it once in your app shell or inject it in the page where the form lives. If you prefer a lighter framework-specific integration, CaptchaLa also supports native SDKs for Web, including React, plus iOS, Android, Flutter, and Electron.

2) Render the challenge in your form

In React, you generally want to keep CAPTCHA state separate from form state:

  • form inputs: name, email, message, etc.
  • captcha token: a small piece of transient state
  • submission state: idle, submitting, success, error

A simple pattern is to disable the submit button until the CAPTCHA is completed, then pass the token along with the form payload.

jsx
import { useState } from "react";

export default function ContactForm() {
  const [form, setForm] = useState({ name: "", email: "", message: "" });
  const [captchaToken, setCaptchaToken] = useState("");
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState("");

  async function handleSubmit(e) {
    e.preventDefault();
    setSubmitting(true);
    setError("");

    try {
      const response = await fetch("/api/contact", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          ...form,
          pass_token: captchaToken,
        }),
      });

      if (!response.ok) {
        throw new Error("Submission failed");
      }

      // Reset form after success
      setForm({ name: "", email: "", message: "" });
      setCaptchaToken("");
    } catch (err) {
      setError("Please try again.");
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={form.name}
        onChange={(e) => setForm({ ...form, name: e.target.value })}
        placeholder="Name"
      />
      <input
        value={form.email}
        onChange={(e) => setForm({ ...form, email: e.target.value })}
        placeholder="Email"
      />
      <textarea
        value={form.message}
        onChange={(e) => setForm({ ...form, message: e.target.value })}
        placeholder="Message"
      />

      {/* Render your CAPTCHA widget here and store its pass token */}
      <div id="captcha-container" />

      <button type="submit" disabled={submitting || !captchaToken}>
        {submitting ? "Sending..." : "Send"}
      </button>

      {error ? <p>{error}</p> : null}
    </form>
  );
}

The important part is not the widget shell itself; it’s the token handoff. Your CAPTCHA component or loader should expose a success callback that provides the token, which you then keep in component state or a ref until submission.

3) Validate the token on the backend

Your React app should never call the validation endpoint directly if it requires secrets. Instead, send the token to your API route or server, then validate it there.

CaptchaLa’s server validation endpoint expects:

  • POST https://apiv1.captcha.la/v1/validate
  • body: { pass_token, client_ip }
  • headers: X-App-Key and X-App-Secret

A minimal Node-style server route might look like this:

js
export async function postContact(req, res) {
  const { name, email, message, pass_token } = req.body;
  const client_ip = req.headers["x-forwarded-for"]?.split(",")[0] || req.socket.remoteAddress;

  // Validate captcha before processing the form
  const captchaResponse = await fetch("https://apiv1.captcha.la/v1/validate", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-App-Key": process.env.CAPTCHALA_APP_KEY,
      "X-App-Secret": process.env.CAPTCHALA_APP_SECRET,
    },
    body: JSON.stringify({
      pass_token,
      client_ip,
    }),
  });

  const result = await captchaResponse.json();

  if (!captchaResponse.ok || !result?.success) {
    return res.status(403).json({ error: "Captcha validation failed" });
  }

  // Continue with your normal form handling here
  return res.status(200).json({ ok: true });
}

A few technical details matter here:

  1. Validate before any expensive work, like sending email or writing to your CRM.
  2. Treat a failed CAPTCHA as an authorization failure, not a frontend error.
  3. Include the client IP when possible, because it can strengthen the validation context.
  4. Keep X-App-Secret on the server only.
  5. Expire or discard the token after one successful use.

4) Decide when to challenge

You don’t need to show a challenge on every form interaction. Many apps use risk-based triggering:

  • show CAPTCHA only after suspicious behavior
  • show it after failed attempts
  • show it on sensitive actions like account creation, password reset, or lead submission
  • keep it visible for high-abuse endpoints

That approach can preserve form completion rates while still reducing automation. It also fits well with forms that already have low friction and only need a defense layer for abuse.

Comparing CAPTCHA options for React apps

Different products make different tradeoffs. Here’s a practical, non-hype comparison for React form use:

OptionClient experienceServer-side validationNotes
reCAPTCHAFamiliar, widely recognizedYesOften used, but can feel more intrusive depending on configuration
hCaptchaSimilar pattern to reCAPTCHAYesFrequently chosen as an alternative with comparable integration patterns
Cloudflare TurnstileUsually lower frictionYesOften favored for lightweight verification flows
CaptchaLaSupports 8 UI languages and Web SDKs for ReactYesUses first-party data only; also offers native SDKs and server SDKs

If your app already uses React, the best choice usually comes down to operational fit: language support, backend validation workflow, how much friction you want, and whether you prefer a simple client-loader pattern or a broader SDK ecosystem. CaptchaLa also has server SDKs for PHP and Go (captchala-php, captchala-go) if your backend stack is mixed.

For teams shipping international products, the 8 UI languages can matter more than people expect. A CAPTCHA that users can understand quickly tends to reduce abandonment, especially on forms where completion already requires effort.

Implementation details that save headaches later

A few practical choices make CAPTCHA integration much easier to maintain:

  • Keep the CAPTCHA token out of long-lived React state if possible. A ref or short-lived state is usually enough.
  • Reset the challenge after success or after a failed submission attempt.
  • Avoid duplicating validation logic in the frontend. The frontend should only manage UX; the backend should enforce trust.
  • Log validation failures with enough context to monitor abuse patterns, but avoid storing unnecessary personal data.
  • Test the form in slow-network conditions, since challenge scripts can delay rendering if you block on them too early.

If your backend is not Node-based, the same pattern still applies. For example, server validation can be done from PHP or Go using your backend HTTP client of choice, while the frontend stays a standard React form. That separation keeps your React code focused on experience, not security policy.

CaptchaLa also documents a server-token issue endpoint at POST https://apiv1.captcha.la/v1/server/challenge/issue, which is useful when your backend needs to initiate a challenge flow rather than relying only on a browser-rendered one. The docs are the best place to map that into your exact architecture.

abstract decision tree showing when to challenge, validate, reject, or accept a

When to start with a free tier and when to upgrade

If you’re protecting a small product, a contact form, or a side project, a free tier can be enough to get started. CaptchaLa’s free tier covers 1,000 validations per month, which is useful for early production traffic or internal tools.

As traffic grows, plan around actual abuse exposure rather than pageviews alone:

  • 50K–200K monthly validations tends to fit many growing apps
  • around 1M validations is more common for high-traffic public forms or multiple protected actions
  • if you have several endpoints, count each protected submission, not just each visitor

Because the platform is designed around first-party data only, it may fit teams that want a simpler data posture when comparing providers. If you’re budgeting or deciding how to protect multiple forms, the pricing page is the quickest way to map usage to plan shape.

Adding CAPTCHA to a React form does not have to mean a clunky user experience. If you keep the challenge lightweight, validate on the server, and only ask for proof when it’s warranted, you can block a lot of automated abuse without turning your form into a maze.

Where to go next: check the docs for integration details, or review pricing if you want to estimate usage before rollout.

Articles are CC BY 4.0 — feel free to quote with attribution