Skip to content

User Tasks

User tasks represent human interaction points in a workflow. They create inbox items and wait for user completion.

Definition

dart
builder.userTask(
  'approvalDecision',
  name: 'Approval Decision',
  signal: 'approval_signal',  // Signal to wait for
  title: 'Approve {{entityType}}: {{entityName}}',
  description: 'Review and approve this submission',
  assignToRole: 'approver_role',
  storeAs: 'approvalResult',
);

User Task Properties

PropertyDescription
signalSignal name to wait for (auto-generated if omitted)
schemaTypeType identifier (references UserTaskExecutor)
titleDisplay title (supports template variables)
descriptionDetailed description
assignToRoleRole-based assignment
assignToUserDirect user assignment
storeAsKey for storing user's response
priorityTask priority (low, normal, high, urgent)

User Task Flow

Step Details:

  1. Token arrives at UserTask node
  2. UserTaskNodeExecutor.execute() - uses UserTaskExecutor if provided, otherwise DefaultUserTaskExecutor
  3. Executor returns WaitForUserTaskResult with title, assignee, signal name
  4. Engine creates UserTaskInstance (idempotent - checks if active task exists)
  5. Workflow status becomes waitingForSignal
  6. User completes task via UI
  7. engine.sendSignal(node: nodeId) continues workflow

UserTaskInstance

Created when workflow reaches a user task:

dart
class UserTaskInstance {
  final String id;
  final String workflowInstanceId;
  final String nodeId;
  final String schemaType;       // Executor type for deserialization
  final String title;
  final String? description;
  final String? signalName;
  final String? assignedToRoleId;
  final String? assignedToUserId;
  final String? assignedToGroupId;
  final TaskStatus status;
  final UserTaskPriority priority;
  final Map<String, dynamic> input;
  final Map<String, dynamic>? output;
  final DateTime createdAt;
  final DateTime updatedAt;
  final DateTime? dueAt;
  final DateTime? completedAt;
  final String? completedBy;
}

UserTaskExecutor

Implement custom executors for different task types:

dart
class ApprovalTaskExecutor extends UserTaskExecutor {
  static const _schemaType = 'userTask.approval';

  @override
  String get schemaType => _schemaType;

  @override
  String get name => 'Approval Task';

  @override
  Future<WaitForUserTaskResult> execute(ExecutionContext context) async {
    // Use get<T> for typed access to previous node output
    final entityName = context.get<String>('entityName') ?? 'Item';
    final approverRoleId = context.get<String>('approverRoleId');
    final entityId = context.get<String>('entityId');
    final entityType = context.get<String>('entityType');
    final submittedBy = context.get<String>('submittedBy');

    return WaitForUserTaskResult(
      signalName: context.signalName!,
      config: UserTaskConfiguration(
        title: 'Approve: $entityName',
        description: 'Please review and approve this request.',
        schemaType: schemaType,
        assignedToRoleId: approverRoleId,
        priority: UserTaskPriority.high,
        input: {
          'entityId': entityId,
          'entityType': entityType,
          'submittedBy': submittedBy,
        },
      ),
    );
  }

  static final typeDescriptor = TypeDescriptor<UserTaskExecutor>(
    schemaType: _schemaType,
    fromJson: (_) => ApprovalTaskExecutor(),
    title: 'Approval Task',
  );
}

// Register via descriptor
final descriptor = WorkflowDescriptor(
  title: 'Approval Tasks',
  userTasks: [ApprovalTaskExecutor.typeDescriptor],
);

Querying User Tasks

For Inbox UI

dart
// Get tasks assigned to a role
final tasks = await storage.userTaskInstances.findByRole('approver_role');

// Get tasks assigned to a user
final myTasks = await storage.userTaskInstances.findByUserId(currentUserId);

// Get pending tasks
final pending = await storage.userTaskInstances.findByStatus(
  UserTaskStatus.pending,
);

Task Details

dart
final task = await storage.userTaskInstances.getById(taskId);

// Display in UI
Text(task.title);
Text(task.description ?? '');
Text('Priority: ${task.priority}');
Text('Due: ${task.dueDate}');

// Show input data for user context
JsonView(task.input);

Completing User Tasks

When a user completes a task, send a signal:

dart
// In your UI/API
Future<void> completeApproval(String taskId, String decision, String comments) async {
  final task = await storage.userTaskInstances.getById(taskId);

  await engine.sendSignal(
    workflowInstanceId: task.workflowInstanceId,
    node: task.nodeId,  // The user task node ID
    payload: {
      'decision': decision,
      'comments': comments,
      'completedBy': currentUserId,
      'completedAt': DateTime.now().toIso8601String(),
    },
  );
}

User Task Status

StatusDescription
pendingCreated, waiting to be claimed
claimedUser has taken ownership
completedUser submitted response
cancelledTask was cancelled
expiredPast due date

Assignment Patterns

Role-Based

dart
builder.userTask('approval',
  assignToRole: 'finance_approvers',
  ...
);

Direct User

dart
builder.userTask('review',
  assignToUser: specificUserId,
  ...
);

Dynamic Assignment

dart
class DynamicAssignmentExecutor extends UserTaskExecutor {
  static const _schemaType = 'userTask.dynamic';

  @override
  String get schemaType => _schemaType;

  @override
  String get name => 'Dynamic Assignment';

  @override
  Future<WaitForUserTaskResult> execute(ExecutionContext context) async {
    // Determine assignee from workflow data using get<T>
    final submitterId = context.get<String>('submitterId');
    final managerId = await getManagerId(submitterId);

    return WaitForUserTaskResult(
      signalName: context.signalName!,
      config: UserTaskConfiguration(
        title: 'Review Required',
        schemaType: schemaType,
        assignedToUserId: managerId,
      ),
    );
  }
}

Best Practices

  1. Use meaningful titles - Users scan inbox by title
  2. Include context in input - Help users make decisions
  3. Set appropriate priorities - Guide user attention
  4. Use due dates - Track SLAs
  5. Namespace with storeAs - Keep responses organized

Next Steps