react-turnstile

React bindings for Cloudflare Turnstile — a privacy-first CAPTCHA alternative with a declarative component and a full imperative ref API.

SDK
npm
v1.6.3
856 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
85 /100 Excellent
Development Activity 96
Maintenance 100
Community 48
Maturity 56
Momentum 40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
84 /100 Excellent
Architecture 78
Code Quality 85
Innovation 82
Learning Curve 90

@marsidev/react-turnstile wraps Cloudflare’s Turnstile CAPTCHA replacement in a single <Turnstile siteKey="..." /> React component, handling script injection, container sizing (to avoid layout shift across widget sizes), and widget lifecycle internally so consumers don’t touch Cloudflare’s raw window.turnstile API directly.

For cases that need more control, an optional ref exposes reset(), execute(), remove(), getResponse(), getResponsePromise(), and isExpired() against the underlying widget instance. It ships full TypeScript types for every public shape, works with React 17 through 19, and is built for SSR frameworks like Next.js and Remix out of the box.

What You Get

  • A <Turnstile /> component that handles Cloudflare script injection, widget rendering, and teardown automatically
  • An imperative ref API (reset, execute, remove, getResponse, getResponsePromise, isExpired) for manual control
  • Automatic container sizing per widget size (normal/compact/flexible/invisible) to prevent layout shift while the widget loads
  • Full TypeScript types for props, render parameters, and the server-side siteverify request/response shapes
  • SSR-safe rendering that works with Next.js, Remix, and other React SSR frameworks

Common Use Cases

  • Adding a CAPTCHA-style bot check to a login or signup form
  • Gating form submission behind a solved Turnstile challenge before hitting the server
  • Running an invisible, non-interactive challenge via execution: 'execute' triggered by a user action
  • Rendering multiple independent Turnstile widgets on the same page

Under The Hood

Architecture

The Turnstile component (packages/lib/src/lib.tsx) is a single forwardRef component built around a module-level singleton script-loading state machine (turnstileState, turnstileLoadPromise) so Cloudflare’s api.js is injected exactly once no matter how many widget instances mount. useObserveScript (use-observe-script.ts) backs this with a MutationObserver to detect the script tag even when it’s injected manually. Container (container.tsx) is a thin forwardRef wrapper that lets the rendered tag be customized via an as prop. Everything else is orchestrated through effects: an inject effect calls injectTurnstileScript (utils.ts), a wait effect resolves once window.turnstile exists, and a render effect calls window.turnstile.render with a memoized renderConfig that maps declarative props onto Cloudflare’s raw parameter names, storing the returned widget id in a ref and calling turnstile.remove on cleanup. The imperative handle exposed via useImperativeHandle is a thin, stateful shim directly over window.turnstile, by design, since the underlying widget is a global browser API rather than something the library can reimplement.

Tech Stack

TypeScript throughout, with wide React peer dependencies (^17.0.2 || ^18.0.0 || ^19.0) covering three major versions. Built to ESM-only output with tsdown, typechecked with tsgo (TypeScript’s newer Go-based checker) rather than plain tsc, tested with vitest plus @testing-library/react and jsdom, and linted/formatted with the Rust-based oxlint/oxfmt instead of ESLint/Prettier. The repo is a pnpm workspace with three packages: the library itself, a Next.js demo app under demos/, and a 13-page docs.page documentation site under docs/. Releases are automated with Changesets, and CI includes a dedicated zizmor.yml workflow for GitHub Actions workflow security linting.

Code Quality

The test suite exercises real DOM behavior with @testing-library/react and jsdom across dedicated cases for basic mounting and script injection, manual script injection, invalid render-parameter handling, and offlabel parameter forwarding. Error handling is deliberate rather than incidental: tryRenderWidget wraps window.turnstile.render in a try/catch specifically because Cloudflare’s script throws synchronously for invalid parameters instead of routing them through its own error callback, and this quirk is documented directly in code comments alongside other tracked upstream issues (nonce attribute reflection, invisible-mode container sizing). Every public type is exported and documented with TSDoc comments, and CI lints and tests on every push.

What Makes It Unique

The library’s core value is collapsing Cloudflare’s imperative, global-object Turnstile API into one declarative component while still exposing an escape hatch for the imperative cases (execute() for invisible/execution mode, getResponsePromise() for awaiting a solved token as a promise). rerenderOnCallbackChange is a deliberate, documented API tradeoff: stable callback refs by default to avoid unnecessary widget re-renders, with an opt-in mode for consumers who need live callback updates. Thirteen dedicated documentation pages (dynamic callbacks, form-submission retry logic, multiple widgets, validating a token server-side, and more) go well beyond a typical single-README package.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers