Formulare, die ohne Backend funktionieren
Setz ein Attribut auf ein Formular deiner statischen Site und jede Einsendung landet in deinem Dashboard. Kein JavaScript, kein eingebettetes Widget, kein fremder Dienst — und wer es ausfüllt, muss sich nirgends anmelden.
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>
Drei Schritte, und einer davon ist neu deployen
Es gibt keinen Formularbaukasten zu lernen und keinen Endpunkt zu kopieren. Das Attribut ist die ganze Einrichtung.
- 01
Attribut setzen
Schreib harvis-form an ein beliebiges Formular in deinem HTML. Gib ihm einen Namen, wenn die Site mehr als eines hat. Lass action und method weg — die werden beim Ausliefern der Seite ergänzt.
- 02
Neu deployen
Zieh den Ordner noch einmal rein oder führ npx harvis aus. Nichts zu konfigurieren, kein Schlüssel zum Einfügen, kein Schalter, den du vorher im Dashboard umlegen musst.
- 03
Lesen, was reinkommt
Die Einsendungen erscheinen im Dashboard unter deiner Site, die neuesten zuerst. Öffne eine Zeile, um alles zu lesen, oder nimm den ganzen Stapel als CSV.
Das Ganze sind zwei Attribute
harvis schreibt das Formular auf dem Weg aus dem Server um, damit die Seite, die du geschrieben hast, die Seite bleibt, die du geschrieben hast.
<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>Die zwei Attribute
- harvis-form
- Markiert das Formular als eines, das gesammelt wird. Der Wert benennt es, damit mehrere Formulare auf einer Site auseinandergehalten werden; lässt du den Wert weg, wird unter „default“ gesammelt.
- data-harvis-redirect
- Optional. Die Seite auf deiner Site, auf die Leute nach dem Absenden geschickt werden. Alles, was von deiner Site wegzeigt, wird ignoriert.
Wenn JavaScript das Formular baut, schreib den Endpunkt selbst hinein
harvis trägt das action-Attribut ein, während die Seite den Server verlässt. Ein Formular, das erst existiert, wenn dein Bundle gelaufen ist, steht noch gar nicht in der Seite — es gibt also nichts einzutragen, und du schreibst hin, was das Umschreiben geschrieben hätte. Es bleibt ein ganz gewöhnliches HTML-Post, und alles andere auf dieser Seite gilt weiterhin.
- 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
- Der Endpunkt, den harvis geschrieben hätte, plus post. Das letzte Segment ist der Name des Formulars — Kleinbuchstaben, Ziffern und Bindestriche; alles andere wird unter „default“ gesammelt.
- _harvis_redirect
- Optional, und die handgeschriebene Form von data-harvis-redirect. Ein Pfad auf deiner eigenen Seite; alles, was davon wegführt, wird ignoriert.
- _harvis_hp
- Optional, und das Einzige, was du verlierst, wenn du das Formular selbst schreibst: die Falle, die Bots fängt. Schieb sie aus dem sichtbaren Bereich statt display:none und lass sie leer — alles, was sie ausfüllt, wird verworfen.
- harvis-form
- Lass es weg. Das Attribut ist die Bitte an harvis, das action zu schreiben — und das hast du gerade selbst getan.
Lass den Browser abschicken
Kein preventDefault, kein fetch. Die Antwort ist ein Redirect, dem der Browser von selbst folgt — genau das hält das Formular am Leben, wenn Skripte nicht laufen.
Vorgerendert zählt als JavaScript
Wenn dein Build das Formular ins HTML schreibt und deine App es dann im Browser übernimmt, schreib die Felder trotzdem hin — die von harvis eingefügten stehen nicht in deiner Komponente, also kann die Hydration sie wegwerfen.
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.
Was du bekommst
Alles hier drunter ist von Haus aus an. Für nichts davon gibt es eine Einstellungsseite.
Kein JavaScript
Das Formular sendet so, wie HTML schon immer gesendet hat. Es funktioniert auch mit blockierten Skripten, auf einem langsamen Handy und in einem Browser, der nichts außer Text darstellt.
Spam wird gefiltert
Jedes Formular wird mit einem Lockfeld ausgeliefert, das kein Mensch sehen kann, und was es ausfüllt, wird kommentarlos verworfen. Rate-Limits begrenzen, wie viel ein einzelner Besucher — und eine einzelne Site — pro Stunde senden kann.
So viele Formulare, wie du willst
Gib ihnen Namen — ein Kontaktformular und ein Anmeldeformular auf derselben Site behalten je ihre eigene Liste, und du kannst sie getrennt filtern und exportieren.
Deine eigene Danke-Seite
Zeig mit dem Formular auf eine Seite, die du geschrieben hast, und genau dort landen die Leute nach dem Absenden. Lass es weg, und sie bekommen stattdessen eine schlichte Bestätigungsseite.
Exportieren, wann du willst
Ein Klick gibt dir eine CSV mit einer Spalte für jedes Feld, das jemals jemand abgeschickt hat — ein Formular, das unterwegs eine Frage dazubekommen hat, öffnet sich also trotzdem als eine Tabelle.
Niemandem folgt hier irgendwas
Kein Tracking-Skript, kein Cookie, keine dritte Partei in der Seite. Die Adresse der Besucher wird vor dem Speichern gehasht, und das nur, damit das Rate-Limit zwei Einsendungen auseinanderhalten kann.
Die Grenzen
Großzügig für ein Kontaktformular, eng genug, dass ein auf deine Site gerichtetes Skript dir nicht das Postfach füllt.
- Felder pro Einsendung
- 30
- Länge eines Feldes
- 5.000 Zeichen
- Größe einer Einsendung
- 64 KB
- Einsendungen pro Site
- 60 pro Stunde
- Einsendungen pro Besucher
- 20 pro Stunde
- Aufbewahrt pro Site
- 1.000, neueste zuerst
Fragen
Was Leute fragen, bevor sie ein Formular auf eine statische Site stellen.
Was ist ein statisches Formular?
Ein Formular auf einer Site, die keinen eigenen Server hat. Normalerweise heißt das, dass das Formular nirgendwohin senden kann — deshalb borgen sich statische Sites meist einen fremden Formulardienst. harvis liefert ohnehin jede Seite deiner Site aus und kann die Einsendung deshalb gleich im Vorbeigehen entgegennehmen.
Muss ich programmieren können?
Du musst ein Wort in eine Zeile HTML einfügen können. Wenn eine KI deine Site geschrieben hat, bitte sie, das Attribut harvis-form an das Formular zu schreiben — sie weiß, wie das geht, denn genau das steht in den Anweisungen, die harvis für KI-Assistenten veröffentlicht.
Funktioniert das mit abgeschaltetem JavaScript?
Ja. Genau deshalb ist es so gebaut. Das Formular macht einen ganz gewöhnlichen HTML-Post und der Browser folgt danach einer Weiterleitung — exakt so, wie Formulare funktioniert haben, bevor es JavaScript gab.
Kann ich weiter meinen eigenen Endpunkt oder einen anderen Formulardienst nutzen?
Ja — lass das Attribut einfach weg. Formulare ohne harvis-form werden genau so ausgeliefert, wie du sie geschrieben hast, action inklusive. Bei einem Formular, das es hat, ersetzt harvis die action, denn das Attribut zu setzen ist die Art, zu sagen, wohin die Einsendung gehen soll.
Funktioniert das mit React, Vue oder Svelte?
Ja, mit einem zusätzlichen Schritt. Steht das Formular im HTML, das dein Build erzeugt, reicht das Attribut. Erscheint es erst, wenn JavaScript gelaufen ist, schreib stattdessen action="/__harvis/form/contact" und method="post" in die Komponente — derselbe Endpunkt, dasselbe Dashboard, und abgeschickt wird weiterhin vom Browser.
Können Leute Dateien anhängen?
Noch nicht. Ein Datei-Feld auf einem gesammelten Formular funktioniert für die Besucher weiterhin, aber die Datei wird nicht gespeichert — aufbewahrt werden nur die Textfelder.
Was passiert mit den Einsendungen, wenn ich die Site lösche?
Sie gehen mit. Einsendungen gehören zur Site und nicht zu einem Deploy: Neu deployen behält sie, die Site löschen entfernt sie endgültig. Exportier vorher eine CSV, wenn du sie behalten willst.
Stell eine Site online und probier es aus
Veröffentlichen ist kostenlos und dauert etwa zwei Sekunden. Setz das Attribut auf ein Formular, deploye neu und schick dir selbst eine Testnachricht.