Form (PbotForm)

PbotForm pauses workflow execution to collect structured input from an external party. Unlike PbotApproval, which collects a decision from a designated reviewer, PbotForm collects data from customers, vendors, or partners.


Basic Usage

json
{
  "type": "PbotForm",
  "properties": {
    "expected_keys": ["shipping_address", "delivery_date", "special_instructions"],
    "ttl_seconds": 86400,
    "reminder_intervals": [3600, 7200]
  }
}

When execution reaches this step, the run pauses and waits for form submission.


Properties Reference

Property Type Required Description
expected_keys string[] Yes Fields the form expects. Submission must include these keys
ttl_seconds number No How long the form stays open before expiring (default: no expiration)
reminder_intervals number[] No Seconds after creation to send reminders. [3600, 7200] sends reminders at 1 hour and 2 hours

Submission Flow

sequenceDiagram participant Workflow participant Engine participant External Workflow->>Engine: PbotForm step Engine->>Engine: Run status → "paused" Note over External: Receives form link<br/>(via your app/email) External->>Engine: POST /forms/:runId/:stepId/submit Engine->>Engine: Validate expected_keys Engine->>Engine: Run status → "running" Engine->>Workflow: Resume with form data in context

Submitting Form Data

bash
curl -X POST https://your-hyphen.example.com/forms/{runId}/0/submit \
  -H "X-Org-Id: acme-corp" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "shipping_address": "123 Main St, City, ST 12345",
      "delivery_date": "2025-02-15",
      "special_instructions": "Leave at door"
    }
  }'
POST/forms/:runId/:stepId/submit

Submit form data for a paused PbotForm step.

GET/forms/pending

List all pending form requests across the organization.


Context After Submission

The submitted data is added directly to the execution context. Each key from the submission becomes accessible via @path:

json
{
  "type": "schedule_delivery",
  "properties": {
    "address": "@shipping_address",
    "date": "@delivery_date",
    "notes": "@special_instructions"
  }
}

TTL and Expiration

When ttl_seconds is set, the form closes after that duration and stops accepting submissions. Hyphen emits form_expired, advances the paused step without submission data, and continues the run. Downstream steps should handle missing form values on the expiry path.

Use TTL for time-sensitive workflows where stale input would be harmful, such as a vendor onboarding form that is valid for 24 hours.


Reminder Intervals

The reminder_intervals array specifies when reminders should be sent (in seconds after the form is created). This triggers reminder webhook events that your application can use to send follow-up notifications.

json
{
  "reminder_intervals": [3600, 7200, 43200]
}

This sends reminders at 1 hour, 2 hours, and 12 hours after the form is created.


Two kinds of form, one renderer

Hyphen has two kinds of form. One renderer, <hyphen-form>, serves both.

Form pause (PbotForm) Intake form
What it is A running case waits for named keys A schema and a trigger
Tied to a run Yes. Submitting resumes the run, once No. Submitting starts a workflow or a published process
Created by A workflow step POST /external-forms or POST /forms/generate
Where a person sees it The task sidebar, Process Studio, or <hyphen-form run-id step-id> The hosted page https://<gateway>/forms/<formId>, or <hyphen-form form-id> in your product
Who can submit A signed-in session Anyone with the link, or a signed-in person when the form requires it
Best for A vendor supplies shipping details mid-workflow A client disputes a charge and a case starts

A form pause is a task: it appears on the SDK event stream as task:created with kind: "form", the sidebar lists it beside approvals with Fill it in, and the webhooks carry form_pause_created, form_submitted, and form_expired.

Both kinds share one schema contract, form_schema_v1: pages of fields, each field with a key, a type (text, number, email, date, multiline, content, select, checklist, range, money, and placeholders for file, signature, table, lookup, computed), a label, and optional help, required, default, options, min, max, step, unit. The renderer also accepts the legacy builder's component array, a flat field list, and a JSON-Schema object. The submitted payload is always flat.

A paused step's fields come, in order, from a schema on the element, a schema authored for the step, a schema generated for the step when the organization has the agent_forms feature, or one text field per expected key.

See Forms for the components and the hosted page, and Forms that start work for the walkthrough.

→ Next: Custom Table