biểu mẫu

Biểu mẫu chạy được mà không cần backend

Thêm một thuộc tính vào biểu mẫu trên trang tĩnh của bạn và mọi lượt gửi sẽ về thẳng bảng điều khiển. Không JavaScript, không widget nhúng, không dịch vụ bên thứ ba, và người điền cũng chẳng phải đăng ký gì.

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

Ba bước, và một trong số đó là triển khai lại

Không có trình tạo biểu mẫu nào phải học, cũng không có endpoint nào phải sao chép. Thuộc tính đó là toàn bộ phần thiết lập.

  1. 01

    Thêm thuộc tính

    Đặt harvis-form vào bất kỳ biểu mẫu nào trong HTML của bạn. Đặt tên cho nó nếu trang có nhiều hơn một biểu mẫu. Bỏ qua action và method — chúng sẽ được điền vào lúc trang được phục vụ.

  2. 02

    Triển khai lại

    Kéo thư mục vào lần nữa, hoặc chạy npx harvis. Không có gì để cấu hình, không khóa nào phải dán, không công tắc nào phải bật trước trong bảng điều khiển.

  3. 03

    Đọc những gì gửi đến

    Các lượt gửi hiện ra dưới trang của bạn trong bảng điều khiển, mới nhất lên trước. Mở một dòng để đọc toàn bộ, hoặc lấy tất cả dưới dạng CSV.

the markup

Tất cả gói gọn trong hai thuộc tính

harvis viết lại biểu mẫu trên đường nó rời khỏi máy chủ, nên trang bạn viết vẫn cứ là trang bạn viết.

những gì bạn viết
<form harvis-form="contact"
      data-harvis-redirect="/thanks.html">
  <input name="email" type="email" required>
  <button>Send</button>
</form>
những gì khách của bạn nhận được
<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>

Hai thuộc tính

harvis-form
Đánh dấu biểu mẫu là loại cần thu thập. Giá trị chính là tên của nó, để nhiều biểu mẫu trên cùng một trang không lẫn vào nhau; bỏ trống giá trị thì nó được thu thập dưới tên “default”.
data-harvis-redirect
Không bắt buộc. Trang trên site của bạn để đưa mọi người tới sau khi họ gửi. Bất cứ địa chỉ nào trỏ ra ngoài site của bạn đều bị bỏ qua.
frameworks

Khi JavaScript dựng biểu mẫu, hãy tự viết endpoint vào

harvis điền action vào lúc trang rời khỏi máy chủ. Một biểu mẫu chỉ xuất hiện sau khi bundle của bạn chạy thì chưa có trong trang, nên chẳng có gì để điền — bạn viết ra đúng thứ mà phần ghi lại kia lẽ ra đã viết. Nó vẫn là một lượt post HTML bình thường, và mọi điều khác trên trang này vẫn đúng.

  • 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
Chính là endpoint mà harvis lẽ ra sẽ viết, kèm post. Đoạn cuối là tên của biểu mẫu — chữ thường, số và dấu gạch ngang; mọi thứ khác được gom vào “default”.
_harvis_redirect
Không bắt buộc, và là bản viết tay của data-harvis-redirect. Phải là đường dẫn trên chính trang của bạn; thứ gì trỏ ra ngoài đều bị bỏ qua.
_harvis_hp
Không bắt buộc, và là thứ duy nhất bạn mất khi tự viết biểu mẫu: cái bẫy bắt bot. Hãy đẩy nó ra ngoài màn hình thay vì display:none, và để trống — thứ gì điền vào ô đó đều bị loại.
harvis-form
Đừng thêm nó. Thuộc tính đó là lời nhờ harvis viết action, mà bạn thì vừa tự viết xong rồi.

Cứ để trình duyệt gửi

Không preventDefault, không fetch. Phản hồi là một lệnh chuyển hướng mà trình duyệt tự đi theo, và chính điều đó giữ cho biểu mẫu vẫn chạy khi script không chạy.

Prerender cũng tính là JavaScript

Nếu bản build viết biểu mẫu vào HTML rồi ứng dụng của bạn tiếp quản nó trong trình duyệt, vẫn cứ viết các trường vào — những trường harvis chèn không có trong component của bạn, nên quá trình hydrate có thể vứt chúng đi.

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

Bạn nhận được gì

Mọi thứ bên dưới đều bật sẵn. Chẳng có trang cài đặt nào cho bất kỳ mục nào cả.

  • Không cần JavaScript

    Biểu mẫu gửi đi theo đúng cách HTML vẫn luôn gửi. Nó vẫn chạy khi script bị chặn, trên một chiếc điện thoại chậm, và trong trình duyệt chỉ hiển thị được văn bản.

  • Spam bị lọc

    Mỗi biểu mẫu được phục vụ kèm một trường mồi mà không ai nhìn thấy, và bất cứ thứ gì điền vào đó đều bị bỏ đi mà không báo lại. Giới hạn tần suất khống chế lượng mà một khách truy cập, và một trang, có thể gửi trong một giờ.

  • Bao nhiêu biểu mẫu tùy bạn

    Hãy đặt tên cho chúng — một biểu mẫu liên hệ và một biểu mẫu đăng ký trên cùng một trang mỗi cái giữ danh sách riêng, và bạn có thể lọc cũng như xuất riêng từng cái.

  • Trang cảm ơn của riêng bạn

    Trỏ biểu mẫu tới một trang do bạn viết và đó là nơi mọi người sẽ đến sau khi gửi. Bỏ trống thì họ sẽ thấy một trang xác nhận đơn giản.

  • Xuất ra bất cứ lúc nào

    Một nút bấm cho bạn tệp CSV với một cột cho mọi trường mà bất kỳ ai từng gửi, nên một biểu mẫu có thêm câu hỏi giữa chừng vẫn mở ra thành một bảng duy nhất.

  • Không có gì đi theo ai cả

    Không script theo dõi, không cookie, không bên thứ ba nào trong trang. Địa chỉ của khách truy cập được băm trước khi lưu, và chỉ để giới hạn tần suất phân biệt được hai lượt gửi.

limits

Các giới hạn

Rộng rãi với một biểu mẫu liên hệ, và chặt vừa đủ để một script nhắm vào trang của bạn không làm đầy hộp thư.

Số trường mỗi lượt gửi
30
Độ dài một trường
5.000 ký tự
Dung lượng một lượt gửi
64 KB
Lượt gửi mỗi trang
60 mỗi giờ
Lượt gửi mỗi khách
20 mỗi giờ
Lưu mỗi trang
1.000, mới nhất trước
faq

Câu hỏi

Những gì mọi người hỏi trước khi đặt một biểu mẫu lên trang tĩnh.

Biểu mẫu tĩnh là gì?

Là biểu mẫu trên một trang không có máy chủ riêng. Thường thì điều đó nghĩa là biểu mẫu chẳng có chỗ nào để gửi đi, và đó là lý do các trang tĩnh hay phải mượn một dịch vụ biểu mẫu bên thứ ba. harvis vốn đã phục vụ mọi trang của site bạn rồi, nên nó có thể nhận luôn lượt gửi trên đường đi.

Tôi có cần biết lập trình không?

Bạn chỉ cần thêm được một từ vào một dòng HTML. Nếu site của bạn do AI viết, hãy bảo nó thêm thuộc tính harvis-form vào biểu mẫu — nó biết cách, vì hướng dẫn mà harvis công bố cho các trợ lý AI đã nói vậy.

Nó có chạy khi tắt JavaScript không?

Có. Đó chính là lý do nó được làm theo cách này. Biểu mẫu thực hiện một lượt post HTML thông thường rồi trình duyệt đi theo một chuyển hướng, y hệt cách biểu mẫu hoạt động từ trước khi có JavaScript.

Tôi có thể tiếp tục dùng endpoint riêng hoặc một dịch vụ biểu mẫu khác không?

Được — chỉ cần đừng thêm thuộc tính. Biểu mẫu không có harvis-form sẽ được phục vụ đúng như bạn viết, kể cả action. Với biểu mẫu có thuộc tính đó, harvis sẽ thay action, vì thêm thuộc tính chính là cách bạn nói lượt gửi nên đi về đâu.

Nó có chạy với React, Vue hay Svelte không?

Có, chỉ thêm một bước. Nếu biểu mẫu nằm sẵn trong HTML mà bản build tạo ra thì chỉ cần thuộc tính là đủ. Nếu nó chỉ xuất hiện sau khi JavaScript chạy, hãy viết action="/__harvis/form/contact" và method="post" vào component — vẫn endpoint đó, vẫn bảng điều khiển đó, và trình duyệt vẫn là bên gửi đi.

Mọi người có đính kèm tệp được không?

Chưa. Ô chọn tệp trên biểu mẫu được thu thập vẫn hoạt động với khách truy cập, nhưng tệp không được lưu — chỉ các trường văn bản mới được giữ lại.

Nếu tôi xóa trang thì các lượt gửi ra sao?

Chúng đi theo luôn. Lượt gửi thuộc về trang chứ không thuộc về một lần triển khai, nên triển khai lại thì vẫn còn, còn xóa trang thì mất hẳn. Hãy xuất CSV trước nếu bạn muốn giữ.

Đưa một trang lên mạng và thử xem

Xuất bản thì miễn phí và mất khoảng hai giây. Thêm thuộc tính vào một biểu mẫu, triển khai lại, rồi tự gửi cho mình một tin nhắn thử.

Đưa một trang lên mạng