Enable Workflows to Generate Widgets in Agentic Applications

In your workflow, the LLM and agent nodes can generate widgets. Initial graphics and insight displays are produced by agent output blocks that carry a patternId and a widget-specific config. In App Builder, the display prompt tells the runtime agent what widget output to generate.

Enable Workflows to Generate Widgets

To enable workflows to generate widgets, do these steps:

  1. Go to AI Agent Studio and open the Workflows Workflows tab.
  2. Select Edit Edit to edit the workflow you want to generate widgets.
  3. Go to the LLM or agent node that should generate widgets.
  4. Edit the node and open the App Experience tab.
  5. Select the options to enable widgets for actions and communications as needed.
  6. Select the types of widget that are to be generated by the workflow.
  7. Add instructions for the widget.

Types of Widgets

Here are the types of widgets an LLM or agent node can generate:

Type of Widget Sample Prompt Details Example
Card Use a cardWidget to present one important update as a clean, scannable card. Include a short subject, an optional subtitle for context, a brief summary, and if helpful a status line or time stamp, so the update feels informative without being crowded.
  • Use it for: a single alert, update, or status card
  • Required: subject
  • Common fields: summary, timestamp, badgeText, variant
  • Actions: link and optional additionalLink, each with text and action
  • Variant values: error, warning, info, or omitted for neutral

Use commands such as ora.Invoke(...).

<oraInfoDisplay key="maintenance-card">
{
  "patternId": "cardWidget",
  "config": {
    "subject": "Maintenance Complete",
    "summary": "Scheduled database maintenance finished successfully.",
    "timestamp": "1 hour ago",
    "badgeText": "Info",
    "variant": "info",
    "link": {
      "text": "View Details",
      "action": "ora.Invoke(\"viewMaintenanceSummary\")"
    }
  }
}
</oraInfoDisplay>
Multi Card Use a multiCardWidget to display multiple cards in one widget. For example, for generic alerts or status summaries.
  • Use it for: multiple peer cards in one widget when a table or message list would be the wrong shape
  • Required: cards[]
  • Optional top-level fields: layoutMode
  • layoutMode values: default, collapsed, single-col
  • cards[] entries: each card uses the same fields as cardWidget including subject, summary, optional subtitle, status, statusPriority, timestamp, badgeText, link, additionalLink, and variant
  • Card color values: use the same variant and statusPriority values as cardWidget, including success and neutral

Use layoutMode only if you need a compact two-column layout (collapsed) or a one-card-per-row layout (single-col). Use the top-level widget title and text for shared context, and keep card-specific details inside each cards[] entry.

<oraInfoDisplay key="ops-status-cards">
{
  "patternId": "multiCardWidget",
  "config": {
    "cards": [
      {
        "subject": "Database Cluster",
        "subtitle": "Primary region",
        "summary": "Replication lag returned to normal after failover testing.",
        "status": "Healthy",
        "statusPriority": "success",
        "timestamp": "8 min ago",
        "badgeText": "Info",
        "link": {
          "text": "View Details",
          "action": "ora.Invoke(\"openDatabaseStatus\")"
        }
      },
      {
        "subject": "API Capacity",
        "subtitle": "Public gateway",
        "summary": "Traffic remains elevated and is close to the scaling threshold.",
        "status": "Watching autoscale behavior",
        "statusPriority": "warning",
        "timestamp": "12 min ago",
        "badgeText": "Warning",
        "variant": "warning"
      }
    ]
  }
}
</oraInfoDisplay>
Artifact Preview Use a artifactPreviewWidget when the artifact content itself should be previewed inline and opened in the artifact viewer. For generic alerts or status summaries, use cardWidget or multiCardWidget.
  • Use it for: reviewable text, rich text, structured rich text, PDF, or URL artifacts that should open directly in the artifact viewer
  • Required: items[]
  • Each item requires: artifactId, title, mode, and content
  • mode values: preview, edit, custom
  • Optional item fields: subtitle, actionText
  • action: only for custom items
  • content shape: mirrors the artifact viewer / editArtifact payload with id, type, optional title, value, optional sections, url, mediaType, highlightText, commitText, and optional metadata

For preview and edit, don't use an action property. The runtime opens the artifact viewer directly from the item's content payload. Only custom items run an explicit command.

preview opens the viewer in read-only mode, and edit opens the viewer in editable mode. On save, the runtime returns the edited value using the normal oraFormSubmit format and adds the item's artifactId under metadata.artifactId.

Some artifact types are not writable. For example, PDFs and URLs are always treated as read-only, so mode: "edit" behaves the same as mode: "preview" for PDF and URL content.

For type: "url", provide url. The preview card and artifact viewer show the URL in an iframe, and no edited value is submitted back to the workflow.

For type: "structuredRichText", provide sections instead of value. Each section should include name, locked, and text. Locked sections remain visible in the editor but cannot be modified. The runtime renders the sections into the rich text editor using those locked boundaries.

<oraInfoDisplay key="artifact-previews">
{
  "patternId": "artifactPreviewWidget",
  "properties": {
    "items": [
      {
        "artifactId": "customer-note-rich-text",
        "title": "Customer Note",
        "subtitle": "Standard rich text draft",
        "mode": "edit",
        "content": {
          "id": "customer-note-1",
          "type": "richText",
          "title": "Customer Note",
          "value": "<p><strong>Team,</strong></p><p>Please review the latest customer note before the renewal call.</p><ul><li>Pricing review is still open</li><li>Legal requested one contract edit</li><li>Customer wants final confirmation by Friday</li></ul>",
          "commitText": "Save",
          "metadata": {
            "source": "customer-note"
          }
        }
      },
      {
        "artifactId": "qbr-email-preview",
        "title": "QBR Customer Email",
        "subtitle": "Structured rich text draft",
        "mode": "edit",
        "content": {
          "id": "qbr-follow-up",
          "type": "structuredRichText",
          "title": "QBR Follow-Up Email",
          "sections": [
            {
              "name": "Greeting",
              "locked": false,
              "text": "<p><strong>Hi Jordan,</strong></p>"
            },
            {
              "name": "Summary",
              "locked": false,
              "text": "<p>Thanks again for the time today...</p>"
            },
            {
              "name": "Closing",
              "locked": true,
              "text": "<p>Best,<br/>Account Team</p>"
            }
          ],
          "commitText": "Save",
          "metadata": {
            "source": "qbr"
          }
        }
      },
      {
        "artifactId": "renewal-brief-pdf",
        "title": "Renewal Brief",
        "subtitle": "Executive package",
        "mode": "preview",
        "content": {
          "id": "renewal-brief",
          "type": "pdf",
          "title": "Renewal Brief",
          "url": "/artifacts/renewal-brief.pdf",
          "mediaType": "binary"
        }
      },
      {
        "artifactId": "order-dashboard-url",
        "title": "Order Dashboard",
        "subtitle": "Live dashboard",
        "mode": "preview",
        "content": {
          "id": "order-dashboard",
          "type": "url",
          "title": "Order Dashboard",
          "url": "https://example.com/orders/dashboard"
        }
      }
    ]
  }
}
</oraInfoDisplay>

When a writable artifact is saved, the app sends the edited result back to the workflow as an oraFormSubmit block. The example below shows the exact payload shape the workflow receives, including the edited value and the widget's metadata.artifactId.

<oraFormSubmit id="qbr-follow-up">
{"newValue":"Updated text from the editor","metadata":{"source":"qbr","artifactId":"qbr-email-preview"}}
</oraFormSubmit>

For structuredRichText, the same submit envelope is used, but newValue contains the updated sections[] array instead of a string.

<oraFormSubmit id="qbr-follow-up">
{"newValue":[{"name":"Greeting","locked":false,"text":"<p><strong>Hi Jordan,</strong></p>"},{"name":"Summary","locked":false,"text":"<p>Updated summary from the editor.</p>"},{"name":"Closing","locked":true,"text":"<p>Best,<br/>Account Team</p>"}],"metadata":{"source":"qbr","artifactId":"qbr-email-preview"}}
</oraFormSubmit>
  • submitId: the artifact payload content.id when present; otherwise artifact
  • newValue: for text and richText, this is a string; for structuredRichText, this is the updated sections[] array with the latest text for each section
  • metadata: the original metadata payload, passed back unchanged except that the widget adds artifactId under metadata.artifactId
Messages List

Use a messageListWidget to show a short list of recent messages, alerts, or notable events. Each item should have a clear title, optional supporting context like a subtitle or summary, and a time stamp when useful, so the list feels friendly, current, and easy to scan.

  • Use it for: alerts, activity feeds, and small collections of highlight items
  • Required: items[] with at least title per item
  • Item fields: subtitle, summary, status, badgeText, priority, timestamp, image
  • Interactivity: item action and item additionalAction with text + command

Runtime behavior is considered. additionalAction is only shown when the widget is in a focused panel. Also, the runtime supports success as a priority in addition to alert, warning, and medium.

<oraInfoDisplay key="system-alerts">
{
  "patternId": "messageListWidget",
  "config": {
    "subtitle": "Real-time metrics",
    "items": [
      {
        "title": "Memory Usage Spike Detected",
        "subtitle": "82% confidence",
        "summary": "Memory consumption increased by 40% in the last 5 minutes",
        "status": "Investigating root cause",
        "badgeText": "Medium",
        "priority": "medium",
        "timestamp": "3 min ago",
        "action": "ora.Invoke(\"openMemoryIncident\", { \"metric\": \"memory\" })",
        "additionalAction": {
          "text": "View Metrics",
          "command": "ora.Invoke(\"viewMetrics\", { \"metric\": \"memory\" })"
        }
      },
      {
        "title": "Network Latency Increase",
        "summary": "Average response time increased from 120ms to 180ms.",
        "status": "Under investigation",
        "badgeText": "Warning",
        "priority": "warning",
        "timestamp": "16 min ago"
      }
    ]
  }
}
</oraInfoDisplay>
Change List Use a changeListWidget to compare a few key metrics between a previous value and a current value. Add a short subtitle to explain what’s being measured, keep the rows easy to read, and only include a message if there’s a meaningful insight or anomaly worth calling out.
  • Use it for: current-versus-previous metric comparisons
  • Required: displayType, items
  • displayType: percentage, raw, or currency
  • items[]: title, currentValue, previousValue
  • Optional: subtitle, messages

Values should stay numeric. Formatting comes from displayType, not from embedding percent signs or currency symbols inside the numbers.

<oraInfoDisplay key="campaign-change-list">
{
  "patternId": "changeListWidget",
  "config": {
    "subtitle": "Campaign performance comparison",
    "displayType": "percentage",
    "items": [
      { "title": "Email Open Rate", "currentValue": 28.2, "previousValue": 24.5 },
      { "title": "Click-through Rate", "currentValue": 4.6, "previousValue": 3.8 },
      { "title": "Conversion Rate", "currentValue": 1.9, "previousValue": 2.1 }
    ],
    "messages": [
      "Conversion rate drop may indicate landing page issues"
    ]
  }
}
</oraInfoDisplay>
Chart Use a chartWidget to visualize a simple trend, comparison, or proportion in a way that's easy to understand at a glance. Choose the chart type that fits the story, use clear labels, and include one or two short insights underneath to help explain what the chart is showing.
  • Use it for: trends, comparisons, or proportions
  • Required: type, data
  • type: line, bar, or pie
  • data.labels[]: x-axis labels or categories
  • data.datasets[]: each dataset needs label and numeric data[]
  • Optional: insights[]
<oraInfoDisplay key="sales-trend-chart">
{
  "patternId": "chartWidget",
  "config": {
    "type": "line",
    "data": {
      "labels": ["January", "February", "March", "April", "May"],
      "datasets": [
        {
          "label": "Sales Revenue",
          "data": [10000, 25000, 15000, 40000, 30000]
        }
      ]
    },
    "insights": [
      "Revenue peaked in April",
      "Steady growth trend overall"
    ]
  }
}
</oraInfoDisplay>
Record Use a recordWidget to show a structured record as a simple form that can either be reviewed or edited. Include clearly labeled fields, choose field types that match the data, and keep the layout straightforward so it feels comfortable for someone filling in or checking details.
  • Use it for: forms and read-only detail views
  • Required: id, fields[]
  • Optional: readOnly
  • Field types: text, textarea, number, date, select, system
  • Field fields: id, type, value, plus label and options when applicable

In editable mode, the runtime automatically submits the form through an ora.Agent(<oraFormSubmit ...>) command. You don't configure a separate submit command in the widget itself.

<oraInfoDisplay key="employee-form">
{
  "patternId": "recordWidget",
  "config": {
    "id": "employee_form",
    "readOnly": false,
    "fields": [
      {
        "id": "full_name",
        "type": "text",
        "label": "Full Name",
        "value": "John Doe"
      },
      {
        "id": "employee_id",
        "type": "number",
        "label": "Employee ID",
        "value": 12345
      },
      {
        "id": "department",
        "type": "select",
        "label": "Department",
        "value": "engineering",
        "options": [
          { "value": "engineering", "label": "Engineering" },
          { "value": "sales", "label": "Sales" }
        ]
      },
      {
        "id": "session_context",
        "type": "system",
        "value": { "conversation_id": "abc123" }
      }
    ]
  }
}
</oraInfoDisplay>
Multi Record Use a multiRecordWidget to present structured information in a compact table with clear columns and rows. Keep the values short and scannable, use badge-style cells for statuses when helpful, and only add row actions if the experience should explicitly support taking action from the table.
  • Use it for: tabular record lists
  • Required: cols[] and rows[]
  • Column forms: plain string, or object with label and optional showOnExpand
  • Row cells: plain string, or badge object with type, text, and priority
  • Badge priority values: alert, warning, medium, success, neutral
  • Optional row actions: action button and drillDownAction on the first column
  • Optional: subtitle for table context and accessibility
  • Optional multiselect fields: id, selectMode, multiSelectActionText, multiSelectMetadata

The showOnExpand columns only appear when the widget is focused. This is useful for secondary columns that should stay hidden in compact mode.

Badge priorities follow the same meaning as message list priorities: alert is red, warning is orange, medium is informational blue, success is green, and neutral is gray.

To enable checkbox selection, set selectMode to multiSelect. This renders a checkbox for each row and a submit button below the table. The button label defaults to Submit unless you provide multiSelectActionText.

multiSelectMetadata is an optional JSON object or value that's copied through unchanged into the submitted oraFormSubmit payload under the top-level metadata property. Use it to tell the receiving workflow what the selection is for, such as an action name, source context, routing hint, or any other extra data the workflow needs when processing the selected rows.

<oraInfoDisplay key="pending-approvals-table">
{
  "patternId": "multiRecordWidget",
  "config": {
    "subtitle": "Pending approvals",
    "cols": ["Request ID", "Type", "Status"],
    "rows": [
      {
        "cells": [
          "REQ-101",
          "Budget",
          { "type": "badge", "text": "PENDING", "priority": "warning" }
        ],
        "action": {
          "text": "Review",
          "command": "ora.Invoke(\"reviewRequest\", { \"id\": \"REQ-101\" })"
        },
        "drillDownAction": "ora.Invoke(\"openRequest\", { \"id\": \"REQ-101\" })"
      },
      {
        "cells": [
          "REQ-102",
          "Travel",
          { "type": "badge", "text": "PENDING", "priority": "warning" }
        ],
        "action": {
          "text": "Review",
          "command": "ora.Invoke(\"reviewRequest\", { \"id\": \"REQ-102\" })"
        }
      }
    ]
  }
}
</oraInfoDisplay>

Example for multiselect table with checkbox submit.

<oraInfoDisplay key="selected-orders">
{
  "patternId": "multiRecordWidget",
  "config": {
    "id": "selected_orders",
    "subtitle": "Orders awaiting batch approval",
    "selectMode": "multiSelect",
    "multiSelectActionText": "Approve Selected",
    "multiSelectMetadata": {
      "action": "approve_orders",
      "source": "batch_review_queue",
      "approvalLevel": "manager"
    },
    "cols": ["Order ID", "Customer", "Amount", "Status"],
    "rows": [
      {
        "cells": [
          "ORD-5001",
          "Acme Corp",
          "$12,500",
          { "type": "badge", "text": "PENDING", "priority": "warning" }
        ]
      },
      {
        "cells": [
          "ORD-5002",
          "Global Inc",
          "$8,750",
          { "type": "badge", "text": "PENDING", "priority": "warning" }
        ]
      }
    ]
  }
}
</oraInfoDisplay>

When the user submits selected rows in multiselect mode, the app sends an oraFormSubmit block back to the workflow using the widget's id.

In the example below, multiSelectMetadata is copied directly into metadata, while the checked table rows are returned under value[0].value.

<oraFormSubmit id="selected_orders">
{    "value":[{"id":"selectedRows","value":
    [
        {"cells":["ORD-5001","Acme Corp","$12,500",
        {"type":"badge","text":"PENDING","priority":"warning"}
    ]
},
{"cells":["ORD-5002","Global Inc","$8,750",
{"type":"badge","text":"PENDING","priority":"warning"
}]}]}],
"metadata":{"action":"approve_orders","source":"batch_review_queue","approvalLevel":"manager"}}
</oraFormSubmit>
Sankey Use a sankeyWidget to show how volume or activity flows from one stage to another across a process. Define clear node names, keep the flow easy to follow from left to right, and use realistic values so the visualization tells a simple story about where things are going.
  • Use it for: flows between stages or states
  • Required: nodes[], edges[]
  • nodes[]: id, name
  • edges[]: source, target, value

Node IDs are numeric and edges point to those IDs. Keep values positive and keep the flow easy to follow.

<oraInfoDisplay key="support-flow-sankey">
{
  "patternId": "sankeyWidget",
  "config": {
    "nodes": [
      { "id": 0, "name": "Web Visitor" },
      { "id": 1, "name": "Web Chat" },
      { "id": 2, "name": "AI Chat Bot" },
      { "id": 3, "name": "Resolved by Bot" },
      { "id": 4, "name": "Transfer to Human" },
      { "id": 5, "name": "Resolved by Human" }
    ],
    "edges": [
      { "source": 0, "target": 1, "value": 1000 },
      { "source": 1, "target": 2, "value": 1000 },
      { "source": 2, "target": 3, "value": 700 },
      { "source": 2, "target": 4, "value": 300 },
      { "source": 4, "target": 5, "value": 300 }
    ]
  }
}
</oraInfoDisplay>

Interactive widgets

For interactive widgets, use ora.Invoke("actionCode", payload), which represents the app-defined action path.