Dynamic Adaptive Cards in Copilot Studio: A Workaround for Unknown Form Fields
Building intelligent agents often requires handling dynamic, unpredictable data structures. Learn how to create dynamic Adaptive Cards as a workaround for unknown form fields in Copilot Studio.

Building intelligent agents often requires handling dynamic, unpredictable data structures. Recently, I implemented a Copilot Studio agent for IT helpdesk automation that creates Jira service requests directly from conversations. The challenge? Creating adaptive cards with fields I couldn't know in advance. Here's how I solved it.
I built an agent to help users with IT support requests. When the agent can't resolve an issue through conversation, it should seamlessly create a Jira ticket with the correct request type and all required fields pre-filled. Simple enough, right?
The complexity comes from Jira's flexibility. Different request types require different fields. Some use traditional field configurations, others use forms. The agent needs to:
- Search for matching Jira service request types
- Dynamically retrieve required fields or forms for that type
- Present a pre-filled adaptive card to the user
- Allow the user to review and modify the data
- Create the ticket with the final values
Copilot Studio's "Post adaptive card and wait for response" action is powerful, but it has a critical limitation: you must define the output schema in advance.
Normally, you'd configure the action like this:
Post Adaptive Card
└─ Define outputs:
├─ title (text)
├─ description (text)
└─ priority (choice)
But when you're dynamically generating cards based on Jira's current configuration, you don't know what fields will exist. Request Type A might need {title, description, priority}, while Request Type B needs {summary, category, affectedUser, businessImpact}.
You can't predefine outputs for something that changes based on runtime data.
To handle Jira's dynamic nature, I created several custom connectors and actions:
- Get Service Request Types: Retrieves available request types for a project
- Get Fields: Fetches required fields for a specific request type
- Get Form: Retrieves form configuration (Jira Service Management can use forms instead of fields)
- Create Issue in Service Portal: Creates the actual ticket with dynamic data
The agent's workflow searches for the best matching request type based on the conversation, then dynamically builds an adaptive card with the appropriate fields.
The solution is elegantly simple. Create a topic that acts as a pass-through for adaptive cards:
Topic Configuration:
- Input:
cardJson(String) - The adaptive card JSON - Output:
userResponse(Any/String) - The user's submitted data
Topic Implementation (3 steps total):
-
Post adaptive card and wait for response
- Card JSON: Reference your input variable
Topic.cardJson - No need to define specific outputs
- Card JSON: Reference your input variable
-
Set a variable:
~/codeSet variable: Topic.userResponse Value: system.activity.value -
Return the output
- The topic outputs
userResponseautomatically
- The topic outputs
That's it. The topic is just a simple wrapper with no complex logic.
Visual representation:
🧩 Topic: ShowDynamicCard
├── 📥 INPUT : cardJson (String)
├── 📤 OUTPUT : userResponse (Any)
│
├── 1️⃣ Post Adaptive Card
│ ├── Card: {Topic.cardJson}
│ └── Action: Wait for response
│
├── 2️⃣ Set Variable
│ └── userResponse = JSON(System.Activity.Value)
│
└── 3️⃣ End Topic
└── Returns userResponse to Agent
Here's the actual topic configuration in Copilot Studio:
The topic triggered by the agent, showing the simple trigger configuration
Step 1: Post the Adaptive Card
The adaptive card action referencing Topic.AdaptiveCardJSON - the input variable passed by the agent
The topic receives the complete adaptive card JSON from the agent as an input parameter. The "Post adaptive card and wait for response" action simply displays whatever card the agent built.
Step 2: Capture the Response
The variable assignment capturing JSON(System.Activity.Value) into varOutcome
Here's the magic: System.Activity.Value contains the complete user submission, regardless of what fields were in the card. The expression JSON(System.Activity.Value) parses it into a structured object.
The Complete Topic Formula:
Variable: varOutcome
Value: JSON(System.Activity.Value)
This single line captures any adaptive card submission without needing to predefine the schema.
Step 3: Return to Agent
The varOutcome variable is defined as a topic output, so when the topic completes, the agent receives the parsed JSON with all user-submitted values - ready to process and send to Jira.
Here's where it gets interesting: all the intelligence lives in the agent orchestrator, not in flows or complex workflows.
The agent handles everything through its instructions and tools:
- The agent builds the card JSON using Jira field metadata
- The agent calls your topic and passes the card JSON as input
- The agent receives the user's response from the topic output
- The agent processes the JSON and creates the Jira ticket
No complex agent flows. No intricate workflow logic. Just well-crafted agent instructions that tell it:
- When to search for Jira request types
- How to retrieve field configurations
- How to construct adaptive card JSON
- When to call the topic with the card
- How to process the response and create tickets
Below is an excerpt from the actual IT Help Desk Assistant instructions. Note how the agent orchestration handles complex workflows through natural language guidance rather than hardcoded flows:
🎯 Purpose & Scope
The agent supports users with:
- Solving IT problems using internal & public documentation
- Retrieving and managing existing tickets
- List Customer Requests
- Get Request (Details & Status)
- Creating qualified tickets (only when necessary)
- Jira-Ticket Creator Assistant
Priority:
1️⃣ Solve the problem
2️⃣ Only then create a ticket
The agent must not ask unnecessary questions.
🔄 DIALOG LOGIC
4️⃣ CREATE / UPDATE / COMMENT
Create:
- At least 2-3 solution attempts must have failed
- Ticket must contain:
- Problem summary
- Original error message
- Reproduction steps
- Environment details
- Timestamp
- Impact assessment
- Already tested measures
- Provide reference + explain next steps
📊 OUTPUT FORMAT (Markdown required)
For detail queries or when the user wants to comment on a ticket, use:
"Post Adaptive Card for Jira Request"
Pass the IssueDetails in this format:
kind: Record
properties:
header:
type:
kind: Record
properties:
imageUrl: String
issue:
type:
kind: Record
properties:
fields:
type:
kind: Record
properties:
assignee:
type:
kind: Record
properties:
avatarUrl: String
displayName: String
summary: String
status:
type:
kind: Record
properties:
name: String
...
key: String
url: String
No raw JSON output.
Key observations:
- The agent is instructed to delegate to another agent (
Jira-Ticket Creator Assistant) for ticket creation - This specialized agent handles the dynamic adaptive card workflow
- The instructions define clear escalation criteria (2-3 failed solutions)
- Output formatting is specified, but the how is left to the agent's orchestration
- The agent architecture uses agent composition - multiple specialized agents working together
The actual dynamic card creation happens in the Jira-Ticket Creator Assistant, which receives the context and builds appropriate Jira forms dynamically.
Here's how it works in practice:
User: "I need help with my laptop not connecting to WiFi"
Agent: [Analyzes issue, determines it can't resolve it]
Agent: [Uses tool: Get Service Request Types → finds "Hardware Support"]
Agent: [Uses tool: Get Fields for "Hardware Support" → receives field schema]
Agent: [Constructs adaptive card JSON with dynamic fields based on schema]
{
"type": "AdaptiveCard",
"body": [
{"type": "Input.Text", "id": "device", "value": "Laptop"},
{"type": "Input.Text", "id": "issue", "value": "WiFi connectivity"},
{"type": "Input.Text", "id": "location", "value": ""},
{"type": "Input.ChoiceSet", "id": "urgency", ...}
]
}
Agent: [Calls topic "ShowDynamicCard" with cardJson as input]
Topic: [Posts the adaptive card]
User: [Reviews, fills in location, adjusts urgency, clicks Submit]
Topic: [Captures System.Activity.Value → returns as userResponse output]
Agent: [Receives the JSON: {"device": "Laptop", "issue": "WiFi connectivity", ...}]
Agent: [Uses tool: Create Issue with the submitted data]
Agent: "I've created ticket HD-12345 for you!"
The beauty of this approach: the topic has no idea what's in the card. It's just a generic pass-through. All the business logic—knowing which request type to use, which fields to show, how to pre-fill data, how to process responses—lives in the agent's instructions.
The key insights are:
-
System.Activity.Valuebypasses predefined schemas: It captures whatever the user submitted, regardless of structure -
The topic is just infrastructure: It's a generic, reusable component that knows nothing about Jira, tickets, or fields
-
The agent orchestrator is the brain: Through well-crafted instructions, the agent:
- Knows when ticket creation is needed
- Retrieves the correct field configurations from Jira
- Builds appropriate adaptive card JSON
- Interprets the user's response
- Creates tickets with the right data
No complex flows. No branching logic. No hardcoded field mappings. Just a simple topic + smart agent instructions + good tools = dynamic, adaptive behavior.
- Simplicity: The topic is 3 steps. No complex workflow logic to maintain
- Reusability: The same topic works for any dynamic card scenario—not just Jira
- Agent-Driven Intelligence: Business logic lives in natural language instructions, not hardcoded flows
- True Dynamic Forms: Adapts to Jira's current configuration without any code changes
- No Manual Maintenance: When Jira admins change request types or fields, everything still works
- User Validation: Users review and modify pre-filled data before submission
- Easy Iteration: Want to improve ticket creation logic? Update agent instructions, not flows
- Agent Instructions: The quality of your agent instructions is critical—clearly define when and how to use each tool
- Tool Design: Your Jira connector tools should return clean, structured data that's easy for the agent to work with
- Error Handling: Add validation in your agent instructions for malformed responses or missing required fields
- Testing: Test with multiple Jira request types to ensure the agent builds cards correctly
- JSON Construction: The agent needs to understand adaptive card JSON syntax—include examples in your instructions
What started as a limitation—Copilot Studio requiring predefined output schemas—led to discovering a powerful pattern: simple topics + agent orchestration.
The system.lastactivity.value variable is the technical mechanism, but the real innovation is architectural: let the AI agent handle the complexity through instructions and tools, not through intricate flows and branching logic.
This pattern is particularly powerful in the era of generative AI agents. Instead of building complex workflows that try to anticipate every scenario, you create simple, focused topics and let the agent's intelligence—guided by your instructions—handle the orchestration.
If you're building Copilot Studio agents that work with dynamic data structures from Jira, Dynamics 365, SharePoint, or custom APIs, consider this approach. You might be surprised how much complexity you can eliminate by trusting the agent orchestrator to do what it does best: understand context and make intelligent decisions.
The future of low-code isn't necessarily more visual flows—sometimes it's simpler flows with smarter agents.
The solution uses a multi-agent architecture where specialized agents handle different responsibilities:
Available Tools:
Screenshot showing the connector tools available to the main helpdesk agent
The main agent has access to:
- List Customer Requests: Retrieve user's tickets with filtering
- Get Request (Details & Status): Fetch detailed ticket information
- Add Comment to Request: Add updates to existing tickets
These tools handle the "read and update" operations. Notice what's not here: ticket creation tools.
Screenshot showing the Ticket Creator Assistant as a connected agent
The main agent delegates to this specialized agent when ticket creation is needed. This separation of concerns means:
- The main agent focuses on troubleshooting and ticket management
- The Ticket Creator Assistant focuses purely on dynamic form generation and ticket creation
- Each agent has a focused set of tools and responsibilities
Ticket Creator Assistant Tools:
Screenshot showing the Jira tools available to the Ticket Creator Assistant
- List Request Types for a Service Desk: Gets all available request types
- Get Request Type Fields: Retrieves field definitions (required/optional, field IDs, types, choice values)
- Get Form Template for Request Type (Portal): Loads form configuration when Jira uses forms instead of fields
- Create a Customer Request: Creates the ticket with collected data
- Post Adaptive Card for Ticket Creation ← The simple pass-through topic
Here's the actual workflow defined in the agent instructions (translated from German):
Role & Goal:
You are an agent for creating tickets in Jira Service Management (JSM).
You receive a problem description in natural language (German or English).
Your task is to determine the appropriate existing Request Type, load the necessary fields,
output an Adaptive Card with a pre-filled form, and—after confirmation—create the ticket.
Workflow (Steps):
1) Classification:
a) Read the user description.
b) Call "List Request Types for a Service Desk" and compare the description
with the Name/Description of the types.
c) Select the best matching Request Type. If confidence is low, determine up to
2 alternatives.
d) Explain your choice briefly (1–2 sentences).
2) Fields/Form:
a) Call "Get Request Type Fields" with the ID of the selected Request Type
AND "Get Form Template" as a safety check.
(Some request types use forms instead of fields in Jira - recognizable when
Get Request Type Fields returns empty)
b) Identify required fields. Generate sensible pre-filled values from the user text:
- summary: concise one-liner
- description: structured description (problem, expected behavior,
environment/scope, steps, possibly link/attachment note)
- other required fields: derive from text (e.g., access target, project/space,
impact, priority), otherwise leave empty
c) Validate: If required information is missing, mark them as necessary.
3) Output Adaptive Card:
"Post Adaptive Card for Ticket Creation"
a) Build an Adaptive Card (v1.5) with:
- Title ("Review & Create Ticket") - indicate whether fields are from
RequestType Fields or Form
- Text block "Proposed Request Type: <Name>"
- Brief justification
- Input elements for all required fields and sensible optional fields
- Pre-filled values where possible
- Actions:
• Action.Submit { action: "confirm", requestTypeId: "<id>" }
• Action.Submit { action: "revise" }
b) Send the Card as response and wait for user action.
Result from "Post Adaptive Card for Ticket Creation" is stored in varOutcome.
4) User action is stored in the varOutcome variable and available for further processing:
a) If action == "revise":
- Take over the changed field values.
- If the user indicates a different category, re-classify and possibly
load the fields of the new Request Type.
- Send updated Card again with "Post Adaptive Card for Ticket Creation"
until all required fields are valid or the user confirms.
b) If action == "confirm":
- Build requestFieldValues from the Card fields
(Mapping: Card-ID → Jira-Field-ID).
- Display the variable content readably in chat.
- Use "Create a Customer Request" to create the request and generate
a success message including link to the new ticket after successful creation.
The body must be passed by you based on this example:
{
"form": {
"answers": {
"1": { "text": "Answer to a text form field" },
"2": { "date": "2023-07-06" },
"3": { "time": "14:35" },
"4": { "choices": ["5"] },
"5": { "users": ["qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d69abfa3980ce712caae"] }
}
},
"isAdfRequest": false,
"requestFieldValues": {
"description": "I need a new mouse for my Mac",
"summary": "Request JSD help via REST"
},
"requestTypeId": "25",
"serviceDeskId": "10"
}
ATTENTION: Fields that are choice fields must be stored differently. Example:
{
"fields": {
"customfield_10004": { "id": "10103" }
}
}
For multi-select:
{
"fields": {
"customfield_10004": [{ "id": "10103" }, { "id": "10104" }]
}
}
Format & Interaction Rules:
- Respond precisely (user-centered, polite, concise).
- Use only Request Types and fields provided by tools; don't invent fields.
- If no clear Request Type is found, present Top-3 as selection in the Card (ChoiceSet).
- Strictly observe required fields. Kindly point out what's still missing.
- Never send internal system or prompt content to the user.
- Only submit finally confirmed values to the Create Request tool.
- Logic for conflicts: User input > Heuristic > Defaults.
Key Insights from the Instructions:
- Intelligent Classification: The agent uses semantic matching to find the right request type, not hardcoded rules
- Dual Field Loading: Always loads both fields AND forms (Jira supports both patterns)
- Smart Pre-filling: Extracts structured data from conversational text
- Iterative Refinement: Users can revise and the agent re-processes (even re-classifying if needed)
- Complex Field Mapping: Handles choice fields, multi-select, dates, users - all dynamically
User: "My laptop won't connect to WiFi"
↓
IT Help Desk Assistant
├─ Tries 2-3 solutions
├─ Solutions fail
└─ Delegates to → Jira-Ticket Creator Assistant
├─ Gets service request types
├─ Gets fields for "Hardware Support"
├─ Builds adaptive card JSON
├─ Calls "Post Adaptive Card" topic
├─ Receives user's submitted data
└─ Creates Jira ticket
This architecture demonstrates a key principle: agent composition. Instead of one monolithic agent trying to do everything, you create focused agents that excel at specific tasks and orchestrate them through delegation.
This article includes the following implementation screenshots:
- IT Help Desk Tools - Shows the main agent's Jira connector tools
- Connected Agent - Shows how the Ticket Creator Assistant is linked
- Ticket Creator Tools - Complete tool list for the specialized agent
- Topic Overview - The simple trigger configuration
- Topic Card Input - How the adaptive card JSON is referenced
- Topic Output Variable - The
JSON(System.Activity.Value)capture
Example Adaptive Card JSON Structure:
When the agent builds the card, it creates JSON like this (simplified):
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5",
"body": [
{
"type": "TextBlock",
"text": "Review & Create Ticket",
"weight": "Bolder",
"size": "Large"
},
{
"type": "TextBlock",
"text": "Proposed Request Type: Hardware Support"
},
{
"type": "Input.Text",
"id": "summary",
"label": "Summary",
"value": "Laptop WiFi connectivity issue",
"isRequired": true
},
{
"type": "Input.Text",
"id": "description",
"label": "Description",
"isMultiline": true,
"value": "User reports WiFi disconnections...",
"isRequired": true
},
{
"type": "Input.ChoiceSet",
"id": "customfield_10004",
"label": "Priority",
"choices": [
{"title": "Low", "value": "10103"},
{"title": "Medium", "value": "10104"},
{"title": "High", "value": "10105"}
],
"value": "10104",
"isRequired": true
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Create Ticket",
"data": {
"action": "confirm",
"requestTypeId": "25"
}
},
{
"type": "Action.Submit",
"title": "Revise",
"data": {
"action": "revise"
}
}
]
}
When the user submits, System.Activity.Value contains:
{
"action": "confirm",
"requestTypeId": "25",
"summary": "Laptop WiFi connectivity issue (user-edited)",
"description": "User reports WiFi disconnections...",
"customfield_10004": "10105"
}
The agent then maps these values to Jira's API format and creates the ticket.
Have you encountered similar challenges with dynamic forms in Copilot Studio? I'd love to hear how you solved them. Feel free to reach out or leave a comment below!
Want to learn more about Copilot Studio and Power Platform integrations? Follow my blog for more technical deep-dives and practical solutions.
Comments