Overview
Commands returned by your backend inside the feCommand object control all frontend behavior — opening modals, showing messages, redirecting, validating, and more.
Response Structure
Your backend endpoints return commands in this format:
{
"fieldAllowedValues": null,
"data": {
"fieldName1": "fieldValue1",
"fieldName2": "fieldValue2"
},
"widgetData": {},
"definition": null,
"widgetsState": null,
"feCommand": {
"CommandName1": null,
"CommandName2": null
}
}
Multiple commands can be included in a single response by adding multiple keys to the feCommand object.
Available Commands
| Command | Category | Description |
|---|---|---|
OpenModal | Navigation | Open a form in a modal window |
Redirect | Navigation | Redirect the browser to a URL |
MoveToTab | Navigation | Switch to a specific tab |
FetchUserMenu | Navigation | Reload the user navigation menu |
RedrawScreen | Form State | Force the form to reload |
AfterRecordSave | Form State | Close modal and refresh parent after save |
ReplaceContextRecordGuid | Form State | Replace the current record GUID |
FormValidationData | Form State | Display server-side validation errors |
Messages | Notifications | Show notification messages |
DisplayErrorPage | Notifications | Show a full-page error |
Download | Files | Trigger a file download |
PreviewDocument | Files | Open a document preview modal |
DisplayComponent | Components | Open a custom UI component |
CloseDisplayComponent | Components | Close a custom UI component |
CustomResponse | Advanced | Return a custom data object |
Command Combinations
Multiple commands can be combined in a single response.
Save and Show Message
{
"feCommand": {
"Messages": [{ "Code": "success", "Text": "Record saved successfully!" }],
"AfterRecordSave": { "guid": "550e8400-e29b-41d4-a716-446655440000" },
"RedrawScreen": true
}
}
- C# / .NET
- PHP
string newRecordGuid = SaveRecordToDatabase(formData);
response.Set("FECommand", new Dictionary<string, object>
{
["Messages"] = new List<object> { new { Code = "success", Text = "Record saved successfully!" } },
["AfterRecordSave"] = new { guid = newRecordGuid },
["RedrawScreen"] = true
});
$newRecordGuid = $this->saveRecordToDatabase($formData);
$response['feCommand'] = [
'Messages' => [['Code' => 'success', 'Text' => 'Record saved successfully!']],
'AfterRecordSave' => ['guid' => $newRecordGuid],
'RedrawScreen' => true
];
Validate and Focus Tab
{
"feCommand": {
"FormValidationData": {
"email": "Invalid email format",
"phone": "Phone number is required"
},
"MoveToTab": { "widgetName": "contactInfoTab" }
}
}
- C# / .NET
- PHP
response.Set("FECommand", new Dictionary<string, object>
{
["FormValidationData"] = new Dictionary<string, string>
{
{ "email", "Invalid email format" },
{ "phone", "Phone number is required" }
},
["MoveToTab"] = new { widgetName = "contactInfoTab" }
});
$response['feCommand'] = [
'FormValidationData' => [
'email' => 'Invalid email format',
'phone' => 'Phone number is required'
],
'MoveToTab' => ['widgetName' => 'contactInfoTab']
];
Download
{
"feCommand": {
"Download": { "outFileName": "Monthly_Report.pdf", "filePath": "/path/to/report.pdf" }
}
}
- C# / .NET
- PHP
// At the top of your runEvent method — handle the follow-up download request
DownloadFileDetailsDto downloadFileData = _pluginContextProvider.GetFECommandProvider().GetDownloadDetails();
if (downloadFileData != null && System.IO.File.Exists(downloadFileData.filePath))
{
S3FileStorageService s3FileStorageService = (S3FileStorageService)_pluginServiceProvider.GetFileStorageProvider();
Response.ContentType = S3FileStorageService.GetMimeType(downloadFileData.outFileName);
Response.Headers.Add("Content-Disposition", $"attachment; filename*=UTF-8''{Uri.EscapeDataString(downloadFileData.outFileName)}");
using (FileStream fileStream = new FileStream(downloadFileData.filePath, FileMode.Open, FileAccess.Read))
{
await s3FileStorageService.WriteFileInChunks(Response.Body, fileStream);
}
System.IO.File.Delete(downloadFileData.filePath);
return new EmptyResult();
}
// Normal event handling — set the Download command to trigger the follow-up request
string filePath = GenerateReport();
response.Set("FECommand", new Dictionary<string, object>
{
["Download"] = new { outFileName = "Monthly_Report.pdf", filePath = filePath }
});
$filePath = $this->generateReport();
$response['feCommand']['Download'] = [
'outFileName' => 'Monthly_Report.pdf',
'filePath' => $filePath
];
if (!empty($response['feCommand']['Download'])) {
$download = $response['feCommand']['Download'];
$fileName = $download['outFileName'];
$filePath = $download['filePath'];
if (file_exists($filePath)) {
$fileResponse = response()->file($filePath, [
'Content-Type' => $this->getMimeType($fileName),
'Content-Disposition' => 'attachment; filename="' . $fileName . '"'
]);
$fileResponse->deleteFileAfterSend(true);
return $fileResponse;
}
}
When Download is present, the frontend makes a follow-up request to runEvent solely to stream the file. Any other commands included in the same response will not be executed.
Important Notes
Command Execution Order
Commands in the feCommand object are executed in the order they are added.
Validation Clears Other Commands
FormValidationData clears all other commands. Validation errors stop all other form actions.
Modal Context
When a form is opened in a modal:
AfterRecordSavecloses the modal instead of redirectingRedirectcommands may behave differently
Multiple Messages
Messages accepts an array — multiple messages can be displayed at once:
"Messages": [
{ "Code": "warning", "Text": "Some fields are missing" },
{ "Code": "info", "Text": "Please complete all required fields" }
]