Page Step
Show a confirmation, thank-you, or informational page with headings, rich text, third-party embeds, and links. Use it as a landing page or as a pause-and-continue screen between other steps.
The Page step displays a content page during a workflow. Use it to thank a participant, confirm a submission, embed a scheduling widget, or link out to an external site. Unlike a Forms step, a Page does not collect input and does not produce fields that later steps can map from.
A Page can be the first step in a workflow and it can be the last. When it is not the last step, set waitForContinue to control whether the workflow waits for the participant to click Continue or advances on its own. See Controlling when the workflow advances.
In the project payload, set the step's type to page. The JSON below is the step configuration that belongs in workflow.steps[].config.
Top-level configuration
| name | type | required | description |
|---|---|---|---|
type | string | no | Discriminator for the step configuration. Set to page when present. |
components | array | yes | Ordered list of components. Put exactly one page container here; nest every other component inside it. |
waitForContinue | boolean | no | Whether the session waits for the participant to click Continue. Default: false. |
Component types
A page is built from an ordered list of components. Each component has a type field that determines its behavior and available properties.
Common component properties
Every component supports these base properties in addition to its type-specific ones:
| name | type | required | description |
|---|---|---|---|
id | string | no | Stable component id. Auto-generated if omitted. |
type | string | yes | The component type. |
label | string | no | Internal label for the component. |
parentId | string | no | ID of the parent component, if nested. |
Page container
The page container is the canvas that holds every other component. Place one page component in the top-level components array and nest headings, descriptions, embeds, and buttons inside it.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be page. |
components | array | no | Nested components: heading, description, code_block, external_button_group, external_button, or continue_button. |
See Page content for the single-page expectation.
Heading
Use for the page title or section titles.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be heading. |
presentation.content | string | no | Heading text. |
presentation.level | number | no | Heading level: 1, 2, or 3. Default: 1. |
presentation.textAlign | string | no | Alignment: left, center, or right. Default: left. |
Description
Use for body copy. Description content is HTML and is the only page component that supports mapping placeholders. Placeholders such as {{Form.first_name}} are replaced with values from earlier steps when the page is shown.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be description. |
presentation.content | string | no | HTML body content. Mapping placeholders are resolved at display time. |
presentation.textAlign | string | no | Alignment: left, center, or right. Default: left. |
A few things to note:
- Allowed HTML includes headings, paragraphs, lists, tables, links (
a), images (img), figures, and inline formatting (strong,em,u, and similar). <script>,<iframe>,<form>,<style>, and event-handler attributes (for exampleonclick) are removed.alinks accepthttp,https, andmailtoURLs.imgsources accepthttpandhttpsURLs.
Code block
Use to embed third-party HTML such as a scheduling widget, chat snippet, or analytics tag. The snippet is rendered in an isolated frame, separate from the rest of the session.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be code_block. |
presentation.language | string | no | Snippet language. Use html for embed snippets. |
presentation.content | string | no | The HTML or script to embed. |
Code-block content is not sanitized. Only include snippets from sources you trust.
External button group
A container for outbound link buttons. Nest external_button components inside it. A group shows up to three buttons; additional buttons are ignored.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be external_button_group. |
components | array | no | Nested external_button components (up to three). |
External button
A button that opens an external URL. Nest it inside an external_button_group.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be external_button. |
presentation.label | string | no | Button label. Default: "Button". |
presentation.url | string | no | Absolute http or https URL. Other schemes do not open when clicked. |
presentation.variant | string | no | Visual style: primary, secondary, or tertiary. Default: primary. |
presentation.openInNewTab | boolean | no | Whether the URL opens in a new tab. Default: true. |
Continue button
Advances the workflow to the next step. Include this only when waitForContinue is true; otherwise it is removed. See Controlling when the workflow advances.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be continue_button. |
presentation.label | string | no | Button label. Default: "Next step". |
presentation.variant | string | no | Visual style: primary, secondary, or tertiary. Default: primary. |
Page content
Put exactly one page container in the top-level components array. Every heading, description, embed, and button belongs inside that container — not as a sibling of it.
A few things to note:
- If you send more than one
pagecontainer, only the first is used. - A
pagecontainer cannot be nested inside anotherpagecontainer. - An
external_button_groupshows at most threeexternal_buttonchildren. Extra buttons are ignored. - Unrecognized
typevalues are ignored.
Sending extra page containers, nested pages, extra buttons, or unknown component types does not fail the request. The extra content is simply not shown.
Controlling when the workflow advances
waitForContinue decides whether the participant must click Continue before the workflow moves on.
true— the session waits for the participant to click Continue. If you omitcontinue_button, one is added for you. If you include more than one, the first is used.false(default) — the page is shown and the workflow advances on its own. Anycontinue_buttonyou send is removed.
When a Page is the last step and waitForContinue is true, clicking Continue completes the session.
Examples
A minimal page with a heading. waitForContinue is omitted, so it defaults to false and the workflow advances without a Continue button:
{
"type": "page",
"components": [
{
"id": "835b17f8-f133-434e-a8d1-b43e1249557d",
"type": "page",
"label": "Page",
"components": [
{
"id": "page-heading",
"type": "heading",
"label": "Header",
"parentId": "835b17f8-f133-434e-a8d1-b43e1249557d",
"presentation": {
"content": "Thank you",
"level": 1,
"textAlign": "left"
}
}
]
}
]
}Building on that, here's a confirmation page that waits for Continue. It greets the participant with a mapped name from an earlier Forms step, embeds a scheduling widget, and offers two outbound links:
{
"type": "page",
"waitForContinue": true,
"components": [
{
"id": "835b17f8-f133-434e-a8d1-b43e1249557d",
"type": "page",
"label": "Page",
"components": [
{
"id": "page-heading",
"type": "heading",
"label": "Header",
"parentId": "835b17f8-f133-434e-a8d1-b43e1249557d",
"presentation": {
"content": "You're all set",
"level": 1,
"textAlign": "left"
}
},
{
"id": "page-description",
"type": "description",
"label": "Description",
"parentId": "835b17f8-f133-434e-a8d1-b43e1249557d",
"presentation": {
"content": "<p>Thanks, {{`Form`.`first_name`}}. We've received your information.</p>",
"textAlign": "left"
}
},
{
"id": "page-embed",
"type": "code_block",
"label": "Code block",
"parentId": "835b17f8-f133-434e-a8d1-b43e1249557d",
"presentation": {
"language": "html",
"content": "<div class=\"calendly-inline-widget\" data-url=\"https://calendly.com/example/intro\"></div>"
}
},
{
"id": "page-buttons",
"type": "external_button_group",
"label": "External buttons",
"parentId": "835b17f8-f133-434e-a8d1-b43e1249557d",
"components": [
{
"id": "btn-site",
"type": "external_button",
"label": "Primary",
"parentId": "page-buttons",
"presentation": {
"label": "Visit our site",
"url": "https://example.com",
"variant": "primary",
"openInNewTab": true
}
},
{
"id": "btn-help",
"type": "external_button",
"label": "Secondary",
"parentId": "page-buttons",
"presentation": {
"label": "Get help",
"url": "https://example.com/help",
"variant": "secondary",
"openInNewTab": true
}
}
]
},
{
"id": "page-continue",
"type": "continue_button",
"label": "Continue",
"parentId": "835b17f8-f133-434e-a8d1-b43e1249557d",
"presentation": {
"label": "Continue",
"variant": "primary"
}
}
]
}
]
}A few things to note:
- Every nested component sets
parentIdto its parent: children of the page container use the page id, and buttons inside the group use the group's id. - The description uses a mapping placeholder (
{{Form.first_name}}) so the greeting is filled in from an earlier Forms step namedForm. See Mapping. waitForContinueistrue, so the Continue button is shown and the session waits for a click. If this page is the last step, that click completes the session.
Updated about 1 month ago
