Borang yang berfungsi tanpa backend
Tambah satu atribut pada borang di laman statik anda dan setiap kiriman akan masuk ke papan pemuka. Tiada JavaScript, tiada widget terbenam, tiada perkhidmatan pihak ketiga, dan orang yang mengisinya pun tidak perlu mendaftar apa-apa.
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>
Tiga langkah, dan satu daripadanya ialah deploy semula
Tiada pembina borang untuk dipelajari dan tiada endpoint untuk disalin. Atribut itulah keseluruhan tetapannya.
- 01
Tambah atribut
Letak harvis-form pada mana-mana borang dalam HTML anda. Beri nama kalau laman itu ada lebih daripada satu. Tinggalkan action dan method — kedua-duanya diisi semasa halaman dihidangkan.
- 02
Deploy semula
Seret folder itu masuk sekali lagi, atau jalankan npx harvis. Tiada apa untuk dikonfigurasi, tiada kunci untuk ditampal, tiada suis untuk dihidupkan dahulu dalam papan pemuka.
- 03
Baca apa yang masuk
Kiriman muncul di bawah laman anda dalam papan pemuka, yang terbaharu di atas. Buka satu baris untuk membaca semuanya, atau ambil kesemuanya sebagai CSV.
Semuanya cuma dua atribut
harvis menulis semula borang itu semasa ia keluar dari pelayan, jadi halaman yang anda tulis kekal sebagai halaman yang anda tulis.
<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>Dua atribut itu
- harvis-form
- Menanda borang itu sebagai borang yang perlu dikumpul. Nilainya menjadi namanya, supaya beberapa borang pada satu laman kekal berasingan; tinggalkan nilainya dan ia dikumpul di bawah “default”.
- data-harvis-redirect
- Pilihan. Halaman di laman anda untuk membawa orang selepas mereka menghantar. Apa-apa yang menghala ke luar laman anda diabaikan.
Apabila JavaScript yang membina borang itu, tulis sendiri endpoint-nya
harvis mengisi action ketika halaman meninggalkan pelayan. Borang yang hanya wujud selepas bundle anda berjalan belum lagi berada dalam halaman itu, jadi tiada apa untuk diisi — anda menulis apa yang sepatutnya ditulis oleh penulisan semula itu. Ia tetap satu post HTML biasa, dan segala yang lain di halaman ini masih terpakai.
- 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
- Endpoint yang sepatutnya ditulis harvis, tambah post. Segmen terakhir ialah nama borang itu — huruf kecil, nombor dan sengkang; selain itu dikumpulkan di bawah “default”.
- _harvis_redirect
- Pilihan, dan bentuk tulisan tangan bagi data-harvis-redirect. Mesti laluan di laman anda sendiri; apa-apa yang menghala ke luar diabaikan.
- _harvis_hp
- Pilihan, dan satu-satunya perkara yang anda hilang apabila menulis borang itu sendiri: umpan yang menangkap bot. Alihkan ia ke luar skrin dan bukan display:none, serta biarkan kosong — apa-apa yang mengisinya akan dibuang.
- harvis-form
- Jangan letakkan. Atribut itu ialah permintaan supaya harvis menulis action, dan anda baru sahaja menulisnya sendiri.
Biar pelayar yang menghantarnya
Tiada preventDefault, tiada fetch. Balasannya ialah lencongan yang diikuti pelayar dengan sendirinya, dan itulah yang mengekalkan borang berfungsi apabila skrip tidak berjalan.
Praterap juga dikira JavaScript
Jika binaan anda menulis borang itu ke dalam HTML dan aplikasi anda kemudian mengambil alihnya dalam pelayar, tuliskan juga medan-medan itu — yang dimasukkan harvis tiada dalam komponen anda, jadi penghidratan boleh membuangnya.
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.
Apa yang anda dapat
Semua di bawah ini hidup secara lalai. Tiada halaman tetapan untuk mana-mana daripadanya.
Tiada JavaScript
Borang menghantar cara HTML sentiasa menghantar. Ia tetap berfungsi walaupun skrip disekat, pada telefon yang perlahan, dan dalam pelayar yang hanya memaparkan teks.
Spam ditapis
Setiap borang dihidangkan dengan satu medan umpan yang tidak dapat dilihat sesiapa, dan apa sahaja yang mengisinya digugurkan tanpa diberitahu. Had kadar mengehadkan berapa banyak seorang pelawat, dan satu laman, boleh hantar dalam sejam.
Seberapa banyak borang yang anda mahu
Namakan borang anda — borang hubungi dan borang pendaftaran pada laman yang sama masing-masing menyimpan senarai sendiri, dan boleh ditapis serta dieksport secara berasingan.
Halaman terima kasih anda sendiri
Halakan borang ke halaman yang anda tulis sendiri dan di situlah orang mendarat selepas menghantar. Tinggalkan sahaja, dan mereka akan dapat halaman pengesahan biasa.
Eksport bila-bila masa
Satu butang memberi anda CSV dengan satu lajur untuk setiap medan yang pernah dihantar sesiapa, jadi borang yang bertambah satu soalan di pertengahan jalan tetap dibuka sebagai satu jadual.
Tiada apa yang mengekori sesiapa
Tiada skrip penjejak, tiada kuki, tiada pihak ketiga dalam halaman itu. Alamat pelawat di-hash sebelum disimpan, dan itu pun hanya supaya had kadar dapat membezakan dua kiriman.
Hadnya
Murah hati untuk borang hubungi, cukup ketat supaya skrip yang dihalakan ke laman anda tidak memenuhi peti masuk anda.
- Medan setiap kiriman
- 30
- Panjang satu medan
- 5,000 aksara
- Saiz satu kiriman
- 64 KB
- Kiriman setiap laman
- 60 sejam
- Kiriman setiap pelawat
- 20 sejam
- Disimpan setiap laman
- 1,000, terbaharu dahulu
Soalan
Apa yang orang tanya sebelum meletakkan borang pada laman statik.
Apa itu borang statik?
Borang pada laman yang tiada pelayan sendiri. Biasanya itu bermakna borang itu tiada tempat untuk menghantar apa-apa, sebab itulah laman statik selalunya meminjam perkhidmatan borang pihak ketiga. harvis sudah pun menghidangkan setiap halaman laman anda, jadi ia boleh menyambut kiriman itu semasa lalu.
Perlukah saya tahu menulis kod?
Anda cuma perlu boleh menambah satu perkataan pada satu baris HTML. Kalau laman anda ditulis oleh AI, minta ia tambah atribut harvis-form pada borang itu — ia tahu caranya, kerana arahan yang harvis terbitkan untuk pembantu AI memang menyatakannya.
Adakah ia berfungsi dengan JavaScript dimatikan?
Ya. Itulah sebabnya ia dibina begini. Borang membuat post HTML biasa dan pelayar mengikut satu ubah hala selepas itu, betul-betul macam borang berfungsi sebelum JavaScript wujud.
Boleh saya terus guna endpoint sendiri atau perkhidmatan borang lain?
Boleh — jangan tambah atribut itu sahaja. Borang tanpa harvis-form dihidangkan betul-betul seperti anda menulisnya, termasuk action. Pada borang yang ada atribut itu, harvis menggantikan action-nya, kerana menambah atribut itulah cara anda memberitahu ke mana kiriman patut pergi.
Adakah ia berfungsi dengan React, Vue atau Svelte?
Ya, dengan satu langkah tambahan. Jika borang itu berada dalam HTML yang dihasilkan binaan anda, atribut itu sudah memadai. Jika ia hanya muncul selepas JavaScript berjalan, tulis action="/__harvis/form/contact" dan method="post" ke dalam komponen itu — endpoint sama, papan pemuka sama, dan pelayar tetap yang menghantarnya.
Boleh orang lampirkan fail?
Belum lagi. Medan fail pada borang yang dikumpul masih berfungsi untuk pelawat, tetapi failnya tidak disimpan — hanya medan teks yang dikekalkan.
Apa jadi pada kiriman kalau saya padam laman itu?
Ia hilang sekali. Kiriman milik laman, bukan milik satu deploy, jadi deploy semula mengekalkannya dan memadam laman membuangnya terus. Eksport CSV dahulu kalau anda mahu menyimpannya.
Terbitkan satu laman dan cubalah
Menerbitkan adalah percuma dan mengambil masa kira-kira dua saat. Tambah atribut pada borang, deploy semula, dan hantar mesej ujian kepada diri sendiri.