--- name: netlify-forms-ts description: Handle Netlify Forms in TanStack Start. Use when implementing contact forms, signup forms, or any form submission handling on Netlify-hosted TanStack Start sites. license: Apache-2.0 metadata: author: netlify version: "1.0" --- # Netlify Forms — TanStack Start Addendum > **Base skill**: General Netlify Forms usage (HTML setup, spam filtering, honeypot fields, notifications) is covered by the `netlify-forms` skill in the agent-runner base skills. This skill covers **only** the TanStack Start-specific requirement on top of that. --- ## The Problem: Netlify Can't Detect React-Rendered Forms TanStack Start renders pages via React on the client side. Netlify's build-time form detection works by scanning the **static HTML output** of your build. Because TanStack Start's forms live inside React components, Netlify never sees them during the build — so the form is never registered and submissions will silently fail. Without build-time detection, POSTs containing form data pass through to the SSR function instead of being intercepted by Netlify's form processing. --- ## The Solution: Dummy Static Form in `./public/` Place a minimal static HTML file in `./public/` containing a hidden form that mirrors your React form's fields. Netlify scans `public/` at build time, registers the form name, and starts accepting submissions to it. ### `public/contact-form.html` ```html ``` **Rules:** - `name="contact"` must exactly match the `name` attribute in your React component's fetch call. - Include every field your React form submits — Netlify validates field names against the registered form. - Add `netlify-honeypot="bot-field"` here if your React form uses a honeypot field. --- ## The React Component Submit via AJAX using `fetch` with `application/x-www-form-urlencoded` encoding. Do **not** use a plain `
` — that causes a full-page reload and breaks TanStack Router's client-side navigation. ```tsx import { useState } from 'react' function encode(data: Record) { return Object.entries(data) .map(([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`) .join('&') } export function ContactForm() { const [fields, setFields] = useState({ name: '', email: '', message: '' }) const [submitted, setSubmitted] = useState(false) const handleChange = ( e: React.ChangeEvent ) => setFields({ ...fields, [e.target.name]: e.target.value }) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() await fetch('/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: encode({ 'form-name': 'contact', ...fields }), }) setSubmitted(true) } if (submitted) return

Thanks! We'll be in touch.

return ( {/* Required hidden field — tells Netlify which registered form this maps to */}