Formularios que funcionan sin backend
Añade un atributo a un formulario de tu sitio estático y cada envío llega a tu panel. Sin JavaScript, sin widgets incrustados, sin servicios de terceros y sin nada a lo que tenga que registrarse quien lo rellena.
Its markup, styles and deploy script are on GitHub — copy it, change the fields, redeploy.
<form harvis-form="contact"> <input name="email" type="email" required> <textarea name="message"></textarea> <button>Send</button> </form>
Tres pasos, y uno de ellos es volver a desplegar
No hay ningún creador de formularios que aprender ni ningún endpoint que copiar. El atributo es toda la configuración.
- 01
Añade el atributo
Pon harvis-form en cualquier formulario de tu HTML. Dale un nombre si el sitio tiene más de uno. Deja fuera action y method — se rellenan al servir la página.
- 02
Vuelve a desplegar
Arrastra la carpeta otra vez o ejecuta npx harvis. Nada que configurar, ninguna clave que pegar, ningún interruptor que activar antes en un panel.
- 03
Lee lo que llega
Los envíos aparecen bajo tu sitio en el panel, los más recientes primero. Abre una fila para leerlo entero, o llévate el conjunto en CSV.
Todo se reduce a dos atributos
harvis reescribe el formulario al salir del servidor, así que la página que escribiste sigue siendo la página que escribiste.
<form harvis-form="contact"
data-harvis-redirect="/thanks.html">
<input name="email" type="email" required>
<button>Send</button>
</form><form harvis-form="contact"
data-harvis-redirect="/thanks.html"
action="/__harvis/form/contact" method="post">
<input type="hidden" name="_harvis_redirect" value="/thanks.html">
<input type="text" name="_harvis_hp" tabindex="-1" aria-hidden="true" style="…">
<input name="email" type="email" required>
<button>Send</button>
</form>Los dos atributos
- harvis-form
- Marca el formulario como uno que hay que recoger. El valor le pone nombre, para que varios formularios de un mismo sitio no se mezclen; si dejas el valor fuera, se recoge bajo “default”.
- data-harvis-redirect
- Opcional. La página de tu sitio a la que enviar a la gente después de que envíen. Cualquier cosa que apunte fuera de tu sitio se ignora.
Si el formulario lo construye JavaScript, escribe tú el endpoint
harvis rellena el action cuando la página sale del servidor. Un formulario que solo existe después de ejecutar tu bundle todavía no está en la página, así que no hay nada que rellenar: escribes tú lo que habría escrito la reescritura. Sigue siendo un post HTML normal, y todo lo demás de esta página sigue valiendo.
- React
- Vue
- Svelte
- Angular
<form action="/__harvis/form/contact" method="post">
<input name="email" type="email" required />
<textarea name="message" />
{/* optional — where to land after sending */}
<input type="hidden" name="_harvis_redirect" value="/thanks" />
{/* optional — the decoy harvis would have added */}
<input
type="text"
name="_harvis_hp"
tabIndex={-1}
autoComplete="off"
aria-hidden="true"
style={{ position: "absolute", left: "-9999px", opacity: 0 }}
/>
<button>Send</button>
</form>- action + method
- El endpoint que habría escrito harvis, más post. El último segmento es el nombre del formulario: minúsculas, números y guiones; cualquier otra cosa se recoge bajo «default».
- _harvis_redirect
- Opcional, y la versión escrita a mano de data-harvis-redirect. Una ruta de tu propio sitio; cualquier cosa que apunte fuera se ignora.
- _harvis_hp
- Opcional, y lo único que pierdes al escribir el formulario tú mismo: el señuelo que caza bots. Mantenlo fuera de pantalla en vez de display:none, y déjalo vacío: cualquier cosa que lo rellene se descarta.
- harvis-form
- No lo pongas. El atributo es una petición para que harvis escriba el action, y acabas de escribirlo tú.
Deja que lo envíe el navegador
Sin preventDefault, sin fetch. La respuesta es una redirección que el navegador sigue por su cuenta, y eso es lo que mantiene el formulario funcionando cuando no hay scripts.
Prerenderizado cuenta como JavaScript
Si tu build escribe el formulario en el HTML y luego tu app lo toma en el navegador, escribe igualmente los campos: los que insertó harvis no están en tu componente, así que la hidratación puede tirarlos.
Hand it to the agent that wrote your site
If an AI built the site, it can wire the forms up too. Copy the brief below into whatever has your project open — Claude Code, Cursor, Copilot, the chat you built the page in — and it will find the forms you already have and convert them. It covers both cases on this page, so you don't have to know which one you're in.
Wire the forms in this project up to harvis.dev (https://harvis.dev), which collects form submissions for static sites that have no backend. Read all of this before editing anything.
HOW IT WORKS
harvis serves every page of a site it hosts, so it can take a form post on the way past. Marking a <form> with the harvis-form attribute is the whole integration: as the page is served, harvis rewrites that tag to action="/__harvis/form/<name>" method="post", inserts a hidden honeypot field, and turns data-harvis-redirect into a hidden field. The form then posts natively, same-origin, with no JavaScript, no API key and no third-party service. Submissions appear in the site owner's dashboard (https://harvis.dev/dashboard) and are emailed to them.
STEP 1 — FIND THE FORMS
Look for every <form> in the project: .html files, and templates or components if a framework is in use. Skip search boxes and any form that posts to an API this project owns and expects a reply from. For each remaining form, decide which case it is:
- CASE A — the form is in the HTML that gets deployed: plain HTML, or a generator/framework that renders it at build time. This is the common case.
- CASE B — the form only exists once JavaScript has run: a React/Vue/Svelte/Angular component mounted in the browser, or markup a script writes. harvis rewrites the served HTML, so a form that is not in it yet is never rewritten. A form that is prerendered and then hydrated is CASE B too, because the fields harvis inserts are not in the component tree and hydration can discard them.
STEP 2A — CASE A: ADD THE ATTRIBUTE
- Add harvis-form="<name>" to the opening <form> tag. Name it for what it is — contact, signup, feedback. The name must match ^[a-z0-9][a-z0-9_-]{0,39}$ or it is collected under "default". Two forms on one site should not share a name unless they should share one list of submissions.
- Delete that form's existing action and method attributes. harvis overwrites both, so leaving them there only misleads whoever reads the file next.
- Optional: add data-harvis-redirect="/thanks.html" to choose where the visitor lands after sending. It must be a path on this same site and the file must exist in the deploy; anything pointing off-site is ignored. Without it, visitors get a plain harvis confirmation page.
- Do not add a honeypot, _harvis_hp or _harvis_redirect by hand — harvis inserts them, and a second copy is a bug.
STEP 2B — CASE B: WRITE THE ENDPOINT YOURSELF
- Set action="/__harvis/form/<name>" and method="post" on the form, and do NOT add harvis-form: that attribute is a request to rewrite, and here you have written the rewrite yourself.
- Optional redirect: a hidden input named _harvis_redirect whose value is a path on this site.
- Optional honeypot, since nothing will add one for you: an empty text input named _harvis_hp with tabindex -1, autocomplete off, aria-hidden true, positioned off-screen with position:absolute;left:-9999px rather than display:none.
- Let the browser submit it: no onSubmit handler, no preventDefault, no fetch. The reply is a 303 that the browser follows on its own, and intercepting it is what breaks the form when scripts fail.
STEP 3 — IN BOTH CASES
- Every field to be collected needs a name attribute; an input without one is never submitted. Those names become the column headings the owner reads, so prefer name, email and message over field1.
- Remove what is left of any other form service: a Formspree, Getform, Basin or FormSubmit action URL, Netlify's data-netlify attribute and its hidden form-name input, a Web3Forms access_key input, and any handler that POSTed the form somewhere else.
- Keep the client-side validation as it is. required, type="email", minlength and the rest all still work.
- File inputs are dropped: submissions are stored as text and files are not kept. If a form has one, say so rather than leaving it in silently.
- Add no script, SDK, key or config file. There is nothing to install.
- Limits, worth mentioning if a form is likely to meet one: 30 fields per submission, 5,000 characters per field, 64 KB per submission, 60 submissions per site an hour, 20 per visitor an hour, and the newest 1,000 per site are kept.
STEP 4 — DEPLOY AND REPORT
- The attribute only does anything on a served page, so deploy the site again: run npx harvis from the site folder, or tell the user to drag the folder onto https://harvis.dev/drop.
- Then tell the user which files changed, the name you gave each form, and that submissions arrive at https://harvis.dev/dashboard and by email — noting that a site deployed anonymously has no owner to email until it is claimed with the claim link.
- Suggest they send one test submission through the live site.What it tells the agent to do
- Find every form in the project and leave the search boxes and API calls alone.
- Add harvis-form with a sensible name, and strip the action and method that harvis replaces anyway.
- Write the endpoint by hand instead when the form only exists after JavaScript runs.
- Clear out whatever the last form service left behind, and flag a file upload as something that won't be kept.
- Redeploy, then tell you what changed and where the submissions land.
Or leave it in the repo
The same text works as a file — save it as AGENTS.md, CLAUDE.md or a project rule and an agent reads it on its own, so the next form someone adds is collected without anyone asking.
Lo que consigues
Todo lo de abajo viene activado. No hay página de ajustes para nada de esto.
Sin JavaScript
El formulario envía como HTML ha enviado siempre. Sigue funcionando con los scripts bloqueados, en un móvil lento y en un navegador que solo pinta texto.
El spam se filtra
Cada formulario se sirve con un campo señuelo que nadie puede ver, y todo lo que lo rellena se descarta sin avisar. Los límites de frecuencia acotan lo que un visitante, y un sitio, pueden enviar en una hora.
Tantos formularios como quieras
Ponles nombre — un formulario de contacto y uno de registro en el mismo sitio mantienen cada uno su lista, y puedes filtrarlos y exportarlos por separado.
Tu propia página de gracias
Apunta el formulario a una página que hayas escrito tú y ahí es donde aterriza la gente después de enviar. Si no la pones, verán una página de confirmación sencilla.
Exporta cuando quieras
Un botón te da un CSV con una columna por cada campo que alguien haya enviado alguna vez, así que un formulario que ganó una pregunta por el camino se sigue abriendo como una sola tabla.
Nadie sigue a nadie
Sin script de seguimiento, sin cookies, sin terceros dentro de la página. La dirección del visitante se hashea antes de guardarse, y solo para que el límite de frecuencia pueda distinguir dos envíos.
Los límites
Generosos para un formulario de contacto, y lo bastante estrechos como para que un script apuntado a tu sitio no te llene la bandeja de entrada.
- Campos por envío
- 30
- Longitud de un campo
- 5.000 caracteres
- Tamaño de un envío
- 64 KB
- Envíos por sitio
- 60 por hora
- Envíos por visitante
- 20 por hora
- Guardados por sitio
- 1.000, los más recientes primero
Preguntas
Lo que se pregunta la gente antes de poner un formulario en un sitio estático.
¿Qué es un formulario estático?
Un formulario en un sitio que no tiene servidor propio. Normalmente eso significa que el formulario no tiene a dónde mandar nada, y por eso los sitios estáticos suelen tomar prestado un servicio de formularios de terceros. harvis ya sirve todas las páginas de tu sitio, así que puede recoger el envío al vuelo.
¿Necesito saber programar?
Necesitas saber añadir una palabra a una línea de HTML. Si tu sitio lo escribió una IA, pídele que añada el atributo harvis-form al formulario — sabe hacerlo, porque así lo dicen las instrucciones que harvis publica para asistentes de IA.
¿Funciona con JavaScript desactivado?
Sí. Justamente por eso está construido así. El formulario hace un post HTML normal y corriente y luego el navegador sigue una redirección, exactamente como funcionaban los formularios antes de que existiera JavaScript.
¿Puedo seguir usando mi propio endpoint u otro servicio de formularios?
Sí — simplemente no añadas el atributo. Los formularios sin harvis-form se sirven exactamente como los escribiste, action incluido. En uno que sí lo tenga, harvis sustituye el action, porque añadir el atributo es la forma de decir a dónde debe ir el envío.
¿Funciona con React, Vue o Svelte?
Sí, con un paso extra. Si el formulario está en el HTML que produce tu build, basta con el atributo. Si solo aparece después de ejecutar JavaScript, escribe en el componente action="/__harvis/form/contact" y method="post" — mismo endpoint, mismo panel, y el envío lo sigue haciendo el navegador.
¿Puede la gente adjuntar archivos?
Todavía no. Un campo de archivo en un formulario recogido le sigue funcionando al visitante, pero el archivo no se guarda — solo se conservan los campos de texto.
¿Qué pasa con los envíos si elimino el sitio?
Se van con él. Los envíos pertenecen al sitio y no a un despliegue, así que volver a desplegar los conserva y eliminar el sitio los borra para siempre. Exporta un CSV antes si quieres quedártelos.
Pon un sitio online y pruébalo
Publicar es gratis y tarda unos dos segundos. Añade el atributo a un formulario, vuelve a desplegar y mándate un mensaje de prueba.