Pular para o conteúdo principal

Flow Screen

WhatsApp

Flow Screen

WhatsApp Flow screen UI

Node typewa.flowScreen
CategoriaWhatsApp
Setups obrigatorioswhatsapp

Paleta de componentes da tela

Embed da paleta real de componentes do WhatsApp Flow Screen.

Paleta WAFlowComponentPalette

Nao foi possivel carregar a paleta embedada automaticamente.

Painel de configuracao

Esta visualizacao usa o frontend real do ConfigPanel em modo embed, carregando um no de exemplo com o JSON base desta documentacao.

Preview real do painel

Nao foi possivel carregar o painel embedado automaticamente.

JSON base do node
{
  "_nodeType": "wa.flowScreen",
  "screenDefinition": {},
  "response": {}
}

Portas

Entradas

Input
input · control

Saidas

Output
default · control

Notas de uso

Each wa.flowScreen node represents one screen in a WhatsApp Interactive Flow.

    ### screenDefinition structure (MANDATORY fields)
    ```json
    {
      "id": "SCREEN_ID",
      "title": "Screen Title",
      "terminal": false,
      "data": {
        "field_name": { "type": "string", "__example__": "Example value" }
      },
      "layout": {
        "type": "SingleColumnLayout",
        "children": [ ...components... ]
      }
    }
    ```
    - `__example__` is REQUIRED on every field in `data`. Meta rejects the flow without it.
    - `terminal: true` = last screen. Terminal screens MUST NOT have outgoing edges.
    - `layout.type` is always `"SingleColumnLayout"`.

    ### Available layout components (children array)

    **Text**
    - `{ "type": "TextHeading", "text": "Title" }`
    - `{ "type": "TextSubheading", "text": "Sub" }`
    - `{ "type": "TextBody", "text": "Body text" }`
    - `{ "type": "TextCaption", "text": "Small text" }`

    **Input (must be inside a Form)**
    - `{ "type": "TextInput", "name": "field_name", "label": "Label", "input-type": "text", "required": true }`
    - `{ "type": "TextArea", "name": "field_name", "label": "Label", "required": false }`
    - `{ "type": "DatePicker", "name": "date_field", "label": "Date", "required": true }`
    - `{ "type": "RadioButtonsGroup", "name": "field", "label": "Choose", "data-source": [{"id":"v1","title":"Option 1"}] }`
    - `{ "type": "Dropdown", "name": "field", "label": "Select", "data-source": "${data.list_field}", "required": true }`
    - `{ "type": "CheckboxGroup", "name": "field", "label": "Select all that apply", "data-source": [{"id":"v1","title":"Yes"}] }`
    - `{ "type": "OptIn", "name": "opt_in", "label": "I agree to the terms" }`

    **Form container (wraps inputs + Footer)**
    ```json
    {
      "type": "Form",
      "name": "form",
      "children": [
        { "type": "TextInput", "name": "customer_name", "label": "Nome", "required": true },
        { "type": "Footer", "label": "Continuar", "on-click-action": {
            "name": "data_exchange",
            "payload": { "screen": "SCREEN_ID", "customer_name": "${form.customer_name}" }
        }}
      ]
    }
    ```

    **⚠️ CRITICAL — WA Flow variable syntax: `${FORM_NAME.field_name}`**
    The `${...}` syntax references a field from a **named Form component**.
    - `FORM_NAME` = the `name` attribute of the `Form` component (e.g. `"name": "form"` → `${form.field}`).
    - If you name the Form `"form_cliente"`, you MUST write `${form_cliente.field}` — NOT `${form.field}`.
    - **Best practice:** name all Form components `"form"` to keep payloads simple: `${form.field_name}`.
    - If you use a custom Form name for any reason, every `${}` reference in that screen's Footer payload MUST use that same name prefix.

    **Footer (navigation/submit — REQUIRED on every screen)**
    ```json
    { "type": "Footer", "label": "Button label", "on-click-action": {
        "name": "data_exchange",
        "payload": { "screen": "NEXT_SCREEN_ID", "field": "${form.field}" }
    }}
    ```
    - `"name": "data_exchange"` → submits the screen to the SERVER. Use on ALL screens, including the last one, whenever downstream nodes (wa.sendText, etc.) must run after the flow.
    - `"name": "navigate"` → navigate without server call (data is NOT sent to the server).
    - `"name": "complete"` → closes the flow CLIENT-SIDE without any HTTP call to the server. **NEVER use `complete` if there are downstream nodes that need to execute (e.g. wa.sendText to confirm the data).** Only use `complete` when the flow truly ends with no server-side follow-up.

    **Image**
    `{ "type": "Image", "src": "${data.image_url}", "width": 100, "height": 100, "scale-type": "contain" }`

    ### Dynamic data from `data` section
    Reference data fields with `${data.field_name}` in component properties.
    Reference form values with `${FORM_NAME.field_name}` in Footer payload, where FORM_NAME is the `name` attribute of the Form component.
    **Default pattern (recommended):** name every Form `"form"` → use `${form.field_name}` everywhere.

    ### ABSOLUTE RULE — every screen MUST have at least one visible component inside layout.children
    A wa.flowScreen with an empty layout.children array is INVALID. Always include at minimum:
    - One text component (TextHeading, TextSubheading, or TextBody) describing what the screen is for, AND
    - One Form component with at least one input (TextInput, DatePicker, RadioButtonsGroup, etc.) and a Footer.
    **If you are configuring a wa.flowScreen node, you MUST populate screenDefinition.layout.children. Never leave it as [] or omit it.**

    ### UX / modeling recommendation: prefer multiple short screens over one long screen
    For flows that collect many fields, prefer a progressive multi-screen journey instead of a single overloaded screen.
    - Split large forms into 2+ screens with clear steps (e.g. identification -> preferences -> confirmation).
    - Keep each screen focused on a small number of fields to reduce abandonment and validation errors.
    - Use `data_exchange` to carry accumulated data between screens (see accumulation rules below).
    - Reserve one-screen forms for very short payloads only.

    ### CRITICAL: accumulate data across screens
    Each `data_exchange` is an independent HTTP call. Data collected in previous screens is NOT
    automatically available in subsequent screens. You MUST propagate all previously collected
    fields in every `data_exchange` payload:
    ```json
    // Screen 1 footer — Form named "form": use ${form.field}
    { "name": "data_exchange", "payload": { "screen": "TELA_2", "nome": "${form.nome}" } }

    // Screen 2 footer — re-sends all previous fields + its own
    { "name": "data_exchange", "payload": { "screen": "TELA_3", "nome": "${data.nome}", "email": "${form.email}" } }

    // Screen 3 footer (last) — re-sends all fields + its own
    { "name": "data_exchange", "payload": { "nome": "${data.nome}", "email": "${data.email}", "telefone": "${form.telefone}" } }
    ```
    The server merges `${data.X}` (fields echoed from previous responses) with `${form.X}` (current form input — requires Form named `"form"`).

    ### CRITICAL: include ALL fields submitted in the payload — they feed the condition
    When the next step is a `condition` node that evaluates a field from this screen,
    that field MUST appear in the Footer's `data_exchange` payload. The engine stores
    each screen's payload as `local.[nodeId].response.data.FIELD`, which the condition reads.
    ```json
    // ✅ CORRECT — eh_cliente is included so the downstream condition can evaluate it
    { "name": "data_exchange", "payload": { "screen": "PROXIMO", "eh_cliente": "${form_cliente.eh_cliente}" } }

    // ❌ WRONG — missing eh_cliente; the condition will always receive null → wrong branch
    { "name": "data_exchange", "payload": { "screen": "PROXIMO" } }
    ```

    ### Condition / branching between screens
    You CAN insert `condition` or `switch` nodes between wa.flowScreen nodes to branch
    the flow based on user input. The execution engine automatically traverses these
    logic nodes and sends the correct NEXT_SCREEN_ID back to WhatsApp:
    ```
    wa.flowScreen (PERGUNTA_CLIENTE)
          │ default
    condition  ←── evaluates "local.node_screen_cliente.response.data.eh_cliente" == "sim"
     true │           │ false
    wa.flowScreen    wa.flowScreen
    (PERGUNTA_CPF)   (PERGUNTA_SERVICO)
    ```
    Rules:
    - The condition variable path MUST be `local.NODE_ID.response.data.FIELD` where
      NODE_ID is the ID of the wa.flowScreen node whose footer submitted that field.
    - The field MUST also appear in that screen's `data_exchange` payload (see above).
    - Condition nodes between flowScreens do NOT produce a WA Flow API call — they are
      evaluated server-side to decide which next screen ID to return.
    - Do NOT mark intermediate screens as `terminal:true` when a condition follows them.
      Only the final screen(s) that lead to wa.sendText (or another non-flowScreen node)
      should be `terminal:true`.

    ### Executable nodes BETWEEN screens (http, postgres, setVariable, ...)
    You CAN place executable nodes between two screens — they run on the server
    DURING the navigation (after screen A is submitted, before screen B is rendered):
    ```
    wa.flowScreen (BUSCA_CEP) ──► httpRequest (consulta API) ──► wa.flowScreen (CONFIRMA_ENDERECO)
    ```
    - The intermediate node's results are available to screen B via the SOURCE screen's
      response data templates, rendered AFTER the intermediate nodes run:
      set screen A's response data to e.g. `"endereco": "{{local.http_cep.response.logradouro}}"`.
    - Use this for: fetching data to show on the next screen, saving partial progress
      to a database, validating against an API.
    - Terminal screens: nodes AFTER the terminal screen also run in the same
      data_exchange execution (e.g. save to DB + wa.sendText confirmation).

    ### Server-side validation with per-field error messages (CPF, CEP, availability...)
    Route the FALSE branch of a condition BACK to the same screen node to re-render it
    with an error message under the field:
    ```
    wa.flowScreen (CADASTRO) ──► condition (cpf válido?) ──true──► wa.flowScreen (PROXIMA)
             ▲                                            │false
             └────────────────────────────────────────────┘
    ```
    1. Declare the error field in the screen's `data`: `"erro_cpf": { "type": "string", "__example__": "" }`.
    2. Bind it on the input component: `"error-message": "${data.erro_cpf}"` (TextInput supports it).
    3. Set the screen's response data with a conditional template:
       `"erro_cpf": "{{#if local.valida_cpf.response.valido}}{{else}}CPF inválido — confira os dígitos{{/if}}"`.
    4. Self-routes are allowed by the compiler (data_exchange refresh) and the response is
       filtered to the screen's declared `data` keys — error fields never leak to other screens.

    ### Accessing flow data in downstream nodes (wa.sendText, etc.)
    After the last `data_exchange`, all accumulated payload fields are available via `{{input.data.FIELD}}`:
    - `{{input.data.nome}}` — value of "nome" from any screen's payload
    - `{{input.data.email}}` — value of "email"
    - `{{input.data.cidade}}` — value of "cidade"
    **DO NOT use `{{local.SCREEN_ID.response.data.FIELD}}` in wa.sendText or post-flow nodes —
    use `{{input.data.FIELD}}` instead. `local.X.response.data.FIELD` only works inside
    the condition node that immediately evaluates the submitted screen.**

    ### response object (tells the executor what to send back to WhatsApp)
    ```json
    {
      "screenId": "THIS_SCREEN_ID",
      "data": {
        "field_name": "{{local.someNode.result}}"
      }
    }
    ```
    Use Handlebars `{{local.NODE_ID.key}}` / `{{global.key}}` / `{{input.key}}` for dynamic data.

Objetos filhos e estruturas aninhadas

WAFlowScreenResponse

Screen response returned to WhatsApp

CampoTipoObrigatorioDescricao
nextScreenIdstringNaoNext screen ID to navigate to
actionstringNaoResponse action type
dataobjectNaoKey-value data sent back to WhatsApp

Exemplos de configuracao

Tela intermediária com Form + TextInput + Footer (data_exchange)

{
      "screenDefinition": {
        "id": "TELA_DADOS",
        "title": "Seus dados",
        "terminal": false,
        "data": {
          "nome": { "type": "string", "__example__": "João Silva" },
          "email": { "type": "string", "__example__": "joao@email.com" }
        },
        "layout": {
          "type": "SingleColumnLayout",
          "children": [
            { "type": "TextHeading", "text": "Preencha seus dados" },
            {
              "type": "Form",
              "name": "form",
              "children": [
                { "type": "TextInput", "name": "nome", "label": "Nome completo", "input-type": "text", "required": true },
                { "type": "TextInput", "name": "email", "label": "E-mail", "input-type": "email", "required": true },
                { "type": "Footer", "label": "Continuar", "on-click-action": {
                    "name": "data_exchange",
                    "payload": { "screen": "TELA_CONFIRMACAO", "nome": "${form.nome}", "email": "${form.email}" }
                }}
              ]
            }
          ]
        }
      }
    }

Tela terminal (última tela) com TextBody + Footer de conclusão

{
      "screenDefinition": {
        "id": "TELA_CONFIRMACAO",
        "title": "Confirmação",
        "terminal": true,
        "data": {},
        "layout": {
          "type": "SingleColumnLayout",
          "children": [
            { "type": "TextBody", "text": "Obrigado! Seus dados foram recebidos com sucesso." },
            {
              "type": "Form",
              "name": "form",
              "children": [
                { "type": "Footer", "label": "Concluir", "on-click-action": {
                    "name": "data_exchange",
                    "payload": {}
                }}
              ]
            }
          ]
        }
      }
    }