Skip to main content

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.

Unified Form Loading

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}
Optional Authentication

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

FieldTypeRequiredDescription
instanceGuidstringConditional*Process instance unique identifier (fastest lookup)
correlationIdstringConditional*External correlation key for the process

*Either instanceGuid OR correlationId must be provided

Response Status Codes

GetTaskFormQuery returns one of 4 distinct status codes:

StatusHTTP CodeDescription
SUCCESS200Form loaded successfully, user has access
ACCESS_DENIED403User doesn't have permission to access this form
ALREADY_PROCESSED410Task was already completed or cancelled
NOT_FOUND404Process 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 Behavior

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:

  1. Direct Index Lookup: InstanceGuid column (fastest)
  2. Pre-filtering: State, ProcessRunStatus, CurrentActivityType columns
  3. JSON Deserialization: Only when process matches filters
{
"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

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.');
}
}

See Also