Overview
A PublicForm is a customer-/employee-facing intake form, hosted by Vigilo at a stable public URL, that creates a Change Request, a Task, or an Incident in the workspace on submit — no Vigilo login required by the submitter. Forms are designed in Settings → Public forms with a drag-and-drop field configurator, and the public URL is shareable to anyone (internal SSO portal, external customer site, Slack message).
The classic use case: an internal "request a change" form for non-engineering teams, or a customer-facing "report a service issue" form embedded in your status page.
Why it exists
Not every person who needs to file something with Vigilo should have to be provisioned as a workspace member. Customers want to file incidents anonymously, employees from other departments want to file change requests without learning the full RBAC model, partners want to submit intake without joining your SSO. A PublicForm gives those people a low-friction front door while still creating a fully-structured Vigilo entity (with proper workspace scoping, audit trail, and downstream automation) on the back end.
Key concepts
PublicForm model
PublicForm (workspace, title, slug (unique per workspace), form_type, description, fields_config JSONB, default_assignee (FK to UserProfile, nullable), default_priority, redirect_url (string, nullable), is_active, created_at).
- form_type — one of
change | task | incident. Selects which entity is created on submit. - slug — controls the public URL:
https://{your-host}/forms/{workspace_slug}/{form_slug}. - fields_config — JSON array of field definitions; see Field schema below.
- default_assignee — workspace member who owns submissions when no other assignee is specified.
- default_priority — initial priority for created entities; submitter can be permitted to override via a priority field in the form.
- redirect_url — post-submit redirect destination; if null, the form renders a thank-you screen with the created entity number.
Field schema
Each entry in fields_config is {key, label, type, required, help_text, options, validation}:
- type —
text,textarea,email,number,dropdown,multi_select,date,checkbox,hidden. - options — choice list for dropdowns/multi-selects.
- validation —
{min, max, regex, max_length}per type.
A predefined set of "system" field keys (title, description, priority, affected_service) auto-bind to the created entity's columns; everything else is stored as custom-field values on the new entity (using the Custom fields infrastructure).
Public submit endpoint
The public submit endpoint is POST /forms/{workspace_slug}/{form_slug}/submit/. It accepts a JSON body or a multipart form encoded application/x-www-form-urlencoded. The handler:
- Validates the payload against
fields_config(required, types, validation). - Throttles per source IP (default 10 req/min, configurable per workspace).
- Creates the target entity (
ChangeRequest,Task, orIncident) withcreated_via='public_form'and the form's defaults. - Persists custom-field answers as
FieldValuerows. - Dispatches an event (T0.3):
change.submittedortask.createdorincident.opened, withactor=nullandsource='public_form:{form_slug}'. Downstream playbooks, webhooks, and integrations fire normally. - Responds with
{ok: true, entity: {number, url}}or redirects perredirect_url.
The endpoint is unauthenticated but rate-limited and workspace-RLS-aware — it can only create entities in its own workspace.
Anti-abuse
The form endpoint has multiple defences:
- Per-IP rate limit (default 10/min, configurable).
- HCaptcha (when configured) — admins can enable HCaptcha on a per-form basis; the captcha token is validated server-side.
- Per-form daily quota — admins can set a max submissions per day; over-quota requests return 429.
- Honeypot field — a hidden field that legit users leave blank; bots fill it and are silently dropped.
Per-form analytics — deferred
A first-class analytics page per form (submission funnel, drop-off per field, abandonment rate) is on the roadmap. Today, basic counters surface on the form list:
- Total submissions in the last 24h / 7d / 30d.
- Last submission timestamp.
- Throttle hit count.
For deeper analytics, subscribe to the form.submitted webhook event and pipe to your existing analytics tool.
Known limitations (WD.10 roadmap)
The current form builder intentionally lacks several features common in dedicated intake-form products. These are tracked as WD.10:
- No attachments — file upload is not supported in v1. The blocker is anti-abuse (uploaded files would need malware-scanning before storing). Use a "link to file" URL field as a workaround.
- No e-signature — capturing a signed approval at submit-time is not in v1.
- No payment — Stripe/Adyen embedding is not in v1 and is not planned for the core product; integrate via a webhook to your own payment surface and link from the form description.
- No multi-page wizards — all fields render on one page; long forms must be split into multiple PublicForms.
- No conditional fields — every field renders regardless of other field values; use multiple forms for variant intake flows.
Common workflows
Build a change-request intake form
- Settings → Public forms → New form, set type
change, slugrequest-a-change. - Add fields:
title(text, required),description(textarea, required),priority(dropdown: low/medium/high),affected_service(dropdown bound to assets),cost_centre(custom field). - Set
default_assigneeto the change manager,default_prioritytomedium. - Set
redirect_urlto your IT portal's confirmation page, or leave blank to use the built-in thank-you screen. - Activate. The public URL renders the form immediately.
Embed in your IT portal
- Copy the public URL from the form detail.
- Either link directly, or use the Embed code snippet to iframe the form into your portal.
- The iframe respects the
redirect_urlso users land on your portal's confirmation page after submit.
Wire a playbook to the form
Build a Playbook with trigger change.submitted and condition {"eq": ["${trigger.source}", "public_form:request-a-change"]} to add a Slack notification, assign a reviewer round-robin, or auto-link to a Jira issue.
Pause a form
Toggle is_active=False on the form. The public URL returns a 403 with the message "This form is not currently accepting submissions." Reactivate to resume.
Permissions
- Owners and admins can create, edit, and pause forms.
- Engineers can view forms and submission history.
- Anyone with the URL can submit (no authentication required) subject to the rate limit and captcha.
Troubleshooting
Form returns 400 validation_error — A required field is missing or fails a validation rule. The response includes a field_errors map with per-field error keys.
Form returns 429 — Per-IP rate limit hit, or per-form daily quota exhausted. Raise the limit under the form's settings, or wait the cooldown window.
Submission did not create the entity — Check the form's Submissions tab. A 5xx during entity creation logs the error class; common causes are a required custom field with no default and the form not collecting it. Add the field to fields_config.
Captcha says "invalid token" — The HCaptcha secret in the workspace settings is wrong. Re-paste from HCaptcha's dashboard.
Default assignee removed from workspace — The form will fail at create with assignee_not_workspace_member. Set a new default_assignee from the form editor.
File upload field missing — File uploads are not in v1. Use a URL field and ask the submitter to host the file elsewhere (or accept the limitation as documented above).