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.
- HTML
- React
- Next.js
- Astro
- Vue
- Svelte
A plain form, no JavaScript required.
<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>
- TypeScript
- JavaScript
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>
);
}
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>
);
}
A client component - add
"use client" since it holds form state.- TypeScript
- JavaScript
"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>
);
}
"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>
);
}
A no-JS form - works the same as the plain HTML version.
---
// 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>
- TypeScript
- JavaScript
<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>
<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>
- TypeScript
- JavaScript
<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}
<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}
Check out our customizable and copy-paste ready contact form templates here.
Getting the snippet with your real access key already filled in
Rather than copy-pastingYOUR_ACCESS_KEY by hand, you can:
- Copy the snippet from the form’s Setup tab in the dashboard - it has your access key baked in.
- Ask an agent connected via the MCP server to call
get_snippetwith yourformId- 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. |
_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. |
email, message, name, or anything
else your form needs.