The short answer
AI website builders generate the markup and styling for a contact form but not the backend that receives it, so the form usually looks finished and silently does nothing. To make it real, give it a hosted endpoint and have the submit handler POST to it. RG Forms creates that endpoint inside your own Google account in about ninety seconds and exports an RGFORMS.md spec file listing your endpoint URL, form tabs, exact field keys and calling convention — drop that file into your project and an AI coding assistant can wire the form up correctly without guessing.
Why it happens
A generated form is usually one of three things: a bare <form> with no action, an onSubmit handler that logs to the console or fakes a delay with setTimeout, or a TODO comment where the fetch should be. The model produced exactly what it was asked for — the interface — and the part it can’t produce is an endpoint that exists on the internet with your data behind it.
So test before you launch. Submit your own form and check that the message actually reached you. If nothing arrives, the backend is missing, not broken.
Wiring it up
Create the endpoint
Sign in to RG Forms with Google and define fields that match the form your builder generated — same labels, same order. Takes about ninety seconds and produces a permanent endpoint URL.
Export RGFORMS.md and drop it in the repo
From your dashboard, download RGFORMS.md — a spec file listing your endpoint URL, every form tab, the exact field keys, the
text/plaincalling convention, and the honeypot and captcha snippets. Put it at the root of the project.Ask your AI assistant to wire it up
In Claude Code, Cursor, Copilot, Windsurf or whatever you’re using, say: *“Read RGFORMS.md and connect the contact form on the homepage to the endpoint, with sending, success and error states.”* The spec file is what stops it from inventing an API shape.
Verify with a real submission
Submit the form on the deployed site and confirm the row lands in your Google Sheet and the notification hits your inbox. If nothing arrives, check the browser console — a CORS error means something set
Content-Type: application/json, which is the one mistake assistants reliably make here.Test the endpoint from your terminal curl -L -X POST "https://script.google.com/macros/s/AKfycb.../exec" \ -H "Content-Type: text/plain" \ -d '{"tab":"contact","fields":{"name":"Ada","email":"ada@example.com","message":"Hello"}}' # → {"result":"success"} and a new row appears in your Google Sheet
If you’d rather paste the code yourself
Most AI-generated sites are React — Next.js, Vite, or a builder-specific wrapper. The component below drops in as-is; the React guide covers the variations. If your site is plain HTML, use the HTML version.
import { useState } from 'react';
const ENDPOINT = import.meta.env.VITE_FORM_ENDPOINT; // or process.env.NEXT_PUBLIC_FORM_ENDPOINT
export default function ContactForm() {
const [state, setState] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const form = event.currentTarget;
setState('sending');
try {
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' }, // no preflight
body: JSON.stringify({
tab: 'contact',
fields: Object.fromEntries(new FormData(form)),
}),
});
const data = await res.json();
setState(data.result === 'success' ? 'sent' : 'error');
if (data.result === 'success') form.reset();
} catch {
setState('error');
}
}
if (state === 'sent') return <p>Thanks — we'll be in touch.</p>;
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<input type="text" name="_hp" tabIndex={-1} autoComplete="off" aria-hidden="true"
style={{ position: 'absolute', left: '-9999px' }} />
<button disabled={state === 'sending'}>
{state === 'sending' ? 'Sending…' : 'Send'}
</button>
{state === 'error' && <p role="alert">Something went wrong. Please try again.</p>}
</form>
);
}Common questions
Which AI builders does this work with?
All of them — v0, Lovable, Bolt, Replit, Framer exports, or anything Claude, Cursor or Copilot generated. The integration is a browser fetch call, so it doesn’t care what wrote the markup.
What exactly is RGFORMS.md?
A plain-English spec of your project: endpoint URL, tab names, field keys and types, required flags, the calling convention, and copy-paste snippets. It exists so an AI assistant working in your codebase has the real API in front of it instead of guessing at one.
My AI assistant wrote the fetch with `Content-Type: application/json`. Why did it break?
That header triggers a CORS preflight OPTIONS request, and Apps Script web apps can’t answer OPTIONS, so the browser blocks the call before it’s sent. Change it to text/plain — the body stays JSON and the script parses it identically.
Can the AI create the RG Forms project too?
No — provisioning needs a real Google sign-in from you, and that’s deliberate: it’s what keeps the endpoint and the data inside your own account rather than someone else’s.
More answers in the full FAQ.