PPactDocs
Administration

Forms

Build hosted lead-capture forms with a typed field library, conditional logic, spam protection, consent capture, and submission actions that create leads or update contacts.

Forms

Pact forms are hosted, embeddable lead-capture pages. You design the fields, publish to a slug, and every submission is stored, analyzed, and routed to a CRM action — a new lead or a contact update — with consent captured inline. Admin CRUD lives at /v1/forms; the public render and submit endpoints live at /public/forms. Backing tables are tenant_forms and form_submissions (alembic 0117).

The field library

A form definition is a list of typed fields. Supported types (_VALID_FIELD_TYPES): text, email, phone, textarea, select, multi_select, checkbox, radio, number, date, file, signature, payment, rating, hidden, and consent. Each field can be required, half- or full-width, carry options, and map to a tenant custom field on the target entity via custom_field_id.

Two capabilities worth calling out:

  • Conditional logic — a field's show_if predicate shows it only when another field equals a given value.
  • Consent fields carry consent_text and record a consent grant on submit (see below).

Create, update, delete

code
GET    /v1/forms                 → list (paginated)
POST   /v1/forms                 → create
GET    /v1/forms/{form_id}       → get one
PATCH  /v1/forms/{form_id}       → update name/slug/definition/status/action/settings
DELETE /v1/forms/{form_id}       → soft delete

A form has a status (draft, published, archived), a URL slug (lowercase, ^[a-z0-9_-]+$), and a submission_actioncreate_lead, update_contact, or none. Reads are open to any authenticated member; writes require member-or-higher, consistent with other CRM objects. Tenant isolation is derived from the auth context.

Start from a template

code
GET  /v1/forms/templates          → list the built-in catalog
POST /v1/forms/templates/clone    → clone a template into a new form

Pact ships a catalog of ready-made templates in core/form_templates.py — including lead capture, demo request, support ticket, NPS, event and webinar registration, free-trial signup, quote request, job application, and a GDPR data-request form — each pre-wired with sensible fields, a consent field, and a suggested slug.

Theme and behavior

The form definition carries a success_message, an optional redirect_url (scheme-validated to block javascript:/data: XSS), notification emails (notify_emails), and a theme object (primary/accent/background colors, logo, header text, font) that layers per-form overrides on top of tenant branding. The logo_url and redirect_url are both validated to allow only http(s).

The public submit path (/public/forms, api/routes/public_forms.py) applies several safeguards before a submission is accepted:

  • CAPTCHA — Google reCAPTCHA (g_recaptcha_response) and Cloudflare Turnstile are both verified server-side when configured; secrets come from the tenant credential store.
  • Honeypot field and IP rate-limiting.
  • Consent capture — when a consent field is present, the submission records a consent grant through core.consent.consent_service, tying the lead to a verifiable opt-in.
  • UTM + sourceutm_params and source_url are captured for attribution.

Submission action dispatch

After validation, the submission is dispatched per the form's submission_action:

  • create_lead — creates a lead via lead_service, carrying the captured UTM and source data, ready for an AE to convert.
  • update_contact — matches an existing contact (e.g. by email) and updates it.
  • none — stores the submission only.

Custom hosting domains

Public forms can be served from your own domain. Register and DNS-verify a form-hosting domain at /v1/form-domains (backed by tenant_form_domains); Pact resolves the tenant from the request host at render time.