QuickForm

preview

Form reception for agents — your form stays on your own site, the definition and the answers live on your own server, and every answer fans out to a signed webhook and a push notification.

Author: Poul Kjeldager
Platform: linuxmacoswindowsdocker
Category: agents

QuickForm

pks-agent-quickform is form reception for agents. It owns forms — it does not own form pages.

Your marketing site, your customer portal, your landing page: those stay exactly where they are, on your domain, in your code. The server hands out the form definition, receives the answers, stores them as files under your own USER_DATA_DIR, and fans each one out to a signed webhook and a Web Push notification.

It ships as one Go binary in one container, in the same shape as its siblings (pks-agent-inbox, pks-agent-consent, pks-agent-share): stdlib net/http, folder-state storage, no database.

The seam: submitUrl

The whole integration is one line — and it is a line we write, not you.

A QuickForm definition can carry a submit block:

{ "submit": { "submitUrl": "…", "submitMethod": "POST" } }

GET /api/orgs/{org}/forms/{form}/definition injects that block into whatever definition you stored, pointing at this server's ingest endpoint for that exact form. So the page that renders your form:

  • fetches one URL,
  • renders it with @eavfw/quickform-core,
  • and ships no submit code at all.

Change the form, change the org, move the server: the page keeps working, because the address it submits to arrived with the questions.

Do not pass onSubmitAsync. The core runtime only honours submitUrl when no submit handler is supplied — a handler bypasses the seam entirely and the answers never reach the server.

What you get

  • Orgs that own forms. The same container model as a GitHub organisation. One org, many forms, one token scope.
  • Definitions as files. The questions are JSON, versioned on every write, stored verbatim — we never re-shape a customer's definition.
  • Answers as files. Every submission is durable under orgs/<org>/forms/<form>/submissions/ with its timestamp, origin, referer, IP and the untouched raw body.
  • Signed webhooks. X-QuickForm-Signature: sha256=<hex(hmac)> over the exact bytes we posted, so the receiving agent can prove the call came from you.
  • Web Push. The notification names the form; it never carries the answers.
  • A PWA in the Agentics look, served by the same binary, for orgs, forms and the incoming feed.

Two public endpoints, one admin surface

SurfaceEndpointsAuth
Public (the customer's page)GET …/definition, POST …/submissionsnone — gated by the form's origin allow-list
Admin (PWA, portal, agents)orgs, forms, definitions, submissions, pushAuthorization: Bearer <token>

The public pair is unauthenticated by design — anyone who can load the form can post to it, exactly as with any form on the open web. The defences are the per-form origin allow-list and a 256 KiB body cap, not a secret the browser would have to carry.

Run it

There is no published image to pull: the hosted instance is git-built by Coolify straight from the repository, so you build the same Dockerfile yourself.

git clone https://github.com/pksorensen/pks-agent-quickform
docker build -t quickform pks-agent-quickform/src/quickform-server

docker run --rm -p 8080:8080 \
  -e QUICKFORM_ADMIN_TOKEN=$(openssl rand -hex 32) \
  -e PUBLIC_BASE_URL=https://forms.example.com \
  -e USER_DATA_DIR=/data -v $PWD/quickform-data:/data \
  quickform

Or, straight from a checkout, with no container at all:

cd src/quickform-server
PORT=8123 USER_DATA_DIR=./user-data PUBLIC_BASE_URL=http://localhost:8123 go run .

With no QUICKFORM_ADMIN_TOKEN the server runs open — the single-user dev mode its siblings default to. That is correct on your laptop and wrong on the internet: an open instance lets anyone read every org's answers and mint tokens. Set the token before the first public deploy, not after.

Environment

VariableDefaultMeaning
PORT8080HTTP listen port
USER_DATA_DIR./user-dataRoot of the folder-state store
QUICKFORM_ADMIN_TOKEN(empty)Global admin credential. Empty ⇒ open/dev mode
PUBLIC_BASE_URL(derived per request)Externally reachable origin used to build submitUrl and the embed URL. Set it whenever anything reaches the server through a proxy or from another container
WEB_DIR./webStatic PWA root
PUSH_CONTACTquickform@agentics.dkVAPID contact — bare, no mailto: prefix

VAPID keys are generated into USER_DATA_DIR on first boot; keep that volume and push subscriptions survive a restart.

Pages

Where the designer lives

There is deliberately no form designer in this server. QuickForm's own designer was built for humans clicking a canvas; the plan is a chat-driven, AI-first designer in the portal that exposes QuickForm, where an agent writes the definition and shows you the result. Until then, the definition is JSON — a format both a person and an agent can write.