Skip to main content
A form on any site can post straight to a Hookie endpoint. There is no backend to write and no JavaScript to add: the endpoint URL takes the browser’s post, checks it, stores it, runs your rules and workflows, and delivers it to Slack, your own webhook or your customers’ portal.

One command

The CLI sets up the endpoint, its checks and the form itself:
forms init does four things:
  1. It creates the endpoint, with the template’s fields as its form fields, a honeypot and the origins and redirect you give it. Run it again and it updates the same endpoint, found by its --slug.
  2. With --to slack or --to webhook, it adds the destination, filtered to the form’s dataset. Slack with no --destination-url prints the link to Add to Slack on your project’s Connections tab, which you press yourself.
  3. It writes the form into your project: plain HTML, a React component or an Astro component, chosen from your package.json (--framework to choose yourself, --out for the directory). It never replaces a file that exists unless you pass --force.
  4. It records the endpoint in hookie-forms.json, so hookie forms test knows which form you mean.
--json prints one result object: the endpoint URL, the dataset, the destination, the files written, warnings and next steps. --dry-run shows the plan and changes nothing. Then check the whole path:
It posts a sample submission built from the endpoint’s fields and reports the record it made. An endpoint that requires Turnstile refuses it, because a terminal cannot pass a Turnstile check: test from your page in a browser.

The form

What forms init writes for plain HTML is an ordinary form:
The URL in action is public by design. It can only add submissions, and every check below runs on it. If it leaks to someone you would rather not hear from, rotate it. After a submit. With no redirect URL, the visitor lands on Hookie’s thank-you page, in light or dark to match their system, with a link back to your form. Give the endpoint a redirect URL (--redirect https://example.com/thanks, or Redirect after a form submit under Edit…) to send them to your own page instead. When a submission fails a check, the visitor sees a page that lists what to fix. Without leaving the page. The React component submits with fetch() and shows the result in place. That needs your site’s origin in the endpoint’s allowed origins (--origin, repeatable, or Allowed browser origins under Edit…), or the browser will not let the page read the answer. It sends one Idempotency-Key per submission and reuses it if the visitor presses Send again after an error, so a double click stores one submission.

@hookie/forms

For your own markup, the @hookie/forms package does the submitting. It has no dependencies.
In React, useHookieForm(url) from @hookie/forms/react gives you submit, status, fieldErrors and error. defineFields([...] as const) and FieldsOf<typeof fields> type a payload from the endpoint’s fields, so a missing required field fails to compile.
@hookie/forms is not on the npm registry yet. Until it is, the files hookie forms init writes work on their own, with nothing to install.

Stop bots

Both checks run before anything is stored or counted, so spam never uses your monthly events or reaches Slack. Honeypot. A field that people never see and bots fill in. Set its name with honeypot (forms init uses _gotcha). A submission that fills it is answered exactly as if it had worked, so the bot learns nothing, but it is not stored, counted or delivered. It shows under the endpoint’s Activity as Spam caught by the honeypot. The empty field is removed from what is stored. Cloudflare Turnstile. For forms that attract more determined bots. Create a widget in your Cloudflare dashboard, then give the endpoint its keys: turnstile_site_key (public, for the widget) and turnstile_secret. With the secret set, every submission must carry the token the widget adds (cf-turnstile-response). Hookie checks it with Cloudflare before storing anything: The secret is encrypted when stored and never shown again: the endpoint reports only turnstile_enabled. The token is removed from what is stored. With forms init, pass --turnstile-site-key and --turnstile-secret, and the form renders the widget.

Check the fields

Give the endpoint form_fields and it refuses a submission that does not meet them, with 422, before anything is stored or counted:
The answer names every problem at once:
Fields you did not declare are still stored, so a tracking field or a new input does not break anything. Up to 50 fields can be declared.

Where submissions go

A submission is an ordinary event, so everything else in Hookie works on it:
  • Rules and mappings reshape or filter what is stored. See Rules and mappings.
  • Workflows branch on a field, call AI to classify or summarise a message, wait, or call another API. See Workflows.
  • Destinations deliver it: a Slack card, your own signed webhook, S3, SQS or Pub/Sub. See Destinations.
  • The customer portal lets your own customers subscribe to the form’s dataset. See Customer portal.
Each submission counts as one event, and each destination it is delivered to as one delivery. The Free plan’s 1,000 events a month suit a contact form, and refused spam costs nothing. See Limits and plans.

Settings, wherever you work

A new version of an endpoint keeps its honeypot, Turnstile keys and form fields, so publishing one never opens the form to bots.