フォーム

バックエンドなしで動くフォーム

静的サイトのフォームに属性を1つ足すだけで、送信内容がすべてダッシュボードに届きます。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

3ステップ、うち1つはデプロイし直すだけ

覚えるべきフォームビルダーも、コピーするエンドポイントもありません。この属性が設定のすべてです。

  1. 01

    属性を足す

    HTMLの好きなフォームに harvis-form を書きます。サイトにフォームが複数あるなら名前を付けてください。action と method は書かなくて大丈夫 — ページが配信されるときに埋められます。

  2. 02

    デプロイし直す

    フォルダをもう一度ドラッグするか、npx harvis を実行するだけ。設定も、貼り付けるキーも、事前にダッシュボードで切り替えるスイッチもありません。

  3. 03

    届いたものを読む

    送信内容はダッシュボードのサイトの下に、新しいものから順に並びます。行を開けば全文を読めますし、まとめてCSVで持ち出すこともできます。

the markup

全体でも属性は2つだけ

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>

2つの属性

harvis-form
そのフォームを収集対象として印を付けます。値が名前になるので、1つのサイトに複数のフォームがあっても混ざりません。値を省くと「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が昔からしてきたとおりに送信します。スクリプトがブロックされていても、遅いスマホでも、テキストしか表示しないブラウザでも動きます。

  • スパムは弾かれます

    どのフォームにも人間には見えないおとり項目が付いて配信され、そこに入力があったものは黙って捨てられます。レート制限が、1人の訪問者と1つのサイトが1時間に送れる量を抑えます。

  • フォームはいくつでも

    名前を付けましょう — 同じサイトの問い合わせフォームと登録フォームはそれぞれ別のリストを持ち、別々に絞り込んでエクスポートできます。

  • 自作のサンクスページ

    フォームの向き先を自分で書いたページにすれば、送信後はそこに着きます。指定しなければ、素朴な確認ページが表示されます。

  • いつでもエクスポート

    ボタン1つで、これまで誰かが送信したすべての項目に列があるCSVが手に入ります。途中で質問が増えたフォームでも、1つの表として開けます。

  • 誰も追跡しません

    トラッキングスクリプトも、Cookieも、ページ内の第三者もありません。訪問者のアドレスは保存前にハッシュ化され、それもレート制限が2つの送信を区別するためだけに使われます。

limits

制限

問い合わせフォームには十分に余裕があり、サイトに向けられたスクリプトが受信箱を埋め尽くさない程度には厳しくしてあります。

1件あたりの項目数
30
1項目の長さ
5,000文字
1件のサイズ
64 KB
サイトごとの送信数
1時間あたり60件
訪問者ごとの送信数
1時間あたり20件
サイトごとの保存数
1,000件、新しい順
faq

よくある質問

静的サイトにフォームを置く前に、みんなが聞くこと。

静的フォームとは何ですか?

自前のサーバーを持たないサイトに置かれたフォームのことです。ふつうそれは送信先がないということで、だから静的サイトは外部のフォームサービスを借りるのが一般的です。harvis はもともとサイトの全ページを配信しているので、そのついでに送信を受け取れます。

コードが書けないとだめですか?

HTMLの1行に単語を1つ足せれば大丈夫です。サイトをAIが書いたなら、フォームに harvis-form 属性を足すよう頼んでください — harvis がAIアシスタント向けに公開している手順にそう書いてあるので、AIはやり方を知っています。

JavaScriptをオフにしても動きますか?

動きます。むしろそのためにこの作りにしています。フォームはごく普通のHTMLのPOSTを行い、そのあとブラウザがリダイレクトをたどります。JavaScriptが存在する前のフォームとまったく同じです。

自分のエンドポイントや別のフォームサービスを使い続けられますか?

はい — 属性を足さなければいいだけです。harvis-form のないフォームは、action も含めて書いたとおりに配信されます。属性が付いたフォームでは harvis が action を置き換えます。属性を足すことが、送信先を指定する方法だからです。

React や Vue、Svelte でも使えますか?

使えます。ひと手間だけ増えます。ビルドが出力する HTML にフォームが含まれているなら、属性だけで済みます。JavaScript が動いて初めて現れる場合は、代わりにコンポーネントへ action="/__harvis/form/contact" と method="post" を書いてください。エンドポイントもダッシュボードも同じで、送信するのは変わらずブラウザです。

ファイルを添付できますか?

まだできません。収集対象のフォームでもファイル入力は訪問者にとっては動きますが、ファイルは保存されません — 残るのはテキスト項目だけです。

サイトを削除したら送信内容はどうなりますか?

一緒に消えます。送信内容はデプロイではなくサイトに属するので、デプロイし直しても残り、サイトを削除すると完全に消えます。残したいなら先にCSVをエクスポートしてください。

サイトを公開して試してみる

公開は無料で、2秒ほどで終わります。フォームに属性を足してデプロイし直し、自分宛てにテスト送信してみてください。

サイトを公開する