Framework Integrations
Copy-paste examples to connect Formtorch to Next.js, React, Vue, Astro, and plain HTML.
https://formtorch.com/f/abc123 with your real endpoint from the Formtorch dashboard.Frameworks
HTML
No-frameworkThe simplest integration. Just point a standard HTML form's action attribute at your Formtorch endpoint. No JavaScript required for basic submissions.
A plain HTML form that submits via a full-page POST.
<form action="https://formtorch.com/f/abc123" method="POST">
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message"></textarea>
<button type="submit">Send message</button>
</form>
Next.js
App RouterNext.js 15+Three patterns for Next.js App Router: a client component with fetch, a server action that runs on the server, and a lightweight API route handler.
A 'use client' form component that posts to Formtorch via fetch.
"use client";
import { useState } from "react";
export default function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "done">("idle");
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const res = await fetch("https://formtorch.com/f/abc123", {
method: "POST",
headers: { "X-Requested-With": "XMLHttpRequest" },
body: new FormData(e.currentTarget),
});
setStatus(res.ok ? "done" : "idle");
}
if (status === "done") {
return <p>Thanks! We will be in touch.</p>;
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" />
<button type="submit" disabled={status === "sending"}>
{status === "sending" ? "Sending..." : "Send message"}
</button>
</form>
);
}
React
Client-sideReact 18+Two approaches for React: controlled inputs with a JSON payload, and uncontrolled inputs using the native FormData API.
Manage input state with useState and POST as JSON.
import { useState } from "react";
type Fields = { name: string; email: string; message: string };
export function ContactForm() {
const [fields, setFields] = useState<Fields>({
name: "",
email: "",
message: "",
});
const [sending, setSending] = useState(false);
const [sent, setSent] = useState(false);
function set(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
setFields((f) => ({ ...f, [e.target.name]: e.target.value }));
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSending(true);
try {
await fetch("https://formtorch.com/f/abc123", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
},
body: JSON.stringify(fields),
});
setSent(true);
} finally {
setSending(false);
}
}
if (sent) return <p>Thanks! We will be in touch.</p>;
return (
<form onSubmit={handleSubmit}>
<input name="name" value={fields.name} onChange={set} required />
<input
name="email"
type="email"
value={fields.email}
onChange={set}
required
/>
<textarea name="message" value={fields.message} onChange={set} />
<button type="submit" disabled={sending}>
{sending ? "Sending..." : "Send"}
</button>
</form>
);
}
Astro
Static siteAstro 5+Astro renders to static HTML, so a plain form with an action attribute works out of the box — no JavaScript island required.
Drop a standard HTML form into any .astro file.
---
// src/pages/contact.astro
---
<form
action="https://formtorch.com/f/abc123"
method="POST"
>
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message"></textarea>
<button type="submit">Send message</button>
</form>
Vue
Client-sideVue 3+A single-file component using v-model for reactive input binding and @submit.prevent for AJAX submission.
v-model bindings with a Fetch POST on submit.
<template>
<div v-if="sent">
<p>Thanks! Message sent.</p>
</div>
<form v-else @submit.prevent="handleSubmit">
<input v-model="name" name="name" placeholder="Name" required />
<input
v-model="email"
type="email"
name="email"
placeholder="Email"
required
/>
<textarea v-model="message" name="message" placeholder="Message"></textarea>
<button type="submit" :disabled="sending">
{{ sending ? "Sending..." : "Send message" }}
</button>
</form>
</template>
<script setup>
import { ref } from "vue";
const name = ref("");
const email = ref("");
const message = ref("");
const sending = ref(false);
const sent = ref(false);
async function handleSubmit() {
sending.value = true;
try {
await fetch("https://formtorch.com/f/abc123", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: name.value,
email: email.value,
message: message.value,
}),
});
sent.value = true;
} finally {
sending.value = false;
}
}
</script>
SvelteKit
Client-sideSvelteKit 2+Use Svelte 5 runes ($state) with bind:value and onsubmit for a clean, minimal integration.
$state runes for reactive inputs with onsubmit handler.
<script lang="ts">
let name = $state('');
let email = $state('');
let message = $state('');
let sending = $state(false);
let sent = $state(false);
async function handleSubmit(e: SubmitEvent) {
e.preventDefault();
sending = true;
try {
await fetch('https://formtorch.com/f/abc123', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, message }),
});
sent = true;
} finally {
sending = false;
}
}
</script>
{#if sent}
<p>Thanks! Message sent.</p>
{:else}
<form onsubmit={handleSubmit}>
<input bind:value={name} name="name" placeholder="Name" required />
<input bind:value={email} type="email" name="email" placeholder="Email" required />
<textarea bind:value={message} name="message" placeholder="Message"></textarea>
<button type="submit" disabled={sending}>
{sending ? 'Sending...' : 'Send message'}
</button>
</form>
{/if}
Gatsby
Client-sideGatsby 5+Gatsby is built on React, so the same hooks-based pattern applies. Submit with FormData for the simplest integration.
A standard React form component using FormData and fetch.
import React, { useState } from "react";
export default function ContactForm() {
const [sent, setSent] = useState(false);
async function handleSubmit(e) {
e.preventDefault();
await fetch("https://formtorch.com/f/abc123", {
method: "POST",
headers: { "X-Requested-With": "XMLHttpRequest" },
body: new FormData(e.target),
});
setSent(true);
}
if (sent) return <p>Thanks! Message sent.</p>;
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" />
<button type="submit">Send message</button>
</form>
);
}
Webflow
No-codeAdd a Formtorch-powered form to any Webflow project using an HTML Embed element. No custom code editor access required.
Paste this into a Webflow HTML Embed block on any page.
<!-- Paste into a Webflow HTML Embed element -->
<form action="https://formtorch.com/f/abc123" method="POST">
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message"></textarea>
<button type="submit">Send message</button>
</form>
Ship forms without maintaining a backend
Create an endpoint, paste it into your form, and start receiving submissions.