> ## Documentation Index
> Fetch the complete documentation index at: https://formpaste.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Framework snippets

> A bare, wired form for every supported framework - paste it in and it works.

Every snippet below does the same thing: it POSTs to `https://api.formpaste.com/submit`
with your form's `access_key`, and handles the success/error response. These
are intentionally plain - an email field, a message field, and submit
handling - not a styled component library. Swap `YOUR_ACCESS_KEY` for the
access key shown on your form's Setup tab.

<Tabs>
  <Tab title="HTML">
    A plain form, no JavaScript required.

    ```html theme={null}
    <form action="https://api.formpaste.com/submit" method="POST">
      <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
      <input type="email" name="email" placeholder="Your email" required>
      <textarea name="message" placeholder="Your message" required></textarea>
      <button type="submit">Send</button>
    </form>
    ```
  </Tab>

  <Tab title="React">
    <Tabs>
      <Tab title="TypeScript">
        ```tsx theme={null}
        import { useState } from "react";

        export function ContactForm() {
          const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle");

          async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
            e.preventDefault();
            setStatus("sending");
            const form = new FormData(e.currentTarget);
            const res = await fetch("https://api.formpaste.com/submit", { method: "POST", body: new URLSearchParams(form as any) });
            setStatus(res.ok ? "ok" : "error");
          }

          if (status === "ok") return <p>Thanks - your message was sent.</p>;

          return (
            <form onSubmit={onSubmit}>
              <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
              <input type="email" name="email" placeholder="Your email" required />
              <textarea name="message" placeholder="Your message" required />
              <button type="submit" disabled={status === "sending"}>Send</button>
              {status === "error" && <p>Something went wrong. Please try again.</p>}
            </form>
          );
        }
        ```
      </Tab>

      <Tab title="JavaScript">
        ```jsx theme={null}
        import { useState } from "react";

        export function ContactForm() {
          const [status, setStatus] = useState("idle"); // "idle" | "sending" | "ok" | "error"

          async function onSubmit(e) {
            e.preventDefault();
            setStatus("sending");
            const form = new FormData(e.currentTarget);
            const res = await fetch("https://api.formpaste.com/submit", { method: "POST", body: new URLSearchParams(form) });
            setStatus(res.ok ? "ok" : "error");
          }

          if (status === "ok") return <p>Thanks - your message was sent.</p>;

          return (
            <form onSubmit={onSubmit}>
              <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
              <input type="email" name="email" placeholder="Your email" required />
              <textarea name="message" placeholder="Your message" required />
              <button type="submit" disabled={status === "sending"}>Send</button>
              {status === "error" && <p>Something went wrong. Please try again.</p>}
            </form>
          );
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Next.js">
    A client component - add `"use client"` since it holds form state.

    <Tabs>
      <Tab title="TypeScript">
        ```tsx theme={null}
        "use client";
        import { useState } from "react";

        export function ContactForm() {
          const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle");

          async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
            e.preventDefault();
            setStatus("sending");
            const form = new FormData(e.currentTarget);
            const res = await fetch("https://api.formpaste.com/submit", { method: "POST", body: new URLSearchParams(form as any) });
            setStatus(res.ok ? "ok" : "error");
          }

          if (status === "ok") return <p>Thanks - your message was sent.</p>;

          return (
            <form onSubmit={onSubmit}>
              <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
              <input type="email" name="email" placeholder="Your email" required />
              <textarea name="message" placeholder="Your message" required />
              <button type="submit" disabled={status === "sending"}>Send</button>
              {status === "error" && <p>Something went wrong. Please try again.</p>}
            </form>
          );
        }
        ```
      </Tab>

      <Tab title="JavaScript">
        ```jsx theme={null}
        "use client";
        import { useState } from "react";

        export function ContactForm() {
          const [status, setStatus] = useState("idle"); // "idle" | "sending" | "ok" | "error"

          async function onSubmit(e) {
            e.preventDefault();
            setStatus("sending");
            const form = new FormData(e.currentTarget);
            const res = await fetch("https://api.formpaste.com/submit", { method: "POST", body: new URLSearchParams(form) });
            setStatus(res.ok ? "ok" : "error");
          }

          if (status === "ok") return <p>Thanks - your message was sent.</p>;

          return (
            <form onSubmit={onSubmit}>
              <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
              <input type="email" name="email" placeholder="Your email" required />
              <textarea name="message" placeholder="Your message" required />
              <button type="submit" disabled={status === "sending"}>Send</button>
              {status === "error" && <p>Something went wrong. Please try again.</p>}
            </form>
          );
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Astro">
    A no-JS form - works the same as the plain HTML version.

    ```astro theme={null}
    ---
    // src/components/ContactForm.astro - a no-JS form that posts to Formpaste.
    ---
    <form action="https://api.formpaste.com/submit" method="POST">
      <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
      <input type="email" name="email" placeholder="Your email" required />
      <textarea name="message" placeholder="Your message" required></textarea>
      <button type="submit">Send</button>
    </form>
    ```
  </Tab>

  <Tab title="Vue">
    <Tabs>
      <Tab title="TypeScript">
        ```vue theme={null}
        <script setup lang="ts">
        import { ref } from "vue";

        const status = ref<"idle" | "sending" | "ok" | "error">("idle");

        async function onSubmit(e: Event) {
          status.value = "sending";
          const form = new FormData(e.target as HTMLFormElement);
          const res = await fetch("https://api.formpaste.com/submit", { method: "POST", body: new URLSearchParams(form as any) });
          status.value = res.ok ? "ok" : "error";
        }
        </script>

        <template>
          <p v-if="status === 'ok'">Thanks - your message was sent.</p>
          <form v-else @submit.prevent="onSubmit">
            <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
            <input type="email" name="email" placeholder="Your email" required />
            <textarea name="message" placeholder="Your message" required></textarea>
            <button type="submit" :disabled="status === 'sending'">Send</button>
            <p v-if="status === 'error'">Something went wrong. Please try again.</p>
          </form>
        </template>
        ```
      </Tab>

      <Tab title="JavaScript">
        ```vue theme={null}
        <script setup>
        import { ref } from "vue";

        const status = ref("idle"); // "idle" | "sending" | "ok" | "error"

        async function onSubmit(e) {
          status.value = "sending";
          const form = new FormData(e.target);
          const res = await fetch("https://api.formpaste.com/submit", { method: "POST", body: new URLSearchParams(form) });
          status.value = res.ok ? "ok" : "error";
        }
        </script>

        <template>
          <p v-if="status === 'ok'">Thanks - your message was sent.</p>
          <form v-else @submit.prevent="onSubmit">
            <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
            <input type="email" name="email" placeholder="Your email" required />
            <textarea name="message" placeholder="Your message" required></textarea>
            <button type="submit" :disabled="status === 'sending'">Send</button>
            <p v-if="status === 'error'">Something went wrong. Please try again.</p>
          </form>
        </template>
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Svelte">
    <Tabs>
      <Tab title="TypeScript">
        ```svelte theme={null}
        <script lang="ts">
          let status: "idle" | "sending" | "ok" | "error" = "idle";

          async function onSubmit(e: SubmitEvent) {
            status = "sending";
            const form = new FormData(e.target as HTMLFormElement);
            const res = await fetch("https://api.formpaste.com/submit", { method: "POST", body: new URLSearchParams(form as any) });
            status = res.ok ? "ok" : "error";
          }
        </script>

        {#if status === "ok"}
          <p>Thanks - your message was sent.</p>
        {:else}
          <form on:submit|preventDefault={onSubmit}>
            <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
            <input type="email" name="email" placeholder="Your email" required />
            <textarea name="message" placeholder="Your message" required></textarea>
            <button type="submit" disabled={status === "sending"}>Send</button>
            {#if status === "error"}<p>Something went wrong. Please try again.</p>{/if}
          </form>
        {/if}
        ```
      </Tab>

      <Tab title="JavaScript">
        ```svelte theme={null}
        <script>
          let status = "idle"; // "idle" | "sending" | "ok" | "error"

          async function onSubmit(e) {
            status = "sending";
            const form = new FormData(e.target);
            const res = await fetch("https://api.formpaste.com/submit", { method: "POST", body: new URLSearchParams(form) });
            status = res.ok ? "ok" : "error";
          }
        </script>

        {#if status === "ok"}
          <p>Thanks - your message was sent.</p>
        {:else}
          <form on:submit|preventDefault={onSubmit}>
            <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
            <input type="email" name="email" placeholder="Your email" required />
            <textarea name="message" placeholder="Your message" required></textarea>
            <button type="submit" disabled={status === "sending"}>Send</button>
            {#if status === "error"}<p>Something went wrong. Please try again.</p>{/if}
          </form>
        {/if}
        ```
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

> Check out our customizable and copy-paste ready [contact form templates here](https://formpaste.com/templates/).

## Getting the snippet with your real access key already filled in

Rather than copy-pasting `YOUR_ACCESS_KEY` by hand, you can:

* Copy the snippet from the form's **Setup** tab in the [dashboard](https://app.formpaste.com) - it has your access key baked in.
* Ask an agent connected via the [MCP server](/docs/agents/mcp-server) to call
  [`get_snippet`](/docs/agents/get-snippet) with your `formId` - it returns the
  same snippet with the real access key filled in instead of a placeholder.

## Reserved field names

A few field names are handled specially by `/submit` - avoid reusing them
for your own data:

| Field        | Purpose                                                                                                  |
| ------------ | -------------------------------------------------------------------------------------------------------- |
| `access_key` | Required. Identifies which form the submission belongs to.                                               |
| `botcheck`   | Honeypot. Leave it empty and hidden from real visitors - see [Spam filtering](/docs/features/spam-filtering). |
| `_ts`        | Optional. A timestamp used for timing-based spam heuristics.                                             |
| `redirect`   | Optional. A URL to redirect to after a successful no-JS submit - see [Redirects](/docs/features/redirects).   |

Every other field name is yours - `email`, `message`, `name`, or anything
else your form needs.
