FamilyHQ Sign out Sign in

Forms

Add contact forms with built-in anti-spam and configurable dispatch.

Forms

The :::form block defines an inline form with honeypot, HMAC timestamp, and rate limiting built in. Submissions are dispatched to configured targets (email, API, Slack) via a CGI handler.

Front matter

Enable forms on a page with the form: key:

---
title: Contact
form: contact
---

The name must be alphanumeric with hyphens and underscores. Without form: in front matter, :::form blocks are not rendered.

Field syntax

::: form
field_name | Label text | rules
submit | Button label
:::

Field rules

Rules are whitespace-separated. A value that needs spaces (a placeholder, or a pattern with a literal space) is quoted: placeholder:"Your full name" or pattern:"[0-9 +()-]{7,20}".

Validation is enforced in the browser (HTML5 attributes); the handler accepts the submitted fields.

Handler configuration

Create lazysite/forms/FORMNAME.conf:

targets:
  - type: smtp
    url: http://localhost/plugins/form-smtp.pl

Target types: smtp (email via helper), api with format: json (webhook), api with format: slack (Slack notification).

File uploads

A form accepts binary uploads only when its .conf declares upload limits (so a form never accepts files by accident). Add any of these keys to lazysite/forms/FORMNAME.conf:

targets:
  - handler: jsonl          # a "file" target is required to STORE the files
upload_max_files: 3         # max files per submission (default 5)
upload_max_kb: 5120         # max size of EACH file, KiB (default 5120 = 5 MiB)
upload_accept: png, jpg, pdf  # allowed extensions (default: any)

A submission that breaks a limit is rejected before any handler runs, with a specific message to the visitor ("File 'x.png' is too large...", "File type not allowed...", "Too many files...").

Uploaded files are stored by the file (jsonl) target, in a per-submission subdirectory next to the FORMNAME.jsonl:

lazysite/forms/submissions/FORMNAME.jsonl
lazysite/forms/submissions/FORMNAME.files/<submission-id>/photo.png

The submission record names the files (it never stores the bytes inline):

{ "name": "Ada", "_files": ["photo.png"],
  "_files_dir": "FORMNAME.files/20260629T101500-1a2b", ... }

Filenames are sanitised to a safe basename (any path component is stripped, so a crafted ../../etc/passwd cannot escape the submission directory). A form with a file field but no file target validates uploads but does not keep them - add a file target to store them.

Emailing uploads. The SMTP (email) handler has an Attach uploaded files option (attach_files, off by default). When on, the uploaded files are attached to the notification email and listed (name + size) below the message. Leave it off to keep emails small and just store the files; mind your mail server's attachment size limits when enabling it.

Example

---
title: Contact
form: contact
---
::: form
name    | Your name     | required max:200
email   | Email address | required email
message | Your message  | required textarea max:5000
submit  | Send message
:::

Multi-step (wizard) forms

Split a long form into steps with a --- step --- line. Add a title with --- step: Title ---. The visitor moves through the steps with Back / Next (each step is validated before advancing) and submits once at the end.

::: form
name    | Your name     | required max:200
email   | Email address | required email
--- step: Your enquiry ---
subject | Subject       | required max:200
message | Your message  | required textarea max:5000
submit  | Send
:::

Notes:

Notes