formulir

Formulir yang jalan tanpa backend

Tambahkan satu atribut ke formulir di situs statis kamu dan setiap kiriman mendarat di dashboard. Tanpa JavaScript, tanpa widget tempelan, tanpa layanan 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.

index.html
<form harvis-form="contact">
  <input name="email" type="email" required>
  <textarea name="message"></textarea>
  <button>Send</button>
</form>
how it works

Tiga langkah, dan salah satunya cuma deploy ulang

Tidak ada pembuat formulir yang harus dipelajari dan tidak ada endpoint yang harus disalin. Atribut itulah seluruh setelannya.

  1. 01

    Tambahkan atributnya

    Taruh harvis-form di formulir mana pun dalam HTML kamu. Beri nama kalau situsnya punya lebih dari satu. Biarkan action dan method kosong — keduanya diisi saat halaman disajikan.

  2. 02

    Deploy ulang

    Seret foldernya lagi, atau jalankan npx harvis. Tidak ada yang perlu dikonfigurasi, tidak ada kunci yang perlu ditempel, tidak ada sakelar yang harus dinyalakan dulu di dashboard.

  3. 03

    Baca yang masuk

    Kiriman muncul di bawah situs kamu di dashboard, yang terbaru di atas. Buka satu baris untuk membaca semuanya, atau ambil semuanya sekaligus sebagai CSV.

the markup

Semuanya cuma dua atribut

harvis menulis ulang formulirnya saat keluar dari server, jadi halaman yang kamu tulis tetap jadi halaman yang kamu tulis.

yang kamu tulis
<form harvis-form="contact"
      data-harvis-redirect="/thanks.html">
  <input name="email" type="email" required>
  <button>Send</button>
</form>
yang diterima pengunjung kamu
<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
Menandai formulir sebagai formulir yang dikumpulkan. Nilainya jadi namanya, supaya beberapa formulir di satu situs tetap terpisah; kalau nilainya dikosongkan, kiriman dikumpulkan di bawah “default”.
data-harvis-redirect
Opsional. Halaman di situs kamu yang jadi tujuan orang setelah mengirim. Apa pun yang mengarah ke luar situs kamu akan diabaikan.
frameworks

Kalau formulirnya dibangun JavaScript, tulis sendiri endpoint-nya

harvis mengisi action tepat saat halaman meninggalkan server. Formulir yang baru ada setelah bundle-mu berjalan belum ada di halaman itu, jadi tak ada yang bisa diisi — kamu menuliskan apa yang seharusnya ditulis oleh penulisan ulang tadi. Ini tetap post HTML biasa, dan semua hal lain di halaman ini tetap berlaku.

  • React
  • Vue
  • Svelte
  • Angular
ContactForm.jsx
<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 tadinya akan ditulis harvis, ditambah post. Segmen terakhir adalah nama formulirnya — huruf kecil, angka, dan tanda hubung; selain itu dikumpulkan di bawah “default”.
_harvis_redirect
Opsional, dan versi tulis tangan dari data-harvis-redirect. Harus path di situsmu sendiri; apa pun yang menunjuk ke luar diabaikan.
_harvis_hp
Opsional, dan satu-satunya yang hilang kalau kamu menulis formulirnya sendiri: umpan penangkap bot. Dorong ke luar layar alih-alih display:none, dan biarkan kosong — apa pun yang mengisinya dibuang.
harvis-form
Jangan dipasang. Atribut itu adalah permintaan agar harvis menuliskan action-nya, dan kamu baru saja menulisnya sendiri.

Biarkan peramban yang mengirim

Tanpa preventDefault, tanpa fetch. Balasannya adalah pengalihan yang diikuti peramban sendiri, dan itulah yang membuat formulir tetap jalan saat skrip tidak jalan.

Prerender tetap terhitung JavaScript

Kalau build-mu menulis formulir ke dalam HTML lalu aplikasimu mengambil alih di peramban, tulis saja tetap field-nya — yang disisipkan harvis tidak ada di komponenmu, jadi hidrasi bisa membuangnya.

ai agents

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.

paste this into your ai agent
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.

included

Apa yang kamu dapat

Semua di bawah ini sudah aktif sejak awal. Tidak ada halaman pengaturan untuk satu pun dari ini.

  • Tanpa JavaScript

    Formulir mengirim seperti cara HTML mengirim sejak dulu. Tetap jalan meski skrip diblokir, di ponsel yang lambat, dan di browser yang cuma menampilkan teks.

  • Spam disaring

    Setiap formulir disajikan dengan kolom umpan yang tidak terlihat manusia, dan apa pun yang mengisinya dibuang tanpa pemberitahuan. Batas laju membatasi berapa banyak yang bisa dikirim satu pengunjung, dan satu situs, dalam sejam.

  • Formulir sebanyak yang kamu mau

    Beri nama masing-masing — formulir kontak dan formulir pendaftaran di situs yang sama sama-sama punya daftar sendiri, dan bisa kamu filter serta ekspor terpisah.

  • Halaman terima kasih buatan sendiri

    Arahkan formulir ke halaman yang kamu tulis sendiri, dan di situlah orang mendarat setelah mengirim. Kalau tidak diisi, mereka akan melihat halaman konfirmasi sederhana.

  • Ekspor kapan saja

    Satu tombol memberi kamu CSV dengan satu kolom untuk setiap kolom yang pernah dikirim siapa pun, jadi formulir yang menambah pertanyaan di tengah jalan tetap terbuka sebagai satu tabel.

  • Tidak ada yang mengikuti siapa pun

    Tanpa skrip pelacak, tanpa cookie, tanpa pihak ketiga di dalam halaman. Alamat pengunjung di-hash sebelum disimpan, dan itu pun hanya supaya batas laju bisa membedakan dua kiriman.

limits

Batasannya

Longgar untuk formulir kontak, dan cukup ketat supaya skrip yang diarahkan ke situs kamu tidak membanjiri kotak masuk.

Kolom per kiriman
30
Panjang satu kolom
5.000 karakter
Ukuran satu kiriman
64 KB
Kiriman per situs
60 per jam
Kiriman per pengunjung
20 per jam
Disimpan per situs
1.000, terbaru dulu
faq

Pertanyaan

Yang biasa ditanyakan orang sebelum menaruh formulir di situs statis.

Apa itu formulir statis?

Formulir di situs yang tidak punya server sendiri. Biasanya itu berarti formulirnya tidak punya tempat untuk mengirim apa pun, dan karena itulah situs statis biasanya meminjam layanan formulir pihak ketiga. harvis toh sudah menyajikan setiap halaman situs kamu, jadi dia bisa menerima kirimannya sekalian di jalan.

Apakah aku harus bisa coding?

Kamu cuma perlu bisa menambahkan satu kata ke satu baris HTML. Kalau situsmu ditulis AI, minta saja dia menambahkan atribut harvis-form ke formulirnya — dia tahu caranya, karena instruksi yang harvis terbitkan untuk asisten AI memang menyebutkannya.

Apakah tetap jalan kalau JavaScript dimatikan?

Ya. Justru itu alasannya dibuat seperti ini. Formulirnya melakukan post HTML biasa dan browser mengikuti sebuah pengalihan setelahnya, persis seperti cara formulir bekerja sebelum JavaScript ada.

Bisakah aku tetap pakai endpoint sendiri atau layanan formulir lain?

Bisa — cukup jangan tambahkan atributnya. Formulir tanpa harvis-form disajikan persis seperti yang kamu tulis, action dan semuanya. Pada formulir yang punya atribut itu, harvis mengganti action-nya, karena menambahkan atribut itulah cara kamu menyatakan ke mana kiriman harus pergi.

Apakah ini jalan dengan React, Vue, atau Svelte?

Jalan, dengan satu langkah tambahan. Kalau formulirnya ada di HTML yang dihasilkan build-mu, cukup atributnya saja. Kalau ia baru muncul setelah JavaScript berjalan, tulis action="/__harvis/form/contact" dan method="post" di komponennya — endpoint sama, dasbor sama, dan yang mengirim tetap peramban.

Bisakah orang melampirkan file?

Belum. Kolom file di formulir yang dikumpulkan tetap berfungsi untuk pengunjung, tapi filenya tidak disimpan — hanya kolom teks yang dipertahankan.

Apa yang terjadi pada kiriman kalau situsnya aku hapus?

Ikut hilang. Kiriman milik situs, bukan milik satu deploy, jadi deploy ulang tetap menyimpannya dan menghapus situs menghilangkannya untuk selamanya. Ekspor CSV dulu kalau kamu ingin menyimpannya.

Bikin situs online dan coba sendiri

Publikasi itu gratis dan makan waktu sekitar dua detik. Tambahkan atributnya ke sebuah formulir, deploy ulang, lalu kirim pesan uji ke dirimu sendiri.

Bikin situs online