Skip to content
FitMyKB

FitMyKB Verify

Add a CAPTCHA to a React form

A 30-line component loads the widget once, renders it with fmkcaptcha.render and hands your form the token. No npm package to install.

Keys

Put the site key in VITE_FMK_SITE_KEY (or your bundler's public variable). The secret stays on the server.

The component

It loads api.js with render=explicit the first time it mounts, renders the box into a div, and removes it on unmount, so it behaves well with React's strict mode and route changes.

TSX · FitMyKBCaptcha.tsx
// FitMyKBCaptcha.tsx: loads the widget once and hands you the token
import { useEffect, useRef } from "react";

declare global { interface Window { fmkcaptcha?: any } }

let loading: Promise<any> | null = null;
function loadApi(): Promise<any> {
  loading ??= new Promise((resolve) => {
    const script = document.createElement("script");
    script.src = "https://captcha.fitmykb.com/api.js?render=explicit";
    script.async = true;
    script.onload = () => resolve(window.fmkcaptcha);
    document.head.append(script);
  });
  return loading;
}

export function FitMyKBCaptcha({ siteKey, onToken }: { siteKey: string; onToken: (token: string) => void }) {
  const box = useRef<HTMLDivElement>(null);
  useEffect(() => {
    let id: string | undefined;
    let gone = false;
    loadApi().then((fmk) => {
      if (!gone && box.current) {
        id = fmk.render(box.current, { sitekey: siteKey, callback: onToken, "expired-callback": () => onToken("") });
      }
    });
    return () => { gone = true; if (id !== undefined) window.fmkcaptcha?.remove(id); };
  }, [siteKey]);
  return <div ref={box} />;
}

Using it in a form

The widget adds fmk-captcha-response inside the form, so new FormData(form) already carries the token. The button waits until the visitor has passed.

TSX · a sign-up form
// SignUp.tsx
import { useState } from "react";
import { FitMyKBCaptcha } from "./FitMyKBCaptcha";

export function SignUp() {
  const [token, setToken] = useState("");

  async function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    // The widget adds fmk-captcha-response inside the form, so FormData already carries the token.
    const res = await fetch("/api/signup", { method: "POST", body: new FormData(event.currentTarget) });
    if (!res.ok) setToken(""); // tokens work once: the widget resets for the next try
  }

  return (
    <form onSubmit={submit}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <FitMyKBCaptcha siteKey={import.meta.env.VITE_FMK_SITE_KEY} onToken={setToken} />
      <button disabled={!token}>Create account</button>
    </form>
  );
}

On your server

Check the token with siteverify before you create anything: the Node.js guide has the code. Tokens work once, so after a failed request the widget gives a fresh one.

Next.js

For the App Router, the Next.js guide uses next/script and a server action instead.

Get free keys · Full developer docs

Questions

Good questions

Anything else? Read the docs or ask us.

Is there an npm package?

You don't need one: the component above is all of it, and api.js always stays up to date.