File Upload Events
File upload events handle file uploads through form widgets or datatable upload buttons. These events use multipart form data instead of JSON.
Event Types
| Event | Triggered When | Upload Type |
|---|---|---|
onFileUpload | User uploads files through a file widget | Standard upload |
onFileUploadCreateDocumentEvent | User uploads files through datatable upload button | Datatable upload |
Separate Upload Endpoint
File uploads require a separate endpoint that accepts multipart form data, not the regular runEvent endpoint. Configure this in your FormHostProvider settings.
File Upload Request Structure
Unlike other events, file uploads send multipart form data:
files- Array of uploaded fileswidgetName- Name of the widgetwidgetEvent- Event type (onFileUploadCreateDocumentEvent)formData- Current form data as JSON stringwidgetContext- Additional context as JSON stringfieldName- Name of the field/widgetuploadMode- Mode of upload ("single", "file", "container")
Standard File Upload Widget
Upload Endpoint Implementation
- C# / .NET
- PHP
Controllers/FormsPublicController.cs
[HttpPost("uploadfiles")]
[AllowAnonymous]
public async Task<IActionResult> UploadFiles(
[FromForm] IFormFileCollection files,
[FromForm] string widgetName,
[FromForm] string widgetEvent,
[FromForm] string formData,
[FromForm] string widgetContext,
[FromForm] string fieldName)
{
var uploadedFiles = new List<Dictionary<string, object>>();
var formDataDict = JsonSerializer.Deserialize<Dictionary<string, object>>(formData);
var feCommands = new List<object>();
try
{
foreach (var file in files)
{
// Validate file
if (file.Length > 10 * 1024 * 1024) // 10MB limit
{
feCommands.Add(new
{
command = "ShowMessage",
args = new
{
type = "error",
message = $"File {file.FileName} exceeds 10MB limit"
}
});
continue;
}
// Generate unique file name
var fileExtension = Path.GetExtension(file.FileName);
var uniqueFileName = $"{Guid.NewGuid()}{fileExtension}";
var uploadPath = Path.Combine("uploads", DateTime.Now.ToString("yyyy-MM-dd"));
// Create directory if it doesn't exist
Directory.CreateDirectory(uploadPath);
var filePath = Path.Combine(uploadPath, uniqueFileName);
// Save file to disk
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// Save file metadata to database
var fileRecord = new UploadedFile
{
Id = Guid.NewGuid(),
OriginalFileName = file.FileName,
StoredFileName = uniqueFileName,
FilePath = filePath,
FileSize = file.Length,
ContentType = file.ContentType,
UploadedAt = DateTime.UtcNow,
UploadedBy = formDataDict.GetValueOrDefault("userId", "").ToString()
};
await _dbContext.UploadedFiles.AddAsync(fileRecord);
uploadedFiles.Add(new Dictionary<string, object>
{
{ "_id", fileRecord.Id.ToString() },
{ "fileName", file.FileName },
{ "fileSize", file.Length },
{ "uploadedAt", fileRecord.UploadedAt.ToString("yyyy-MM-dd HH:mm:ss") }
});
}
await _dbContext.SaveChangesAsync();
// Show success message
feCommands.Add(new
{
command = "ShowMessage",
args = new
{
type = "success",
message = $"{files.Count} file(s) uploaded successfully"
}
});
// Reload widget if it's a datatable
if (widgetEvent == "onFileUploadCreateDocumentEvent")
{
feCommands.Add(new
{
command = "ReloadWidget",
args = new { widgetName }
});
}
return Ok(new
{
success = true,
uploadedFiles = uploadedFiles,
FECommand = feCommands
});
}
catch (Exception ex)
{
return Ok(new
{
success = false,
error = ex.Message,
FECommand = new[]
{
new
{
command = "ShowMessage",
args = new
{
type = "error",
message = $"Upload failed: {ex.Message}"
}
}
}
});
}
}
controllers/FormsPublicController.php
<?php
public function uploadFiles(Request $request) {
$files = $request->file('files');
$widgetName = $request->input('widgetName');
$widgetEvent = $request->input('widgetEvent');
$formData = json_decode($request->input('formData'), true);
$widgetContext = json_decode($request->input('widgetContext'), true);
$fieldName = $request->input('fieldName');
$uploadedFiles = [];
$feCommands = [];
try {
foreach ($files as $file) {
// Validate file
if ($file->getSize() > 10 * 1024 * 1024) { // 10MB limit
$feCommands[] = [
'command' => 'ShowMessage',
'args' => [
'type' => 'error',
'message' => "File {$file->getClientOriginalName()} exceeds 10MB limit"
]
];
continue;
}
// Generate unique file name
$fileExtension = $file->getClientOriginalExtension();
$uniqueFileName = uniqid() . '.' . $fileExtension;
$uploadPath = 'uploads/' . date('Y-m-d');
// Save file to storage
$filePath = $file->storeAs($uploadPath, $uniqueFileName, 'public');
// Save file metadata to database
$fileId = DB::table('uploaded_files')->insertGetId([
'original_file_name' => $file->getClientOriginalName(),
'stored_file_name' => $uniqueFileName,
'file_path' => $filePath,
'file_size' => $file->getSize(),
'content_type' => $file->getMimeType(),
'uploaded_at' => now(),
'uploaded_by' => $formData['userId'] ?? null
]);
$uploadedFiles[] = [
'_id' => $fileId,
'fileName' => $file->getClientOriginalName(),
'fileSize' => $file->getSize(),
'uploadedAt' => now()->format('Y-m-d H:i:s')
];
}
// Show success message
$feCommands[] = [
'command' => 'ShowMessage',
'args' => [
'type' => 'success',
'message' => count($files) . ' file(s) uploaded successfully'
]
];
// Reload widget if it's a datatable
if ($widgetEvent === 'onFileUploadCreateDocumentEvent') {
$feCommands[] = [
'command' => 'ReloadWidget',
'args' => ['widgetName' => $widgetName]
];
}
return response()->json([
'success' => true,
'uploadedFiles' => $uploadedFiles,
'FECommand' => $feCommands
]);
} catch (Exception $ex) {
return response()->json([
'success' => false,
'error' => $ex->getMessage(),
'FECommand' => [
[
'command' => 'ShowMessage',
'args' => [
'type' => 'error',
'message' => 'Upload failed: ' . $ex->getMessage()
]
]
]
]);
}
}
Upload to Cloud Storage (S3, Azure, etc.)
AWS S3 Example
- C# / .NET
- PHP
using Amazon.S3;
using Amazon.S3.Transfer;
[HttpPost("uploadfiles")]
public async Task<IActionResult> UploadFiles([FromForm] IFormFileCollection files)
{
var uploadedFiles = new List<Dictionary<string, object>>();
var feCommands = new List<object>();
// Initialize S3 client
var s3Client = new AmazonS3Client(
_configuration["AWS:AccessKey"],
_configuration["AWS:SecretKey"],
Amazon.RegionEndpoint.USEast1
);
var transferUtility = new TransferUtility(s3Client);
var bucketName = _configuration["AWS:BucketName"];
foreach (var file in files)
{
try
{
// Generate S3 key
var s3Key = $"uploads/{DateTime.UtcNow:yyyy-MM-dd}/{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
// Upload to S3
using (var stream = file.OpenReadStream())
{
await transferUtility.UploadAsync(stream, bucketName, s3Key);
}
// Save metadata to database
var fileRecord = new UploadedFile
{
Id = Guid.NewGuid(),
OriginalFileName = file.FileName,
S3Key = s3Key,
FileSize = file.Length,
ContentType = file.ContentType,
UploadedAt = DateTime.UtcNow
};
await _dbContext.UploadedFiles.AddAsync(fileRecord);
uploadedFiles.Add(new Dictionary<string, object>
{
{ "_id", fileRecord.Id.ToString() },
{ "fileName", file.FileName },
{ "fileSize", file.Length },
{ "s3Url", $"https://{bucketName}.s3.amazonaws.com/{s3Key}" }
});
}
catch (Exception ex)
{
feCommands.Add(new
{
command = "ShowMessage",
args = new { type = "error", message = $"Failed to upload {file.FileName}: {ex.Message}" }
});
}
}
await _dbContext.SaveChangesAsync();
feCommands.Add(new
{
command = "ShowMessage",
args = new { type = "success", message = $"{uploadedFiles.Count} file(s) uploaded to S3" }
});
return Ok(new { success = true, uploadedFiles, FECommand = feCommands });
}
use Aws\S3\S3Client;
public function uploadFiles(Request $request) {
$files = $request->file('files');
$uploadedFiles = [];
$feCommands = [];
// Initialize S3 client
$s3Client = new S3Client([
'version' => 'latest',
'region' => config('aws.region'),
'credentials' => [
'key' => config('aws.access_key'),
'secret' => config('aws.secret_key')
]
]);
$bucketName = config('aws.bucket_name');
foreach ($files as $file) {
try {
// Generate S3 key
$s3Key = 'uploads/' . date('Y-m-d') . '/' . uniqid() . '.' . $file->getClientOriginalExtension();
// Upload to S3
$s3Client->putObject([
'Bucket' => $bucketName,
'Key' => $s3Key,
'Body' => fopen($file->getRealPath(), 'r'),
'ContentType' => $file->getMimeType()
]);
// Save metadata to database
$fileId = DB::table('uploaded_files')->insertGetId([
'original_file_name' => $file->getClientOriginalName(),
's3_key' => $s3Key,
'file_size' => $file->getSize(),
'content_type' => $file->getMimeType(),
'uploaded_at' => now()
]);
$uploadedFiles[] = [
'_id' => $fileId,
'fileName' => $file->getClientOriginalName(),
'fileSize' => $file->getSize(),
's3Url' => "https://{$bucketName}.s3.amazonaws.com/{$s3Key}"
];
} catch (Exception $ex) {
$feCommands[] = [
'command' => 'ShowMessage',
'args' => [
'type' => 'error',
'message' => "Failed to upload {$file->getClientOriginalName()}: {$ex->getMessage()}"
]
];
}
}
$feCommands[] = [
'command' => 'ShowMessage',
'args' => [
'type' => 'success',
'message' => count($uploadedFiles) . ' file(s) uploaded to S3'
]
];
return response()->json([
'success' => true,
'uploadedFiles' => $uploadedFiles,
'FECommand' => $feCommands
]);
}
File Validation
Validate File Types and Sizes
- C# / .NET
- PHP
private bool ValidateFile(IFormFile file, out string errorMessage)
{
errorMessage = null;
// Check file size (10MB max)
if (file.Length > 10 * 1024 * 1024)
{
errorMessage = $"{file.FileName} exceeds 10MB limit";
return false;
}
// Check file type
var allowedExtensions = new[] { ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".jpg", ".jpeg", ".png" };
var extension = Path.GetExtension(file.FileName).ToLower();
if (!allowedExtensions.Contains(extension))
{
errorMessage = $"{file.FileName} has invalid file type. Allowed: {string.Join(", ", allowedExtensions)}";
return false;
}
// Check MIME type
var allowedMimeTypes = new[]
{
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"image/jpeg",
"image/png"
};
if (!allowedMimeTypes.Contains(file.ContentType))
{
errorMessage = $"{file.FileName} has invalid MIME type: {file.ContentType}";
return false;
}
return true;
}
// Usage in upload handler
foreach (var file in files)
{
if (!ValidateFile(file, out var errorMessage))
{
feCommands.Add(new
{
command = "ShowMessage",
args = new { type = "error", message = errorMessage }
});
continue;
}
// Process valid file...
}
private function validateFile($file, &$errorMessage) {
$errorMessage = null;
// Check file size (10MB max)
if ($file->getSize() > 10 * 1024 * 1024) {
$errorMessage = "{$file->getClientOriginalName()} exceeds 10MB limit";
return false;
}
// Check file type
$allowedExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'jpeg', 'png'];
$extension = strtolower($file->getClientOriginalExtension());
if (!in_array($extension, $allowedExtensions)) {
$errorMessage = "{$file->getClientOriginalName()} has invalid file type. Allowed: " . implode(', ', $allowedExtensions);
return false;
}
// Check MIME type
$allowedMimeTypes = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'image/jpeg',
'image/png'
];
if (!in_array($file->getMimeType(), $allowedMimeTypes)) {
$errorMessage = "{$file->getClientOriginalName()} has invalid MIME type: {$file->getMimeType()}";
return false;
}
return true;
}
// Usage in upload handler
foreach ($files as $file) {
$errorMessage = null;
if (!$this->validateFile($file, $errorMessage)) {
$feCommands[] = [
'command' => 'ShowMessage',
'args' => ['type' => 'error', 'message' => $errorMessage]
];
continue;
}
// Process valid file...
}
Best Practices
- Validate all files - Check file size, type, and MIME type
- Use unique file names - Prevent file name collisions
- Store metadata - Save file information in database for tracking
- Handle errors gracefully - Provide clear error messages for each file
- Limit file sizes - Set reasonable upload limits
- Scan for viruses - Integrate virus scanning for uploaded files
- Use cloud storage - For scalability and reliability
Next Steps
- Frontend Commands - Control the frontend after upload
- Datatable Events - Handle datatable file uploads
- Button Events - Trigger file downloads