Get Task Form
Overview
The GetTaskFormQuery retrieves form data for both UserTask and ReceiveTask (form mode) nodes. This unified command works for any waiting task that requires human interaction, whether it's a traditional UserTask or a ReceiveTask configured with a form.
Both UserTask and ReceiveTask (form mode) use the same API endpoint and return structure. Your frontend doesn't need to distinguish between task types!
API Endpoint
POST /api/core/cmd
Headers
Content-Type: application/json
Authorization: Bearer {access_token} (Optional)
X-Tenant-ID: {tenant_id}
This command has CommandAuthorisationType.None, meaning authentication is optional. If a user is logged in (token provided), their details will be used for access control. If no token is provided, forms without ResponsibleUser or ResponsibleTeam restrictions will still be accessible.
Request Structure
You can retrieve forms using either instanceGuid or correlationId:
Option 1: By Instance GUID (Fastest)
{
"cmd": "GetTaskFormQuery",
"data": {
"instanceGuid": "abc-123-def-456"
}
}
Option 2: By Correlation ID
{
"cmd": "GetTaskFormQuery",
"data": {
"correlationId": "ORDER-12345"
}
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
instanceGuid | string | Conditional* | Process instance unique identifier (fastest lookup) |
correlationId | string | Conditional* | External correlation key for the process |
*Either instanceGuid OR correlationId must be provided
Response Status Codes
GetTaskFormQuery returns one of 4 distinct status codes:
| Status | HTTP Code | Description |
|---|---|---|
| SUCCESS | 200 | Form loaded successfully, user has access |
| ACCESS_DENIED | 403 | User doesn't have permission to access this form |
| ALREADY_PROCESSED | 410 | Task was already completed or cancelled |
| NOT_FOUND | 404 | Process instance doesn't exist |
Response Structures
1. SUCCESS (200) - Form Loaded
{
"isSuccessful": true,
"statusCode": "00",
"message": "Form loaded successfully",
"data": {
"status": "SUCCESS",
"formId": "loan-approval-form",
"formJson": {
"components": [
{
"type": "textfield",
"key": "applicantName",
"label": "Applicant Name",
"disabled": true,
"defaultValue": "John Doe"
},
{
"type": "number",
"key": "approvedAmount",
"label": "Approved Amount",
"validate": {
"required": true,
"min": 1000,
"max": 100000
}
},
{
"type": "textarea",
"key": "approverComments",
"label": "Comments"
}
]
},
"clientScript": "/* Browser validation code */",
"context": {
"loanAmount": 50000,
"customerName": "John Doe",
"creditScore": 720,
"recommendation": "Approve"
},
"processInfo": {
"processName": "Loan Approval Workflow",
"taskName": "Manager Approval Required",
"initiator": "system@bank.com",
"startTime": "2026-01-15T10:00:00Z"
},
"taskId": "Task_ManagerApproval",
"taskName": "Manager Approval Required",
"taskType": "userTask",
"processInstanceGuid": "abc-123-def-456",
"errorInfo": null
}
}
2. ACCESS_DENIED (403) - Permission Required
{
"isSuccessful": false,
"statusCode": "403",
"message": "Access Denied",
"data": {
"status": "ACCESS_DENIED",
"formJson": null,
"clientScript": null,
"context": {},
"processInfo": {
"processName": "Loan Approval Workflow",
"taskName": "Manager Approval Required",
"initiator": "system@bank.com"
},
"errorInfo": {
"code": "ACCESS_DENIED",
"message": "You do not have permission to access this form",
"reason": "User 'john.user@bank.com' is not assigned to this task",
"requiredRole": "Manager",
"responsibleUsers": ["manager1@bank.com", "manager2@bank.com"],
"responsibleTeams": ["APPROVAL_TEAM"],
"currentUser": "john.user@bank.com",
"suggestedAction": "Contact the responsible team or user for access"
}
}
}
3. ALREADY_PROCESSED (410) - Task Completed
{
"isSuccessful": false,
"statusCode": "410",
"message": "Request already processed",
"data": {
"status": "ALREADY_PROCESSED",
"errorInfo": {
"code": "TASK_NO_LONGER_WAITING",
"message": "This task has already been processed and is no longer available",
"reason": "Process status is 'COMPLETED' and state is 'completed'",
"suggestedAction": "The task has been completed or cancelled. No action is required.",
"processStatus": "COMPLETED",
"processState": "completed",
"completedAt": "2026-01-15T11:30:00Z",
"processInstanceGuid": "abc-123-def-456"
},
"processInfo": {
"processName": "Loan Approval Workflow",
"taskName": "Manager Approval Required",
"initiator": "system@bank.com",
"status": "COMPLETED",
"startTime": "2026-01-15T10:00:00Z",
"endTime": "2026-01-15T11:30:00Z"
}
}
}
4. NOT_FOUND (404) - Process Doesn't Exist
{
"isSuccessful": false,
"statusCode": "404",
"message": "Process instance not found",
"data": {
"status": "NOT_FOUND",
"errorInfo": {
"code": "PROCESS_NOT_FOUND",
"message": "The specified process instance does not exist",
"reason": "No process found with instance GUID: abc-123-def-456",
"suggestedAction": "Verify the correlation ID or instance GUID and try again",
"searchedBy": "instanceGuid",
"searchedValue": "abc-123-def-456"
}
}
}
Task Types Supported
UserTask - Always Has Form
UserTask nodes always require human interaction and have forms:
<bpmn:userTask id="Task_Approval" name="Approval Required">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="approval-form"/>
<custom:property name="ClientScript" value="/* UI logic */"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>
ReceiveTask - Form Mode vs Webhook Mode
ReceiveTask can operate in two modes:
Form Mode (Human Interaction Required)
ReceiveTask with form property requires human approval after external event:
<bpmn:receiveTask id="Task_ReviewPayment" name="Review Payment Details">
<bpmn:extensionElements>
<custom:properties>
<!-- Form mode: has form property -->
<custom:property name="form" value="payment-review-form"/>
<custom:property name="correlationKey" value="context.paymentId"/>
<custom:property name="message" value="PaymentReceived"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:receiveTask>
Use Case: Payment gateway processes payment → Manager reviews payment details in form → Approves/Rejects
Webhook Mode (Pure External Callback)
ReceiveTask without form property is pure external webhook callback:
<bpmn:receiveTask id="Task_WaitForWebhook" name="Wait for External Callback">
<bpmn:extensionElements>
<custom:properties>
<!-- Webhook mode: NO form property -->
<custom:property name="correlationKey" value="context.transactionId"/>
<custom:property name="message" value="PaymentConfirmation"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:receiveTask>
Use Case: Payment gateway processes payment → Sends webhook automatically → Process resumes (no human interaction)
GetTaskFormQuery returns forms for both UserTask and ReceiveTask (form mode). It will NOT return results for ReceiveTask webhook mode (since there's no form to display).
Performance Optimization
GetTaskFormQuery uses indexed database columns for fast retrieval:
- Direct Index Lookup:
InstanceGuidcolumn (fastest) - Pre-filtering:
State,ProcessRunStatus,CurrentActivityTypecolumns - JSON Deserialization: Only when process matches filters
Recommended: Use InstanceGuid
{
"cmd": "GetTaskFormQuery",
"data": {
"instanceGuid": "abc-123-def-456" // ✅ Fastest - direct index lookup
}
}
Alternative: Use CorrelationId
{
"cmd": "GetTaskFormQuery",
"data": {
"correlationId": "ORDER-12345" // ⚠️ Slower - requires JSON search
}
}
Access Control
No Authentication (Public Forms)
Forms without ResponsibleUser or ResponsibleTeam are accessible to anyone:
<bpmn:userTask id="Task_PublicForm" name="Public Feedback Form">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="feedback-form"/>
<!-- No ResponsibleUser or ResponsibleTeam - publicly accessible -->
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>
User-Based Access Control
Restrict form access to specific users:
<bpmn:userTask id="Task_ManagerApproval" name="Manager Approval">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="approval-form"/>
<custom:property name="ResponsibleUser" value="manager@bank.com"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>
Team-Based Access Control
Restrict form access to team members:
<bpmn:userTask id="Task_TeamReview" name="Team Review">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="review-form"/>
<custom:property name="ResponsibleTeam" value="APPROVAL_TEAM"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>
Frontend Integration
Complete Form Loading Flow
// 1. Load form (works for both UserTask and ReceiveTask!)
async function loadTaskForm(instanceGuid) {
const response = await fetch('/api/core/cmd', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`, // Optional
'X-Tenant-ID': tenantId
},
body: JSON.stringify({
cmd: 'GetTaskFormQuery',
data: { instanceGuid }
})
});
const result = await response.json();
// Handle different status codes
switch (result.data.status) {
case 'SUCCESS':
// Render form with Form.io
const formio = await Formio.createForm(
document.getElementById('form-container'),
result.data.formJson,
{
readOnly: false,
data: result.data.context // Pre-fill form with context
}
);
// Execute client-side script if provided
if (result.data.clientScript) {
eval(result.data.clientScript);
}
// Store task metadata for submission
return {
formio,
taskId: result.data.taskId,
taskType: result.data.taskType,
instanceGuid: result.data.processInstanceGuid
};
case 'ACCESS_DENIED':
showError('Access Denied', result.data.errorInfo.message);
break;
case 'ALREADY_PROCESSED':
showInfo('Already Completed', result.data.errorInfo.message);
break;
case 'NOT_FOUND':
showError('Not Found', result.data.errorInfo.message);
break;
}
}
// 2. Submit form (unified for both UserTask and ReceiveTask)
async function submitForm(instanceGuid, taskId, formData) {
const response = await fetch('/api/core/cmd', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
'X-Tenant-ID': tenantId
},
body: JSON.stringify({
cmd: 'SignalProcessInstanceCommand',
data: {
instanceGuid,
taskId, // Optional, but recommended
callbackData: formData // New parameter name
}
})
});
return await response.json();
}
React Hook Example
import { useState, useEffect } from 'react';
interface TaskFormData {
status: 'SUCCESS' | 'ACCESS_DENIED' | 'ALREADY_PROCESSED' | 'NOT_FOUND';
formJson?: any;
context?: any;
clientScript?: string;
taskId?: string;
taskType?: string;
processInstanceGuid?: string;
errorInfo?: any;
}
export function useTaskForm(instanceGuid: string) {
const [formData, setFormData] = useState<TaskFormData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchForm() {
try {
const response = await fetch('/api/core/cmd', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'X-Tenant-ID': 'your-tenant-id'
},
body: JSON.stringify({
cmd: 'GetTaskFormQuery',
data: { instanceGuid }
})
});
const result = await response.json();
setFormData(result.data);
if (result.data.status !== 'SUCCESS') {
setError(result.data.errorInfo?.message || 'Failed to load form');
}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchForm();
}, [instanceGuid]);
const submitForm = async (data: any) => {
const response = await fetch('/api/core/cmd', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'X-Tenant-ID': 'your-tenant-id'
},
body: JSON.stringify({
cmd: 'SignalProcessInstanceCommand',
data: {
instanceGuid: formData.processInstanceGuid,
taskId: formData.taskId,
callbackData: data
}
})
});
return await response.json();
};
return { formData, loading, error, submitForm };
}
Common Use Cases
1. Email Link to Form (Public Access)
Customer receives email with link: https://bank.com/forms?correlationId=ORDER-12345
const urlParams = new URLSearchParams(window.location.search);
const correlationId = urlParams.get('correlationId');
const response = await fetch('/api/core/cmd', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
cmd: 'GetTaskFormQuery',
data: { correlationId }
})
});
2. Internal Dashboard (Authenticated Access)
Manager views pending tasks in dashboard:
// User already authenticated with JWT token
const pendingTasks = await fetchPendingTasks();
// Load specific form when clicked
async function openTask(instanceGuid) {
const response = await fetch('/api/core/cmd', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${userToken}`,
'X-Tenant-ID': tenantId
},
body: JSON.stringify({
cmd: 'GetTaskFormQuery',
data: { instanceGuid }
})
});
// If SUCCESS, render form
// If ACCESS_DENIED, show permission error
}
3. External System Callback with Form
Payment gateway processes payment, sends webhook, then manager reviews:
// Step 1: External system sends webhook (ReturnCallbackCommand)
// Process moves to ReceiveTask (form mode)
// Step 2: Manager receives notification and opens form
const response = await fetch('/api/core/cmd', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${managerToken}`
},
body: JSON.stringify({
cmd: 'GetTaskFormQuery',
data: { correlationId: 'PAYMENT-12345' }
})
});
// Step 3: Manager reviews payment details and approves
// Form shows: payment amount, payer details, gateway response, etc.
// Manager fills: approval decision, comments, adjusted amount
Error Handling Best Practices
async function loadAndRenderForm(instanceGuid) {
try {
const response = await fetch('/api/core/cmd', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({
cmd: 'GetTaskFormQuery',
data: { instanceGuid }
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
switch (result.data.status) {
case 'SUCCESS':
// Render form
renderForm(result.data);
break;
case 'ACCESS_DENIED':
// Show permission error with details
showAccessDeniedMessage(result.data.errorInfo);
break;
case 'ALREADY_PROCESSED':
// Show completion message
showAlreadyProcessedMessage(result.data.errorInfo);
break;
case 'NOT_FOUND':
// Show not found error
showNotFoundMessage(result.data.errorInfo);
break;
default:
throw new Error('Unknown status: ' + result.data.status);
}
} catch (error) {
console.error('Failed to load form:', error);
showGenericError('Unable to load form. Please try again later.');
}
}
Related Commands
- SignalProcessInstanceCommand - Submit form data (works for both UserTask and ReceiveTask form mode)
- ReturnCallbackCommand - External webhook callback for ReceiveTask webhook mode
- GetProcessInstanceStateCommand - Check current process status