양식

백엔드 없이 동작하는 양식

정적 사이트의 양식에 속성 하나만 붙이면 모든 제출 내용이 대시보드로 들어옵니다. JavaScript도, 삽입 위젯도, 외부 서비스도 없고, 양식을 채우는 사람이 가입할 것도 없습니다.

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

세 단계, 그중 하나는 다시 배포하기

익혀야 할 양식 빌더도, 복사해 넣을 엔드포인트도 없습니다. 이 속성이 설정의 전부입니다.

  1. 01

    속성 붙이기

    HTML의 아무 양식에나 harvis-form을 씁니다. 사이트에 양식이 여러 개면 이름을 지어 주세요. action과 method는 빼도 됩니다 — 페이지가 전송될 때 채워집니다.

  2. 02

    다시 배포하기

    폴더를 다시 끌어다 놓거나 npx harvis를 실행하면 됩니다. 설정할 것도, 붙여 넣을 키도, 대시보드에서 먼저 켜야 할 스위치도 없습니다.

  3. 03

    들어온 내용 읽기

    제출 내용은 대시보드의 사이트 아래에 최신순으로 쌓입니다. 행을 열면 전문을 읽을 수 있고, 전체를 CSV로 받아 갈 수도 있습니다.

the markup

전부 합쳐 속성 두 개

harvis는 양식이 서버를 빠져나가는 길에 고쳐 씁니다. 그래서 당신이 쓴 페이지는 당신이 쓴 페이지 그대로 남습니다.

당신이 쓰는 것
<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>

두 가지 속성

harvis-form
그 양식을 수집 대상으로 표시합니다. 값이 이름이 되어 한 사이트의 여러 양식이 섞이지 않습니다. 값을 비워 두면 “default”로 수집됩니다.
data-harvis-redirect
선택 사항. 제출 후 사람들을 보낼 내 사이트의 페이지입니다. 사이트 밖을 가리키는 주소는 무시됩니다.
frameworks

폼을 JavaScript가 만든다면 엔드포인트를 직접 적으세요

harvis는 페이지가 서버를 떠나는 순간 action을 채웁니다. 번들이 실행된 뒤에야 생기는 폼은 그 시점에 아직 페이지에 없으니 채울 것도 없습니다 — 다시 쓰기가 적었을 내용을 당신이 적는 셈입니다. 여전히 평범한 HTML post이고, 이 페이지의 다른 설명도 그대로 적용됩니다.

  • 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
harvis가 써 넣었을 엔드포인트에 post를 더한 것입니다. 마지막 조각이 폼의 이름이고 — 소문자, 숫자, 하이픈만 쓸 수 있으며, 그 밖의 것은 'default'로 모입니다.
_harvis_redirect
선택 사항이며, data-harvis-redirect를 손으로 쓴 형태입니다. 내 사이트 안의 경로여야 하고, 바깥을 가리키는 것은 무시됩니다.
_harvis_hp
선택 사항이자, 폼을 직접 쓸 때 잃게 되는 단 하나 — 봇을 잡는 미끼입니다. display:none 대신 화면 밖으로 밀어 두고, 빈 채로 두세요. 이 칸이 채워진 제출은 버려집니다.
harvis-form
넣지 마세요. 이 속성은 action을 대신 써 달라는 harvis에 대한 요청인데, 방금 직접 쓰셨으니까요.

전송은 브라우저에 맡기세요

preventDefault도, fetch도 쓰지 마세요. 응답은 브라우저가 알아서 따라가는 리다이렉트이고, 스크립트가 동작하지 않을 때도 폼이 살아 있게 해 주는 게 바로 그것입니다.

프리렌더도 JavaScript입니다

빌드가 폼을 HTML에 써 넣고 앱이 브라우저에서 그것을 넘겨받는다면, 필드는 그래도 적어 두세요 — harvis가 넣은 것들은 당신의 컴포넌트에 없으므로 하이드레이션이 버릴 수 있습니다.

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

이런 것들이 따라옵니다

아래 항목은 모두 기본으로 켜져 있습니다. 어느 것에도 설정 페이지는 없습니다.

  • JavaScript 없음

    양식은 HTML이 늘 해 오던 방식 그대로 전송합니다. 스크립트가 차단돼 있어도, 느린 휴대폰에서도, 텍스트만 그리는 브라우저에서도 동작합니다.

  • 스팸은 걸러집니다

    모든 양식에는 사람 눈에 보이지 않는 미끼 항목이 함께 전송되고, 그걸 채운 제출은 알리지 않고 버립니다. 요청 제한이 방문자 한 명과 사이트 하나가 한 시간에 보낼 수 있는 양을 묶어 둡니다.

  • 양식은 얼마든지

    이름을 붙이세요 — 같은 사이트의 문의 양식과 가입 양식은 각자 목록을 유지하고, 따로 걸러 보고 따로 내보낼 수 있습니다.

  • 직접 만든 감사 페이지

    양식이 직접 작성한 페이지를 가리키게 하면 제출 후 사람들이 그곳에 도착합니다. 지정하지 않으면 간단한 확인 페이지가 대신 보입니다.

  • 언제든 내보내기

    버튼 하나로, 지금까지 누군가 보낸 모든 항목이 열로 들어간 CSV를 받습니다. 중간에 질문이 하나 늘어난 양식도 하나의 표로 열립니다.

  • 아무도 따라다니지 않습니다

    추적 스크립트도, 쿠키도, 페이지 안의 제3자도 없습니다. 방문자의 주소는 저장 전에 해시 처리되며, 그것도 요청 제한이 두 제출을 구분하기 위해서일 뿐입니다.

limits

제한

문의 양식에는 넉넉하고, 사이트를 겨눈 스크립트가 받은편지함을 채우지는 못할 만큼은 빠듯합니다.

제출당 항목 수
30개
항목 하나의 길이
5,000자
제출 하나의 크기
64 KB
사이트당 제출
시간당 60건
방문자당 제출
시간당 20건
사이트당 보관
1,000건, 최신순
faq

질문

정적 사이트에 양식을 올리기 전에 사람들이 묻는 것들.

정적 양식이 뭔가요?

자기 서버가 없는 사이트에 놓인 양식입니다. 보통은 양식이 보낼 곳이 없다는 뜻이고, 그래서 정적 사이트는 대개 외부 양식 서비스를 빌려 씁니다. harvis는 이미 사이트의 모든 페이지를 전송하고 있으니, 지나가는 길에 제출을 대신 받을 수 있습니다.

코딩할 줄 알아야 하나요?

HTML 한 줄에 단어 하나를 넣을 수 있으면 됩니다. 사이트를 AI가 썼다면 양식에 harvis-form 속성을 붙여 달라고 하세요 — harvis가 AI 어시스턴트용으로 공개한 안내에 그렇게 적혀 있어서, AI는 방법을 알고 있습니다.

JavaScript를 꺼도 동작하나요?

네. 오히려 그러라고 이렇게 만들었습니다. 양식은 평범한 HTML 전송을 하고 브라우저가 그 뒤에 리디렉션을 따라갑니다. JavaScript가 있기 전 양식이 동작하던 방식 그대로입니다.

제 엔드포인트나 다른 양식 서비스를 계속 써도 되나요?

됩니다 — 속성을 붙이지 않으면 그만입니다. harvis-form이 없는 양식은 action까지 포함해 쓴 그대로 전송됩니다. 속성이 붙은 양식에서는 harvis가 action을 바꿉니다. 속성을 붙이는 것이 제출을 어디로 보낼지 말하는 방법이기 때문입니다.

React나 Vue, Svelte에서도 되나요?

됩니다. 한 단계만 더 있으면 됩니다. 빌드가 만들어 내는 HTML 안에 폼이 있다면 속성 하나로 충분합니다. JavaScript가 실행된 뒤에야 나타난다면, 컴포넌트에 action="/__harvis/form/contact"와 method="post"를 직접 적으세요 — 같은 엔드포인트, 같은 대시보드이고, 보내는 일은 여전히 브라우저가 합니다.

파일을 첨부할 수 있나요?

아직은 안 됩니다. 수집 대상 양식에서도 파일 입력은 방문자에게 정상적으로 보이지만, 파일은 저장되지 않습니다 — 텍스트 항목만 남습니다.

사이트를 삭제하면 제출 내용은 어떻게 되나요?

같이 사라집니다. 제출 내용은 배포가 아니라 사이트에 속하므로, 다시 배포하면 유지되고 사이트를 삭제하면 완전히 없어집니다. 남기고 싶다면 먼저 CSV로 내보내세요.

사이트를 하나 올려서 직접 해 보세요

공개는 무료이고 2초쯤 걸립니다. 양식에 속성을 붙이고 다시 배포한 다음, 자기 자신에게 테스트 메시지를 보내 보세요.

사이트 공개하기