# Cloudflare Turnstile でフォームを保護する

> Formspree Docs · フォームとプロジェクトの設定 · 2026年4月14日

[**Cloudflare Turnstile**](https://www.cloudflare.com/products/turnstile/) は、プライバシーを重視した最新の CAPTCHA 代替手段です。ユーザーへの問題提示なしに摩擦のないボット検出を提供し、アクセシビリティとフォームセキュリティの両方を向上させます。

Formspree は Turnstile をすぐに使える形でサポートしています。キーを設定してフォーム設定で CAPTCHA を有効にするだけです。

## ステップ 1: Turnstile サイトを作成する

まず [**Cloudflare Turnstile ダッシュボード**](https://dash.cloudflare.com/login) にアクセスします。

**Add widget** をクリックして新しい Turnstile サイトを作成します。

![](/images/zendesk/bc4a8a1144d62f97.png)

1.  サイトの **name**（名前）を入力します。
2.  _Allowed Domains_ にあなたの **domain**（例：`example.com`）を追加します。開発目的には [localhost](http://localhost) を使用してください。
3.  **widget mode**（ウィジェットモード）を選択します。

![](/images/zendesk/521525158f0b0557.png)

設定を保存します。作成完了後、新しいウィジェットキーが表示されます。

![](/images/zendesk/215916e29d42d475.png)

この **Site Key と Secret Key** をコピーします。HTML フォームと Formspree の設定で使用します。

## ステップ 2: Formspree で CAPTCHA を設定する

[**Formspree ダッシュボード**](https://formspree.io/dashboard) でフォームの設定ページに移動します。

**CAPTCHA** セクションで CAPTCHA 保護が **enabled**（有効）になっていることを確認します。

**Adjust settings** をクリックし、**Cloudflare Turnstile** を選択して、提供されたフィールドに **Secret Key** を貼り付けます。

![](/images/zendesk/bf1ee8ffccbe6861.png)

変更を保存します。

## ステップ 3: フォーム HTML に Turnstile を追加する

次に、フォームに Turnstile を追加します。HTML コードに **Turnstile スクリプト**と **Site Key** を含めます。

基本的な例を以下に示します。

```html
<html>
  <head>
    <title>Turnstile Demo</title>
    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
  </head>
  <body>
    <form action="https://formspree.io/f/{your-form-id}" method="POST">
      <input type="email" name="email" placeholder="Email" required />
      <div class="cf-turnstile" data-sitekey="{your-site-key}"></div>
      <br />
      <input type="submit" value="Submit" />
    </form>
  </body>
</html>
```

ユーザーがフォームを送信すると、Turnstile ウィジェットが自動的にトークン（`cf-turnstile-response`）を生成し、Formspree があなたの Secret Key を使って検証します。

## AJAX の使用（オプション）

AJAX でフォームを送信する場合、リクエストに Turnstile トークンを手動で含めることができます。

以下は **Vanilla JS** を使用した例です。

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Turnstile AJAX Demo</title>
    <!-- Turnstile script -->
    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
  </head>
  <body>
    <form id="form" method="POST" action="https://formspree.io/f/<form-hashid>">
      <input type="email" name="email" placeholder="email@example.com" required />

      <!-- Turnstile widget (automatically adds hidden cf-turnstile-response input) -->
      <div class="cf-turnstile" data-sitekey="{site-key}"></div>

      <br />
      <button type="submit">Submit</button>
    </form>

    <script>
      const form = document.getElementById("form");

      form.addEventListener("submit", function (event) {
        event.preventDefault();

        // Grab all form fields, including the hidden cf-turnstile-response
        const data = new FormData(form);

        fetch(form.action, {
          method: form.method,
          body: data,
          headers: {
            "Accept": "application/json"
          }
        })
          .then(function (response) {
            return response.json();
          })
          .then(function (data) {
            console.log("Formspree response:", data);
          })
          .catch(function (error) {
            console.error("Error:", error);
          });
      });
    </script>
  </body>
</html>
```

または [Turnstile API](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#widget-lifecycle-management) を使用することもできます。

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <script
    src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
    defer
  ></script>
</head>
<body>
  <form id="contact-form">
    <input type="email" name="email" placeholder="Email" required />
    <div id="turnstile-container"></div>
    <button type="submit">Send</button>
  </form>

  <script>
    let widgetId = null;

    // Render widget when the page finishes loading
    window.onload = function () {
      if (!window.turnstile) {
        console.error("Turnstile did not load.");
        return;
      }

      widgetId = window.turnstile.render("#turnstile-container", {
        sitekey: "{your-site-key}",
        callback: function (token) {
          console.log("Turnstile success:", token);
        }
      });
    };

    // AJAX submit
    document.getElementById("contact-form").addEventListener("submit", function (event) {
      event.preventDefault();
      const form = this;
      const formData = new FormData(form);

      // Ensure we send the token under the standard field name
      // If Turnstile already added a hidden input, this overwrites it.
      formData.set("cf-turnstile-response", token);

      fetch("/submit", {
        method: "POST",
        body: formData
      })
        .then((res) => {
          if (!res.ok) {
            throw new Error("Server error: " + res.status);
          }
          return res.text();
        })
        .then((text) => {
          alert("Server replied: " + text);
          // Reset for next submission
          window.turnstile.reset(widgetId);
          form.reset();
        })
        .catch((err) => {
          console.error(err);
          alert("Request failed: " + err.message);
        });
    });
  </script>
</body>
</html>
```
