Share via


Ask with Adaptive Cards

Adaptive Cards let you add snippets of content to Copilot Studio agents that can also be openly exchanged with other cloud apps and services. To provide rich conversation capability for your agent, you can include text, graphics, and buttons. Because they're platform-agnostic, you can easily tailor Adaptive Cards to your needs.

With an Adaptive Card node, your agent can show an Adaptive Card containing one or more submit buttons, and optionally, one or more form input fields. Your agent stores user input in variables for use later in the conversation.

Note

Copilot Studio supports the Adaptive Cards schema versions 1.6 and earlier. However, the appropriate schema version depends on the targeted host app:

  • The Bot Framework Web Chat component (that is, the default website integration pattern) supports version 1.6 but doesn't support Action.Execute
  • The live chat widget (used for Dynamics 365 Omnichannel for Customer Service) is limited to version 1.5
  • Teams is also limited to version 1.5

In addition, Copilot Studio only renders version-1.6 cards in the test chat, not on the canvas.

For more information about the Adaptive Cards schema, see Schema Explorer.

Copilot Studio includes a built-in Adaptive Card designer, which offers the most useful features from the Adaptive Cards Designer.

Alternatively, you can:

  • Use a JSON representation for the card you want to show to the user.
  • Use a Power Fx formula to include dynamic information on the card.

You can also control the behavior of the card, such as what to do when the user enters an invalid response or if the node can be interrupted.

The Adaptive Card node is meant for interactive cards, where the user is expected to submit a response. Use Message and Question nodes to present the user with a non-interactive card that displays information.

Tip

Rename nodes to make them easier to identify. Select the node's name field to update the name directly, or select the three dots () of the node and select Rename from the menu. You can also rename nodes in the code editor.

It's not possible to rename Trigger nodes and Go to step nodes.

Node names can be up to 500 characters in length.

Add an Adaptive Card node

  1. Select the Add node icon under the node after which you want to add an Adaptive Card node, then select Ask with Adaptive Card.

  2. Select the three dots () of the node, and then select Properties.

  3. In the Adaptive Card node properties panel, select Edit adaptive card. The Adaptive card designer panel opens.

  4. Add the desired elements for your card and configure their properties. Alternatively, in the Card payload editor pane, replace the default payload with the JSON literal for your card.

    Tip

    Your card must contain at least one submit button, as it must be an interactive card that allows a user to submit information back to the agent. If it doesn't and is only intended to show information, you should add your Adaptive Card to a Message node.

  5. When you're done with the initial design, select Save and close the designer panel. A preview of your card appears on the node. Copilot Studio automatically creates output variables based on the inputs specified in the code.

    Screenshot of an Adaptive Card node, with a preview of the card.

    Tip

    If the output variables generated for your card are incorrect, you can manually update the list of variables and their types by selecting Edit Schema in the Adaptive Card node properties panel.

    Your interactive Adaptive Card is ready. When a user of your agent selects a submit button on a card, the output variables are populated with the information the user provided in their interaction with the card.

Other properties

You can use other properties to control how the behavior of the Adaptive Card node, such as:

  • How the agent responds to an invalid response
  • If it can be interrupted

If the agent is awaiting a submission from an Adaptive Card and the user sends a text message instead, this response is considered invalid, unless the message triggers an interruption. In this case, the following properties determine the behavior.

  • How many reprompts: The number of times your agent tries to get a valid submission from the card. The default is Repeat up to 2 times. You can also select Repeat once or Don't repeat. For each retry, the card is resent to the user.

  • Retry prompt: Use this property to define a message to send when a retry occurs, along with a repeat of the card. To define a retry message, select Customize and then enter the new prompt.

  • Allow switching to another topic: If selected (default), an incoming message from a user when the agent is awaiting a card submission triggers an interruption and switches to another topic. If a topic switch occurs, the card is sent again to the user once the interrupting topic ends.

Submit button behavior for agents with consecutive cards

By design, Adaptive Cards allow selecting their submit buttons multiple times. If an agent has consecutive Adaptive Cards, and the user selects a button on an earlier card, the user might experience unexpected behavior.

To prevent the submit action on one card from interfering with another card:

  • Isolate submit actions: Ensure that each Adaptive Card has its own unique identifier and action handlers.

  • Use submit actions with unique data: When you define the submit actions for your cards, include unique identifiers or data payloads that help differentiate between the cards when the user selects a submit button.

  • Add robust event handling logic to your agent: Define conditions based on the distinctive identifiers or payload elements associated with your submit buttons.

  • Debug and log: Add detailed logging to your agent's event handling code to capture the sequence of actions and identify where unintended submissions occur.

Use a submit identifier in Action.Submit data

If your agent sends multiple Adaptive Cards in a conversation (for example, consecutive cards, retries, or interruptions), users might select Submit on an earlier card. To help your agent or custom client distinguish which card and action a response came from, include a unique identifier in each submit action's data payload and validate it when processing the response.

Example:

{
  "type": "Action.Submit",
  "title": "Confirm",
  "data": {
    "actionSubmitId": "booking_confirm_card_v3_confirm"
  }
}

Web Chat UX tip to avoid stale clicks

Some chat clients, including web experiences, can leave earlier cards clickable after a user submits a latter card. If you build a custom web chat experience, consider disabling submit buttons after the first click or updating the previous card message to reduce accidental duplicate or stale submissions.

The following example shows one of the ways to disable Action.Submit buttons after the first click in a custom web chat experience:

  1. Render Adaptive Cards using the Adaptive Cards SDK or another renderer that creates real HTML buttons or inputs.

  2. When you receive a submit action, immediately mark the current card as submitted in your UI. For example, set a submitted flag on the message.

  3. Re-render or mutate the card Document Object Model (DOM), so that all interactive elements are disabled, and then send the submit payload to your bot or service.

  4. If your chat supports multiple cards per conversation, repeat the same pattern for every card message to prevent stale submissions from older cards.

Example:

// Example: disable Adaptive Card submit interactions after the first click.
// This is UI-side logic for custom web chat experiences.

// When you render a card, keep a reference to its container element.
// For example, each chat message could render into its own <div>.
function disableCardInteractivity(cardContainer) {
    // Disable buttons (including Action.Submit rendered as <button>).
    for (const el of cardContainer.querySelectorAll('button, input, select, textarea')) {
        el.disabled = true;
        el.setAttribute('aria-disabled', 'true');
    }

    // Optional: prevent click handlers from firing (defense-in-depth).
    cardContainer.addEventListener(
        'click',
        (evt) => {
            const target = /** @type {HTMLElement} */ (evt.target);
            if (target && target.closest && target.closest('button, input, select, textarea')) {
                evt.preventDefault();
                evt.stopPropagation();
            }
        },
        true
    );
}

// Wire the behavior into your Adaptive Cards host.
// The Adaptive Cards SDK surfaces submits via onExecuteAction.
function wireCardSubmitHandling(adaptiveCard, cardContainer, sendToBot) {
    let submitted = false;

    adaptiveCard.onExecuteAction = async (action) => {
        // Only allow the first submit from this card instance.
        if (submitted) {
            return;
        }
        submitted = true;

        // Disable the UI immediately to avoid duplicate/stale clicks.
        disableCardInteractivity(cardContainer);

        // Send the submit payload to your bot/service.
        // If you're using Action.Submit with a unique ID (for example, actionSubmitId),
        // include it in the payload so your bot can de-duplicate safely.
        await sendToBot({
            type: 'adaptiveCard/submit',
            data: action && action.data ? action.data : {},
            verb: action && action.verb ? action.verb : undefined
        });
    };
}

Use Power Fx to make your card dynamic

You can use a Power Fx formula to include dynamic information on your card by referencing variables from your topic or agent.

  1. Select the three dots () of the node, and then select Properties.

  2. In the Adaptive Card Node properties panel, switch to Formula. Selecting Formula automatically converts the JSON representation of your card into a Power Fx formula.

    Screenshot of the option to switch to a Power Fx formula instead of JSON on the Adaptive Card node properties panel.

    For example, start with the following JSON literal for a card:

    {
      "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
      "type": "AdaptiveCard",
      "version": "1.5",
      "body": [{
          "type": "ColumnSet",
          "columns": [{
              "type": "Column",
              "width": 2,
              "items": [{
                  "type": "TextBlock",
                  "text": "Tell us about yourself",
                  "weight": "Bolder",
                  "size": "Medium",
                  "wrap": true,
                  "style": "heading"
                }, {
                  "type": "TextBlock",
                  "text": "We just need a few more details to get you booked for the trip of a lifetime!",
                  "isSubtle": true,
                  "wrap": true
                }, {
                  "type": "Input.Text",
                  "id": "myName",
                  "label": "Your name (Last, First)",
                  "isRequired": true,
                  "regex": "^[A-Z][a-z]+, [A-Z][a-z]+$",
                  "errorMessage": "Please enter your name in the specified format"
                }
              ]
            }
          ]
        }
      ],
      "actions": [{
          "type": "Action.Submit",
          "title": "Submit"
        }
      ]
    }
    

    Here's the resulting Power Fx formula, using two variables Topic.Title and Topic.Subtitle instead of the hard-coded text from the JSON literal. (This example assumes the variables are defined in your topic.)

    {
      '$schema': "http://adaptivecards.io/schemas/adaptive-card.json",
      type: "AdaptiveCard",
      version: "1.5",
      body: [
        {
          type: "ColumnSet",
          columns: [
            {
              type: "Column",
              width: "2",
              items: [
                {
                  type: "TextBlock",
                  text: Topic.Title,
                  weight: "Bolder",
                  size: "Medium",
                  wrap: true,
                  style: "heading"
                },
                {
                  type: "TextBlock",
                  text: Topic.Subtitle,
                  isSubtle: true,
                  wrap: true
                },
                {
                  type: "Input.Text",
                  id: "myName",
                  label: "Your name (Last, First)",
                  isRequired: true,
                  regex: "^[A-Z][a-z]+, [A-Z][a-z]+$",
                  errorMessage: "Please enter your name in the specified format"
                }
              ]
            }
          ]
        }
      ],
      actions: [
        {
          type: "Action.Submit",
          title: "Submit"
        }
      ]
    }
    

Important

Once you begin editing in the formula panel, you can't go back to the original JSON code. To allow iterative design and changes, save a copy of the original JSON in your own notes or as a comment in the node. This precaution helps you revert changes if needed.