# Wexflow - Full Documentation Context
> Auto-generated full documentation context compiled from the Wexflow Wiki.
> Generated on: 2026-10-11T08:58:39Z
---
# Document: Android App
> Source: https://github.com/aelassas/wexflow/wiki/Android-App
The Android app allows you to do the following things:
- See all the workflows loaded by Wexflow Engine
- See the status of the selected workflow (running, suspended, waiting for approval or disabled)
- Start a workflow
- Stop a workflow
- Suspend a workflow
- Resume a workflow
- Approve a workflow
- Reject a workflow
When you open the Android App, the first thing you'll need to do is to set up Wexflow API URL in the settings.
Then, you will get a login screen.
Here are the credentials to sign in:
- **Username**: admin
- **Password**: wexflow2018
You can change the password from the backend.
---
# Document: Approval
> Source: https://github.com/aelassas/wexflow/wiki/Approval
```xml
```
---
# Document: ApprovalRecordsCreator
> Source: https://github.com/aelassas/wexflow/wiki/ApprovalRecordsCreator
```xml
```
---
# Document: ApprovalWorkflowsCreator
> Source: https://github.com/aelassas/wexflow/wiki/ApprovalWorkflowsCreator
```xml
```
---
# Document: ApproveRecord
> Source: https://github.com/aelassas/wexflow/wiki/ApproveRecord
```xml
```
---
# Document: C# Client
> Source: https://github.com/aelassas/wexflow/wiki/C#-Client
Here is a sample C# client:
```csharp
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Wexflow.Core.Service.Contracts;
namespace Wexflow.Core.Service.Client
{
public class WexflowServiceClient(string uri)
{
public string Uri { get; } = uri.TrimEnd('/');
private static async Task DownloadStringAsync(HttpClient client, string url, string token)
{
HttpRequestMessage request = new(HttpMethod.Get, url);
if (!string.IsNullOrEmpty(token))
{
request.Headers.Add("Authorization", $"Bearer {token}");
}
var response = await client.SendAsync(request);
var byteArray = await response.Content.ReadAsByteArrayAsync();
var responseString = Encoding.UTF8.GetString(byteArray, 0, byteArray.Length);
return responseString;
}
private static async Task UploadStringAsync(HttpClient client, string url, string token, string body = "")
{
HttpRequestMessage request = new(HttpMethod.Post, url);
if (!string.IsNullOrEmpty(token))
{
request.Headers.Add("Authorization", $"Bearer {token}");
}
if (!string.IsNullOrEmpty(body))
{
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
}
var response = await client.SendAsync(request);
var byteArray = await response.Content.ReadAsByteArrayAsync();
var responseString = Encoding.UTF8.GetString(byteArray, 0, byteArray.Length);
return responseString;
}
public async Task Login(string username, string password)
{
var uri = $"{Uri}/login";
using HttpClient webClient = new();
var requestBody = JsonConvert.SerializeObject(new { username, password });
var response = await UploadStringAsync(webClient, uri, null, requestBody);
// Deserialize response JSON into a dynamic object
dynamic res = JsonConvert.DeserializeObject(response);
// Return the access_token property
return res?.access_token;
}
public async Task Search(string keyword, string token)
{
var uri = $"{Uri}/search?s={keyword}";
using HttpClient webClient = new();
var response = await DownloadStringAsync(webClient, uri, token);
var workflows = JsonConvert.DeserializeObject(response);
return workflows;
}
public async Task StartWorkflow(int id, string token)
{
var uri = $"{Uri}/start?w={id}";
using HttpClient webClient = new();
var instanceId = await UploadStringAsync(webClient, uri, token);
return Guid.Parse(instanceId.Replace("\"", string.Empty));
}
public async Task StartWorkflowWithVariables(string payload, string token)
{
var uri = $"{Uri}/start-with-variables";
using HttpClient webClient = new();
var instanceId = await UploadStringAsync(webClient, uri, token, payload);
return Guid.Parse(instanceId.Replace("\"", string.Empty));
}
public async Task StopWorkflow(int id, Guid instanceId, string token)
{
var uri = $"{Uri}/stop?w={id}&i={instanceId}";
using HttpClient webClient = new();
_ = await UploadStringAsync(webClient, uri, token);
}
public async Task SuspendWorkflow(int id, Guid instanceId, string token)
{
var uri = $"{Uri}/suspend?w={id}&i={instanceId}";
using HttpClient webClient = new();
_ = await UploadStringAsync(webClient, uri, token);
}
public async Task ResumeWorkflow(int id, Guid instanceId, string token)
{
var uri = $"{Uri}/resume?w={id}&i={instanceId}";
using HttpClient webClient = new();
_ = await UploadStringAsync(webClient, uri, token);
}
public async Task ApproveWorkflow(int id, Guid instanceId, string token)
{
var uri = $"{Uri}/approve?w={id}&i={instanceId}";
using HttpClient webClient = new();
_ = await UploadStringAsync(webClient, uri, token);
}
public async Task RejectWorkflow(int id, Guid instanceId, string token)
{
var uri = $"{Uri}/reject?w={id}&i={instanceId}";
using HttpClient webClient = new();
_ = await UploadStringAsync(webClient, uri, token);
}
public async Task GetWorkflow(string token, int id)
{
var uri = $"{Uri}/workflow?w={id}";
using HttpClient webClient = new();
var response = await DownloadStringAsync(webClient, uri, token);
var workflow = JsonConvert.DeserializeObject(response);
return workflow;
}
public async Task GetUser(string username, string token)
{
var uri = $"{Uri}/user?username={System.Uri.EscapeDataString(username)}";
using HttpClient webClient = new();
var response = await DownloadStringAsync(webClient, uri, token);
var user = JsonConvert.DeserializeObject(response);
return user;
}
}
}
```
---
# Document: C# SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/C#-SSE-Client
## Prerequisites
* Install [.NET](https://dotnet.microsoft.com/)
* Create a new console app:
```
dotnet new console -n dotnet
```
# SSE Client Sample
Here is a sample C# SSE client `Program.cs`:
```cs
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text;
using System.IO;
var baseUrl = "http://localhost:8000/api/v1";
var username = "admin";
var password = "wexflow2018";
var workflowId = 41;
using var httpClient = new HttpClient();
async Task LoginAsync(string user, string pass, bool stayConnected = false)
{
var payload = new
{
username = user,
password = pass,
stayConnected = stayConnected
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{baseUrl}/login", content);
if (!response.IsSuccessStatusCode)
throw new Exception($"Login failed: HTTP {response.StatusCode} - {response.ReasonPhrase}");
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
return doc.RootElement.GetProperty("access_token").GetString();
}
async Task StartWorkflowAsync(string token, int workflowId)
{
var request = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/start?w={workflowId}");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
throw new Exception($"Start failed: HTTP {response.StatusCode} - {response.ReasonPhrase}");
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize(json);
}
async Task ListenToSseAsync(string url, string token)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
Console.WriteLine("SSE connection opened");
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
if (!string.IsNullOrWhiteSpace(line) && line.StartsWith("data: "))
{
var json = line.Substring("data: ".Length);
try
{
var doc = JsonDocument.Parse(json);
Console.WriteLine("Received SSE JSON:");
Console.WriteLine(doc.RootElement.ToString());
break; // Close after first message
}
catch (Exception ex)
{
Console.WriteLine("Failed to parse SSE JSON: " + ex.Message);
}
}
}
Console.WriteLine("SSE connection closed");
}
try
{
var token = await LoginAsync(username, password);
var jobId = await StartWorkflowAsync(token, workflowId);
Console.WriteLine($"Workflow {workflowId} started. Job ID: {jobId}");
var sseUrl = $"{baseUrl}/sse/{workflowId}/{jobId}";
await ListenToSseAsync(sseUrl, token);
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
```
To run the client, use the following command:
```bash
cd dotnet
dotnet run
```
---
# Document: CPP Client
> Source: https://github.com/aelassas/wexflow/wiki/CPP-Client
## Prerequisites
### Windows
* Install [MSYS2](https://www.msys2.org/)
* Open the MSYS2 MinGW 64-bit shell and run:
```
pacman -Syu # Update package database and core system
pacman -Su # Finish update (may require closing/reopening terminal)
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-curl
```
### Linux
On Debian/Ubuntu:
```bash
sudo apt update
sudo apt install libcurl4-openssl-dev build-essential
```
On Fedora:
```bash
sudo dnf install libcurl-devel gcc-c++
```
On Arch Linux:
```bash
sudo pacman -S curl
```
## Client Sample
Here is a sample C++ client `client.cpp`:
```cpp
#include
#include
#include
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::string *s)
{
size_t totalSize = size * nmemb;
s->append((char *)contents, totalSize);
return totalSize;
}
std::string login(const std::string &baseUrl, const std::string &username, const std::string &password)
{
CURL *curl = curl_easy_init();
if (!curl)
throw std::runtime_error("Failed to init curl");
std::string url = baseUrl + "/login";
std::string readBuffer;
std::string jsonData = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\",\"stayConnected\":false}";
struct curl_slist *headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
throw std::runtime_error(std::string("curl_easy_perform() failed: ") + curl_easy_strerror(res));
}
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (http_code != 200)
throw std::runtime_error("Login failed with HTTP code " + std::to_string(http_code));
// Simple parsing to extract access_token value from JSON response
// For production, use a JSON parser like nlohmann/json
auto startPos = readBuffer.find("\"access_token\":\"");
if (startPos == std::string::npos)
throw std::runtime_error("access_token not found in response");
startPos += strlen("\"access_token\":\"");
auto endPos = readBuffer.find("\"", startPos);
if (endPos == std::string::npos)
throw std::runtime_error("Malformed access_token in response");
return readBuffer.substr(startPos, endPos - startPos);
}
std::string startWorkflow(const std::string &baseUrl, const std::string &token, int workflowId)
{
CURL *curl = curl_easy_init();
if (!curl)
throw std::runtime_error("Failed to init curl");
std::string url = baseUrl + "/start?w=" + std::to_string(workflowId);
std::string readBuffer;
struct curl_slist *headers = nullptr;
std::string authHeader = "Authorization: Bearer " + token;
headers = curl_slist_append(headers, authHeader.c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "Content-Length: 0");
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
throw std::runtime_error(std::string("curl_easy_perform() failed: ") + curl_easy_strerror(res));
}
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (http_code != 200)
throw std::runtime_error("Start workflow failed with HTTP code " + std::to_string(http_code));
return readBuffer;
}
int main()
{
const std::string baseUrl = "http://localhost:8000/api/v1";
const std::string username = "admin";
const std::string password = "wexflow2018";
const int workflowId = 41;
try
{
std::string token = login(baseUrl, username, password);
std::string jobId = startWorkflow(baseUrl, token, workflowId);
std::cout << "Workflow " << workflowId << " started successfully. Job ID: " << jobId << std::endl;
}
catch (const std::exception &e)
{
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
```
## Run the Client
### Windows
Open the MSYS2 MinGW 64-bit shell (not the default MSYS shell) and run:
```bash
g++ client.cpp -o wexflow_client.exe -lcurl -lws2_32 -lcrypt32
./wexflow_client.exe
```
### Linux
```bash
g++ client.cpp -o wexflow_client -lcurl
./wexflow_client
```
---
# Document: CPP SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/CPP-SSE-Client
## Prerequisites
### Windows
* Install [MSYS2](https://www.msys2.org/)
* Open the MSYS2 MinGW 64-bit shell and run:
```
pacman -Syu # Update package database and core system
pacman -Su # Finish update (may require closing/reopening terminal)
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-curl
```
### Linux
On Debian/Ubuntu:
```bash
sudo apt update
sudo apt install libcurl4-openssl-dev build-essential
```
On Fedora:
```bash
sudo dnf install libcurl-devel gcc-c++
```
On Arch Linux:
```bash
sudo pacman -S curl
```
## SSE Client Sample
Here is a sample C++ SSE client `sse.cpp`:
```cpp
#include
#include
#include
#include
#include
#include
using json = nlohmann::json;
static std::string baseUrl = "http://localhost:8000/api/v1";
static std::string username = "admin";
static std::string password = "wexflow2018";
static int workflowId = 41;
// Helper for libcurl response data
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
((std::string *)userp)->append((char *)contents, size * nmemb);
return size * nmemb;
}
// Perform HTTP POST with JSON payload, return response body as string
std::string httpPost(const std::string &url, const std::string &jsonPayload, const std::string &bearerToken = "")
{
CURL *curl = curl_easy_init();
if (!curl)
throw std::runtime_error("Failed to init curl");
std::string readBuffer;
struct curl_slist *headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
if (!bearerToken.empty())
{
std::string authHeader = "Authorization: Bearer " + bearerToken;
headers = curl_slist_append(headers, authHeader.c_str());
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonPayload.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
CURLcode res = curl_easy_perform(curl);
long httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
{
throw std::runtime_error(std::string("curl_easy_perform() failed: ") + curl_easy_strerror(res));
}
if (httpCode < 200 || httpCode >= 300)
{
throw std::runtime_error("HTTP error code: " + std::to_string(httpCode));
}
return readBuffer;
}
// Login to get token
std::string login(const std::string &user, const std::string &pass)
{
json j;
j["username"] = user;
j["password"] = pass;
j["stayConnected"] = false;
std::string url = baseUrl + "/login";
return json::parse(httpPost(url, j.dump()))["access_token"];
}
// Start workflow, returns jobId string
std::string startWorkflow(const std::string &token, int workflowId)
{
std::string url = baseUrl + "/start?w=" + std::to_string(workflowId);
// POST empty body for start
std::string raw = httpPost(url, "", token);
json j = json::parse(raw);
return j.get();
}
// SSE event handler
size_t sseWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
size_t totalSize = size * nmemb;
std::string chunk(ptr, totalSize);
std::cout << ">>> SSE Chunk received:\n"
<< chunk << std::endl;
static std::string buffer;
buffer += chunk;
size_t pos;
while ((pos = buffer.find("\n\n")) != std::string::npos)
{
std::string eventBlock = buffer.substr(0, pos);
buffer.erase(0, pos + 2);
size_t dataPos = eventBlock.find("data: ");
if (dataPos != std::string::npos)
{
std::string jsonData = eventBlock.substr(dataPos + 6);
std::cout << "Received SSE data: " << jsonData << std::endl;
try
{
auto j = json::parse(jsonData);
if (j.contains("status") && j["status"] == "Done")
{
std::cout << "Workflow status Done received, exiting SSE loop." << std::endl;
std::atomic *doneFlag = static_cast *>(userdata);
doneFlag->store(true);
}
}
catch (const std::exception &e)
{
std::cerr << "JSON parse error: " << e.what() << std::endl;
}
}
}
return totalSize;
}
// Listen to SSE stream until "Done" status
void listenToSse(const std::string &sseUrl, const std::string &token)
{
CURL *curl = curl_easy_init();
if (!curl)
{
throw std::runtime_error("Failed to init curl");
}
std::atomic done(false);
struct curl_slist *headers = nullptr;
headers = curl_slist_append(headers, "Accept: text/event-stream");
headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());
headers = curl_slist_append(headers, "Expect:"); // Disable 'Expect: 100-continue'
curl_easy_setopt(curl, CURLOPT_URL, sseUrl.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, sseWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &done);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L); // Keep connection open indefinitely
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); // Keep connection alive
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); // Required for multithreaded / Windows apps
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // Optional: enable debug output
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
std::cerr << "SSE connection error: " << curl_easy_strerror(res) << std::endl;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
int main()
{
try
{
std::string token = login(username, password);
std::string jobId = startWorkflow(token, workflowId);
std::cout << "Workflow " << workflowId << " started. Job ID: " << jobId << std::endl;
std::string sseUrl = baseUrl + "/sse/" + std::to_string(workflowId) + "/" + jobId;
listenToSse(sseUrl, token);
std::cout << "SSE listening finished. Exiting." << std::endl;
}
catch (const std::exception &e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
```
## Run the Client
Download [samples/clients/sse/cpp](https://github.com/aelassas/wexflow/tree/main/samples/clients/sse/cpp) example from GitHub repo.
### Windows
Open the MSYS2 MinGW 64-bit shell (not the default MSYS shell), and run:
```bash
cd cpp
g++ sse.cpp -o wexflow_sse.exe -Iinclude -lcurl -lws2_32 -lcrypt32
./wexflow_sse.exe
```
### Linux
```bash
cd cpp
g++ sse.cpp -o wexflow_sse -Iinclude -lcurl -pthread
./wexflow_sse
```
---
# Document: Command Line Client
> Source: https://github.com/aelassas/wexflow/wiki/Command-Line-Client
Wexflow provides a command line client for querying Wexflow server. The command line tool is available for both .NET and .NET Core and works on Windows, Linux and macOS.
# Windows (.NET)
The command line tool is located in C:\Program Files\Wexflow\Wexflow.Clients.CommandLine. To run the command line tool, just run the executable C:\Program Files\Wexflow\Wexflow.Clients.CommandLine\Wexflow.Clients.CommandLine.exe
The configuration file C:\Program Files\Wexflow\Wexflow.Clients.CommandLine\Wexflow.Clients.CommandLine.exe.config contains WexflowWebServiceUri, Username and Password settings.
# Windows (.NET Core)
The command line tool is located in .\Wexflow.Clients.CommandLine. To run the command line tool, just run the following command
```bash
cd .\Wexflow.Clients.CommandLine
dotnet Wexflow.Clients.CommandLine.dll
```
The configuration file .\Wexflow.Clients.CommandLine\appsettings.json contains WexflowWebServiceUri, Username and Password settings.
# Linux
After installing Wexflow on Linux, the command line tool is located in /opt/wexflow/Wexflow.Clients.CommandLine. To run the command line tool, just run the following command
```bash
cd /opt/wexflow/Wexflow.Clients.CommandLine
dotnet Wexflow.Clients.CommandLine.dll
```
The configuration file /opt/wexflow/Wexflow.Clients.CommandLine/appsettings.json contains WexflowWebServiceUri, Username and Password settings.
# macOS
After installing Wexflow on macOS, the command line tool is located in /Applications/wexflow/Wexflow.Clients.CommandLine. To run the command line tool, just run the following command
```bash
cd /Applications/wexflow/Wexflow.Clients.CommandLine
dotnet Wexflow.Clients.CommandLine.dll
```
The configuration file /Applications/wexflow/Wexflow.Clients.CommandLine/appsettings.json contains WexflowWebServiceUri, Username and Password settings.
# Options
```
-o, --operation Required. start|suspend|resume|stop|approve|reject
-i, --workflowId Required. Workflow Id
-j, --jobId Job instance id (Guid)
-w, --wait (Default: false) Wait until workflow finishes
--help Display this help screen.
--version Display version information.
```
# Examples
## Fire and wait
The following command starts the workflow 41 and waits until it finishes its jobs:
```bash
Wexflow.Clients.CommandLine.exe -o start -i 41 -w
```
## Fire and forget
The following command starts the workflow 41:
```bash
Wexflow.Clients.CommandLine.exe -o start -i 41
```
## Stop
The following command stops the workflow 41:
```bash
Wexflow.Clients.CommandLine.exe -o stop -i 41 -j 9144e328-dde3-468e-a8ba-913e3d5b7b92
```
## Suspend
The following command suspends the workflow 41:
```bash
Wexflow.Clients.CommandLine.exe -o suspend -i 41 -j 9144e328-dde3-468e-a8ba-913e3d5b7b92
```
## Resume
The following command resumes the workflow 41:
```bash
Wexflow.Clients.CommandLine.exe -o resume -i 41 -j 9144e328-dde3-468e-a8ba-913e3d5b7b92
```
## Approve
The following command approves the workflow 126:
```bash
Wexflow.Clients.CommandLine.exe -o approve -i 126 -j 9144e328-dde3-468e-a8ba-913e3d5b7b92
```
## Reject
The following command rejects the workflow 126:
```bash
Wexflow.Clients.CommandLine.exe -o reject -i 126 -j 9144e328-dde3-468e-a8ba-913e3d5b7b92
```
---
# Document: Configuration
> Source: https://github.com/aelassas/wexflow/wiki/Configuration
# Wexflow Configuration Guide
Wexflow works out of the box with zero configuration. However, if you want to customize settings, this page explains how to configure Wexflow server and its main configuration files.
## Table of Contents
1. [Wexflow Server Configuration](https://github.com/aelassas/wexflow/wiki/Configuration#wexflow-server-configuration)
1. [.NET 4.8](https://github.com/aelassas/wexflow/wiki/Configuration#net-48)
1. [.NET 9.0+](https://github.com/aelassas/wexflow/wiki/Configuration#net-90)
1. [Wexflow.xml Configuration File](https://github.com/aelassas/wexflow/wiki/Configuration#wexflowxml-configuration-file)
1. [.NET 4.8](https://github.com/aelassas/wexflow/wiki/Configuration#net-48-1)
1. [.NET 9.0+](https://github.com/aelassas/wexflow/wiki/Configuration#net-90-1)
1. [Windows](https://github.com/aelassas/wexflow/wiki/Configuration#windows)
1. [Linux](https://github.com/aelassas/wexflow/wiki/Configuration#linux)
1. [macOS](https://github.com/aelassas/wexflow/wiki/Configuration#macos)
1. [Admin Panel Configuration](https://github.com/aelassas/wexflow/wiki/Configuration#admin-panel-configuration)
1. [.NET 4.8](https://github.com/aelassas/wexflow/wiki/Configuration#net-48-2)
1. [.NET 9.0+](https://github.com/aelassas/wexflow/wiki/Configuration#net-90-2)
## Wexflow Server Configuration
### .NET 4.8
You can configure Wexflow Server from C:\Program Files\Wexflow\Wexflow.Server.exe.config:
```xml
...
```
LogLevel options:
* Debug: All logs and debug logs.
* All: All logs without debug logs (Default).
* Severely: Only last workflow log and error logs.
* Minimum: Only last workflow log.
* None: No logs.
### .NET 9.0+
You can configure Wexflow Server from Wexflow.Server/appsettings.json:
```js
{
"WexflowSettingsFile": "C:\\Wexflow-netcore\\Wexflow.xml",
"LogLevel": "All",
"WexflowServicePort": 8000,
"SuperAdminUsername": "admin",
"EnableWorkflowsHotFolder": false,
"EnableRecordsHotFolder": true,
"EnableEmailNotifications": false,
"DateTimeFormat": "dd-MM-yyyy HH:mm:ss", /* Date and time format in the backend. */
"Smtp.Host": "smtp.gmail.com",
"Smtp.Port": 587,
"Smtp.EnableSsl": true,
"Smtp.User": "user",
"Smtp.Password": "password",
"Smtp.From": "user",
"AdminFolder": "..\\Admin",
"HTTPS": false,
"PfxFile": "C:\\Wexflow-netcore\\wexflow.pfx",
"PfxPassword": "wexflow2018"
}
```
> [!NOTE]
> To change Wexflow's server and API port, change `WexflowServicePort` and `port` in `Admin\js\settings.js`. Then restart Wexflow server to apply changes.
## Wexflow.xml Configuration File
Wexflow.xml is the main configuration file of Wexflow server. Its path is configured from:
* .NET 4.8 — in `C:\Program Files\Wexflow\Wexflow.Server.exe.config`
* .NET 9.0+ — in `Wexflow.Server/appsettings.json`
### .NET 4.8
Wexflow.xml is located in: C:\Wexflow\Wexflow.xml
Below is the configuration file Wexflow.xml for the .NET version:
```xml
```
Wexflow ships with 6 persistence providers. You can choose from the following `dbType` options:
* SQLite (Default)
* MongoDB
* SQLServer
* PostgreSQL
* MySQL
* LiteDB
If you change the persistence provider, don't forget to update `connectionString` setting.
### .NET 9.0+
#### Windows
For the .NET 9.0+ version on Windows, Wexflow.xml is located in: C:\Wexflow-netcore\Wexflow.xml
Below is the configuration file Wexflow.xml for the .NET 9.0+ version:
```xml
```
#### Linux
For the .NET 9.0+ version on Linux, Wexflow.xml is located in: /opt/wexflow/Wexflow/Wexflow.xml
Below is the configuration file Wexflow.xml for the .NET 9.0+ version:
```xml
```
#### macOS
For the .NET 9.0+ version on Linux, Wexflow.xml is located in: /Applications/wexflow/Wexflow/Wexflow.xml
Below is the configuration file Wexflow.xml for the .NET 9.0+ version:
```xml
```
## Admin Panel Configuration
It is possible to format date and time in the backend through **DateTimeFormat** setting option. The date is local and can be formatted however you want. The default format is **dd-MM-yyyy HH:mm:ss**.
If you modify this setting, you must restart the Wexflow server to apply changes.
### .NET 4.8
To change the setting option **DateTimeFormat** simply open the settings file C:\Program Files\Wexflow\Wexflow.Server.exe.config and edit the setting option.
### .NET 9.0+
To change the setting option **DateTimeFormat** simply open the settings file Wexflow.Server/appsettings.json and edit the setting option.
---
# Document: Cron Scheduling
> Source: https://github.com/aelassas/wexflow/wiki/Cron-Scheduling
# Introduction
cron is a UNIX tool that has been around for a long time, so its scheduling capabilities are powerful and proven. Wexflow provides the ability to create workflows that start depending on a cron expression.
Cron workflows are often more useful than trigger or periodic workflows, if you need a job-firing schedule that recurs based on calendar-like notions, rather than on the exactly specified intervals.
With cron workflows, you can specify firing-schedules such as "every Friday at noon", or "every weekday and 9:30 am", or even "every 5 minutes between 9:00 am and 10:00 am on every Monday, Wednesday and Friday".
A cron expression is a string comprised of 6 or 7 fields separated by white space. Fields can contain any of the allowed values, along with various combinations of the allowed special characters for that field. The fields are as follows:
```
* * * * * * *
┬ ┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │ └ Year (1970 - 2099) (Optional) (Allowed Special Characters: , - * /)
│ │ │ │ │ └────── Day of week (1 - 7) (Sunday=1) (Allowed Special Characters: , - * ? / L #)
│ │ │ │ └─────────── Month (1 - 12) (Jan=1) (Allowed Special Characters: , - * /)
│ │ │ └──────────────── Day of month (1 - 31) (Allowed Special Characters: , - * ? / L W)
│ │ └───────────────────── Hours (0 - 23) (Allowed Special Characters: , - * /)
│ └────────────────────────── Minutes (0 - 59) (Allowed Special Characters: , - * /)
└─────────────────────────────── Seconds (0 - 59) (Allowed Special Characters: , - * /)
```
So cron expressions can be as simple as this: * * * * ? *
or more complex, like this: 0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010
# Pay attention
Wexflow is using [Quartz.NET](https://www.quartz-scheduler.net/) cron expressions. UNIX Cron expressions and Quartz ones are different. Simply:
- In Unix:
```
(minute, hour, day, month, day_of_week, year)
```
- In Quartz:
```
(second, minute, hour, day, month, day_of_week, year)
```
# Special characters
- **""** (“all values”) - used to select all values within a field. For example, "" in the minute field means * “every minute”.
- **?** (“no specific value”) - useful when you need to specify something in one of the two fields in which the character is allowed, but not the other. For example, if I want my workflow to fire on a particular day of the month (say, the 10th), but don’t care what day of the week that happens to be, I would put “10” in the day-of-month field, and “?” in the day-of-week field. See the examples below for clarification.
- **-** used to specify ranges. For example, “10-12” in the hour field means “the hours 10, 11 and 12”.
- **,** used to specify additional values. For example, “MON,WED,FRI” in the day-of-week field means “the days Monday, Wednesday, and Friday”.
- **/** - used to specify increments. For example, “0/15” in the seconds field means “the seconds 0, 15, 30, and 45”. And “5/15” in the seconds field means “the seconds 5, 20, 35, and 50”. You can also specify ‘/’ after the ‘’ character - in this case ‘’ is equivalent to having ‘0’ before the ‘/’. ‘1/3’ in the day-of-month field means “fire every 3 days starting on the first day of the month”.
- **L** (“last”) - has different meaning in each of the two fields in which it is allowed. For example, the value “L” in the day-of-month field means “the last day of the month” - day 31 for January, day 28 for February on non-leap years. If used in the day-of-week field by itself, it simply means “7” or “SAT”. But if used in the day-of-week field after another value, it means “the last xxx day of the month” - for example “6L” means “the last friday of the month”. You can also specify an offset from the last day of the month, such as “L-3” which would mean the third-to-last day of the calendar month. When using the ‘L’ option, it is important not to specify lists, or ranges of values, as you’ll get confusing/unexpected results.
- **W** (“weekday”) - used to specify the weekday (Monday-Friday) nearest the given day. As an example, if you were to specify “15W” as the value for the day-of-month field, the meaning is: “the nearest weekday to the 15th of the month”. So if the 15th is a Saturday, the workflow will fire on Friday the 14th. If the 15th is a Sunday, the workflow will fire on Monday the 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you specify “1W” as the value for day-of-month, and the 1st is a Saturday, the workflow will fire on Monday the 3rd, as it will not ‘jump’ over the boundary of a month’s days. The ‘W’ character can only be specified when the day-of-month is a single day, not a range or list of days. ** The ‘L’ and ‘W’ characters can also be combined in the day-of-month field to yield ‘LW’, which translates to “last weekday of the month”.
- **#** used to specify “the nth” XXX day of the month. For example, the value of “6#3” in the day-of-week field means “the third Friday of the month” (day 6 = Friday and “#3” = the 3rd one in the month). Other examples: “2#1” = the first Monday of the month and “4#5” = the fifth Wednesday of the month. Note that if you specify “#5” and there is not 5 of the given day-of-week in the month, then no firing will occur that month. ** The legal characters and the names of months and days of the week are not case sensitive. MON is the same as mon.
# Examples
Here are some examples:
```
0 0 12 * * ? Fire at 12pm (noon) every day.
0 15 10 ? * * Fire at 10:15am every day.
0 15 10 * * ? Fire at 10:15am every day.
0 15 10 * * ? * Fire at 10:15am every day.
0 15 10 * * ? 2019 Fire at 10:15am every day during the year 2019.
0 * 14 * * ? Fire every minute starting at 2pm and ending at 2:59pm, every day.
0 0/5 14 * * ? Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day.
0 0/5 14,18 * * ? Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day.
0 0-5 14 * * ? Fire every minute starting at 2pm and ending at 2:05pm, every day.
0 10,44 14 ? 3 WED Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.
0 15 10 ? * MON-FRI Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday.
0 15 10 15 * ? Fire at 10:15am on the 15th day of every month.
0 15 10 L * ? Fire at 10:15am on the last day of every month.
0 15 10 L-2 * ? Fire at 10:15am on the 2nd-to-last last day of every month.
0 15 10 ? * 6L Fire at 10:15am on the last Friday of every month.
0 15 10 ? * 6L Fire at 10:15am on the last Friday of every month.
0 15 10 ? * 6L 2019-2020 Fire at 10:15am on every last friday of every month during the years 2019 and 2020.
0 15 10 ? * 6#3 Fire at 10:15am on the third Friday of every month.
0 0 12 1/5 * ? Fire at 12pm (noon) every 5 days every month, starting on the first day of the month.
0 11 11 11 11 ? Fire every November 11th at 11:11am.
0 0 * ? * * * Fire the top of every hour of every day.
*/10 * * * * ? Fire every ten seconds.
0 0 8-10 * * ? 2020 Fire at 8, 9 and 10 o'clock of every day during the year 2020.
0 0 6,19 ? * * Fire at 6:00 AM and 7:00 PM every day.
0 0/30 8-10 ? * * Fire at 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day.
0 0 9-17 * * MON-FRI Fire on the hour nine-to-five weekdays.
0 0 0 25 12 ? Fire at every Christmas Day at midnight, no matter what day of the week it is.
0 15 10 ? * 6L 2022-2025 Fire at 10:15 AM on every Friday of every month during the years 2022, 2023, 2024 and 2025.
0 30 11 ? * 6#2 Fire at 11:30 AM on the second Friday of every month.
```
Pay attention to the effects of ‘?’ and ‘*’ in the day-of-week and day-of-month fields!
# Cron launch type
It is possible to create this type of workflows from Wexflow Designer or through XML editing. However, here is a sample workflow that starts every two minutes:
```xml
```
---
# Document: CsvToJson
> Source: https://github.com/aelassas/wexflow/wiki/CsvToJson
```xml
```
---
# Document: CsvToSql
> Source: https://github.com/aelassas/wexflow/wiki/CsvToSql
```xml
```
---
# Document: CsvToXml
> Source: https://github.com/aelassas/wexflow/wiki/CsvToXml
```xml
```
---
# Document: CsvToYaml
> Source: https://github.com/aelassas/wexflow/wiki/CsvToYaml
```xml
```
---
# Document: Custom Tasks
> Source: https://github.com/aelassas/wexflow/wiki/Custom-Tasks
## Table of Contents
1. [Introduction](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#introduction)
1. [General](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#general)
1. [Creating a Custom Task](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#creating-a-custom-task)
1. [Wexflow Task Class Example](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#wexflow-task-class-example)
1. [Task Status](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#task-status)
1. [Settings](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#settings)
1. [Loading Files](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#loading-files)
1. [Loading Entities](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#loading-entities)
1. [Need A Starting Point?](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#need-a-starting-point)
1. [Installing Your Custom Task in Wexflow](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#installing-your-custom-task-in-wexflow)
1. [.NET Framework 4.8 (Legacy Version)](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#net-framework-48-legacy-version)
1. [.NET 8.0+ (Stable Version)](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#net-80-stable-version)
1. [Referenced Assemblies](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#referenced-assemblies)
1. [Updating a Custom Task](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#updating-a-custom-task)
1. [Using Your Custom Task](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#using-your-custom-task)
1. [Suspend/Resume](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#suspendresume)
1. [Logging](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#logging)
1. [Files](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#files)
1. [Entities](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#entities)
1. [Shared Memory](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#shared-memory)
1. [Designer Integration](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#designer-integration)
1. [Registering the Task](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#registering-the-task)
1. [Adding Settings](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#adding-settings)
1. [How to Debug a Custom Task?](https://github.com/aelassas/wexflow/wiki/Custom-Tasks#how-to-debug-a-custom-task)
## Introduction
Custom tasks are essential in any workflow engine, and Wexflow is no exception.
They allow you to extend the capabilities of Wexflow by integrating your own logic, operations, or external system interactions directly into workflows. Whether you're calling an API, processing files, querying a database, sending notifications, or triggering other services, custom tasks give you the flexibility to automate and orchestrate exactly what your application needs.
In Wexflow, tasks are modular building blocks. While Wexflow includes many built-in tasks, custom tasks empower you to build domain-specific solutions tailored to your environment or business logic.
By creating your own task class that inherits from `Wexflow.Core.Task`, you can plug into Wexflow's engine and leverage features such as:
- Configuration through XML settings
- File and entity sharing across tasks
- Cross-task communication via shared memory
- Logging and debugging integration
- Suspend/resume/stop support
- Flow control using conditions and switch values
This guide walks you through everything you need to know to build, register, and run your own custom tasks in Wexflow.
## General
### Creating a Custom Task
To create a custom task—for example, `MyTask`—follow these steps:
1. **Create a Class Library Project**
- For the **legacy version** (Wexflow on .NET Framework 4.8):
Create a new **Class Library (.NET Framework)** project in **Visual Studio** and name it `Wexflow.Tasks.MyTask`.
Make sure the target framework is set to **.NET Framework 4.8**.
- For the **stable version** (Wexflow on .NET 8.0+):
Use the .NET CLI to create the project:
```bash
dotnet new classlib -n Wexflow.Tasks.MyTask
```
Then edit the `.csproj` file to target .NET 8.0 or later:
```xml
net8.0
```
2. **Add the Wexflow NuGet Package**
You can add the Wexflow package using the NuGet Package Manager in Visual Studio:
```ps1
Install-Package Wexflow
```
Or via the CLI:
```bash
dotnet add package Wexflow
```
**Important:** The project name must start with `Wexflow.Tasks.` and the output DLL file **must also** begin with `Wexflow.Tasks.` for Wexflow to recognize and load it.
### Wexflow Task Class Example
To define your own task, inherit from the `Task` class and override either `RunAsync` (asynchronous) or`Run` (synchronous).
#### Example using `RunAsync`
If you want to use `async/await` functionality, override `RunAsync` instead of `Run`. Here's a simple example of a custom task:
```cs
using System;
using System.Xml.Linq;
using Wexflow.Core;
using Task = Wexflow.Core.Task;
using TaskStatus = Wexflow.Core.TaskStatus;
namespace Wexflow.Tasks.MyTask
{
public class MyTask : Task
{
public MyTask(XElement xe, Workflow wf) : base(xe, wf)
{
// Initialize task settings from the XML element if needed.
// Example: string settingValue = GetSetting("mySetting");
}
public async override System.Threading.Tasks.Task RunAsync()
{
try
{
// Check for workflow cancellation at the start of execution.
// Always include this check in any long-running or looped logic.
Workflow.CancellationTokenSource.Token.ThrowIfCancellationRequested();
// Main task logic goes here.
Info("Running my custom task...");
// Simulate work using asynchronous delay.
await System.Threading.Tasks.Task.Delay(2000);
// Support workflow suspension. This call will block if the workflow is paused.
// Only call WaitOne if cancellation hasn't already been requested.
if (!Workflow.CancellationTokenSource.Token.IsCancellationRequested)
{
WaitOne();
}
// Return success when the task completes successfully
return new TaskStatus(Status.Success);
}
catch (OperationCanceledException)
{
// Don't suppress this exception; it allows proper workflow stop handling.
throw;
}
catch (Exception ex)
{
// Log unexpected errors and return error status.
ErrorFormat("An error occurred while executing the task.", ex);
return new TaskStatus(Status.Error);
}
}
}
}
```
#### Example using `Run`
If you don't need `async/await` functionality, you can use the synchronous `Run` method instead. Here's how the same task would look using `Run`:
```cs
using System;
using System.Threading;
using System.Xml.Linq;
using Wexflow.Core;
using Task = Wexflow.Core.Task;
using TaskStatus = Wexflow.Core.TaskStatus;
namespace Wexflow.Tasks.MyTask
{
public class MyTask : Task
{
public MyTask(XElement xe, Workflow wf) : base(xe, wf)
{
// Initialize task settings from the XML element if needed
// Example: string settingValue = GetSetting("mySetting");
}
public override TaskStatus Run()
{
try
{
// Check for workflow cancellation at the start of execution.
// Always include this check in any long-running or looped logic.
// Required for .NET 8.0+ stable version.
Workflow.CancellationTokenSource.Token.ThrowIfCancellationRequested();
// Main task logic goes here.
Info("Running my custom task...");
// WaitOne() enables suspend/resume support in .NET 8.0+.
// Call this to pause the task when the workflow is suspended.
// Only call WaitOne if cancellation hasn't already been requested.
if (!Workflow.CancellationTokenSource.Token.IsCancellationRequested)
{
WaitOne();
}
// Return success when the task completes successfully
return new TaskStatus(Status.Success);
}
catch (ThreadInterruptedException)
{
// Required for .NET 4.8 legacy version.
// Don't suppress this exception; it allows proper workflow stop handling.
throw;
}
catch (OperationCanceledException)
{
// Required for .NET 8.0+ stable version.
// Don't suppress this exception; it allows proper workflow stop handling.
throw;
}
catch (Exception ex)
{
// Log unexpected errors and return error status.
ErrorFormat("An error occurred while executing the task.", ex);
return new TaskStatus(Status.Error);
}
}
}
}
```
### Task Status
Each task returns a `TaskStatus` object when it finishes performing its job. `TaskStatus` is composed of the following elements:
```csharp
public Status Status { get; set; }
public bool Condition { get; set; }
public string SwitchValue { get; set; }
```
The `Status` can be one of the followings:
```csharp
public enum Status
{
Success,
Warning,
Error
}
```
For example, if a task performs an opetation on a collection of files and if this operation succeeds for all the files then its `Status` should be `Success`. Otherwise, if this operation succeeds for some files and fails for others then its `Status` should be `Warning`. Otherwise, if this operation fails for all the files then its `Status` should be `Error`.
The `Condition` property is designed for flowchart tasks (`If` and `While`). In addition to the `Status` of the task, a flowchart task returns either `true` or `false` after performing its operation.
The `Condition` property should always be set to `false` for sequential tasks.
The `SwitchValue` is designed to be used by `Switch` flowchart nodes. If you set a value in the `SwitchValue` property and use this task in a `Switch` flowchart node, the case corresponding to the value will be executed. Otherwise, if the `Default` case is set, it will be executed.
You can use the `TaskStatus` constructor that suits your needs.
### Settings
To retrieve settings, you can use the following methods:
```csharp
string settingValue = this.GetSetting("settingName");
string settingValue = this.GetSetting("settingName", defaultValue);
string[] settingValues = this.GetSettings("settingName");
bool settingValue = this.GetSettingBool("settingName", defaultValue);
int settingValue = this.GetSettingInt("settingName", defaultValue);
int[] settingValues = this.GetSettingsInt("settingName", defaultValue);
```
### Loading Files
To load a file within a task, you can do it as follows:
```csharp
this.Files.Add(new FileInf(path, this.Id));
```
### Loading Entities
To load an entity within a task, you can do it as follows:
```csharp
this.Entities.Add(myEntity);
```
### Need a starting point?
- For **.NET Framework 4.8 (Legacy)**, you can find a complete example of a custom task here:
[Wexflow.Tasks.Template (.NET 4.8)](https://github.com/aelassas/wexflow/tree/main/src/net/Wexflow.Tasks.Template)
- For **.NET 8.0+ (Stable)**, check out the full example here:
[Wexflow.Tasks.Template (.NET 8.0+)](https://github.com/aelassas/wexflow/tree/main/src/netcore/Wexflow.Tasks.Template)
## Installing Your Custom Task in Wexflow
### .NET Framework 4.8 (Legacy Version)
Once you've finished coding your custom task, compile the class library project and copy the `Wexflow.Tasks.MyTask.dll` assembly into one of the following folders:
- `C:\Program Files\Wexflow\`
- `C:\Wexflow\Tasks\` *(default for Wexflow .NET 4.8 version)*
The `Tasks` folder path can be configured via the `tasksFolder` setting in the configuration file: `C:\Wexflow\Wexflow.xml`
**Important:** The namespace and DLL filename of your task **must start with `Wexflow.Tasks`**.
### .NET 8.0+ (Stable Version)
If you're using the .NET 8.0+ version of Wexflow, copy `Wexflow.Tasks.MyTask.dll` to the appropriate platform-specific folder:
- **Windows**:
- `.\Wexflow.Server`
- `C:\Wexflow-netcore\Tasks`
- **Linux**:
- `/opt/wexflow/Wexflow.Server`
- `/opt/wexflow/Wexflow/Tasks`
- **macOS**:
- `/Applications/wexflow/Wexflow.Server`
- `/Applications/wexflow/Wexflow/Tasks`
### Referenced Assemblies
If your custom task depends on additional assemblies (DLLs), copy them as follows:
- **.NET 4.8**: `C:\Program Files\Wexflow\`
- **.NET 8.0+**:
- **Windows**: `.\Wexflow.Server` or `C:\Wexflow-netcore\Tasks`
- **Linux**: `/opt/wexflow/Wexflow.Server` or `/opt/wexflow/Wexflow/Tasks`
- **macOS**: `/Applications/wexflow/Wexflow.Server` or `/Applications/wexflow/Wexflow/Tasks`
### Updating a Custom Task
To update an existing custom task:
1. Stop Wexflow Server
1. Replace the old DLL and its referenced assemblies with the new versions in the correct folder.
1. Start Wexflow Server:
- **.NET 4.8**: Start the **Wexflow Windows Service**
- **.NET 8.0+**:
- **Windows**: Run `.\run.bat` or start Wexflow Service if you installed it as a Windows Service
- **Linux**: Run `sudo systemctl start wexflow`
- **macOS**: Run `dotnet /Applications/wexflow/Wexflow.Server/Wexflow.Server.dll`
### Using Your Custom Task
Once installed, your task can be used in workflows like this:
```xml
```
**Important:** Make sure the `name` attribute matches the class name of your task (e.g., `MyTask`).
You can also define settings for your task using the `` elements, which can be accessed in your task code via:
```cs
string settingValue = this.GetSetting("settingName");
string settingValue = this.GetSetting("settingName", defaultValue);
string[] settingValues = this.GetSettings("settingName");
bool settingValue = this.GetSettingBool("settingName", defaultValue);
int settingValue = this.GetSettingInt("settingName", defaultValue);
int[] settingValues = this.GetSettingsInt("settingName", defaultValue);
```
You can then test your custom task by creating a new workflow using either the **Designer** or the **XML editor**.
Example using the XML editor:
```xml
```
This workflow will appear in the Wexflow Manager. You can launch and monitor it from there.
That's it! You're now ready to create, install, and run your own custom tasks in Wexflow.
## Suspend/Resume
For .NET 8.0+, if you want to enable suspend/resume for your custom task you need to use `this.WaitOne();` in your custom task. Here is an example:
```cs
using System;
using System.Xml.Linq;
using Wexflow.Core;
namespace Wexflow.Tasks.MyTask
{
public class MyTask : Task
{
public MyTask(XElement xe, Workflow wf) : base(xe, wf)
{
// Initialize task settings from the XML element if needed.
// Example: string settingValue = GetSetting("mySetting");
}
public async override System.Threading.Tasks.Task RunAsync()
{
try
{
// Check for workflow cancellation at the start of execution.
// Always include this check in any long-running or looped logic.
Workflow.CancellationTokenSource.Token.ThrowIfCancellationRequested();
// Main task logic goes here.
Info("Running my custom task...");
// Simulate work using asynchronous delay.
await System.Threading.Tasks.Task.Delay(2000);
// Support workflow suspension. This call will block if the workflow is paused.
// Only call WaitOne if cancellation hasn't already been requested.
if (!Workflow.CancellationTokenSource.Token.IsCancellationRequested)
{
WaitOne();
}
// Return success when the task completes successfully
return new TaskStatus(Status.Success);
}
catch (OperationCanceledException)
{
// Don't suppress this exception; it allows proper workflow stop handling.
throw;
}
catch (Exception ex)
{
// Log unexpected errors and return error status.
ErrorFormat("An error occurred while executing the task.", ex);
return new TaskStatus(Status.Error);
}
}
}
}
```
## Logging
The following methods are available from the Task class for logging:
```cs
public void Info(string msg);
public void InfoFormat(string msg, params object[] args);
public void Debug(string msg);
public void DebugFormat(string msg, params object[] args);
public void Error(string msg);
public void ErrorFormat(string msg, params object[] args);
public void Error(string msg, Exception e);
public void ErrorFormat(string msg, Exception e, params object[] args);
```
## Files
Files can be loaded in a task by calling the methods `Add` or `AddRange`:
```cs
this.Files.Add(myFile);
this.Files.AddRange(myFiles);
```
Then the files loaded can be selected in other tasks by their task `Id` as follows:
```xml
```
To select the files loaded by the running instance of a workflow through the `selectFiles` settings option, you can do it as follows:
```csharp
FileInf[] files = this.SelectFiles();
```
## Entities
Entity is an abstract class having the `Id` of the task as property:
```cs
namespace Wexflow.Core
{
public abstract class Entity
{
public int TaskId { get; set; }
}
}
```
The `Entity` class is designed to be inherited by other classes such as objects retrieved from a database or a web service or an API or whatever. Then, these objects can be loaded in a task by calling the methods `Add` or `AddRange`:
```cs
this.Entities.Add(myEntity);
this.Entities.AddRange(myEntities);
```
Then, the entities loaded can be selected in other tasks by their task `Id` as follows:
```xml
```
Entities are designed to be used in custom tasks.
To select entities loaded by the running instance of a workflow through the `selectEntities` settings option, you can do it as follows:
```csharp
Entity[] entities = this.SelectEntities();
```
The `Entity` class could be very useful when working with custom tasks that manipulate objects from a database or Web Services for example.
## Shared Memory
Tasks contains a `Hashtable` that can be used as a shared memory between them.
To add an object to the `SharedMemory`, simply proceed as follows:
```cs
this.SharedMemory.Add("myKey", myObject);
```
To retrieve an object from the `SharedMemory`, simply proceed as follows:
```cs
var myObject = this.SharedMemory["myKey"];
```
To remove an object from the `SharedMemory`, simply proceed as follows:
```cs
this.SharedMemory.Remove("myKey");
```
**Important:** Always access `this.SharedMemory` in `RunAsync` or `Run` method of a task to get updated values at runtime.
## Designer Integration
### Registering the Task
To make your custom task `MyTask` appear in the available tasks in the designer, simply open the file `C:\Wexflow\TasksNames.json` and add `MyTask` in it as follows:
```js
[
...
{ "Name": "MyTask", "Description": "MyTask description."},
]
```
If you use the .NET 8.0+ version of Wexflow, you need to edit this file:
* **Windows**: `C:\Wexflow-netcore\TasksNames.json`
* **Linux**: `/opt/wexflow/Wexflow/TasksNames.json`
* **macOS**: `/Applications/wexflow/Wexflow/TasksNames.json`
### Adding Settings
You must also add the settings by opening the file `C:\Wexflow\TasksSettings.json` and adding your custom settings as follows:
```js
{
...
"MyTask": [ {"Name": "settingName", "Required": true, "Type": "string", "List": [], "DefaultValue": ""} ],
}
```
If you use the .NET 8.0+ version of Wexflow, you need to edit this file:
* **Windows**: `C:\Wexflow-netcore\TasksSettings.json`
* **Linux**: `/opt/wexflow/Wexflow/TasksSettings.json`
* **macOS**: `/Applications/wexflow/Wexflow/TasksSettings.json`
The available types are:
* `string`
* `int`
* `bool`
* `password`
* `list`
* `user`
* `record`
`user` type refers to registered users in Wexflow.
`record` type refers to registered records in Wexflow.
If you choose `list` type, you have to set the available list options. Here is an example:
```js
{
...
"MyTask": [ {"Name": "protocol", "Required": true, "Type": "list", "List": ["ftp", "ftps", "sftp"], "DefaultValue": ""} ],
}
```
That's it. `MyTask` will show up in the designer and when selected its settings will show up as well.
## How to Debug a Custom Task?
### 1. Build Your Task DLL in Debug Mode
* Compile your custom task project with Debug configuration.
* Make sure the .pdb symbol files are generated alongside the .dll.
* Copy both .dll and .pdb files to the Wexflow Tasks folder (e.g. C:\Wexflow-netcore\Tasks).
### 2. Attach Visual Studio Debugger to Wexflow Process
* Launch Wexflow server.
* In Visual Studio, go to Debug → Attach to Process...
* Find and select the Wexflow process:
* Enable **Show processes from all users**
* If you're running Wexflow as a Windows Service (legacy), make sure the service is started and select: `Wexflow.Server.exe`
* If you're running Wexflow as a Windows Service (stable via NSSM), make sure the service is started and select: `dotnet.exe` process that's running `Wexflow.Server.dll`.
* If you're running Wexflow from the command prompt using `dotnet Wexflow.Server.dll`, Find and select the `dotnet.exe` process that's running `Wexflow.Server.dll`.
* Click Attach.
### 3. Ensure Symbols (.pdb) Are Loaded
* After attaching, open Modules window (Debug → Windows → Modules).
* Locate your custom DLL (e.g. Wexflow.Tasks.MyTask.dll).
* Check if symbols are loaded:
* If not loaded, right-click the module → Load Symbols and browse to your .pdb file.
* Confirm the symbol path is correct and matches your DLL.
### 4. Set Breakpoints in Your Task Code
* Open your custom task source code in Visual Studio as Admin.
* Set breakpoints inside the `RunAsync` or `Run` methods or anywhere you want to debug.
### 5. Trigger Your Workflow
* Run the workflow that uses your custom task (via Designer or API).
* When execution reaches your breakpoint, Visual Studio will break.
### 6. Additional Tips
* Make sure your DLL in the Tasks folder is the same version you built and debugged.
* If debugging symbols don’t load automatically, try deleting older DLLs and PDBs before copying new ones.
* You can enable detailed logging inside your task with `Info()`, `Debug()`, and `Error()` calls for extra insights.
---
# Document: Docker
> Source: https://github.com/aelassas/wexflow/wiki/Docker
## Docker Hub
You can run Wexflow using Docker from the official image on Docker Hub:
```bash
docker run -d -p 8000:8000 --name wexflow aelassas/wexflow:latest
```
Then access the Wexflow Admin Panel at: http://localhost:8000
- **Username:** `admin`
- **Password:** `wexflow2018`
Using compose, here's a sample `docker-compose.yml`:
```yml
services:
wexflow:
image: aelassas/wexflow:latest
container_name: wexflow
ports:
- "8000:8000"
volumes:
# - ./wexflow-config/Wexflow.xml:/opt/wexflow/Wexflow/Wexflow.xml
# - ./wexflow-config/Database:/opt/wexflow/Wexflow/Database
# - ./wexflow-config/appsettings.json:/opt/wexflow/Wexflow.Server/appsettings.json
# - ./wexflow-config/settings.js:/opt/wexflow/Admin/js/settings.js
restart: unless-stopped
```
To run the compose:
```bash
docker compose up
```
For full Docker usage and available options, see the [Docker Hub page](https://hub.docker.com/r/aelassas/wexflow).
## Walkthroughs
* [Set up SSL in the Docker image](https://github.com/aelassas/wexflow/blob/main/src/docker/ssl-image.md)
* [Set up SSL with Docker Compose](https://github.com/aelassas/wexflow/blob/main/src/docker/ssl-compose.md)
* [Set up SSL with a gateway or reverse proxy](https://github.com/aelassas/wexflow/blob/main/src/docker/ssl-gateway.md)
* [Run Wexflow inside a Windows container](https://github.com/aelassas/wexflow/blob/main/src/docker/windows.md)
* [Change the default port](https://github.com/aelassas/wexflow/blob/main/src/docker/change-port.md)
## Build and Run Docker Image Locally
This section describes how build and run Wexflow Docker image.
* Download and unzip the latest version of [wexflow-x.x-linux-netcore.zip](https://github.com/aelassas/wexflow/releases/latest):
```bash
unzip wexflow-x.x-linux-netcore.zip
```
The folder **./wexflow/** will be created on your current folder.
* Download [Dockerfile and docker-compose.yml](https://github.com/aelassas/wexflow/tree/main/src/docker) files and copy them on your current folder (next to wexflow folder). The folder structure should be like this:
```
./
├── wexflow/
├── Dockerfile
├── docker-compose.yml
```
* Run the following command on your current folder:
```bash
docker compose up
```
The folder **./wexflow/** will be mounted during the compose at runtime so you can have access to **./wexflow/Wexflow** configuration folder and add custom tasks and their references at runtime, access log files and change the configuration if you want.
That's it Wexflow will start and the backend will be accessible from: http://localhost:8011/
If you've already deployed the Wexflow image and want to upgrade to a newer version, run the following commands to ensure you get all the latest updates:
```bash
docker compose build --no-cache
docker compose up
```
If you want to use MongoDB instead of SQLite, follow the instructions in [docker-compose.yml](https://github.com/aelassas/wexflow/blob/main/src/docker/docker-compose.yml) file.
---
# Document: EnvironmentVariable
> Source: https://github.com/aelassas/wexflow/wiki/EnvironmentVariable
```xml
```
---
# Document: ExecCs
> Source: https://github.com/aelassas/wexflow/wiki/ExecCs
```xml
```
---
# Document: ExecPython
> Source: https://github.com/aelassas/wexflow/wiki/ExecPython
```xml
```
---
# Document: ExecVb
> Source: https://github.com/aelassas/wexflow/wiki/ExecVb
```xml
```
---
# Document: FileContentMatch
> Source: https://github.com/aelassas/wexflow/wiki/FileContentMatch
```xml
```
---
# Document: FileExists
> Source: https://github.com/aelassas/wexflow/wiki/FileExists
```xml
```
---
# Document: FileMatch
> Source: https://github.com/aelassas/wexflow/wiki/FileMatch
```xml
```
---
# Document: FileNotExist
> Source: https://github.com/aelassas/wexflow/wiki/FileNotExist
```xml
```
Here is a sample workflow:
```xml
```
---
# Document: FileNotMatch
> Source: https://github.com/aelassas/wexflow/wiki/FileNotMatch
```xml
```
Here is a sample workflow:
```xml
```
---
# Document: FileSystemWatcher
> Source: https://github.com/aelassas/wexflow/wiki/FileSystemWatcher
```xml
```
---
# Document: FilesConcat
> Source: https://github.com/aelassas/wexflow/wiki/FilesConcat
```xml
```
---
# Document: FilesCopier
> Source: https://github.com/aelassas/wexflow/wiki/FilesCopier
```xml
```
---
# Document: FilesDecryptor
> Source: https://github.com/aelassas/wexflow/wiki/FilesDecryptor
```xml
```
---
# Document: FilesDiff
> Source: https://github.com/aelassas/wexflow/wiki/FilesDiff
```xml
```
---
# Document: FilesEncryptor
> Source: https://github.com/aelassas/wexflow/wiki/FilesEncryptor
```xml
```
---
# Document: FilesEqual
> Source: https://github.com/aelassas/wexflow/wiki/FilesEqual
```xml
```
---
# Document: FilesExist
> Source: https://github.com/aelassas/wexflow/wiki/FilesExist
```xml
```
---
# Document: FilesInfo
> Source: https://github.com/aelassas/wexflow/wiki/FilesInfo
```xml
```
---
# Document: FilesJoiner
> Source: https://github.com/aelassas/wexflow/wiki/FilesJoiner
```xml
```
---
# Document: FilesLoader
> Source: https://github.com/aelassas/wexflow/wiki/FilesLoader
```xml
```
---
# Document: FilesLoaderEx
> Source: https://github.com/aelassas/wexflow/wiki/FilesLoaderEx
```xml
-->
```
---
# Document: FilesMover
> Source: https://github.com/aelassas/wexflow/wiki/FilesMover
```xml
```
---
# Document: FilesRemover
> Source: https://github.com/aelassas/wexflow/wiki/FilesRemover
```xml
```
---
# Document: FilesRenamer
> Source: https://github.com/aelassas/wexflow/wiki/FilesRenamer
```xml
```
---
# Document: FilesSplitter
> Source: https://github.com/aelassas/wexflow/wiki/FilesSplitter
```xml
```
---
# Document: FolderExists
> Source: https://github.com/aelassas/wexflow/wiki/FolderExists
```xml
```
---
# Document: Fork, Customize, and Sync
> Source: https://github.com/aelassas/wexflow/wiki/Fork,-Customize,-and-Sync
This guide shows you how to fork the Wexflow repository, make your own changes, and keep your version up to date with the official repository.
## Table of Contents
1. [Fork the Repository](https://github.com/aelassas/wexflow/wiki/Fork,-Customize,-and-Sync#1-fork-the-repository)
2. [Clone Your Fork](https://github.com/aelassas/wexflow/wiki/Fork,-Customize,-and-Sync#2-clone-your-fork)
3. [Add Upstream Remote](https://github.com/aelassas/wexflow/wiki/Fork,-Customize,-and-Sync#3-add-upstream-remote)
4. [Sync Your Main Branch](https://github.com/aelassas/wexflow/wiki/Fork,-Customize,-and-Sync#4-sync-your-main-branch)
5. [Create a Feature Branch](https://github.com/aelassas/wexflow/wiki/Fork,-Customize,-and-Sync#5-create-a-feature-branch)
6. [Keep Your Feature Branch in Sync](https://github.com/aelassas/wexflow/wiki/Fork,-Customize,-and-Sync#6-keep-your-feature-branch-in-sync)
7. [Creating a Pull Request](https://github.com/aelassas/wexflow/wiki/Fork,-Customize,-and-Sync#7-creating-a-pull-request)
## 1. Fork the Repository
**Fork** the repository on GitHub:
[https://github.com/aelassas/wexflow](https://github.com/aelassas/wexflow)
## 2. Clone Your Fork
**Clone** your fork:
```bash
git clone https://github.com/your-username/wexflow.git
cd wexflow
```
## 3. Add Upstream Remote
**Add the original repository as an upstream remote**:
```bash
git remote add upstream https://github.com/aelassas/wexflow.git
```
## 4. Sync Your Main Branch
**Fetch the latest changes** from the original repo and **merge** them into your `main` branch:
```bash
git fetch upstream
git checkout main
git merge upstream/main
```
## 5. Create a Feature Branch
Create a new branch for your custom changes:
```bash
git checkout -b my-custom-feature
```
If you're just making small quick changes, working directly on `main` might be simpler initially. But for ongoing customization or multiple features, branching is the safer, cleaner approach.
## 6. Keep Your Feature Branch in Sync
To keep your custom branch in sync with the latest upstream `main`, do this regularly:
```bash
# Fetch upstream changes
git fetch upstream
# Switch to your main branch and update it
git checkout main
git merge upstream/main
# Switch back to your custom branch
git checkout my-custom-feature
# Merge the updated main into your branch
git merge main
```
This way, your feature branch stays up to date with the official repository without losing your changes.
## 7. Creating a Pull Request
If you've made changes to your fork and want to contribute them back to the official repository:
1. **Push your branch to your GitHub fork:**
```bash
git push origin my-custom-feature
```
2. Go to your fork on GitHub (e.g. `https://github.com/your-username/wexflow`) and you'll see a **"Compare & pull request"** button.
3. Click the button and create a pull request (PR) targeting the `main` branch of the original repository (`aelassas/wexflow`).
4. Include a clear title and description explaining what your PR changes or fixes.
Once submitted, your PR will be reviewed, and if everything looks good, it can be merged into the official repository.
---
# Document: Ftp
> Source: https://github.com/aelassas/wexflow/wiki/Ftp
```xml
```
---
# Document: Functions
> Source: https://github.com/aelassas/wexflow/wiki/Functions
You can use the following functions in your workflow:
* `$DateTime()`: returns the current Unix timestamp in milliseconds
* `$Guid()`: returns a new Guid
You can place these functions wherever you want in your workflow, in your local variables or in task settings.
Here is a sample workflow that uses these functions:
```xml
```
---
# Document: Getting Started
> Source: https://github.com/aelassas/wexflow/wiki/Getting-Started
Welcome to Wexflow! This guide will help you quickly understand how to start using Wexflow for workflow automation. Whether you are new to Wexflow or workflow automation in general, this guide will walk you through the basics step by step.
Wexflow is a powerful and flexible workflow engine that lets you create automated workflows easily. You can design workflows using a visual designer, write them in XML or JSON, and run them on your computer or server.
## Table of Contents
1. [Prerequisites](https://github.com/aelassas/wexflow/wiki/Getting-Started#prerequisites)
1. [Admin Panel Login](https://github.com/aelassas/wexflow/wiki/Getting-Started#admin-panel-login)
1. [Designer Overview](https://github.com/aelassas/wexflow/wiki/Getting-Started#designer-overview)
1. [Workflow Syntax](https://github.com/aelassas/wexflow/wiki/Getting-Started#workflow-syntax)
1. [Workflow Sample](https://github.com/aelassas/wexflow/wiki/Getting-Started#workflow-sample)
1. [Workflow Configuration](https://github.com/aelassas/wexflow/wiki/Getting-Started#workflow-configuration)
1. [Hot Reloading](https://github.com/aelassas/wexflow/wiki/Getting-Started#hot-reloading)
1. [How Tasks Communicate](https://github.com/aelassas/wexflow/wiki/Getting-Started#how-tasks-communicate)
1. [Wexflow Manager](https://github.com/aelassas/wexflow/wiki/Getting-Started#wexflow-manager)
1. [Admin Panel](https://github.com/aelassas/wexflow/wiki/Getting-Started#admin-panel)
1. [Login](https://github.com/aelassas/wexflow/wiki/Getting-Started#login)
1. [Password Reset](https://github.com/aelassas/wexflow/wiki/Getting-Started#password-reset)
1. [Dashboard](https://github.com/aelassas/wexflow/wiki/Getting-Started#dashboard)
1. [Workflow Manager](https://github.com/aelassas/wexflow/wiki/Getting-Started#workflow-manager)
1. [Workflow Designer](https://github.com/aelassas/wexflow/wiki/Getting-Started#workflow-designer)
1. [Approval](https://github.com/aelassas/wexflow/wiki/Getting-Started#approval)
1. [History](https://github.com/aelassas/wexflow/wiki/Getting-Started#history)
1. [Users](https://github.com/aelassas/wexflow/wiki/Getting-Started#users)
1. [Profiles](https://github.com/aelassas/wexflow/wiki/Getting-Started#profiles)
1. [Configuration](https://github.com/aelassas/wexflow/wiki/Getting-Started#configuration)
1. [.NET 4.8](https://github.com/aelassas/wexflow/wiki/Getting-Started#net-48)
1. [.NET 9.0+](https://github.com/aelassas/wexflow/wiki/Getting-Started#net-90)
## Prerequisites
Before you begin, make sure Wexflow is installed on your system.
You can find detailed installation instructions [here](https://github.com/aelassas/wexflow/wiki/Installing).
## Admin Panel Login
Once Wexflow is installed, you can access the admin panel at:
- **URL:** http://localhost:8000/
- **Username:** `admin`
- **Password:** `wexflow2018`
**Important:** For your security, change the default password after your first login.
## Designer Overview
Wexflow makes it easy to create automated workflows using different methods, depending on your preference or technical level.
You can design workflows in the following ways:
- **UI Designer**
This is a visual, drag-and-drop editor built into Wexflow’s web interface. It lets you create and manage workflows without writing any code. Ideal for beginners or users who prefer a user-friendly interface.
- **XML**
You can define workflows manually in XML files. This method gives you full control and flexibility over workflow configuration. It's great for developers or advanced users who want to customize workflows in detail.
- **JSON**
Similar to XML, but using JSON format. This is useful for users who are more comfortable with JSON syntax. It can also be used when integrating Wexflow into other systems or APIs that work with JSON.
Whether you choose the UI Designer or go with XML/JSON files, all workflows follow the same structure and logic. If you're just getting started, the UI Designer is a great place to begin!
## Workflow Syntax
### Workflow Sample
Understanding XML/JSON syntax is essential to mastering Wexflow.
Let's take a quick look at a simple example to understand how Wexflow works.
```xml
```
This sample workflow is named **"Sample"** and is designed to **load files from a folder and move them to another folder**. It consists of two tasks and is triggered manually.
#### Key Components
- **Workflow ID**: `1`
- **Name**: `Sample`
- **Launch Type**: `trigger` — This means the workflow runs manually (e.g., from Wexflow Manager or API).
- **Enabled**: `true` — The workflow is active.
- **Approval**: `false` — No manual approval is required.
- **Parallel Jobs**: `true` — Workflow jobs can run in parallel.
- **Retry Count**: `0` — Tasks won’t be retried on failure.
- **Retry Timeout**: `1500ms` — Time to wait between retries (if retry was enabled).
#### Tasks
1. **FilesLoader** (`id="1"`)
- **Description**: Loads files.
- **Action**: Reads all files from the folder `C:\Input\`.
2. **FilesMover** (`id="2"`)
- **Description**: Moves files.
- **Action**: Moves the files loaded by the first task into the folder `C:\Output\`.
- It uses the `selectFiles` setting to reference the output of Task 1.
##### Task Names
Each task in a workflow must use a valid task name recognized by Wexflow. These task names correspond to built-in or custom tasks that define the specific action to be performed (e.g., file operations, database queries, sending emails, etc.).
In the sample workflow above, we used two tasks:
- `FilesLoader`: loads files from a specified folder.
- `FilesMover`: moves files to another folder.
You can find the full list of available task names, along with their descriptions, required settings, and usage examples in the official documentation:
* [Wexflow Tasks Documentation](https://github.com/aelassas/wexflow/wiki/Tasks)
If you're building [custom tasks](https://github.com/aelassas/wexflow/wiki/Custom-Tasks), make sure to register them properly and use unique names to avoid conflicts.
> Tip: Use the `TasksNames.json` and `TasksSettings.json` files in the Wexflow configuration folder to customize task metadata used by the Designer UI.
#### What This Workflow Does
When triggered, Wexflow:
1. Loads all files from `C:\Input\`.
2. Passes them to the next task.
3. Moves them into `C:\Output\`.
This workflow illustrates the basics of Wexflow: defining a sequence of tasks, configuring settings, and running them in order.
It's a perfect starting point to understand how Wexflow automates file-based operations.
If you don't define an `` in a workflow, the tasks will run sequentially in the order they are listed. To create a custom execution flow, you can define an ``. This allows you to:
- Control the execution order of tasks.
- Use flowchart logic nodes such as `If`, `While`, and `Switch`.
- Nest flowchart nodes within each other to any depth.
- Handle events like `OnSuccess`, `OnWarning`, and `OnError` to run specific flows when the workflow ends.
These features let you build complex, dynamic workflows adapted to your specific requirements.
You can learn more about the Execution Graph [here](https://github.com/aelassas/Wexflow/wiki/Samples#execution-graph).
### Workflow Configuration
Below are all the configuration options of a workflow (XML):
```xml
```
For cron workflows, read the following [documentation](https://github.com/aelassas/Wexflow/wiki/Cron-Scheduling) for more details.
Local variables are explained [here](https://github.com/aelassas/Wexflow/wiki/Local-Variables).
Global variables are explained [here](https://github.com/aelassas/Wexflow/wiki/Global-Variables).
The name option of a Task must be one of the names listed in the following [documentation](https://github.com/aelassas/wexflow/wiki/Tasks). You can find the documentation of each task in the Documentation folder of Wexflow.
The execution graph is explained in the [samples section](https://github.com/aelassas/Wexflow/wiki/Samples#execution-graph).
To learn how to make your own workflows, you can check out the workflow samples availabe in the Designer, in the [samples](https://github.com/aelassas/Wexflow/wiki/Samples) section, and read the tasks documentation available in Configuration folder.
### Hot Reloading
Wexflow detects changes and automatically adds, deletes, or reloads workflows. No service restart required.
To disable a workflow, set:
```xml
```
Wexflow supports hot reloading from a default folder called `Workflows` if you want to work with XML files without using the Designer. If the setting [EnableWorkflowsHotFolder](https://github.com/aelassas/wexflow/wiki/Configuration) is enabled in the configuration file, any XML placed in the `Workflows` folder will be automatically picked up by Wexflow. This allows you to create, update, or remove workflows on the fly by simply editing files in that folder — no need to restart the service or reload manually.
### How Tasks Communicate
State is transferred between tasks through `selectFiles` and `selectEntities` settings.
This works the following way:
1. A task in a workflow does its job and produces files which it stores in a collection.
2. Another task (must be in the same workflow) can afterwards reference those files with the `selectFiles` XML property, specifying the ID of the task that produced the required files. It then can use these files to do its own job.
More visually (from the examples):
```xml
```
`selectFiles` can also load files through custom tags. Refer to [Xslt](https://github.com/aelassas/wexflow/wiki/Xslt) task for this.
`selectEntities` setting works the same way as `selectFiles`. The only difference is that `selectEntities` is designed to be used for tasks that manipulate custom objects from a database or web services in custom tasks. To go further, read this [documentation](https://github.com/aelassas/Wexflow/wiki/Custom-tasks#entities) regarding entities.
## Wexflow Manager

When you open Wexflow Manager for the first time, you will get a login window.
Here are the credentials to sign in:
- **Username**: admin
- **Password**: wexflow2018
You can change the password from the admin panel.
Wexflow Manager is a simple application that allows you to do the following things:
- See all the workflows loaded by Wexflow Engine.
- See the status of the selected workflow (running, suspended, waiting for approval or disabled).
- Start a workflow.
- Stop a workflow.
- Suspend a workflow.
- Resume a workflow.
- Approve an approval workflow.
- Reject an approval workflow.
- The "Backend" button opens the admin panel from which you can manage workflows, design workflows, track workfows and have real-time statistics on workflows.
- The "Logs" button allows to view the logs of the day.
- The "Refresh" button allows to reload the list of workflows.
- The "Restart server" button allows to restart Wexflow Server.
- The "Search" button allows to search for workflows.
- The "Help" menu opens the help page.
- The "About" menu shows the version of Wexflow and checks if a new version is available.
To see what's going on in Wexflow, open the log file C:\Program Files\Wexflow\Wexflow.log in a text editor like [Notepad++](https://notepad-plus-plus.org/). Notepad ++ will update the log file as it fills up.
## Admin Panel
The admin panel is accessible at: http://localhost:8000
The admin panel gives real-time statistics on workflows. It will let you manage, design and track your workflows with ease and flexibility. You can use the admin panel to access, configure, manage, administer, and develop your workflows with ease.
### Login
When you open the admin panel for the first time, you will get a login window.
Here are the credentials to sign in:
- **Username**: admin
- **Password**: wexflow2018
After you sign in, you can change the password from the "Users" page.
### Password reset
If a user forgot his password, he can click on "Forgot password?" link to reset his password.
When the user clicks on "Submit" button, an email is sent to him with a temporary password that he can change after he signs in.
To allow the admin panel sending emails, the SMPT configuration must be set in the [configuration](https://github.com/aelassas/wexflow/wiki/Configuration).
### Dashboard

After you sign in, you will arrive on the dashboard page. Wexflow gives you a beautiful dashboard to view real-time statistics on your workflows. Indeed, the "Dashboard" page gives you real-time statistics on workflows and will let you track your workflow server with ease and detail. From the dashboard, you can also filter the workflow entries by a keyword or by date. You can also order the workflow entries by date, by name, etc.
### Workflow Manager

The "Manager" page will let you manage your workflows. From this page you can start a workflow, suspend a running workflow, resume a suspended workflow, stop a running workflow and search for workflows.
### Workflow Designer

The "Designer" page will let you design your workflows. From this page you can create a new workflow, edit an existing workflow or delete a workflow. Using the "Designer" page, we get a nice visual overview of the dependency graph of the workflow. Each node represents a task which has to be run.
Furthermore, the "Designer" page allows to edit workflow files through its Web XML or JSON editor.
Press **Ctrl+S** to save your workflow.
Press **Ctrl+Alt+H** in XML or JSON view for keyboard shortcuts.
### Approval
The "Approval" page will let you view all approval workflows and will let you approve or disapprove workflows.
### History

The "History" page will let you track all your workflows and everything that happens on the workflow server.From this page you will have an overview of all the workflow instances executed on the workflow server. Furthermore, you can filter the entries by keywords or date. You can also order the entries by date, by name, etc.
### Users
The "Users" page allows to create new users, change passwords and user's informations, and delete users who have restricted access.
A user who has restricted rights has only access to the "Dashboard" page and the "History" page.
### Profiles
The "Profiles" page allows to assign workflows to users. Once the workflow assigned, the user can run it, modify it and delete it.
## Configuration
Wexflow works out of the box with **zero configuration**. You can install it and start running workflows immediately.
However, if you want to customize the behavior of the engine or environment, it’s helpful to know where the configuration files are and what they do. Below is an overview for both .NET 4.8 and .NET 9.0+ versions.
### .NET 4.8
When installing Wexflow on .NET 4.8, the following folders are created:
- `C:\Wexflow\` — Main configuration folder
- `C:\WexflowTesting\` — Contains test data for workflows
Contents of `C:\Wexflow\`:
- **[Wexflow.xml](https://github.com/aelassas/wexflow/wiki/Configuration#wexflowxml)**
Main configuration file of the Wexflow server. Its path can be customized in
`C:\Program Files\Wexflow\Wexflow.Server.exe.config`.
- **Database/**
Contains the internal database used by the Wexflow engine.
- **Workflows/**
Default folder for workflows. Used for hot reloading if
[`EnableWorkflowsHotFolder`](https://github.com/aelassas/wexflow/wiki/Configuration) is enabled.
- **Temp/**
Temporary working directory.
- **Tasks/** *(optional)*
Can contain `.dll` files for [custom tasks](https://github.com/aelassas/Wexflow/wiki/Custom-tasks).
- **Workflow.xsd**
XML Schema Definition file for validating workflows.
- **GlobalVariables.xml**
Stores [global variables](https://github.com/aelassas/Wexflow/wiki/Global-variables) accessible across workflows.
- **TasksNames.json**
Maps task names for use in the Designer interface. Refer to
[Tasks documentation](https://github.com/aelassas/Wexflow/wiki/Tasks-documentation).
- **TasksSettings.json**
Defines default task settings used by the Designer.
Log files:
- Daily logs are written to `C:\Program Files\Wexflow\Wexflow.log`.
- Archived logs follow this pattern: `Wexflow.logyyyyMMdd`.
### .NET 9.0+
On .NET 9.0+, Wexflow supports **cross-platform configuration**:
#### Windows
- Folders:
`C:\Wexflow-dotnet-core\` and `C:\WexflowTesting\`
- Main config:
`C:\Wexflow-dotnet-core\Wexflow.xml` (configured via `Wexflow.Server\appsettings.json`)
- Logs:
Written to `Wexflow.Server\Wexflow.log`
#### Linux
- Folders:
`/opt/wexflow/Wexflow/` and `/opt/wexflow/WexflowTesting/`
- Main config:
`/opt/wexflow/Wexflow/Wexflow.xml` (configured via `/opt/wexflow/Wexflow.Server/appsettings.json`)
- Logs:
Written to `/opt/wexflow/Wexflow.Server/Wexflow.log`
#### macOS
- Folders:
`/Applications/wexflow/Wexflow/` and `/Applications/wexflow/WexflowTesting/`
- Main config:
`/Applications/wexflow/Wexflow/Wexflow.xml` (configured via `/Applications/wexflow/Wexflow.Server/appsettings.json`)
- Logs:
Written to `/Applications/wexflow/Wexflow.Server/Wexflow.log`
For advanced setup, see the full [Configuration documentation](https://github.com/aelassas/Wexflow/wiki/Configuration).
---
# Document: Global Variables
> Source: https://github.com/aelassas/wexflow/wiki/Global-Variables
Global variables are declared by default in the file C:\Wexflow\GlobalVariables.xml
The path of this file can be edited from the configuration file C:\Wexflow\Wexflow.xml
Here is an example of GlobalVariables.xml:
```xml
```
The variables can then be used in workflow files as follow:
```xml
```
When Wexflow server loads the workflow file, the workflow file is parsed so that the global variables are replaced by their values.
---
# Document: Go Client
> Source: https://github.com/aelassas/wexflow/wiki/Go-Client
## Prerequisites
* Install [Go](https://go.dev/)
## Client Sample
Here is a sample Go client `client.go`:
```go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
const (
baseURL = "http://localhost:8000/api/v1"
username = "admin"
password = "wexflow2018"
workflowId = 41
)
type loginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
StayConnected bool `json:"stayConnected"`
}
type loginResponse struct {
AccessToken string `json:"access_token"`
}
func login(user, pass string, stayConnected bool) (string, error) {
payload := loginRequest{
Username: user,
Password: pass,
StayConnected: stayConnected,
}
data, err := json.Marshal(payload)
if err != nil {
return "", fmt.Errorf("failed to marshal login request: %w", err)
}
resp, err := http.Post(baseURL+"/login", "application/json", bytes.NewBuffer(data))
if err != nil {
return "", fmt.Errorf("failed to send login request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("login failed: HTTP %d %s", resp.StatusCode, resp.Status)
}
var result loginResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("failed to decode login response: %w", err)
}
return result.AccessToken, nil
}
func startWorkflow(token string, id int) (string, error) {
url := fmt.Sprintf("%s/start?w=%d", baseURL, id)
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return "", fmt.Errorf("failed to create start request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to send start request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("start failed: HTTP %d %s", resp.StatusCode, resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read start response: %w", err)
}
return string(body), nil
}
func main() {
token, err := login(username, password, false)
if err != nil {
log.Fatalf("Login failed: %v", err)
}
jobID, err := startWorkflow(token, workflowId)
if err != nil {
log.Fatalf("Failed to start workflow %d: %v", workflowId, err)
}
fmt.Printf("Workflow %d started successfully. Job ID: %s\n", workflowId, jobID)
}
```
To run the client, use the following command:
```bash
go run client.go
```
---
# Document: Go SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/Go-SSE-Client
## Prerequisites
* Install [Go](https://go.dev/)
## Client Sample
Here is a sample SSE Go client `sse.go`:
```go
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const (
baseURL = "http://localhost:8000/api/v1"
username = "admin"
password = "wexflow2018"
workflowId = 41
)
type LoginResponse struct {
AccessToken string `json:"access_token"`
}
func login(user, pass string, stayConnected bool) (string, error) {
payload := map[string]interface{}{
"username": user,
"password": pass,
"stayConnected": stayConnected,
}
body, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", baseURL+"/login", bytes.NewBuffer(body))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("login failed: %s", resp.Status)
}
var result LoginResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", err
}
return result.AccessToken, nil
}
func startWorkflow(token string, workflowId int) (string, error) {
url := fmt.Sprintf("%s/start?w=%d", baseURL, workflowId)
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("start workflow failed: %s", resp.Status)
}
var jobId string
if err := json.NewDecoder(resp.Body).Decode(&jobId); err != nil {
return "", err
}
return jobId, nil
}
func listenToSSE(url, token string) error {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
req.Header.Set("Accept", "text/event-stream")
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println("SSE connection opened")
reader := bufio.NewReader(resp.Body)
for {
line, err := reader.ReadString('\n')
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("error reading SSE: %v", err)
}
if len(line) > 6 && line[:6] == "data: " {
data := line[6:]
var parsed map[string]interface{}
if err := json.Unmarshal([]byte(data), &parsed); err != nil {
fmt.Println("Failed to parse SSE JSON:", err)
} else {
fmt.Println("Received SSE JSON:")
out, _ := json.MarshalIndent(parsed, "", " ")
fmt.Println(string(out))
}
break // Close after first SSE event
}
}
fmt.Println("SSE connection closed")
return nil
}
func main() {
token, err := login(username, password, false)
if err != nil {
fmt.Println("Login error:", err)
return
}
jobId, err := startWorkflow(token, workflowId)
if err != nil {
fmt.Println("Start workflow error:", err)
return
}
fmt.Printf("Workflow %d started. Job ID: %s\n", workflowId, jobId)
sseUrl := fmt.Sprintf("%s/sse/%d/%s", baseURL, workflowId, jobId)
if err := listenToSSE(sseUrl, token); err != nil {
fmt.Println("SSE error:", err)
}
}
```
To run the client, use the following command:
```bash
go run sse.go
```
---
# Document: Guid
> Source: https://github.com/aelassas/wexflow/wiki/Guid
```xml
```
---
# Document: Home
> Source: https://github.com/aelassas/wexflow/wiki/Home
Wexflow is a workflow automation engine that supports a wide range of tasks, from file operations and system processes to scripting, networking, and more. Wexflow targets both developers and technical users who need automation (file ops, tasks, scheduling, alerts, etc.). Wexflow focuses on automating technical jobs like moving or uploading files, sending emails, running scripts, or scheduling batch processes. For more complex scenarios, you can create your own custom activities, install them, and use them to extend its capabilities.
Use the sidebar to browse installation guides, configuration options, and more.
## Is Wexflow a Business Process Management Solution?
Wexflow is primarily a workflow automation engine, not a full BPM suite. Wexflow does not natively support BPMN, human workflows, or built-in user forms. So if your business processes require a lot of human interaction, approvals, or business rule evaluation, Wexflow would be limited.
Wexflow excels at automating technical tasks, such as moving or transforming files, uploading to FTP/SFTP, running scripts (PowerShell, Bash, Python, etc.), scheduling and chaining tasks, triggering workflows by events, manual input, cron or watchfolders, designing flows visually (Designer UI), integrating with APIs and databases, supporting conditional logic (if/else, switch, while).
You can use Wexflow if your processes are mostly system-based, such as back-office automation (file syncing, reporting, monitoring), ETL pipelines, DevOps or IT operations automation or API integrations between systems.
---
# Document: HtmlToPdf
> Source: https://github.com/aelassas/wexflow/wiki/HtmlToPdf
```xml
```
---
# Document: HtmlToText
> Source: https://github.com/aelassas/wexflow/wiki/HtmlToText
```xml
```
---
# Document: Http
> Source: https://github.com/aelassas/wexflow/wiki/Http
```xml
```
---
# Document: HttpDelete
> Source: https://github.com/aelassas/wexflow/wiki/HttpDelete
```xml
```
---
# Document: HttpGet
> Source: https://github.com/aelassas/wexflow/wiki/HttpGet
```xml
```
---
# Document: HttpPatch
> Source: https://github.com/aelassas/wexflow/wiki/HttpPatch
```xml
```
---
# Document: HttpPost
> Source: https://github.com/aelassas/wexflow/wiki/HttpPost
```xml
```
---
# Document: HttpPut
> Source: https://github.com/aelassas/wexflow/wiki/HttpPut
```xml
```
---
# Document: ImagesConcat
> Source: https://github.com/aelassas/wexflow/wiki/ImagesConcat
```xml
```
---
# Document: ImagesCropper
> Source: https://github.com/aelassas/wexflow/wiki/ImagesCropper
```xml
```
---
# Document: ImagesOverlay
> Source: https://github.com/aelassas/wexflow/wiki/ImagesOverlay
```xml
```
---
# Document: ImagesResizer
> Source: https://github.com/aelassas/wexflow/wiki/ImagesResizer
```xml
```
---
# Document: ImagesTransformer
> Source: https://github.com/aelassas/wexflow/wiki/ImagesTransformer
```xml
```
---
# Document: InstagramUploadImage
> Source: https://github.com/aelassas/wexflow/wiki/InstagramUploadImage
```xml
```
---
# Document: InstagramUploadVideo
> Source: https://github.com/aelassas/wexflow/wiki/InstagramUploadVideo
```xml
```
---
# Document: Installing
> Source: https://github.com/aelassas/wexflow/wiki/Installing
Wexflow is easy to install and requires zero configuration. It can be installed and configured in just a few minutes.
This section explains how to install Wexflow on Windows, Linux, and macOS.
## Table of Contents
1. [Windows (.NET 4.8 - Legacy)](https://github.com/aelassas/wexflow/wiki/Installing#windows-net-48---legacy)
1. [Installation Instructions](https://github.com/aelassas/wexflow/wiki/Installing#installation-instructions)
1. [Windows (.NET 10.0+ - Stable)](https://github.com/aelassas/wexflow/wiki/Installing#windows-net-100---stable)
1. [Accessing the Admin Panel](https://github.com/aelassas/wexflow/wiki/Installing#accessing-the-admin-panel)
1. [Configuration](https://github.com/aelassas/wexflow/wiki/Installing#configuration)
1. [Running Wexflow as a Windows Service using Servy/NSSM](https://github.com/aelassas/wexflow/wiki/Installing#running-wexflow-as-a-windows-service-using-servynssm)
1. [Deleting the Wexflow Windows Service](https://github.com/aelassas/wexflow/wiki/Installing#deleting-the-wexflow-windows-service)
1. [Linux (.NET 10.0+ - Stable)](https://github.com/aelassas/wexflow/wiki/Installing#linux-net-100---stable)
1. [Installing the Admin Panel on NGINX](https://github.com/aelassas/wexflow/wiki/Installing#installing-the-admin-panel-on-nginx)
1. [Installing the Admin Panel on Apache2](https://github.com/aelassas/wexflow/wiki/Installing#installing-the-admin-panel-on-apache2)
1. [MongoDB Configuration](https://github.com/aelassas/wexflow/wiki/Installing#mongodb-configuration)
1. [Updating Wexflow](https://github.com/aelassas/wexflow/wiki/Installing#updating-wexflow)
1. [macOS (.NET 10.0+ - Stable)](https://github.com/aelassas/wexflow/wiki/Installing#macos-net-100---stable)
1. [Installing the Admin Panel on a Web Server](https://github.com/aelassas/wexflow/wiki/Installing#installing-the-admin-panel-on-a-web-server)
1. [.NET 4.8](https://github.com/aelassas/wexflow/wiki/Installing#net-48---legacy)
1. [.NET 10.0+](https://github.com/aelassas/wexflow/wiki/Installing#net-90---stable)
## Windows (.NET 4.8 - Legacy)
You can install Wexflow as a Windows Service (targeting .NET Framework 4.8) using one of the following [installers](https://github.com/aelassas/wexflow/releases/latest):
* wexflow-x.x-windows-x64.exe (64-bit)
* wexflow-x.x-windows-x86.exe (32-bit)
### Installation Instructions
1. Install [.NET Framework 4.8](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48)
1. Download the latest release of Wexflow
1. Right click on the installer, click on properties, check **Unblock** then click OK.
1. Launch the installer and follow the instructions
After installation, a Windows Service named Wexflow is installed and starts automatically.
The following menus are added in the start menu:
* The "Admin Panel" menu opens the Admin Panel.
* The "Configuration" menu opens the configuration folder of Wexflow.
* The "Documentation" menu opens the documentation folder of Wexflow.
* The "Logs" menu opens the log file of the day.
* The "Manager" menu opens Wexflow Manager GUI.
* The "Install SQLite samples" menu installs SQLite workflow samples.
* The "Install MongoDB samples" menu installs MongoDB workflow samples.
* The "Install SQL Server samples" menu installs SQL Server workflow samples.
* The "Install PostgreSQL samples" menu installs PostgreSQL workflow samples.
* The "Install MySQL samples" menu installs MySQL workflow samples.
* The "Install LiteDB samples" menu installs LiteDB workflow samples.
* The "Install Oracle samples" menu installs Oracle workflow samples.
The admin panel is accessible at: http://localhost:8000/
You can sign in to the admin panel or [Wexflow Manager](https://github.com/aelassas/wexflow/wiki/Getting-Started#wexflow-manager) using the following credentials:
* **Username**: admin
* **Password**: wexflow2018
Once logged in, you can change the password via the Admin Panel.
You can choose from [6 persistence providers](https://github.com/aelassas/wexflow/wiki/Persistence-Providers):
* SQLite (Default)
* MongoDB
* SQLServer
* PostgreSQL
* MySQL
* LiteDB
See [configuration page](https://github.com/aelassas/wexflow/wiki/Configuration#wexflowxml) to see how to change the persistence provider.
## Windows (.NET 10.0+ - Stable)
Follow these steps to run Wexflow using the .NET 10.0+ version on Windows:
1. Install [ASP.NET 10.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/10.0)
1. Download and extract the latest [Wexflow's .NET 10.0+ package](https://github.com/aelassas/wexflow/releases/latest) (wexflow-x.x-windows-netcore.zip)
1. Double-click on `install.bat` to install the configuration files (the database will **not** be overwritten).
That's it! To start the Wexflow workflow server, simply double-click on `run.bat`.
### Accessing the Admin Panel
Once the server is running, the admin panel will be accessible at: http://localhost:8000/
You can sign in to the admin panel using the following credentials:
* **Username**: admin
* **Password**: wexflow2018
### Configuration
You can find detailed instructions about configuration [here](https://github.com/aelassas/wexflow/wiki/Configuration#net-core).
### Running Wexflow as a Windows Service using Servy/NSSM
If you want to run Wexflow as a Windows Service (.NET 10.0+), it is recommended to use [Servy](https://servy-win.github.io/) or [NSSM (Non-Sucking Service Manager)](https://nssm.cc/). Both tools offer improved control over service startup behavior and working directories.
#### Installing the Service
First, [Download Wexflow .NET 10.0+ package](https://github.com/aelassas/wexflow/releases/latest) (wexflow-x.x-windows-netcore.zip), extract it to `C:\Program Files\Wexflow Server\`.
##### Servy
1. Download and install [Servy](https://servy-win.github.io/)
1. Open Servy
1. In the Servy GUI:
* **Service Name**: WexflowServer
* **Service Description**: Wexflow Workflow Automation Engine
* **Process path**: `C:\Program Files\dotnet\dotnet.exe`
* **Startup Directory**: `C:\Program Files\Wexflow Server\Wexflow.Server\`
* **Process Parameters**: `Wexflow.Server.dll`
1. Click **Install**
##### NSSM
1. [Download NSSM](https://nssm.cc/download), extract it to `C:\Program Files\nssm` and add `C:\Program Files\nssm\win64\` to your `PATH` environment variable
1. Open an elevated Command Prompt (Run as Administrator).
1. Run the following command to install the service:
```cmd
nssm install WexflowServer
```
1. In the NSSM GUI:
* **Application path**: `C:\Program Files\dotnet\dotnet.exe`
* **Arguments**: `Wexflow.Server.dll`
* **Startup directory**: `C:\Program Files\Wexflow Server\Wexflow.Server\`
1. Click **Install service**.
1. Set the service description (optional but recommended):
```cmd
sc description WexflowServer "Wexflow Workflow Automation Engine"
```
1. Start the service:
```cmd
nssm start WexflowServer
```
Once started, the Wexflow admin panel will be accessible at:
* **URL**: http://localhost:8000/
* **Username**: admin
* **Password**: wexflow2018
##### Deleting the Wexflow Windows Service
To remove the Wexflow Windows service installed with NSSM:
1. Stop the service:
```cmd
nssm stop WexflowServer
```
1. Remove the service:
```cmd
nssm remove WexflowServer confirm
```
## Linux (.NET 10.0+ - Stable)
**Note:** Installing systemd services, modifying permissions, and firewall rules on Linux require root or sudo privileges. Use `sudo` as needed when running commands.
Follow these steps to run Wexflow using the .NET 10.0+ version on Linux:
1. Download and install [ASP.NET 10.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/10.0)
1. Download and extract [Wexflow's .NET 10.0+ package](https://github.com/aelassas/wexflow/releases/latest) (wexflow-x.x-windows-linux.zip) in `/opt/`
1. Add permissions:
```bash
sudo chown -R $USER:$USER /opt/wexflow
sudo chmod +x /opt/wexflow/install.sh
```
1. Install wexflow systemd service:
```bash
sudo /opt/wexflow/install.sh
```
Wexflow is now installed and the admin panel will be accessible at: http://localhost:8000/
You can sign in to the admin panel using the following credentials:
* **Username**: admin
* **Password**: wexflow2018
### Installing the Admin Panel on NGINX
The admin panel is self-hosted and directly accessible at http://localhost:8000/ when you install wexflow.
However, if you want to install it on NGINX, follow these steps:
First, install NGINX:
```bash
sudo apt update
sudo apt install nginx-full
```
Then, add the admin panel to NGINX:
```bash
sudo nano /etc/nginx/sites-enabled/default
```
Add the following configuration:
```nginx
server {
listen 8011;
root /opt/wexflow/Admin;
index index.html;
access_log /var/log/nginx/wexflow.access.log;
error_log /var/log/nginx/wexflow.error.log;
location / {
# First attempt to serve request as file, then as directory,
# then as index.html, then fall back to displaying a 404.
try_files $uri $uri/ /index.html =404;
}
}
```
Check NGINX configuration and if it is successful restart NGINX:
```bash
sudo nginx -t
sudo systemctl restart nginx.service
```
That's it! the admin panel is installed and accessible from: http://\:8011
### Installing the Admin Panel on Apache2
To install the admin panel on Apache instead of NGINX, install apache2:
```bash
sudo apt update
sudo apt install apache2
```
Create a new site:
```bash
sudo nano /etc/apache2/sites-enabled/wexflow.conf
```
With the following content:
```apache
Listen 8011
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
#ServerName www.example.com
ServerAdmin webmaster@localhost
DocumentRoot /opt/wexflow/Admin
DirectoryIndex index.html
AllowOverride All
Require all granted
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
```
Enable the new site and reload apache2:
```bash
sudo a2ensite wexflow.conf
sudo systemctl reload apache2
```
If you want to install the admin panel on a web server, you'll need to edit the configuration file `js/settings.js`. Check out this [section](https://github.com/aelassas/wexflow/wiki/Installing#installing-the-admin-panel-on-a-web-server) for full guideline.
### MongoDB Configuration
To use MongoDB [persistence provider](https://github.com/aelassas/wexflow/wiki/Persistence-Providers), update `/opt/wexflow/wexflow.service` file as follows:
```
[Unit]
Description=wexflow
Wants=mongod.service
After=mongod.service
[Service]
ExecStart=/usr/bin/dotnet Wexflow.Server.dll
WorkingDirectory=/opt/wexflow/Wexflow.Server
[Install]
WantedBy=multi-user.target
```
Then, run `install.sh` again:
```bash
sudo /opt/wexflow/install.sh
```
If you want to use SQLServer, MySQL or PostgreSQL persistence provider, make sure to update `Wants=` and `After=` options with the matching services.
If you want to update Wexflow to a newer version, proceed as follows:
1. Backup `Wexflow` folder `/opt/wexflow/Wexflow`
1. Remove /opt/wexflow
1. Download and extract [Wexflow's .NET package](https://github.com/aelassas/wexflow/releases/latest) in /opt/
1. Copy `Wexflow` folder that you have saved in /opt/wexflow
1. Add permissions:
```bash
sudo chown -R $USER:$USER /opt/wexflow
sudo chmod +x /opt/wexflow/install.sh
```
1. Update and restart wexflow systemd service:
```bash
sudo /opt/wexflow/install.sh
```
That's it. Wexflow is updated.
For SQL Server, MySQL, or PostgreSQL adjust the `Wants=` and `After=` fields with the corresponding service names.
## Updating Wexflow
To update Wexflow to a newer version, follow these steps:
1. **Back up the existing `Wexflow` folder:**
```bash
cp -r /opt/wexflow/Wexflow ~/Wexflow-backup
```
1. **Remove the current Wexflow installation:**
```bash
sudo rm -rf /opt/wexflow
```
1. **Download and extract the latest [Wexflow .NET Linux package](https://github.com/aelassas/wexflow/releases/latest) into `/opt/`:**
```bash
cd /opt
# download and extract wexflow-x.x-linux-netcore.zip
```
1. **Restore the backed-up `Wexflow` folder:**
```bash
mv ~/Wexflow-backup /opt/wexflow/Wexflow
```
1. **Set the correct permissions:**
```bash
sudo chown -R $USER:$USER /opt/wexflow
sudo chmod +x /opt/wexflow/install.sh
```
1. **Reinstall and restart the systemd service:**
```bash
sudo /opt/wexflow/install.sh
```
Wexflow is now updated and running.
## macOS (.NET 10.0+ - Stable)
Follow these steps to run Wexflow using the .NET 10.0+ version on macOS:
1. Download and install [ASP.NET 10.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/10.0)
1. Download and extract [Wexflow's .NET package](https://github.com/aelassas/wexflow/releases/latest) (wexflow-x.x-macos-netcore.zip) in `/Applications/`
That's it. You can run Wexflow as follows:
```bash
cd /Applications/wexflow/Wexflow.Server
dotnet Wexflow.Server.dll
```
You can open the admin panel at: http://localhost:8000/
You can sign in using the following credentials:
* **Username**: admin
* **Password**: wexflow2018
## Installing the Admin Panel on a Web Server
The admin panel is self-hosted and by default accessible at: http://localhost:8000/
You can also host it on any web server.
### .NET 4.8 - Legacy
To install the admin panel on a web server:
1. Copy the contents of the folder `C:\Program Files\Wexflow\Admin\` to your web server's root or desired directory.
1. If you're hosting the backend (Wexflow server) on a different machine, update the configuration file `js/settings.js` as follows:
```js
window.Settings = (function () {
// Get the current hostname or fallback to 'localhost'
const hostname = window.location.hostname === "" ? "localhost" : window.location.hostname;
// Default Wexflow backend port
const port = 8000;
// Use current protocol (http or https)
const protocol = `${window.location.protocol}//`;
return {
Hostname: hostname,
Port: port,
Uri: `${protocol}${hostname}:${port}/api/v1/`,
/**
* To enable Server-Sent Events (SSE), set `SSE` to true
* and ensure `Version` is set to "netcore"
*/
SSE: false,
/**
* Version of the Wexflow server: "net48" for .NET Framework 4.8,
* or "netcore" for .NET Core / .NET 5+.
*/
Version: "net48",
/**
* Debounce delay in milliseconds for real-time updates.
*/
DebounceDelay: 300,
};
})();
```
1. Replace `hostname` with the IP address or domain name of the machine where the Wexflow server is installed.
1. Ensure that port 8000 (or the port you're using) is open in the firewall and accessible from the client machine.
1. If your Wexflow server uses a different port, update the `port` value accordingly.
1. If you're using HTTPS in Wexflow Server, be sure to use HTTPS on your web server as well.
### .NET 10.0+ - Stable
To install the admin panel on a web server:
1. Copy the contents of the folder `Admin` to your web server's root or desired directory:
* Windows: `.\Admin\`
* Linux: `/opt/wexflow/Admin/`
* macOS: `/Applications/wexflow/Admin/`
1. If you're hosting the backend (Wexflow server) on a different machine, update the configuration file `js/settings.js` as follows:
```js
window.Settings = (function () {
// Get the current hostname or fallback to 'localhost'
const hostname = window.location.hostname === "" ? "localhost" : window.location.hostname;
// Default Wexflow backend port
const port = 8000;
// Use current protocol (http or https)
const protocol = `${window.location.protocol}//`;
return {
Hostname: hostname,
Port: port,
Uri: `${protocol}${hostname}:${port}/api/v1/`,
/**
* To enable Server-Sent Events (SSE), set `SSE` to true
* and ensure `Version` is set to "netcore"
*/
SSE: true,
/**
* Version of the Wexflow server: "net48" for .NET Framework 4.8,
* or "netcore" for .NET Core / .NET 5+.
*/
Version: "netcore",
/**
* Debounce delay in milliseconds for real-time updates.
*/
DebounceDelay: 300,
};
})();
```
1. Replace `hostname` with the IP address or domain name of the machine where the Wexflow server is installed.
1. Ensure that port 8000 (or the port you're using) is open in the firewall and accessible from the client machine.
1. If your Wexflow server uses a different port, update the `port` value accordingly.
1. If you're using HTTPS in Wexflow Server, be sure to use HTTPS on your web server as well.
---
# Document: IsoCreator
> Source: https://github.com/aelassas/wexflow/wiki/IsoCreator
```xml
```
---
# Document: IsoExtractor
> Source: https://github.com/aelassas/wexflow/wiki/IsoExtractor
```xml
```
---
# Document: Java Client
> Source: https://github.com/aelassas/wexflow/wiki/Java-Client
## Prerequisites
* Install [Java JDK](https://www.oracle.com/java/technologies/downloads/)
## Client Sample
Here is a sample Java client `WexflowClient.java`:
```java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class WexflowClient {
private static final String BASE_URL = "http://localhost:8000/api/v1";
private static final String USERNAME = "admin";
private static final String PASSWORD = "wexflow2018";
private static final int WORKFLOW_ID = 41;
public static void main(String[] args) {
try {
String token = login(USERNAME, PASSWORD);
String jobId = startWorkflow(token, WORKFLOW_ID);
System.out.println("Workflow " + WORKFLOW_ID + " started successfully. Job ID: " + jobId);
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
private static String login(String username, String password) throws Exception {
URL url = new URL(BASE_URL + "/login");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
String jsonInputString = String.format(
"{\"username\":\"%s\",\"password\":\"%s\",\"stayConnected\":false}",
username, password);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = jsonInputString.getBytes("utf-8");
os.write(input);
}
int code = conn.getResponseCode();
if (code != 200) {
throw new RuntimeException("Login failed: HTTP " + code);
}
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
// Response JSON format: { "access_token": "..." }
String json = response.toString();
String token = parseAccessToken(json);
if (token == null) {
throw new RuntimeException("No access_token found in response");
}
return token;
}
private static String startWorkflow(String token, int workflowId) throws Exception {
URL url = new URL(BASE_URL + "/start?w=" + workflowId);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setDoOutput(true);
int code = conn.getResponseCode();
if (code != 200) {
throw new RuntimeException("Start workflow failed: HTTP " + code);
}
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
return response.toString();
}
// Simple method to extract access_token from JSON response
private static String parseAccessToken(String json) {
// This is a naive parse, for production use a JSON library like Jackson or Gson
String tokenKey = "\"access_token\":\"";
int start = json.indexOf(tokenKey);
if (start == -1) return null;
start += tokenKey.length();
int end = json.indexOf("\"", start);
if (end == -1) return null;
return json.substring(start, end);
}
}
```
To run the client, use the following command:
```bash
javac WexflowClient.java
java WexflowClient
```
---
# Document: Java SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/Java-SSE-Client
## Prerequisites
* Install [Java JDK](https://www.oracle.com/java/technologies/downloads/)
* Install [Maven](https://maven.apache.org/)
## Client SSE Sample
Here is a sample Java SSE client `WexflowSSEClient.java`:
```java
package com.example;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import okhttp3.*;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import okhttp3.sse.EventSources;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
public class WorkflowSSEClient {
private static final String BASE_URL = "http://localhost:8000/api/v1";
private static final String USERNAME = "admin";
private static final String PASSWORD = "wexflow2018";
private static final int WORKFLOW_ID = 41;
private static final HttpClient httpClient = HttpClient.newHttpClient();
private static final ObjectMapper objectMapper = new ObjectMapper();
public static void main(String[] args) throws Exception {
String token = login(USERNAME, PASSWORD);
String jobId = startWorkflow(token, WORKFLOW_ID);
System.out.printf("Workflow %d started. Job ID: %s%n", WORKFLOW_ID, jobId);
String sseUrl = String.format("%s/sse/%d/%s", BASE_URL, WORKFLOW_ID, jobId);
listenToSse(sseUrl, token);
}
private static String login(String user, String pass) throws IOException, InterruptedException {
String jsonBody = String.format(
"{\"username\":\"%s\", \"password\":\"%s\", \"stayConnected\": false}", user, pass);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/login"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.timeout(Duration.ofSeconds(10))
.build();
HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Login failed: HTTP " + response.statusCode());
}
JsonNode jsonNode = objectMapper.readTree(response.body());
return jsonNode.get("access_token").asText();
}
private static String startWorkflow(String token, int workflowId) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/start?w=" + workflowId))
.header("Authorization", "Bearer " + token)
.POST(HttpRequest.BodyPublishers.noBody())
.timeout(Duration.ofSeconds(10))
.build();
HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Start workflow failed: HTTP " + response.statusCode());
}
// The start endpoint returns the jobId as JSON string, e.g. "jobid-uuid-string"
return objectMapper.readTree(response.body()).asText();
}
private static void listenToSse(String sseUrl, String token) {
OkHttpClient client = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.build();
Request request = new Request.Builder()
.url(sseUrl)
.addHeader("Authorization", "Bearer " + token)
.build();
EventSourceListener listener = new EventSourceListener() {
@Override
public void onOpen(EventSource eventSource, Response response) {
System.out.println("SSE connection opened");
}
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
System.out.println("Received event:");
System.out.println("Type: " + type);
System.out.println("Data: " + data);
try {
JsonNode json = objectMapper.readTree(data);
System.out.println("Parsed JSON:");
System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json));
} catch (Exception e) {
System.err.println("Failed to parse SSE JSON: " + e.getMessage());
}
// Close connection if you want after first event:
eventSource.cancel();
}
@Override
public void onClosed(EventSource eventSource) {
System.out.println("SSE connection closed");
}
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
System.err.println("SSE connection error: " + t.getMessage());
if (response != null) {
System.err.println("Response code: " + response.code());
}
}
};
EventSource.Factory factory = EventSources.createFactory(client);
factory.newEventSource(request, listener);
// Prevent JVM from exiting immediately to keep SSE alive:
try {
Thread.sleep(10 * 60 * 1000); // 10 minutes; adjust as needed
} catch (InterruptedException ignored) {}
}
}
```
To run the client, download [samples/clients/sse/java](https://github.com/aelassas/wexflow/tree/main/samples/clients/sse/java) example from GitHub and run the following commands:
```bash
cd java
mvn clean package
mvn clean compile
mvn exec:java -X
```
---
# Document: JavaScript Client
> Source: https://github.com/aelassas/wexflow/wiki/JavaScript-Client
Here is a sample JavaScript client:
```js
const baseUrl = 'http://localhost:8000/api/v1'
const username = 'admin'
const password = 'wexflow2018'
const workflowId = 1
async function login(user, pass, stayConnected = false) {
const res = await fetch(`${baseUrl}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: user, password: pass, stayConnected })
})
if (!res.ok) {
throw new Error(`HTTP ${res.status} - ${res.statusText}`)
}
const data = await res.json()
return data.access_token
}
try {
const token = await login(username, password)
const res = await fetch(`${baseUrl}/start?w=${workflowId}`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
})
if (!res.ok) {
throw new Error(`HTTP ${res.status} - ${res.statusText}`)
}
const jobId = await res.json()
console.log(`Workflow ${workflowId} started successfully. Job ID:`, jobId)
} catch (err) {
console.error(`Failed to start workflow ${workflowId}:`, err)
}
```
---
# Document: JavaScript SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/JavaScript-SSE-Client
## Steps to Consume the SSE Event
1. **Start the workflow** using the REST API and get the `jobId`.
2. **Connect to the SSE endpoint** using `workflowId` and `jobId`:
```
GET http://localhost:8000/api/v1/sse/{workflowId}/{jobId}
```
3. **Use this example Node.js code** with the `eventsource` package to listen for the final job status:
* Initialize a new Node.js project with default settings:
```bash
npm init -y
```
* Open the generated `package.json` file and add `"type": "module"` to enable ES module support:
```js
{
"name": "wexflow",
"version": "1.0.0",
"type": "module",
...
}
```
* Install the `eventsource` package to handle Server-Sent Events:
```bash
npm install eventsource
```
* Create your main script file `index.js` and add your SSE client code:
```js
import * as eventsource from 'eventsource'
const baseUrl = 'http://localhost:8000/api/v1'
const username = 'admin'
const password = 'wexflow2018'
const workflowId = 41
async function login(user, pass, stayConnected = false) {
const res = await fetch(`${baseUrl}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: user, password: pass, stayConnected })
})
if (!res.ok) {
throw new Error(`HTTP ${res.status} - ${res.statusText}`)
}
const data = await res.json()
return data.access_token
}
async function startWorkflow(token, workflowId) {
const res = await fetch(`${baseUrl}/start?w=${workflowId}`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
})
if (!res.ok) {
throw new Error(`HTTP ${res.status} - ${res.statusText}`)
}
const jobId = await res.json()
return jobId
}
try {
const token = await login(username, password)
const jobId = await startWorkflow(token, workflowId)
console.log(`Workflow ${workflowId} started. Job ID: ${jobId}`)
const sseUrl = `${baseUrl}/sse/${workflowId}/${jobId}`
const es = new eventsource.EventSource(sseUrl, {
fetch: (input, init) =>
fetch(input, {
...init,
headers: {
...init.headers,
Authorization: `Bearer ${token}`
},
}),
})
es.onopen = () => {
console.log('SSE connection opened')
}
es.onmessage = (event) => {
try {
// This event is triggered when the workflow job finishes or stops.
// The SSE data arrives as a JSON-formatted string in event.data.
// Parse this string into a JavaScript object for easy access to properties.
// For the complete list of workflow statuses, see:
// https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE#statuses
const data = JSON.parse(event.data)
console.log('Received SSE JSON:', data)
// Access properties like data.workflowId, data.jobId, data.status, data.name, data.description
// Close connection if needed, e.g. after final status received
es.close()
} catch (err) {
console.error('Failed to parse SSE JSON:', err)
}
}
es.onerror = (err) => {
console.error('SSE error:', err)
es.close()
}
} catch (err) {
console.error('Error:', err)
}
```
* Run your Node.js program:
```bash
node index.js
```
Use `Workflow_Wait` with a period of 10 secs for instance to test.
Here is a sample output result:
```js
{
workflowId: 41,
jobId: '66aae8f4-e3ab-4fdc-8db2-a0a7692d8906',
status: 'Done',
name: 'Workflow_Wait',
description: 'Workflow_Wait'
}
```
## Summary
* The SSE event sends JSON with `{ workflowId, jobId, status, name, description }`.
* The connection closes automatically after the job finishes.
* Ensure your requests include proper JWT authorization.
---
# Document: JsonToYaml
> Source: https://github.com/aelassas/wexflow/wiki/JsonToYaml
```xml
```
---
# Document: ListEntities
> Source: https://github.com/aelassas/wexflow/wiki/ListEntities
```xml
```
---
# Document: ListFiles
> Source: https://github.com/aelassas/wexflow/wiki/ListFiles
```xml
```
---
# Document: Local Variables
> Source: https://github.com/aelassas/wexflow/wiki/Local-Variables
It is possible to declare local variables in a workflow.
The syntax is as follows:
```xml
```
When Wexflow server loads the workflow file, the workflow file will be parsed and thus the local variables will be replaced by their respective values.
It is also possible to combine global variables with local variables, here is an example:
GlobalVariables.xml:
```xml
```
Below a sample workflow which contains global variables and local variables:
```xml
```
---
# Document: Logging
> Source: https://github.com/aelassas/wexflow/wiki/Logging
Everything that happens in Wexflow is traced and logged. With Wexflow's logging system, you can track your workflows with ease and stay informed with real-time monitoring and email notifications.
Wexflow's logs are written in C:\Program Files\Wexflow\Wexflow.log. There is one log file per day. The old log files are saved in following format: Wexflow.logyyyyMMdd
It is possible to configure Wexflow to send incident reports when an error occurs by using log4net.Appender.SmtpAppender in the configuration file of Wexflow C:\Program Files\Wexflow\Wexflow.Clients.WindowsService.exe.config. Below a sample configuration:
```xml
```
Wexflow gives you a beautiful Dashboard to view real-time statistics on your workflows. The Dashboard will let you track your workflow server with ease.
The History page in the backend will also let you track all your workflows and everything that happens on the workflow server. Indeed, from this page you will have an overview of all the workflow instances executed on the workflow server.
---
# Document: MailsReceiver
> Source: https://github.com/aelassas/wexflow/wiki/MailsReceiver
```xml
```
---
# Document: MailsSender
> Source: https://github.com/aelassas/wexflow/wiki/MailsSender
```xml
```
---
# Document: Md5
> Source: https://github.com/aelassas/wexflow/wiki/Md5
```xml
```
---
# Document: MediaInfo
> Source: https://github.com/aelassas/wexflow/wiki/MediaInfo
```xml
```
---
# Document: MessageCorrect
> Source: https://github.com/aelassas/wexflow/wiki/MessageCorrect
```xml
```
---
# Document: Migration Guide to v10.0
> Source: https://github.com/aelassas/wexflow/wiki/Migration-Guide-to-v10.0
To migrate from Wexflow 9.0 and below to v10.0, follow these instructions.
# .NET 4.8
1. Backup the folder `C:\Wexflow`
1. Download the installer wexflow-10.0-windows-x64.exe
1. Run the installer
1. Login to http://localhost:8000
* Username: `admin`
* Password: `wexflow2018`
# .NET 10.0
## Windows
1. Backup the folder `C:\Wexflow-netcore`
1. Download the package wexflow-10.0-windows-netcore.zip
1. Unzip the package
1. Run `install.bat`
1. Run `run.bat`
1. Login to http://localhost:8000
* Username: `admin`
* Password: `wexflow2018`
## Linux
1. Backup the folder `/opt/wexflow/Wexflow`
1. Install ASP.NET Core Runtime 10.0
1. Remove `/opt/wexflow`
1. Download the package wexflow-10.0-linux-netcore.zip
1. Unzip the package in `/opt/`
1. Remove `/opt/wexflow/Wexflow`
1. Copy the backup `/opt/wexflow/Wexflow` in `/opt/wexflow/`
1. Add permissions:
```bash
sudo chown -R $USER:$USER /opt/wexflow
sudo chmod +x /opt/wexflow/install.sh
```
1. Install wexflow systemd service:
```bash
sudo /opt/wexflow/install.sh
```
1. Login to http://localhost:8000
* Username: `admin`
* Password: `wexflow2018`
## macOS
1. Backup the folder `/Applications/wexflow/Wexflow`
1. Install ASP.NET Core Runtime 10.0
1. Remove `/Applications/wexflow`
1. Download the package wexflow-10.0-macos-netcore.zip
1. Unzip the package in `/Applications/`
1. Remove `/Applications/wexflow/Wexflow`
1. Copy the backup `/Applications/wexflow/Wexflow` in `/Applications/wexflow/`
1. Run Wexflow:
```bash
cd /Applications/wexflow/Wexflow.Server
dotnet Wexflow.Server.dll
```
1. Login to http://localhost:8000
* Username: `admin`
* Password: `wexflow2018`
# Wexflow API
The authentication has changed to JWT. You need to update all your clients to use JWT:
* [Authentication](https://github.com/aelassas/wexflow/wiki/RESTful-API#authentication)
* [Sample Clients](https://github.com/aelassas/wexflow/wiki/RESTful-API#sample-clients)
---
# Document: Mkdir
> Source: https://github.com/aelassas/wexflow/wiki/Mkdir
```xml
```
---
# Document: Movedir
> Source: https://github.com/aelassas/wexflow/wiki/Movedir
```xml
```
---
# Document: Now
> Source: https://github.com/aelassas/wexflow/wiki/Now
```xml
```
---
# Document: PHP SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/PHP-SSE-Client
## Prerequisites
* Install [PHP](https://www.php.net/)
* On Windows, enable `curl` in `php.ini`:
```
extension_dir = "ext"
extension=curl
```
# SSE Client Sample
Here is a sample PHP SSE client `sse.php`:
```php
$username,
'password' => $password,
'stayConnected' => $stayConnected
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($statusCode !== 200) {
throw new Exception("Login failed: HTTP $statusCode");
}
$json = json_decode($response, true);
return $json['access_token'];
}
function startWorkflow($token, $workflowId) {
$url = "http://localhost:8000/api/v1/start?w=$workflowId";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $token"
]
]);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($statusCode !== 200) {
throw new Exception("Start workflow failed: HTTP $statusCode");
}
return json_decode($response, true);
}
function listenToSse($url, $token) {
$headers = [
"Authorization: Bearer $token",
"Accept: text/event-stream"
];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_WRITEFUNCTION => function($ch, $data) {
if (strpos($data, "data: ") === 0) {
$json = trim(substr($data, 6));
$decoded = json_decode($json, true);
if ($decoded !== null) {
echo "Received SSE JSON:\n";
print_r($decoded);
} else {
echo "Invalid SSE JSON\n";
}
}
return strlen($data);
},
CURLOPT_TIMEOUT => 0,
CURLOPT_RETURNTRANSFER => false,
]);
echo "Opening SSE connection...\n";
curl_exec($ch);
if (curl_errno($ch)) {
echo "SSE error: " . curl_error($ch) . "\n";
}
curl_close($ch);
}
// ---- Main execution ----
try {
$username = 'admin';
$password = 'wexflow2018';
$workflowId = 41;
$baseUrl = 'http://localhost:8000/api/v1';
$token = login($username, $password);
$jobId = startWorkflow($token, $workflowId);
echo "Workflow $workflowId started. Job ID: $jobId\n";
$sseUrl = "$baseUrl/sse/$workflowId/$jobId";
listenToSse($sseUrl, $token);
} catch (Exception $e) {
echo 'Error: ' . $e->getMessage() . "\n";
}
```
To run the client, use the following command:
```bash
php sse.php
```
---
# Document: PHP client
> Source: https://github.com/aelassas/wexflow/wiki/PHP-client
## Prerequisites
* Install [PHP](https://www.php.net/)
* On Windows, enable `curl` in `php.ini`:
```
extension_dir = "ext"
extension=curl
```
# Client Sample
Here is a sample PHP client `client.php`:
```php
$username,
'password' => $password,
'stayConnected' => $stayConnected
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("Login failed: HTTP $httpCode - $response");
}
$data = json_decode($response, true);
return $data['access_token'] ?? null;
}
/**
* Starts the workflow with the given ID using the access token.
*/
function startWorkflow($workflowId, $token)
{
global $baseUrl;
$url = $baseUrl . '/start?w=' . urlencode($workflowId);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HTTPHEADER => array(
"Content-Type: application/json",
"Content-Length: 0",
"Authorization: Bearer $token"
),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("Start workflow failed: HTTP $httpCode - $response");
}
$data = json_decode($response, true);
return $data;
}
try {
$token = login($username, $password);
$jobId = startWorkflow($workflowId, $token);
echo "Workflow $workflowId started successfully. Job ID: " . json_encode($jobId) . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>
```
To run the client, use the following command:
```bash
php client.php
```
---
# Document: PdfToText
> Source: https://github.com/aelassas/wexflow/wiki/PdfToText
```xml
```
---
# Document: Persistence Providers
> Source: https://github.com/aelassas/wexflow/wiki/Persistence-Providers
Wexflow ships with 6 persistence providers. You can choose from the following options:
* SQLite (Default)
* MongoDB
* SQLServer
* PostgreSQL
* MySQL
* LiteDB
If you want to change the persistence provider, you'll need to update `dbType` and `connectionString` settings in [Wexflow.xml](https://github.com/aelassas/wexflow/wiki/Configuration#wexflowxml) configuration file.
If you want to use SQL Server `Trusted_Connection=true` in the connection string, you have to run Wexflow Windows Service as a user who has privileges to connect to SQL Server.
Once you change the persistence provider, restart Wexflow server. Then, you can install workflow samples:
* .NET: `Start > Wexflow > Install [dbType] Samples`
* .NET Core Windows: `./install-[dbType].bat`
* .NET Core Linux: `cd /opt/wexflow/Wexflow.Scripts.[dbType] && dotnet Wexflow.Scripts.[dbType].dll`
* .NET Core macOS: `cd /Application/wexflow/Wexflow.Scripts.[dbType] && dotnet Wexflow.Scripts.[dbType].dll`
Don't forget to check and update the connection string of the samples script if necessary:
* .NET: `C:\Program Files\Wexflow\Wexflow.Scripts.[dbType]\Wexflow.Scripts.[dbType].exe.config`
* .NET Core Windows: `./Wexflow.Scripts.[dbType]/appsettings.json`
* .NET Core Linux: `/opt/wexflow/Wexflow.Scripts.[dbType]/appsettings.json`
* .NET Core macOS: `/Application/wexflow/Wexflow.Scripts.[dbType]/appsettings.json`
---
# Document: Ping
> Source: https://github.com/aelassas/wexflow/wiki/Ping
```xml
```
---
# Document: PowerShell Client
> Source: https://github.com/aelassas/wexflow/wiki/PowerShell-Client
# Client Sample
Here is a sample PowerShell client `client.ps1`:
```powershell
# Login and get JWT token
$loginPayload = @{
username = 'admin'
password = 'wexflow2018'
stayConnected = $false
} | ConvertTo-Json
$res = Invoke-WebRequest -Uri "http://localhost:8000/api/v1/login" -Method Post -Body $loginPayload -ContentType "application/json"
$token = ($res.Content | ConvertFrom-Json).access_token
# Build headers
$headers = @{
Authorization = "Bearer $token"
"Content-Type" = "application/json"
}
# Start workflow
$workflowId=1
$res = Invoke-WebRequest -Uri "http://localhost:8000/api/v1/start?w=$workflowId" -Method Post -Headers $headers
# Parse JSON response
$jobId = ($res.Content | ConvertFrom-Json)
Write-Host "Workflow $workflowId started successfully. Job ID: $jobId"
```
# Client Sending XML as rest variable
```powershell
$xmlFile = "C:\WexflowTesting\Xml\Products.xml"
# Explicitly read the file as plain text with no PowerShell object wrapping
$WorkflowXMLString = [System.IO.File]::ReadAllText($xmlFile)
# Build JSON payload
$xmlFile = "C:\WexflowTesting\Xml\Products.xml"
# Explicitly read the file as plain text with no PowerShell object wrapping
$WorkflowXMLString = [System.IO.File]::ReadAllText($xmlFile)
# Build JSON payload
$jsonPayload = @{
WorkflowId = 138
Variables = @(
@{
Name = "xml"
Value = $WorkflowXMLString
}
)
} | ConvertTo-Json
# Login and get JWT token
$loginPayload = @{
username = 'admin'
password = 'wexflow2018'
stayConnected = $false
} | ConvertTo-Json
$res = Invoke-WebRequest -Uri "http://localhost:8000/api/v1/login" -Method Post -Body $loginPayload -ContentType "application/json"
$token = ($res.Content | ConvertFrom-Json).access_token
# Build headers
$headers = @{
Authorization = "Bearer $token"
"Content-Type" = "application/json"
}
# Start workflow with variables
$res = Invoke-WebRequest -Uri "http://localhost:8000/api/v1/start-with-variables" -Method Post -Headers $headers -Body $jsonPayload
# Parse JSON response
$jobId = ($res.Content | ConvertFrom-Json)
Write-Host "Workflow $workflowId started successfully. Job ID: $jobId"
```
---
# Document: PowerShell SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/PowerShell-SSE-Client
## Prerequisites
* Install [PowerShell 5.1+](https://docs.microsoft.com/powershell/scripting/install/installing-powershell) (pre-installed on modern Windows versions).
* Open a PowerShell terminal or ISE session.
## Long-Running Workflows & JWT Authentication
When monitoring workflows that run for extended periods (such as 2 days or more), standard authentication and connection handling require specific configurations:
### 1. Preventing JWT Token Expiration
By default, short-lived JWT tokens will expire before a multi-day workflow finishes. When authenticating via the `/login` endpoint, pass `"stayConnected": true` in the request body:
```json
{
"username": "admin",
"password": "your_password",
"stayConnected": true
}
```
* **`stayConnected: false`**: Produces a standard, short-lived JWT token (suitable for quick API operations).
* **`stayConnected: true`**: Produces a persistent, non-expiring JWT token necessary for long-running monitoring operations spanning days or weeks.
### 2. Stream Resiliency for Multi-Day Execution
Even with a persistent token, HTTP connections across local networks or the internet will periodically drop over 48+ hours due to proxy timeouts, firewall session resets, or transient network hiccups. The sample client handles this automatically:
* **Automatic Re-authentication:** If the server returns `401 Unauthorized` during a reconnection attempt, the script automatically calls `Get-WexflowToken` to fetch a fresh JWT token before retrying.
* **Idle Read Timeouts:** Uses a 5-minute timeout window via `CancellationTokenSource`. If an intermediate network proxy silently drops the connection without sending a TCP disconnect frame, the script detects the quiet socket and re-establishes the SSE stream seamlessly.
* **Terminal Status Detection:** The client stays connected through transient non-terminal states (`Pending`, `Running`) and only terminates the loop when a final status frame (`Done`, `Failed`, `Warning`, `Stopped`, or `Rejected`) is received.
## SSE Client Sample
Here is a sample PowerShell SSE client `sse.ps1`:
```powershell
#Requires -Version 5.1
<#
.SYNOPSIS
Wexflow Server-Sent Events (SSE) Client script for PowerShell 5.1+.
.DESCRIPTION
Authenticates with the Wexflow REST API, starts a specified workflow job,
subscribes to the corresponding SSE endpoint, and streams status updates until completion.
.PARAMETER BaseUrl
The base API endpoint URL for the Wexflow instance (default: "http://localhost:8000/api/v1").
.PARAMETER Username
The Wexflow username for authentication (default: "admin").
.PARAMETER Password
The Wexflow password for authentication.
.PARAMETER WorkflowId
The integer ID of the workflow to execute and monitor (default: 41).
.EXAMPLE
.\Invoke-WexflowSseClient.ps1 -Username "admin" -Password "wexflow2018" -WorkflowId 41
#>
[CmdletBinding()]
param(
[string]$BaseUrl = "http://localhost:8000/api/v1",
[string]$Username = "admin",
[string]$Password = "wexflow2018",
[int]$WorkflowId = 41
)
# Enforce TLS 1.2 for modern HTTP operations
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Assembly load for HttpClient
Add-Type -AssemblyName System.Net.Http
# Functions
function Get-WexflowToken {
param(
[string]$Url,
[string]$User,
[string]$Pass
)
$loginUrl = "$Url/login"
# stayConnected set to $true ensures the JWT token never expires (essential for multi-day jobs)
$body = @{
username = $User
password = $Pass
stayConnected = $true
} | ConvertTo-Json
# Perform REST Login
$response = Invoke-RestMethod -Uri $loginUrl -Method Post -Body $body -ContentType "application/json"
if (-not $response.access_token) {
throw "Failed to acquire JWT access token from response."
}
return $response.access_token
}
function Start-WexflowJob {
param(
[string]$Url,
[string]$Token,
[int]$WfId
)
$startUrl = "$Url/start?w=$WfId"
$headers = @{
Authorization = "Bearer $Token"
}
# Start the workflow via POST request
$jobId = Invoke-RestMethod -Uri $startUrl -Method Post -Headers $headers
return $jobId
}
function Watch-WexflowSse {
<#
.SYNOPSIS
Connects to the Server-Sent Events (SSE) endpoint and reads streamed lines.
.DESCRIPTION
Uses System.Net.Http.HttpClient to establish an HTTP GET request with
HttpCompletionOption.ResponseHeadersRead. This allows line-by-line streaming of
data payload lines prefixed with 'data: '.
#>
param(
[string]$BaseUrl,
[string]$Username,
[string]$Password,
[string]$SseUrl,
[string]$InitialToken
)
# Terminal workflow states that signal job completion
$terminalStatuses = @("Done", "Failed", "Warning", "Stopped", "Rejected")
$isTerminalStateReached = $false
$currentToken = $InitialToken
# Reconnection loop to handle network drops on multi-day running workflows
while (-not $isTerminalStateReached) {
$handler = New-Object System.Net.Http.HttpClientHandler
$client = New-Object System.Net.Http.HttpClient($handler)
# Prevent client-side timeout for multi-day operations
$client.Timeout = [System.TimeSpan]::FromMilliseconds([System.Threading.Timeout]::Infinite)
# Configure required HTTP Headers for SSE stream listening
$request = New-Object System.Net.Http.HttpRequestMessage([System.Net.Http.HttpMethod]::Get, $SseUrl)
$request.Headers.Accept.Add((New-Object System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/event-stream")))
$request.Headers.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", $currentToken)
Write-Host "[SSE] Connecting to SSE stream..." -ForegroundColor Cyan
try {
# ResponseHeadersRead is crucial: it prevents HttpClient from buffering the whole stream into memory
$responseTask = $client.SendAsync($request, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead)
$response = $responseTask.Result
# Handle edge cases where server session resets or invalidates the token
if ($response.StatusCode -eq [System.Net.HttpStatusCode]::Unauthorized) {
Write-Warning "JWT token unauthorized. Re-authenticating with stayConnected=$true..."
$currentToken = Get-WexflowToken -Url $BaseUrl -User $Username -Pass $Password
continue
}
if (-not $response.IsSuccessStatusCode) {
Write-Warning "SSE Request failed with HTTP Status: $($response.StatusCode) - $($response.ReasonPhrase). Retrying in 10 seconds..."
Start-Sleep -Seconds 10
continue
}
$streamTask = $response.Content.ReadAsStreamAsync()
$stream = $streamTask.Result
$reader = New-Object System.IO.StreamReader($stream)
Write-Host "[SSE] Connection established. Listening for events..." -ForegroundColor Green
# Loop through stream line-by-line as data events arrive
while (-not $reader.EndOfStream) {
# Protect against silent TCP deadlocks from intermediate proxies during idle days
$cts = New-Object System.Threading.CancellationTokenSource([TimeSpan]::FromMinutes(5))
try {
$lineTask = $reader.ReadLineAsync()
[System.Threading.Tasks.Task]::WaitAll(@($lineTask), $cts.Token)
$line = $lineTask.Result
}
catch {
Write-Warning "[SSE] Connection idle ping timeout (5 mins without frame). Re-establishing stream connection..."
break
}
finally {
$cts.Dispose()
}
if (-not [string]::IsNullOrWhiteSpace($line) -and $line.StartsWith("data: ")) {
# Extract JSON payload after 'data: ' prefix
$jsonString = $line.Substring("data: ".Length)
Write-Host "`n[SSE Event Received: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')]" -ForegroundColor Yellow
try {
$eventData = $jsonString | ConvertFrom-Json
# Display structured output properties
Write-Host " Workflow ID : $($eventData.workflowId)"
Write-Host " Job ID : $($eventData.jobId)"
Write-Host " Name : $($eventData.name)"
Write-Host " Status : $($eventData.status)" -ForegroundColor Magenta
Write-Host " Description : $($eventData.description)"
# Break loop ONLY after reading a terminal status frame
if ($terminalStatuses -contains $eventData.status) {
$isTerminalStateReached = $true
break
}
}
catch {
Write-Warning "Failed to parse raw SSE JSON payload: $_"
Write-Host "Raw Payload: $jsonString"
}
}
}
}
catch {
if (-not $isTerminalStateReached) {
Write-Warning "SSE connection disconnected or timed out: $_. Reconnecting in 10 seconds..."
Start-Sleep -Seconds 10
}
}
finally {
# Cleanup HTTP connections
if ($null -ne $reader) { $reader.Dispose() }
if ($null -ne $stream) { $stream.Dispose() }
if ($null -ne $client) { $client.Dispose() }
if ($isTerminalStateReached) {
Write-Host "`n[SSE] Terminal status reached. Connection closed." -ForegroundColor Cyan
}
}
}
}
# Main Execution Script Logic
try {
Write-Host "1. Logging into Wexflow ($BaseUrl)..." -ForegroundColor White
$jwtToken = Get-WexflowToken -Url $BaseUrl -User $Username -Pass $Password
Write-Host " Token retrieved successfully (stayConnected = true)." -ForegroundColor Green
Write-Host "2. Starting Workflow ID: $WorkflowId..." -ForegroundColor White
$jobId = Start-WexflowJob -Url $BaseUrl -Token $jwtToken -WfId $WorkflowId
Write-Host " Job started successfully. Job ID: $jobId" -ForegroundColor Green
# Construct SSE URL endpoint: /api/v1/sse/{workflowId}/{jobId}
$sseEndpoint = "$BaseUrl/sse/$WorkflowId/$jobId"
Write-Host "3. Subscribing to Wexflow SSE Endpoint..." -ForegroundColor White
Watch-WexflowSse -BaseUrl $BaseUrl -Username $Username -Password $Password -SseUrl $sseEndpoint -InitialToken $jwtToken
}
catch {
Write-Error "Execution Failed: $_"
}
```
To run the client, execute the script in PowerShell:
```powershell
.\sse.ps1
```
---
# Document: ProcessInfo
> Source: https://github.com/aelassas/wexflow/wiki/ProcessInfo
```xml
```
---
# Document: ProcessKiller
> Source: https://github.com/aelassas/wexflow/wiki/ProcessKiller
```xml
```
---
# Document: ProcessLauncher
> Source: https://github.com/aelassas/wexflow/wiki/ProcessLauncher
```xml
```
---
# Document: Python Client
> Source: https://github.com/aelassas/wexflow/wiki/Python-Client
## Prerequisites
* Install [Python](https://www.python.org/)
* Install [pip](https://pip.pypa.io/)
* Install `requests`:
```bash
pip install requests
```
## Client Sample
Here is a sample Python client `client.py`:
```py
import requests
base_url = 'http://localhost:8000/api/v1'
username = 'admin'
password = 'wexflow2018'
workflow_id = 41
def login(user, passwd, stay_connected=False):
url = f'{base_url}/login'
headers = {'Content-Type': 'application/json'}
payload = {
'username': user,
'password': passwd,
'stayConnected': stay_connected
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code != 200:
raise Exception(f'HTTP {response.status_code} - {response.reason}')
return response.json()['access_token']
try:
token = login(username, password)
headers = {
'Authorization': f'Bearer {token}'
}
response = requests.post(f'{base_url}/start?w={workflow_id}', headers=headers)
if response.status_code != 200:
raise Exception(f'HTTP {response.status_code} - {response.reason}')
job_id = response.json()
print(f'Workflow {workflow_id} started successfully. Job ID: {job_id}')
except Exception as e:
print(f'Failed to start workflow {workflow_id}: {e}')
```
To run the client, use the following command:
```bash
py client.py
```
---
# Document: Python SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/Python-SSE-Client
## Prerequisites
* Install [Python](https://www.python.org/)
* Install [pip](https://pip.pypa.io/)
* Install `requests` and `sseclient-py`:
```bash
pip install requests sseclient-py
```
## SSE Client Sample
Here is a sample Python client `sse.py`:
```py
import requests
import sseclient
import json
base_url = 'http://localhost:8000/api/v1'
username = 'admin'
password = 'wexflow2018'
workflow_id = 41
def login(user, passwd, stay_connected=False):
url = f'{base_url}/login'
headers = {'Content-Type': 'application/json'}
payload = {
'username': user,
'password': passwd,
'stayConnected': stay_connected
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code != 200:
raise Exception(f'HTTP {response.status_code} - {response.reason}')
return response.json()['access_token']
def start_workflow(token, workflow_id):
url = f'{base_url}/start?w={workflow_id}'
headers = {
'Authorization': f'Bearer {token}'
}
response = requests.post(url, headers=headers)
if response.status_code != 200:
raise Exception(f'HTTP {response.status_code} - {response.reason}')
return response.json()
def listen_to_sse(url, token):
headers = {
'Authorization': f'Bearer {token}',
'Accept': 'text/event-stream'
}
response = requests.get(url, headers=headers, stream=True)
client = sseclient.SSEClient(response)
print('SSE connection opened')
for event in client.events():
try:
data = json.loads(event.data)
print('Received SSE JSON:')
print(json.dumps(data, indent=2))
break # Stop after first message
except Exception as e:
print(f'Failed to parse SSE JSON: {e}')
break
print('SSE connection closed')
# Main execution
try:
token = login(username, password)
job_id = start_workflow(token, workflow_id)
print(f'Workflow {workflow_id} started successfully. Job ID: {job_id}')
sse_url = f'{base_url}/sse/{workflow_id}/{job_id}'
listen_to_sse(sse_url, token)
except Exception as e:
print(f'Error: {e}')
```
To run the client, use the following command:
```bash
py sse.py
```
---
# Document: REST Variables
> Source: https://github.com/aelassas/wexflow/wiki/REST-Variables
Local variables and global variables values are set before the workflow is started.
With REST variables, you can set values at runtime when you start a workflow.
REST variables cannot be set from the dashboard at the moment. You need to use Wexflow API from your application or through Swagger: http://localhost:8000/
To send variables when starting a workflow, use the following endpoint:
**POST** http://localhost:8000/api/v1/start-with-variables
Here is a sample payload:
```json
{
"WorkflowId":131,
"Variables":[
{
"Name":"restVar1",
"Value":"C:\\WexflowTesting\\file1.txt"
},
{
"Name":"restVar2",
"Value":"C:\\WexflowTesting\\file2.txt"
}
]
}
```
Here is a sample workflow:
```xml
```
---
# Document: RESTful API
> Source: https://github.com/aelassas/wexflow/wiki/RESTful-API
# Table of Contents
1. [Introduction](https://github.com/aelassas/wexflow/wiki/RESTful-API#introduction)
1. [Authentication](https://github.com/aelassas/wexflow/wiki/RESTful-API#authentication)
1. [Sample Clients](https://github.com/aelassas/wexflow/wiki/RESTful-API#sample-clients)
1. [Security Considerations](https://github.com/aelassas/wexflow/wiki/RESTful-API#security-considerations)
1. [Swagger](https://github.com/aelassas/wexflow/wiki/RESTful-API#swagger)
1. [Workflow Notifications via SSE](https://github.com/aelassas/wexflow/wiki/RESTful-API#workflow-notifications-via-sse)
1. [Endpoints](https://github.com/aelassas/wexflow/wiki/RESTful-API#endpoints)
# Introduction
Wexflow Server is a standalone workflow engine that helps automate tasks such as file handling, data processing, report generation, and more. It is language-independent, which means you can use it with any technology—whether your app is built in PHP, Node.js, .NET, Python, Ruby, or something else. Wexflow provides a RESTful API so your application can communicate with it over HTTP.
Beyond standard request-response operations, you can receive real-time workflow job status updates through **Server-Sent Events (SSE)**. For detailed instructions on how to subscribe to workflow status notifications via SSE, see the [Workflow Notifications via SSE](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE) page.
This API lets you control workflows from your own software. You can start, stop, and monitor workflows, view logs, manage users, and access system information. Wexflow handles all the execution, so you can focus on your application logic while automating background processes with ease.
# Authentication
All Wexflow API endpoints are secured with JWT authentication. Every API request must include an `Authorization` header containing a valid **Bearer token**.
To get a JWT token, call the following endpoint:
```
POST http://localhost:8000/api/v1/login
```
With this JSON payload:
```js
{
'username': 'username',
'password': 'password',
'stayConnected': false,
}
```
If the credentials are valid, you'll receive a response like:
```js
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
Include the token in your `Authorization` header for all subsequent requests:
```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
The default password of the user `admin` is `wexflow2018`. For security reasons, change it after your first login.
# Sample Clients
Wexflow provides a RESTful API that can be consumed from any programming language. Below are sample clients demonstrating how to authenticate, use access tokens, trigger workflows, and interact with the API:
- [C# Client](https://github.com/aelassas/wexflow/wiki/C%23-Client)
- [PowerShell Client](https://github.com/aelassas/wexflow/wiki/PowerShell-Client)
- [JavaScript Client](https://github.com/aelassas/wexflow/wiki/JavaScript-Client)
- [PHP Client](https://github.com/aelassas/wexflow/wiki/PHP-client)
- [Python Client](https://github.com/aelassas/wexflow/wiki/Python-Client)
- [Go Client](https://github.com/aelassas/wexflow/wiki/Go-Client)
- [Rust Client](https://github.com/aelassas/wexflow/wiki/Rust-Client)
- [Ruby Client](https://github.com/aelassas/wexflow/wiki/Ruby-Client)
- [Java Client](https://github.com/aelassas/wexflow/wiki/Java-Client)
- [C++ Client](https://github.com/aelassas/wexflow/wiki/CPP-Client)
You can find the source code for all these clients in the [samples/clients](https://github.com/aelassas/wexflow/tree/main/samples/clients/client) directory of the main Wexflow repository.
These examples serve as practical starting points to integrate Wexflow into your own applications regardless of the tech stack you're using.
# Security Considerations
Wexflow uses a secure authentication mechanism based on **JWT (JSON Web Tokens)**, **PBKDF2-hashed passwords**, HttpOnly secure cookies and HTTPS/SSL.
For production environments, it is strongly recommended to use [HTTPS/SSL](https://github.com/aelassas/wexflow/wiki/SSL) to encrypt all communication between clients and the Wexflow server.
Securing Wexflow behind **HTTPS** and using strong credentials is essential for reducing potential vulnerabilities.
These measures help protect against XSS, XST, CSRF, MITM, token theft, and insecure password storage.
# Swagger
Wexflow provides a built-in Swagger interface for exploring and testing the API: http://localhost:8000/swagger-ui/
Some api routes may not be listed here, check Swagger UI for full list.
# Workflow Notifications via SSE
You can subscribe to a Server-Sent Events (SSE) endpoint that notifies your client when a workflow job finishes or stops.
For detailed instructions on how to subscribe to workflow status notifications via SSE, see the [Workflow Notifications via SSE](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE) page.
# Endpoints
## Dashboard
**GET** http://localhost:8000/api/v1/status-count
Returns status count.
**GET** http://localhost:8000/api/v1/entries-count-by-date?s={keyword}&from={date}&to={date}
Returns entries count by keyword and date filter.
**GET** http://localhost:8000/api/v1/search-entries-by-page-order-by?s={keyword}&from={date}&to={date}&page={page}&entriesCount={entriesCount}&heo={orderBy}
Searches for entries.
**GET** http://localhost:8000/api/v1/entry-status-date-min
Returns entry min date.
**GET** http://localhost:8000/api/v1/entry-status-date-max
Returns entry max date.
**GET** http://localhost:8000/api/v1/entry?w={workflowId}&i={jobId}
Returns workflow job entry. If jobId is not specified, it returns the latest job entry of the workflow.
Here is a sample response:
```json
{
"Id": "2",
"WorkflowId": 43,
"Name": "Workflow_ExecutionGraph",
"LaunchType": 1,
"Description": "Workflow_ExecutionGraph",
"Status": 1,
"StatusDate": "19-06-2025 18:23:54"
}
```
Here are job statuses:
| Status | Integer Value |
|-------------|----------------|
| Pending | 0 |
| Running | 1 |
| Done(success) | 2 |
| Failed | 3 |
| Warning | 4 |
| Disabled | 5 |
| Stopped | 6 |
| Rejected | 7 |
## Manager
**GET** http://localhost:8000/api/v1/search?s={keyword}
Search for workflows.
**GET** http://localhost:8000/api/v1/search-approval-workflows?s={keyword}
Search for approval workflows.
**GET** http://localhost:8000/api/v1/workflow?w={id}
Returns a workflow from its id.
**POST** http://localhost:8000/api/v1/start?w={id}
Starts a workflow.
**POST** http://localhost:8000/api/v1/start-with-variables
Starts a workflow with variables.
Here is a sample payload:
```json
{
"WorkflowId":131,
"Variables":[
{
"Name":"restVar1",
"Value":"C:\\WexflowTesting\\file1.txt"
},
{
"Name":"restVar2",
"Value":"C:\\WexflowTesting\\file2.txt"
}
]
}
```
Here is a sample workflow:
```xml
```
**POST** http://localhost:8000/api/v1/stop?w={id}&i={jobId}
Stops a workflow.
**POST** http://localhost:8000/api/v1/suspend?w={id}&i={jobId}
Suspends a workflow.
**POST** http://localhost:8000/api/v1/resume?w={id}&i={jobId}
Resumes a workflow.
**POST** http://localhost:8000/api/v1/approve?w={id}&i={jobId}
Approves a workflow.
**POST** http://localhost:8000/api/v1/disapprove?w={id}&i={jobId}
Disapproves a workflow.
**GET** http://localhost:8000/api/v1/job?w={workflowId}&i={jobId}
Returns currently running workflow job. When job finishes, it gets removed from jobs queue which will result in an empty response but if it is the latest job it will always return the latest job status even if the job ended.
Here is a sample response:
```json
{
"DbId": "16",
"Id": 43,
"InstanceId": "7d3e39df-2023-445f-9206-dfd8a4ab0a78",
"Name": "Workflow_ExecutionGraph",
"FilePath": null,
"LaunchType": 1,
"IsEnabled": true,
"IsApproval": false,
"EnableParallelJobs": true,
"IsWaitingForApproval": false,
"Description": "Workflow_ExecutionGraph",
"IsRunning": true,
"IsPaused": false,
"Period": "00.00:00:00",
"CronExpression": null,
"IsExecutionGraphEmpty": false,
"LocalVariables": [],
"StartedOn": "19-06-2025 18:23:54",
"RetryCount": 0,
"RetryTimeout": 1500,
"Status": "Running"
}
```
**GET** http://localhost:8000/api/v1/jobs?w={workflowId}
Returns currently running workflow jobs.
## Designer
**GET** http://localhost:8000/api/v1/tasks/{id}
Returns workflow's tasks.
**GET** http://localhost:8000/api/v1/xml/{id}
Returns a workflow as XML.
**GET** http://localhost:8000/api/v1/json/{id}
Returns a workflow as JSON.
**GET** http://localhost:8000/api/v1/task-names
Returns task names.
**GET** http://localhost:8000/api/v1/settings/{taskName}
Returns task settings.
**POST** http://localhost:8000/api/v1/task-to-xml
Returns a task as XML.
**GET** http://localhost:8000/api/v1/is-workflow-id-valid/{id}
Checks if a workflow id is valid.
**GET** http://localhost:8000/api/v1/is-cron-expression-valid?e={cronExpression}
Checks if a cron expression is valid.
**GET** http://localhost:8000/api/v1/is-period-valid/{period}
Checks if a period is valid.
**POST** http://localhost:8000/api/v1/is-xml-workflow-valid
Checks if the XML of a workflow is valid.
**POST** http://localhost:8000/api/v1/save-xml
Saves a workflow from XML.
**POST** http://localhost:8000/api/v1/save
Saves a workflow from JSON.
**POST** http://localhost:8000/api/v1/delete?w={id}
Deletes a workflow.
**POST** http://localhost:8000/api/v1/delete-workflows
Deletes workflows.
**GET** http://localhost:8000/api/v1/graph/{id}
Returns the execution graph of the workflow.
## History
**GET** http://localhost:8000/api/v1/history-entries-count-by-date?s={keyword}&from={date}&to={date}
Returns history entries count by keyword and date filter.
**GET** http://localhost:8000/api/v1/search-history-entries-by-page-order-by?s={keyword}&from={date}&to={date}&page={page}&entriesCount={entriesCount}&heo={orderBy}
Searches for history entries.
**GET** http://localhost:8000/api/v1/history-entry-status-date-min
Returns history entry min date.
**GET** http://localhost:8000/api/v1/history-entry-status-date-max
Returns history entry max date.
## Users
**GET** http://localhost:8000/api/v1/user?username={username}
Returns a user from his username.
**GET** http://localhost:8000/api/v1/search-users?keyword={keyword}&uo={orderBy}
Searches for users.
**POST** http://localhost:8000/api/v1/insert-user?username={username}&password={password}&up={userProfile}&email={email}
Inserts a user.
**POST** http://localhost:8000/api/v1/update-user?userId={userId}&username={username}&password={password}&up={userProfile}&email={email}
Updates a user.
**POST** http://localhost:8000/api/v1/update-username-email-user-profile?userId={userId}&username={username}&password={password}&up={userProfile}&email={email}
Updates the username, the email and the user profile of a user.
**POST** http://localhost:8000/api/v1/delete-user?username={username}&password={password}
Deletes a user.
**POST** http://localhost:8000/api/v1/reset-password?username={username}
Resets a password.
## Profiles
**GET** http://localhost:8000/api/v1/search-admins?keyword={keyword}&uo={orderBy}
Searches for administrators.
**GET** http://localhost:8000/api/v1/user-workflows?u={userId}
Returns user workflows.
**POST** http://localhost:8000/api/v1/save-user-workflows
Saves user workflow relations.
---
# Document: Reddit
> Source: https://github.com/aelassas/wexflow/wiki/Reddit
```xml
```
---
# Document: RedditListComments
> Source: https://github.com/aelassas/wexflow/wiki/RedditListComments
```xml
```
---
# Document: RedditListPosts
> Source: https://github.com/aelassas/wexflow/wiki/RedditListPosts
```xml
```
---
# Document: Rmdir
> Source: https://github.com/aelassas/wexflow/wiki/Rmdir
```xml
```
---
# Document: Ruby Client
> Source: https://github.com/aelassas/wexflow/wiki/Ruby-Client
## Prerequisites
* Install [Ruby](https://www.ruby-lang.org/)
## SSE Client Sample
Here is a sample Ruby client `client.rb`:
```rb
require 'net/http'
require 'json'
require 'uri'
BASE_URL = 'http://localhost:8000/api/v1'
USERNAME = 'admin'
PASSWORD = 'wexflow2018'
WORKFLOW_ID = 41
def login(user, pass, stay_connected = false)
uri = URI("#{BASE_URL}/login")
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req.body = {
username: user,
password: pass,
stayConnected: stay_connected
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) }
unless res.is_a?(Net::HTTPSuccess)
raise "Login failed: HTTP #{res.code} #{res.message}"
end
data = JSON.parse(res.body)
data['access_token']
end
def start_workflow(token, workflow_id)
uri = URI("#{BASE_URL}/start?w=#{workflow_id}")
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{token}"
res = Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) }
unless res.is_a?(Net::HTTPSuccess)
raise "Failed to start workflow: HTTP #{res.code} #{res.message}"
end
res.body
end
begin
token = login(USERNAME, PASSWORD)
job_id = start_workflow(token, WORKFLOW_ID)
puts "Workflow #{WORKFLOW_ID} started successfully. Job ID: #{job_id}"
rescue => e
puts "Error: #{e}"
end
```
To run the client, use the following command:
```bash
ruby client.rb
```
---
# Document: Ruby SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/Ruby-SSE-Client
## Prerequisites
* Install [Ruby](https://www.ruby-lang.org/)
* Add these gems to your project:
```bash
gem install em-eventsource
```
## SSE Client Sample
Here is a sample SSE Ruby client `sse.rb`:
```rb
require 'net/http'
require 'uri'
require 'json'
require 'em-eventsource'
BASE_URL = 'http://localhost:8000/api/v1'
USERNAME = 'admin'
PASSWORD = 'wexflow2018'
WORKFLOW_ID = 41
def login(username, password, stay_connected = false)
uri = URI("#{BASE_URL}/login")
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req.body = { username: username, password: password, stayConnected: stay_connected }.to_json
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(req)
end
unless res.is_a?(Net::HTTPSuccess)
raise "Login failed: #{res.code} #{res.message}"
end
json = JSON.parse(res.body)
json['access_token']
end
def start_workflow(token, workflow_id)
uri = URI("#{BASE_URL}/start?w=#{workflow_id}")
req = Net::HTTP::Post.new(uri, 'Authorization' => "Bearer #{token}")
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(req)
end
unless res.is_a?(Net::HTTPSuccess)
raise "Start workflow failed: #{res.code} #{res.message}"
end
JSON.parse(res.body)
end
begin
token = login(USERNAME, PASSWORD)
job_id = start_workflow(token, WORKFLOW_ID)
puts "Workflow #{WORKFLOW_ID} started. Job ID: #{job_id}"
sse_url = "#{BASE_URL}/sse/#{WORKFLOW_ID}/#{job_id}"
EM.run do
source = EventMachine::EventSource.new(sse_url, nil, { 'Authorization' => "Bearer #{token}" })
source.message do |msg|
begin
data = JSON.parse(msg)
puts "Received SSE JSON: #{data}"
source.close
rescue => e
puts "Failed to parse SSE JSON: #{e.message}"
end
end
source.error do |err|
puts "SSE error: #{err}"
source.close
end
source.open do
puts "SSE connection opened"
end
source.start
end
rescue => e
puts "Error: #{e.message}"
end
```
To run the client, use the following command:
```bash
ruby sse.rb
```
---
# Document: Run From Source
> Source: https://github.com/aelassas/wexflow/wiki/Run-From-Source
To run Wexflow from source on Windows, proceed as follows:
- Clone the source down to your machine:
```
git clone https://github.com/aelassas/wexflow.git
```
- Install Visual Studio 2026.
- **.NET Framework 4.8 (legacy)**: Copy the folders `Wexflow` and `WexflowTesting` in C:\\. You can download them from [here](https://wexflow.github.io/content/net.zip).
- **.NET 9.0+ (stable)**: Copy the folders `Wexflow-netcore` and `WexflowTesting` in C:\\. You can download them from [here](https://wexflow.github.io/content/netcore.zip).
- If you installed Wexflow, make sure that Wexflow Windows Service is stopped and that the port 8000 is available. Othewise, you can change the port from the settings.
- Restore nuget packages with the following command:
```
nuget restore Wexflow.sln
```
- Open Wexflow.sln in Visual Studio.
- Make sure that all the projects are set to **Debug Any CPU**.
- Debug the project Wexflow.Server to start Wexflow Server in debug mode.
- Visit http://localhost:8000/
- Username: `admin`
- Password: `wexflow2018`
---
# Document: Rust Client
> Source: https://github.com/aelassas/wexflow/wiki/Rust-Client
## Prerequisites
* Install [Rust](https://www.rust-lang.org/)
* On Windows Install C++ build tools for Visual Studio and Windows 11 SDK
## Client Sample
1. Create a new Rust project:
```bash
cargo new wexflow_client
cd wexflow_client
```
2. Edit `Cargo.toml`:
```toml
[package]
name = "wexflow_client"
version = "0.1.0"
edition = "2024"
[dependencies]
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
```
3. Create `src/main.rs`:
```rs
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use std::error::Error;
const BASE_URL: &str = "http://localhost:8000/api/v1";
const USERNAME: &str = "admin";
const PASSWORD: &str = "wexflow2018";
const WORKFLOW_ID: u32 = 41;
#[derive(Serialize)]
struct LoginRequest<'a> {
username: &'a str,
password: &'a str,
#[serde(rename = "stayConnected")]
stay_connected: bool,
}
#[derive(Deserialize)]
struct LoginResponse {
access_token: String,
}
#[tokio::main]
async fn main() -> Result<(), Box> {
let token = login(USERNAME, PASSWORD).await?;
let job_id = start_workflow(&token, WORKFLOW_ID).await?;
println!("Workflow {} started successfully. Job ID: {}", WORKFLOW_ID, job_id);
Ok(())
}
async fn login(username: &str, password: &str) -> Result> {
let client = reqwest::Client::new();
let login_body = LoginRequest {
username,
password,
stay_connected: false,
};
let res = client
.post(format!("{}/login", BASE_URL))
.json(&login_body)
.send()
.await?;
if res.status() != StatusCode::OK {
return Err(format!("Login failed: HTTP {} {}", res.status(), res.text().await?).into());
}
let login_response: LoginResponse = res.json().await?;
Ok(login_response.access_token)
}
async fn start_workflow(token: &str, workflow_id: u32) -> Result> {
let client = reqwest::Client::new();
let url = format!("{}/start?w={}", BASE_URL, workflow_id);
let res = client
.post(&url)
.bearer_auth(token)
.send()
.await?;
if res.status() != StatusCode::OK {
return Err(format!("Failed to start workflow: HTTP {} {}", res.status(), res.text().await?).into());
}
let job_id = res.text().await?;
Ok(job_id)
}
```
To run the client, use the following command:
```bash
cargo run
```
---
# Document: Rust SSE Client
> Source: https://github.com/aelassas/wexflow/wiki/Rust-SSE-Client
## Prerequisites
* Install [Rust](https://www.rust-lang.org/)
* On Windows Install C++ build tools for Visual Studio and Windows 11 SDK
## Client Sample
1. Create a new Rust project:
```bash
cargo new rust_sse
cd rust_sse
```
2. Edit `Cargo.toml`:
```toml
[package]
name = "wexflow_sse_client"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json", "stream"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
futures-util = "0.3"
```
3. Create `src/main.rs`:
```rs
use futures_util::StreamExt;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::error::Error;
const BASE_URL: &str = "http://localhost:8000/api/v1";
const USERNAME: &str = "admin";
const PASSWORD: &str = "wexflow2018";
const WORKFLOW_ID: u32 = 41;
#[derive(Serialize)]
struct LoginPayload<'a> {
username: &'a str,
password: &'a str,
#[serde(rename = "stayConnected")]
stay_connected: bool,
}
#[derive(Deserialize)]
struct LoginResponse {
access_token: String,
}
#[tokio::main]
async fn main() -> Result<(), Box> {
let client = Client::new();
let token = login(&client).await?;
let job_id = start_workflow(&client, &token).await?;
println!("Workflow {} started. Job ID: {}", WORKFLOW_ID, job_id);
let sse_url = format!("{}/sse/{}/{}", BASE_URL, WORKFLOW_ID, job_id);
listen_to_sse(&client, &sse_url, &token).await?;
Ok(())
}
async fn login(client: &Client) -> Result> {
let payload = LoginPayload {
username: USERNAME,
password: PASSWORD,
stay_connected: false,
};
let res = client
.post(&format!("{}/login", BASE_URL))
.json(&payload)
.send()
.await?;
if !res.status().is_success() {
return Err(format!("Login failed: {}", res.status()).into());
}
let data: LoginResponse = res.json().await?;
Ok(data.access_token)
}
async fn start_workflow(client: &Client, token: &str) -> Result> {
let url = format!("{}/start?w={}", BASE_URL, WORKFLOW_ID);
let res = client.post(&url).bearer_auth(token).send().await?;
if !res.status().is_success() {
return Err(format!("Start workflow failed: {}", res.status()).into());
}
let job_id: String = res.json().await?;
Ok(job_id)
}
async fn listen_to_sse(client: &Client, url: &str, token: &str) -> Result<(), Box> {
let res = client
.get(url)
.bearer_auth(token)
.header("Accept", "text/event-stream")
.send()
.await?;
println!("SSE connection opened");
let mut lines = res.bytes_stream();
while let Some(chunk) = lines.next().await {
let bytes = chunk?;
let line = String::from_utf8_lossy(&bytes);
for l in line.lines() {
if l.starts_with("data: ") {
let json_data = &l[6..];
match serde_json::from_str::(json_data) {
Ok(value) => {
println!(
"Received SSE JSON:\n{}",
serde_json::to_string_pretty(&value)?
);
println!("SSE connection closed");
return Ok(()); // Exit after first message
}
Err(err) => {
println!("Failed to parse SSE JSON: {}", err);
return Ok(());
}
}
}
}
}
Ok(())
}
```
To run the client, use the following command:
```bash
cargo run
```
---
# Document: SSL
> Source: https://github.com/aelassas/wexflow/wiki/SSL
HTTPS/SSL support is available in Wexflow starting from version 9.2. You can enable it for both .NET 4.8 and .NET 9.0+ versions.
# Table Of Contents
1. [Installation Options](https://github.com/aelassas/wexflow/wiki/SSL#installation-options)
1. [.NET 4.8](https://github.com/aelassas/wexflow/wiki/SSL#net-48)
1. [.NET 9.0+](https://github.com/aelassas/wexflow/wiki/SSL#net-90)
1. [Development Certificates (Self-Signed)](https://github.com/aelassas/wexflow/wiki/SSL#development-certificates-self-signed)
1. [Admin Panel](https://github.com/aelassas/wexflow/wiki/SSL#admin-panel)
1. [.NET 9.0+](https://github.com/aelassas/wexflow/wiki/SSL#net-90-1)
1. [Prerequisites](https://github.com/aelassas/wexflow/wiki/SSL#prerequisites)
1. [Windows](https://github.com/aelassas/wexflow/wiki/SSL#windows)
1. [Linux](https://github.com/aelassas/wexflow/wiki/SSL#linux)
1. [macOS](https://github.com/aelassas/wexflow/wiki/SSL#macos)
1. [Notes](https://github.com/aelassas/wexflow/wiki/SSL#notes)
1. [Wexflow Server (Windows)](https://github.com/aelassas/wexflow/wiki/SSL#wexflow-server-windows)
1. [Wexflow Server (Linux)](https://github.com/aelassas/wexflow/wiki/SSL#wexflow-server-linux)
1. [Wexflow Server (macOS)](https://github.com/aelassas/wexflow/wiki/SSL#wexflow-server-macos)
1. [.NET 4.8](https://github.com/aelassas/wexflow/wiki/SSL#net-48-1)
1. [Enable HTTPS for Wexflow Windows Service](https://github.com/aelassas/wexflow/wiki/SSL#enable-https-for-wexflow-windows-service)
1. [Install the certificate](https://github.com/aelassas/wexflow/wiki/SSL#install-the-certificate)
1. [Get private key path](https://github.com/aelassas/wexflow/wiki/SSL#get-private-key-path)
1. [Grant permissions to Network Service and SYSTEM](https://github.com/aelassas/wexflow/wiki/SSL#grant-permissions-to-network-service-and-system)
1. [Bind the certificate to port 8000](https://github.com/aelassas/wexflow/wiki/SSL#bind-the-certificate-to-port-8000)
# Installation Options
You can find detailed installation instructions [here](https://github.com/aelassas/wexflow/wiki/Installing).
## .NET 4.8
You can install Wexflow as a Windows Service (targeting .NET Framework 4.8) using one of the following [installers](https://github.com/aelassas/wexflow/releases/latest):
* wexflow-x.x-windows-x64.exe (64-bit)
* wexflow-x.x-windows-x86.exe (32-bit)
## .NET 9.0+
You can install Wexflow as a cross-platform app/service (targeting .NET 9.0+) on Windows, Linux, or macOS using one of the following [packages](https://github.com/aelassas/wexflow/releases/latest):
* wexflow-x.x-windows-netcore.zip
* wexflow-x.x-linux-netcore.zip
* wexflow-x.x-macos-netcore.zip
> **Note:**
> Many of the operations in this guide — including installing services, managing certificates, and configuring firewalls or port bindings — require **Administrator (Windows)** or **root (Linux/macOS)** privileges.
> Be sure to run your terminal or PowerShell with elevated permissions where applicable.
# Development Certificates (Self-Signed)
To generate a self-signed certificate for development:
- On **Windows**, use the [ssl.ps1](https://github.com/aelassas/wexflow/blob/main/src/netcore/Wexflow.Server/ssl.ps1) script
- On **Linux**, use the [ssl.sh](https://github.com/aelassas/wexflow/blob/main/src/netcore/Wexflow.Server/ssl.sh) script
You can test with the following endpoint: https://localhost:8000/api/v1/hello
# Admin Panel
No configuration is required to use the admin panel.
If you plan to host the admin panel on a different web server and use **HTTPS** for the Wexflow server, make sure the web server hosting the admin panel also uses **HTTPS**. This is necessary to avoid mixed content issues in modern browsers.
By default, the admin panel is available at: https://localhost:8000/
# .NET 9.0+
## Prerequisites
Generate `.pfx` certificate file.
### Windows
1. Install [Win64 OpenSSL](https://slproweb.com/products/Win32OpenSSL.html)
2. Add `C:\Program Files\OpenSSL-Win64\bin` to your `PATH` environment variable
3. Open a PowerShell and run the following command to export your certificate to PFX:
```powershell
$KEY = "C:\Wexflow-netcore\wexflow.key"
$CRT = "C:\Wexflow-netcore\wexflow.crt"
$PFX = "C:\Wexflow-netcore\wexflow.pfx"
$PASSWORD = "wexflow2018"
openssl pkcs12 -export -out $PFX -inkey $KEY -in $CRT -password pass:$PASSWORD
```
### Linux
```bash
KEY="/opt/wexflow/Wexflow/wexflow.key"
CRT="/opt/wexflow/Wexflow/wexflow.crt"
PFX="/opt/wexflow/Wexflow/wexflow.pfx"
PASSWORD="wexflow2018"
openssl pkcs12 -export -out "$PFX" -inkey "$KEY" -in "$CRT" -password pass:"$PASSWORD"
```
### macOS
```bash
KEY="/Applications/wexflow/Wexflow/wexflow.key"
CRT="/Applications/wexflow/Wexflow/wexflow.crt"
PFX="/Applications/wexflow/Wexflow/wexflow.pfx"
PASSWORD="wexflow2018"
openssl pkcs12 -export -out "$PFX" -inkey "$KEY" -in "$CRT" -password pass:"$PASSWORD"
```
### Notes
- **Self-signed certificate warning:**
If you're using a self-signed certificate, browsers will show a security warning unless the certificate is explicitly trusted on your system.
## Wexflow Server (Windows)
Edit `.\Wexflow.Server\appsettings.json`:
```json
{
"HTTPS": true,
"PfxFile": "C:\\Wexflow-netcore\\wexflow.pfx",
"PfxPassword": "wexflow2018"
}
```
Then restart the server.
## Wexflow Server (Linux)
Place your PXF in `/opt/wexflow/Wexflow/wexflow.pfx`.
Edit `/opt/Wexflow/Wexflow.Server/appsettings.json`:
```json
{
"HTTPS": true,
"PfxFile": "/opt/wexflow/Wexflow/wexflow.pfx",
"PfxPassword": "wexflow2018"
}
```
Open Terminal and run the following command to restart `wexflow` service:
```bash
sudo systemctl restart wexflow
```
## Wexflow Server (macOS)
Place your PFX in `/Applications/wexflow/Wexflow/wexflow.pfx`.
Edit `/Applications/Wexflow/Wexflow.Server/appsettings.json`:
```json
{
"HTTPS": true,
"PfxFile": "/Applications/wexflow/Wexflow/wexflow.pfx",
"PfxPassword": "wexflow2018"
}
```
Open Terminal and run:
```bash
cd /Applications/wexflow/Wexflow.Server
dotnet Wexflow.Server.dll
```
# .NET 4.8
## Enable HTTPS for Wexflow Windows Service
1. Set `HTTPS` option to `true` in `C:\Program Files\Wexflow\Wexflow.Server.exe.config`
2. Restart Wexflow Windows Service
## Install the certificate
- Open the **MMC** console (`Win + R`, then type `mmc`)
- Install `wexflow.crt` in **Trusted Root Certification Authorities** (Local Computer)
- Install `wexflow.pfx` in **Personal** store (Local Computer)
- Locate your SSL certificate, double-click it
- Go to the **Details** tab, find **Thumbprint**
- Copy the thumbprint and **remove all spaces**
## Get private key path
```powershell
$thumb = "81d53a62964240b8d2cc77b40bf7e6c758554afc"
$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq $thumb }
$keyName = $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$keyPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\$keyName"
$keyPath
```
Example output:
```
C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\088395182206fa2acc494753b3099972_4ced353e-566d-4394-821c-bc9f487c4b5b
```
## Grant permissions to Network Service and SYSTEM
```powershell
icacls "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\088395182206fa2acc494753b3099972_4ced353e-566d-4394-821c-bc9f487c4b5b" /grant *S-1-5-20:R
icacls "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\088395182206fa2acc494753b3099972_4ced353e-566d-4394-821c-bc9f487c4b5b" /grant *S-1-5-18:R
```
## Bind the certificate to port 8000
- Generate a new GUID for appid in PowerShell:
```powershell
New-Guid
```
- Run the following command (replace the `certhash` and `appid` with your values):
```powershell
netsh http add sslcert ipport=0.0.0.0:8000 certhash=81d53a62964240b8d2cc77b40bf7e6c758554afc appid="{05e46c28-0ed2-4ac0-9473-e78190a425d4}"
```
- Verify the binding:
```powershell
netsh http show sslcert ipport=0.0.0.0:8000
```
---
# Document: Samples
> Source: https://github.com/aelassas/wexflow/wiki/Samples
Workflows can be designed through the Designer, through XML or JSON. However, it is highly recommended to understand Wexflow workflows syntax in order to become familiar with this workflow engine.
Each workflow in Wexflow has a configuration (XML/JSON). Each configuration contains a set of settings and tasks to do depending on a specified schedule and a specified configuration.
# Workflow samples
In this section, few workflow samples will be presented in order to make the end user familiar with Wexflow workflow syntax:
1. [Sequential workflows](https://github.com/aelassas/wexflow/wiki/Samples#sequential-workflows)
2. [Execution graph](https://github.com/aelassas/wexflow/wiki/Samples#execution-graph)
3. [Flowchart workflows](https://github.com/aelassas/wexflow/wiki/Samples#flowchart-workflows)
1. [If](https://github.com/aelassas/wexflow/wiki/Samples#if)
2. [While](https://github.com/aelassas/wexflow/wiki/Samples#while)
3. [Switch](https://github.com/aelassas/wexflow/wiki/Samples#switch)
4. [Approval workflows](https://github.com/aelassas/wexflow/wiki/Samples#approval-workflows)
1. [Simple approval workflow](https://github.com/aelassas/wexflow/wiki/Samples#simple-approval-workflow)
2. [OnRejected workflow event](https://github.com/aelassas/wexflow/wiki/Samples#onrejected-workflow-event)
3. [YouTube approval workflow](https://github.com/aelassas/wexflow/wiki/Samples#youtube-approval-workflow)
4. [Form submission approval workflow](https://github.com/aelassas/wexflow/wiki/Samples#form-submission-approval-workflow)
5. [Workflow events](https://github.com/aelassas/wexflow/wiki/Samples#workflow-events)
## Sequential workflows
A sequential workflow executes a set of tasks in order, one by one. Tasks are executed in a sequential manner until the last task finishes. The order of the execution of the tasks can be altered by modifying the execution graph of the workflow.
### Workflow 1
This workflow uploads invoices to an SFTP server, then waits for 2 days and then notifies the customers.
```xml
```
First of all, the FilesLoader task loads all the invoices located in the folder C:\WexflowTesting\Invoices\, then the Ftp task uploads them to the SFTP server, then the Wait task waits for 2 days, then the FilesLoader task loads the emails in XML format and then the MailsSender task sends the emails. Finally, the FilesMover task moves the invoices to the folder C:\WexflowTesting\Invoices_sent\.
### Workflow 2
This workflow waits for files to arrive in C:\WexflowTesting\Watchfolder1\ and C:\WexflowTesting\Watchfolder2\ then uploads them to an FTP server then moves them to C:\WexflowTesting\Sent\ folder. This workflow starts every 2 minutes.
```xml
```
First of all, the FilesLoader task loads all the files located in the folders C:\WexflowTesting\Watchfolder1\ and C:\WexflowTesting\Watchfolder2\ then the Ftp task loads the files and uploads them to the FTP server. Finally, the FilesMover task moves the files to the folder C:\WexflowTesting\Sent\.
If you want to trigger tasks on file events, you should use [FileSystemWatcher](https://github.com/aelassas/wexflow/wiki/FileSystemWatcher) task to avoid having a lot of logs. [Here](https://github.com/aelassas/wexflow/blob/main/samples/net/Wexflow/Workflows/Workflow_FileSystemWatcher.xml) is a sample workflow.
### Workflow 3
This workflow transcodes the WAV files located in C:\WexflowTesting\WAV\ to MP3 format through FFMPEG and moves the transcoded files to C:\WexflowTesting\MP3\.
```xml
```
First of all, the FilesLoader task loads all the files located in the folder C:\WexflowTesting\WAV\ then the ProcessLauncher task launches FFMPEG process on every file by specifying the right command in order to create the MP3 file. Finally, the FilesMover task moves the MP3 files to the folder C:\WexflowTesting\MP3\.
### Workflow 4
This workflow waits for WAV files to arrive in C:\WexflowTesting\WAV\ then transcodes them to MP3 files through VLC then uploads the MP3 files to an FTP server then moves the WAV files to C:\WexflowTesting\WAV_processed\. This workflow starts every 2 minutes.
```xml
```
First of all, the FilesLoader task loads all the files located in the folder C:\WexflowTesting\WAV\ then the ProcessLauncher task launches VLC process on every file by specifying the right command in order to create the MP3 file. Then, the Ftp task loads the MP3 files generated by the ProcessLauncher task and then uploads them to the FTP server. Finally, the FilesMover task moves the processed WAV files to the folder C:\WexflowTesting\WAV_processed\.
If you want to trigger tasks on file events, you should use [FileSystemWatcher](https://github.com/aelassas/wexflow/wiki/FileSystemWatcher) task to avoid having a lot of logs. [Here](https://github.com/aelassas/wexflow/blob/main/samples/net/Wexflow/Workflows/Workflow_FileSystemWatcher.xml) is a sample workflow.
### Workflow 5
This workflow downloads specific files from an FTP server. This workflow starts by listing all the files located at the root folder of the server, then the specific files that will be downloaded are tagged through an XSLT (LisFiles.xslt), then the files are downloaded by the Ftp task through todo="toDownload" and from="app4" tags, then the downloaded files are moved to the folder C:\WexflowTesting\Ftp_download\.
```xml
```
Roughly speaking, the Ftp task loads the list of files located at the root folder of the FTP server in the running instance of the workflow, then the ListFiles task outputs and XML file that contains all the files loaded then the Xslt task takes as input this XML and generates an XML which contains a system node called WexflowProcessing which contains the list of files to be tagged and/or renamed.
To understand how tagging and renaming files work, refer to the documentation of the ListFiles and Xslt tasks.
Below is the XSLT ListFiles.xslt used for tagging files:
```xml
```
## Execution graph
This workflow loads the file C:\WexflowTesting\file1.txt then uploads it to an FTP server then moves it to C:\WexflowTesting\Sent\ folder.
```xml
```
First of all, the FilesLoader task loads the file C:\WexflowTesting\file1.txt then the Ftp task loads that file and uploads it to the FTP server. Finally, the FilesMover task moves that file to the folder C:\WexflowTesting\Sent\.
By convention, the parent task id of the first task to be executed must always be -1.
However, if the execution graph is modified as follows:
```xml
```
Task 3 will be executed after task 1.
If the execution graph is modified as follows:
```xml
```
Task 3 will be executed first, then task 2 then task 1.
Two things are forbidden in the execution graph:
- Infinite loops.
- Parallel tasks.
Here is an example of infinite loops:
```xml
```
Here is an example of parallel tasks:
```xml
```
## Flowchart workflows
A flowchart workflow is a workflow that contains at least one flowchart node (If/While/Switch) in its execution graph. A flowchart node takes as input a flowchart task and a set of tasks to execute in order, one by one. The order of the execution of the tasks can be altered by modifying the execution graph of the flowchart node.
### If
The following workflow is a flowchart workflow that is triggered by the file file.trigger. If the file file.trigger is found on the file system then this workflow will upload the file file1.txt to an FTP server then it will notify customers that the upload was successful. Otherwise, if the trigger file.trigger is not found on the file system then the workflow will notify customers that the upload failed.
```xml
```
By convention, the parent task id of the first task to execute in and nodes must always be -1.
You can add If flowchart nodes pretty much wherever you want in the execution graph. Also, you can add as mush as you want. You can also add them in the event nodes OnSuccess, OnWarning and OnError.
An If can be inside an If, a While and a Switch.
### While
This workflow is triggered by the file file.trigger. While the file file.trigger exists, this workflow will upload the file file1.txt to an FTP server then it will notify customers then it will wait for 2 days then it will start again.
```xml
```
By convention, the parent task id of the first task to be executed in the node must always be -1.
You can add While flowchart nodes pretty much wherever you want in the execution graph. Also, you can add as mush as you want. You can also add them in the event nodes OnSuccess, OnWarning and OnError.
A While can be inside a While, an If and a Switch.
### Switch
This workflow starts every 24 hours. On Monday, it uploads files to an FTP server and on Wednesday it notifies customers.
```xml
```
By convention, the parent task id of the first task to be executed in the Case/Default nodes must always be -1.
You can add Switch flowchart nodes pretty much wherever you want in the execution graph. Also, you can add as mush as you want. You can also add them in the event nodes OnSuccess, OnWarning and OnError.
A Switch can be inside a While, an If and a Switch.
## Approval workflows
Approval workflows are workflows marked as approval through **approval** setting option. They can be marked as approval whether from the Designer page in the back end or by XML editing:
```xml
```
Approval workflows must contain at least one [Approval](https://github.com/aelassas/wexflow/wiki/Approval) task. Approval tasks can be put wherever you want in the workflow and can be multiple. You can create workflows where some tasks get done then the workflow waits for approval then the users are notified for example.
Workflows are being approved whether from Wexflow Manager or from Approval page in the backend.
If the workflow is rejected the OnRejected workflow event is raised and the tasks after Approval task are not executed.
The rejection of workflows can be done by clicking on reject button whether from Approval page in the back end or from Wexflow Manager.
### Simple approval workflow
To give you a hint on how approval workflows work, here is a very simple example:
```xml
```
This simple workflow is an approval workflow that waits for approval in order to start. Once approved, this workflow waits for 2 seconds. This workflow can be approved or rejected whether from Wexflow Manager or from Approval page in the backend.
### OnRejected workflow event
Here is another simple approval workflow:
```xml
```
This simple workflow is an approval workflow that waits for approval in order to start. Once approved, this workflow waits for 2 seconds. If this workflow is rejected, the task 2 is not executed and the task 3 is executed. In other words, if this workflow is rejected it waits for 3 seconds. This workflow can be approved or rejected whether from Wexflow Manager or from Approval page in the backend.
### YouTube approval workflow
Here is a more professional approval workflow:
```xml
```
This workflow starts by uploading videos to YouTube then it waits for approval in order to check that videos have been effectively uploaded with success to YouTube and edited by the management team. Then, if this workflow is approved from Approval page in the backend or from Wexflow Manager.
When this workflow arrives to Approval task, it suspends its jobs and waits for approval process until it's being approved and then continues its tasks.
### Form submission approval workflow
Here is another interesting approval workflow:
```xml
```
This approval workflow opens a submission form and waits for approval. If the submission is correct, the workflow is approved and waits for 2 seconds (this is just a simple task for testing but you can add email tasks or whatever). Otherwise, if the submission is incorrect, the workflow is rejected and waits for 3 seconds (this is just a simple task for testing but you can add email tasks or whatever). This workflow works on the .NET Core version of Wexflow only because the .NET version of Wexflow does not support opening GUI from ProcessLauncher task since Wexflow server is running in a Windows service in the .NET version.
Approval workflows are very useful when some tasks get done then you have to wait for approval to check that previous tasks have been done with success then users are notified for example. This is just an example, but you can create and imagine other examples as you want and as you need.
### Records
Wexflow allows approval workflows on generic assets called records. A record is an entity that refers to a file. Each record has a name, a description, file versions, comments, and approval start and end dates.
A manager can assign a record to a user. If the record is updated with required info, the manajer can approve or reject the workflow.
Here is a simple approval workflow on records:
```xml
```
The manager assigns the record 1 to the user wexflow. The user wexflow will receive a notification in Wexflow and an email if email settings are properly configured to edit the record in question. After the record is updated, the manager will receive a notification and can then check the latest version of the recod. If the record info is good, the manager can approve the workflow. Otherwise, he can reject it.
## Workflow events
After a workflow finishes its job, its final result is either success, or warning or error or rejected. If its final result is success, the OnSuccess event is triggered. If its final result is warning, the OnWarning event is triggered. If its final result is error, the OnError event is triggered. If the workflow is rejected, the OnRejected event is triggered. An event contains a set of tasks and/or flowchart nodes to execute in order, one by one. The order of the execution of the tasks and/or flowchart nodes can be altered by modifying the execution graph of the event.
This workflow uploads the file1.txt to an FTP server then notifies customers in case of success.
```xml
```
The flowchart event nodes OnWarning, OnError and OnRejected can be used in the same way. You can put If and While flowchart nodes in event nodes.
For OnRejected workflow event, the workflow must be an approval workflow and must contain at least one Approval task. The OnRejected workflow event is raised once the end user clicks on reject button whether from the Approval page in the backend or from Wexflow Manager.
These are simple and basic workflows to give an idea on how to make your own workflows. However, if you have multiple systems, applications and automations involved in a workflow, the workflow could be very interesting.
---
# Document: Screenshots
> Source: https://github.com/aelassas/wexflow/wiki/Screenshots
### Dashboard

### Manager

### Designer

### History

### Android App
---
# Document: ScssToCss
> Source: https://github.com/aelassas/wexflow/wiki/ScssToCss
```xml
```
---
# Document: SevenZip
> Source: https://github.com/aelassas/wexflow/wiki/SevenZip
```xml
```
---
# Document: Sha1
> Source: https://github.com/aelassas/wexflow/wiki/Sha1
```xml
```
---
# Document: Sha256
> Source: https://github.com/aelassas/wexflow/wiki/Sha256
```xml
```
---
# Document: Sha512
> Source: https://github.com/aelassas/wexflow/wiki/Sha512
```xml
```
---
# Document: Slack
> Source: https://github.com/aelassas/wexflow/wiki/Slack
```xml
```
---
# Document: SpeechToText
> Source: https://github.com/aelassas/wexflow/wiki/SpeechToText
```xml
```
---
# Document: Sql
> Source: https://github.com/aelassas/wexflow/wiki/Sql
```xml
```
---
# Document: SqlToCsv
> Source: https://github.com/aelassas/wexflow/wiki/SqlToCsv
```xml
```
---
# Document: SqlToXml
> Source: https://github.com/aelassas/wexflow/wiki/SqlToXml
```xml
```
---
# Document: SshCmd
> Source: https://github.com/aelassas/wexflow/wiki/SshCmd
```xml
```
---
# Document: SubWorkflow
> Source: https://github.com/aelassas/wexflow/wiki/SubWorkflow
```xml
```
---
# Document: Tar
> Source: https://github.com/aelassas/wexflow/wiki/Tar
```xml
```
---
# Document: Tasks
> Source: https://github.com/aelassas/wexflow/wiki/Tasks
Wexflow is modular. A workflow executes a set of tasks. The user can choose between the built-in tasks that come with Wexflow or create his own custom [tasks](https://github.com/aelassas/Wexflow/wiki/Custom-Tasks).
Every task is a module which can be enabled, disabled or replaced. Wexflow provides 100+ built-in tasks.
\*: The task is not available in the .NET Core version.
\*\*: The task is available in the .NET Core version only.
1. [File system tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#file-system-tasks)
2. [Encryption tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#encryption-tasks)
3. [Compression tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#compression-tasks)
4. [Iso tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#iso-tasks)
5. [Speech tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#speech-tasks)
6. [Hashing tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#hashing-tasks)
7. [Process tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#process-tasks)
8. [Network tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#network-tasks)
9. [XML tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#xml-tasks)
10. [SQL tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#sql-tasks)
11. [WMI tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#wmi-tasks)
12. [Image tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#image-tasks)
13. [Audio and video tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#audio-and-video-tasks)
14. [Email tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#email-tasks)
15. [Workflow tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#workflow-tasks)
16. [Social media tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#social-media-tasks)
17. [Waitable tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#waitable-tasks)
18. [Reporting tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#reporting-tasks)
19. [Web tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#web-tasks)
20. [Script tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#script-tasks)
21. [JSON and YAML tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#json-and-yaml-tasks)
22. [Entities tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#entities-tasks)
23. [Flowchart tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#flowchart-tasks)
24. [Approval tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#approval-tasks)
25. [Notification tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#notification-tasks)
26. [SMS tasks](https://github.com/aelassas/Wexflow/wiki/Tasks#sms-tasks)
# File system tasks
These tasks allow to create, copy, move, rename or delete files and directories on a file system. These tasks allow also to check whether a collection of remote or local files and/or directories exists. These tasks allow also to check whether two files are the same and allow also to calculate the diff of two files.
- [FilesLoader](https://github.com/aelassas/wexflow/wiki/FilesLoader): This task loads a collection of files located in folders or through the file option.
- [FilesLoaderEx](https://github.com/aelassas/wexflow/wiki/FilesLoaderEx): This task loads a collection of files located in folders or through the file option. This task is inherited from FilesLoader task, but by default result is empty, you must configure file system attributes rules to populate result.
- [FilesCopier](https://github.com/aelassas/wexflow/wiki/FilesCopier): This task copies a collection of files to a destination folder.
- [FilesMover](https://github.com/aelassas/wexflow/wiki/FilesMover): This task moves a collection of files to a destination folder.
- [FilesRemover](https://github.com/aelassas/wexflow/wiki/FilesRemover): This task deletes a collection of files.
- [FilesRenamer](https://github.com/aelassas/wexflow/wiki/FilesRenamer): This task allows to rename a collection of files on a file system. The Xslt task can be used along with the ListFiles task to create new file names.
- [FilesExist](https://github.com/aelassas/wexflow/wiki/FilesExist): This task checks whether a collection of files and/or directories exists.
- [FilesEqual](https://github.com/aelassas/wexflow/wiki/FilesEqual): This task checks whether two files are the same.
- [FilesDiff*](https://github.com/aelassas/wexflow/wiki/FilesDiff): This task calculates the diff of two files.
- [FilesConcat](https://github.com/aelassas/wexflow/wiki/FilesConcat): This task concatenates a collection of files.
- [FilesJoiner](https://github.com/aelassas/wexflow/wiki/FilesJoiner): This task concatenates a collection of files. This task should be used to join and restore original file splitted with "FilesSplitter" task. Original file name is restored from splitted file name with end part "_N".
- [FilesConcat](https://github.com/aelassas/wexflow/wiki/FilesConcat): This task splits files into chunks.
- [FilesInfo](https://github.com/aelassas/wexflow/wiki/FilesInfo): This task generates files information of a collection of files and writes the results in an XML file. The format of the output XML file is described in the documentation of the task.
- [Touch](https://github.com/aelassas/wexflow/wiki/Touch): This task creates a collection of empty files.
- [ListFiles](https://github.com/aelassas/wexflow/wiki/ListFiles): This task lists all the files loaded by the workflow tasks in the logs. This task is useful for resolving issues.
- [Mkdir](https://github.com/aelassas/wexflow/wiki/Mkdir): This task creates a collection of folders.
- [Rmdir](https://github.com/aelassas/wexflow/wiki/Rmdir): This task deletes a collection of folders.
- [Movedir](https://github.com/aelassas/wexflow/wiki/Movedir): This task moves a folder and allows to overwrite the destination folder.
- [FileSystemWatcher](https://github.com/aelassas/wexflow/wiki/FileSystemWatcher): This task watches a hot folder and triggers tasks on file created, changed or deleted.
# Encryption tasks
These tasks allow to encrypt and decrypt any type of files of any size. These tasks allow also to encrypt and decrypt text based files.
- [FilesEncryptor](https://github.com/aelassas/wexflow/wiki/FilesEncryptor): This task encrypts a collection of files.
- [FilesDecryptor](https://github.com/aelassas/wexflow/wiki/FilesDecryptor): This task decrypts the files crypted by the task FilesEncryptor.
- [TextsEncryptor](https://github.com/aelassas/wexflow/wiki/TextsEncryptor): This task encrypts a collection of text based files.
- [TextsDecryptor](https://github.com/aelassas/wexflow/wiki/TextsDecryptor): This task decrypts the files crypted by the task TextsEncryptor.
# Compression tasks
These tasks allow to create a .zip, a .tar, a .tar.gz or a .7z from a collection of files. These tasks allow also to extract .zip, .tar, .tar.gz, .rar or .7z archives.
- [Zip](https://github.com/aelassas/wexflow/wiki/Zip): This task creates a zip archive from a collection of files.
- [Tar](https://github.com/aelassas/wexflow/wiki/Tar): This task creates a tar archive from a collection of files.
- [Tgz](https://github.com/aelassas/wexflow/wiki/Tgz): This task creates a tar.gz archive from a collection of files.
- [SevenZip*](https://github.com/aelassas/wexflow/wiki/SevenZip): This task creates a .7z archive from a collection of files.
- [Unzip](https://github.com/aelassas/wexflow/wiki/Unzip): This task extracts ZIP archives.
- [Untar](https://github.com/aelassas/wexflow/wiki/Untar): This task extracts TAR archives.
- [Untgz](https://github.com/aelassas/wexflow/wiki/Untgz): This task extracts TAR.GZ archives.
- [Unrar*](https://github.com/aelassas/wexflow/wiki/Unrar): This task extracts RAR archives.
- [UnSevenZip*](https://github.com/aelassas/wexflow/wiki/UnSevenZip): This task extracts 7Z archives.
# Iso tasks
These tasks allow to create a .iso from a source folder and to extract a .iso to a destination folder.
- [IsoCreator*](https://github.com/aelassas/wexflow/wiki/IsoCreator): This task creates a .iso from a source folder.
- [IsoExtractor*](https://github.com/aelassas/wexflow/wiki/IsoExtractor): This task extracts .iso files.
# Speech tasks
These tasks allow to convert text to speech and speech to text.
- [TextToSpeech*](https://github.com/aelassas/wexflow/wiki/TextToSpeech): This task converts text files to speech.
- [SpeechToText*](https://github.com/aelassas/wexflow/wiki/SpeechToText): This task converts audio files to text files.
# Hashing tasks
These tasks allow to generate MD5, SHA-1, SHA-256 and SHA-512 hashes of a collection of files.
- [Md5](https://github.com/aelassas/wexflow/wiki/Md5): This task generates MD5 sums of a collection of files and writes the results in an XML file. The format of the output XML file is described in the documentation of the task.
- [Sha1](https://github.com/aelassas/wexflow/wiki/Sha1): This task generates SHA-1 hashes of a collection of files and writes the results in an XML file. The format of the output XML file is described in the documentation of the task.
- [Sha256](https://github.com/aelassas/wexflow/wiki/Sha256): This task generates SHA-256 hashes of a collection of files and writes the results in an XML file. The format of the output XML file is described in the documentation of the task.
- [Sha512](https://github.com/aelassas/wexflow/wiki/Sha512): This task generates SHA-512 hashes of a collection of files and writes the results in an XML file. The format of the output XML file is described in the documentation of the task.
# Process tasks
These tasks allow to start or kill processes on the workflow server. They also allow to retrieve information about a process.
- [ProcessLauncher](https://github.com/aelassas/wexflow/wiki/ProcessLauncher): This task launches a process. If the process generates a file as output It is possible to pass a collection of files to the task so that for each file an output file will be generated through the process. Read the documentation of the task for further informations.
- [ProcessKiller*](https://github.com/aelassas/wexflow/wiki/ProcessKiller): This task kills a process.
- [ProcessInfo](https://github.com/aelassas/wexflow/wiki/ProcessInfo): This task shows information about a process.
- [SshCmd](https://github.com/aelassas/wexflow/wiki/SshCmd): This task executes an SSH command.
# Network tasks
These task allow to list, upload, download or delete files over FTP, FTPS or SFTP. These tasks allow also to download files over HTTP or HTTPS. These tasks allow also to download torrent files, to ping servers and to execute GET/POST/PUT/PATCH/DELETE requests.
- [Ftp](https://github.com/aelassas/wexflow/wiki/Ftp): This task allows to list, upload, download or delete files over FTP, FTPS or SFTP.
- [Http](https://github.com/aelassas/wexflow/wiki/Http): This task allows to downoad files over HTTP or HTTPS.
- [HttpGet](https://github.com/aelassas/wexflow/wiki/HttpGet): This task executes a GET request.
- [HttpPost](https://github.com/aelassas/wexflow/wiki/): This task executes a POST request.
- [HttpPut](https://github.com/aelassas/wexflow/wiki/HttpPut): This task executes a PUT request.
- [HttpPatch](https://github.com/aelassas/wexflow/wiki/HttpPatch): This task executes a PATCH request.
- [HttpDelete](https://github.com/aelassas/wexflow/wiki/HttpDelete): This task executes a DELETE request.
- [Torrent](https://github.com/aelassas/wexflow/wiki/Torrent): This task downloads torrent files.
- [Ping](https://github.com/aelassas/wexflow/wiki/Ping): This is a flowchart task that checks whether a server responds to a ping request or not.
# XML tasks
These tasks allow to work with XML and CSV data. XSLT can be used along with XPath to generate XML documents. XSLT 1.0 and XSLT 2.0 are supported.
- [CsvToXml](https://github.com/aelassas/wexflow/wiki/CsvToXml): This task transforms a CSV file to an XML file.
- [XmlToCsv](https://github.com/aelassas/wexflow/wiki/XmlToCsv): This task transforms an XML file to a CSV file. The format of the input XML file is described in the documentation of the task.
- [Xslt](https://github.com/aelassas/wexflow/wiki/Xslt): This task transforms a collection of XML files. It is possible to use XSLT 1.0 processor or XSLT 2.0 processor.
- [Guid](https://github.com/aelassas/wexflow/wiki/Guid): This task generates Guids and outputs the result in an XML file.
# SQL tasks
These tasks allow to execute SQL scripts. These tasks supports Microsoft Sql Server, Microsoft Access, Oracle, MySql, SQLite, PostGreSql and Teradata. These tasks can be used for bulk insert, for database updates, for database cleanup, for rebuilding indexes, for reorganizing indexes, for shrinking databases, for updating statistics, for transfering database data and so on. These tasks allow also to export SQL data to XML or CSV and to import CSV data to a database. These tasks allow also to backup and restore databases.
- [Sql](https://github.com/aelassas/wexflow/wiki/Sql): This task executes SQL scripts. It supports Microsoft Sql Server, Microsoft Access, Oracle, MySql, SQLite, PostGreSql and Teradata.
- [SqlToXml](https://github.com/aelassas/wexflow/wiki/SqlToXml): This task executes SQL scripts and outputs the results in XML files. It supports Microsoft Sql Server, Microsoft Access, Oracle, MySql, SQLite, PostGreSql and Teradata.
- [SqlToCsv](https://github.com/aelassas/wexflow/wiki/SqlToCsv): This task executes SQL scripts and outputs the results in CSV files. It supports Microsoft Sql Server, Microsoft Access, Oracle, MySql, SQLite, PostGreSql and Teradata.
- [CsvToSql](https://github.com/aelassas/wexflow/wiki/CsvToSql): This task converts CSV files to SQL scripts (SQL Server Insert only).
# WMI tasks
- [Wmi*](https://github.com/aelassas/wexflow/wiki/Wmi): This task executes a WMI query and outputs the results in an XML file. The format of the output XML file is described in the documentation of the task.
# Image tasks
These tasks allow to convert images to the following formats Bmp, Emf, Exif, Gif, Icon, Jpeg, Png, Tiff and Wmf. These tasks allow also to resize or to crop or to concatenate or to overlay images.
- [ImagesTransformer](https://github.com/aelassas/wexflow/wiki/ImagesTransformer): This task transforms a collection of image files to a specified format. The output format can be one of the followings Bmp, Emf, Exif, Gif, Icon, Jpeg, Png, Tiff or Wmf.
- [ImagesResizer](https://github.com/aelassas/wexflow/wiki/ImagesResizer): This task resizes a collection of images.
- [ImagesCropper](https://github.com/aelassas/wexflow/wiki/ImagesCropper): This task crops a collection of images.
- [ImagesConcat](https://github.com/aelassas/wexflow/wiki/ImagesConcat): This task concatenates a collection of images.
- [ImagesOverlay](https://github.com/aelassas/wexflow/wiki/ImagesOverlay): This task overlays a collection of images.
# Audio and video tasks
These tasks allow to convert, cut or edit audio and video files through FFMEG, VLC or any other audio/video software. These tasks can also be used to perform custom operations such as generating images and thumbnails from video files. These tasks allow also to generate the most relevant technical and tag data for video and audio files.
- [MediaInfo*](https://github.com/aelassas/wexflow/wiki/MediaInfo): This task generates the most relevant technical and tag data for video and audio files and outputs the results in an XML file. The format of the output XML file is described in the documentation of the task.
- [YouTube**](https://github.com/aelassas/wexflow/wiki/YouTube): This task uploads videos to YouTube.
- [YouTubeSearch**](https://github.com/aelassas/wexflow/wiki/YouTubeSearch): This task searches for content on YouTube.
- [YouTubeListUploads**](https://github.com/aelassas/wexflow/wiki/YouTubeListUploads): This task retrieves a list of videos uploaded to a YouTube channel.
- [Vimeo](https://github.com/aelassas/wexflow/wiki/Vimeo): This task uploads videos to Vimeo.
- [VimeoListUploads](https://github.com/aelassas/wexflow/wiki/VimeoListUploads): This task retrieves a list of videos uploaded to a Vimeo channel.
The task [ProcessLauncher](https://github.com/aelassas/wexflow/wiki/ProcessLauncher) can be used along with FFMPEG, VLC or any other software in order to perform audio and video tasks.
# Email tasks
This task allows to send or fetch a collection of emails.
- [MailsSender](https://github.com/aelassas/wexflow/wiki/MailsSender): This task sends a collection of emails from XML files. The format of the input XML files is described in the documentation of the task.
- [MailsReceiver](https://github.com/aelassas/wexflow/wiki/MailsReceiver): This task fetches a collection of emails.
# Workflow tasks
- [Workflow*](https://github.com/aelassas/wexflow/wiki/Workflow): This task allows to start, suspend, resume, stop, approve or disapprove a list of workflows.
- [SubWorkflow](https://github.com/aelassas/wexflow/wiki/SubWorkflow): This task kicks off a sub workflow.
# Social media tasks
- [Twitter](https://github.com/aelassas/wexflow/wiki/Twitter): This task sends tweets.
- [InstagramUploadImage](https://github.com/aelassas/wexflow/wiki/InstagramUploadImage): This task uploads images to Instagram.
- [InstagramUploadVideo](https://github.com/aelassas/wexflow/wiki/InstagramUploadVideo): This task uploads videos to Instagram.
- [Reddit**](https://github.com/aelassas/wexflow/wiki/Reddit): This task sends posts and links to Reddit.
- [RedditListPosts**](https://github.com/aelassas/wexflow/wiki/RedditListPosts): This task retrieves Reddit post history.
- [RedditListComments**](https://github.com/aelassas/wexflow/wiki/RedditListComments): This task retrieves Reddit comment history.
# Waitable tasks
- [Wait](https://github.com/aelassas/wexflow/wiki/Wait): This task waits for a specified duration of time.
# Reporting tasks
These tasks allow to generate reports in PDF format from HTML or XML or TXT files.
- [TextToPdf*](https://github.com/aelassas/wexflow/wiki/TextToPdf): This task generates PDF files from TEXT files.
- [HtmlToPdf*](https://github.com/aelassas/wexflow/wiki/HtmlToPdf): This task generates PDF files from HTML files.
- [PdfToText*](https://github.com/aelassas/wexflow/wiki/PdfToText): This task extracts TEXT from PDF files.
The task Xslt can be used to generate HTML reports from XML files. Then, the HTML reports can be transformed into PDF reports through HtmlToPdf task.
# Web tasks
These tasks allow to take screenshots from urls and to download the HTML source code from urls after the pages are rendered. These tasks allow also to uglify, minify and compress JavaScript, CSS and HTML files. These tasks allow also to extract text from HTML files. These tasks allow also to convert SCSS files to CSS files.
- [WebToScreenshot*](https://github.com/aelassas/wexflow/wiki/WebToScreenshot): This task takes screenshots from urls.
- [WebToHtml*](https://github.com/aelassas/wexflow/wiki/WebToHtml): This task retrieves HTML sources from urls.
- [UglifyJs*](https://github.com/aelassas/wexflow/wiki/UglifyJs): This task uglifys JavaScript files.
- [UglifyCss*](https://github.com/aelassas/wexflow/wiki/UglifyCss): This task compresses and minifies CSS files.
- [UglifyHtml*](https://github.com/aelassas/wexflow/wiki/UglifyHtml): This task compresses and minifies HTML files.
- [HtmlToText*](https://github.com/aelassas/wexflow/wiki/HtmlToText): This task extracts text from HTML files.
- [ScssToCss**](https://github.com/aelassas/wexflow/wiki/ScssToCss): This task converts SCSS files to CSS files.
# Script tasks
These tasks allow to execute C# and VB scripts.
- [ExecCs*](https://github.com/aelassas/wexflow/wiki/ExecCs): This task executes C# scripts.
- [ExecPython](https://github.com/aelassas/wexflow/wiki/ExecPython): This task executes Python scripts.
- [ExecVb*](https://github.com/aelassas/wexflow/wiki/ExecVb): This task executes Visual Basic scripts.
# JSON and YAML tasks
These tasks allow to convert YAML files to JSON files, JSON files to YAML files, CSV files to JSON files and CSV files to YAML files.
- [YamlToJson*](https://github.com/aelassas/wexflow/wiki/YamlToJson): This task converts YAML files to JSON files.
- [JsonToYaml*](https://github.com/aelassas/wexflow/wiki/JsonToYaml): This task converts JSON files to YAML files.
- [CsvToJson*](https://github.com/aelassas/wexflow/wiki/CsvToJson): This task converts CSV files to JSON files.
- [CsvToYaml*](https://github.com/aelassas/wexflow/wiki/CsvToYaml): This task converts CSV files to YAML files.
# Entities tasks
- [ListEntities](https://github.com/aelassas/wexflow/wiki/): This task lists all the entities loaded by the workflow tasks in the logs. This task is useful for resolving issues.
# Flowchart tasks
These tasks can be used within flowchart workflows to perform specific jobs.
- [FileExists](https://github.com/aelassas/wexflow/wiki/FileExists): This is a flowchart task that checks whether a given file exists on a file system or not.
- [FileMatch](https://github.com/aelassas/wexflow/wiki/FileMatch): This is a flowchart task that checks whether a file exists or not in a directory by using a regex pattern.
- [FileNotExist](https://github.com/aelassas/wexflow/wiki/FileNotExist): This is a flowchart task that checks whether a given file does not exist on a file system.
- [FileNotMatch](https://github.com/aelassas/wexflow/wiki/FileNotMatch): This is a flowchart task that checks whether a file does not exist in a directory by using a regex pattern.
- [Now](https://github.com/aelassas/wexflow/wiki/Now): This is a flowchart task that retrieves the current date in the specified format. This task is designed to be used in a Switch flowchart node.
- [Ping](https://github.com/aelassas/wexflow/wiki/Ping): This is a flowchart task that checks whether a server responds to a ping request or not.
- [EnvironmentVariable](https://github.com/aelassas/wexflow/wiki/EnvironmentVariable): This is a flowchart task that retrieves the value of an environment variable.
- [MessageCorrect](https://github.com/aelassas/wexflow/wiki/MessageCorrect): This is a flowchart task that checks whether a message is in the memory of the task having as key the value of the setting option checkString.
- [FolderExists](https://github.com/aelassas/wexflow/wiki/FolderExists): This is a flowchart task that checks whether a given folder exists on a file system or not.
- [FileContentMatch](https://github.com/aelassas/wexflow/wiki/FileContentMatch): This task checks whether the content of a file matches a regex pattern.
# Approval tasks
- [Approval](https://github.com/aelassas/wexflow/wiki/Approval): This task marks the current workflow as needing approval.
- [ApproveRecord](https://github.com/aelassas/wexflow/wiki/ApproveRecord): This task assigns a record to a user and launches the approval process on that record.
- [ApprovalRecordsCreator](https://github.com/aelassas/wexflow/wiki/ApprovalRecordsCreator): This task creates records from files.
- [ApprovalWorkflowsCreator](https://github.com/aelassas/wexflow/wiki/ApprovalWorkflowsCreator): This task creates approval workflows for records from shared memory and starts them. The record ids are sent from ApprovalRecordsCreator task.
# Notification tasks
- [Slack](https://github.com/aelassas/wexflow/wiki/Slack): This task sends Slack messages.
# SMS tasks
- [Twilio](https://github.com/aelassas/wexflow/wiki/Twilio): This task sends SMS messages.
---
# Document: TextToPdf
> Source: https://github.com/aelassas/wexflow/wiki/TextToPdf
```xml
```
---
# Document: TextToSpeech
> Source: https://github.com/aelassas/wexflow/wiki/TextToSpeech
```xml
```
---
# Document: TextsDecryptor
> Source: https://github.com/aelassas/wexflow/wiki/TextsDecryptor
```xml
```
---
# Document: TextsEncryptor
> Source: https://github.com/aelassas/wexflow/wiki/TextsEncryptor
```xml
```
---
# Document: Tgz
> Source: https://github.com/aelassas/wexflow/wiki/Tgz
```xml
```
---
# Document: Torrent
> Source: https://github.com/aelassas/wexflow/wiki/Torrent
```xml
```
---
# Document: Touch
> Source: https://github.com/aelassas/wexflow/wiki/Touch
```xml
```
---
# Document: Twilio
> Source: https://github.com/aelassas/wexflow/wiki/Twilio
```xml
```
---
# Document: Twitter
> Source: https://github.com/aelassas/wexflow/wiki/Twitter
```xml
```
---
# Document: UglifyCss
> Source: https://github.com/aelassas/wexflow/wiki/UglifyCss
```xml
```
---
# Document: UglifyHtml
> Source: https://github.com/aelassas/wexflow/wiki/UglifyHtml
```xml
```
---
# Document: UglifyJs
> Source: https://github.com/aelassas/wexflow/wiki/UglifyJs
```xml
```
---
# Document: UnSevenZip
> Source: https://github.com/aelassas/wexflow/wiki/UnSevenZip
```xml
```
---
# Document: Unrar
> Source: https://github.com/aelassas/wexflow/wiki/Unrar
```xml
```
---
# Document: Untar
> Source: https://github.com/aelassas/wexflow/wiki/Untar
```xml
```
---
# Document: Untgz
> Source: https://github.com/aelassas/wexflow/wiki/Untgz
```xml
```
---
# Document: Unzip
> Source: https://github.com/aelassas/wexflow/wiki/Unzip
```xml
```
---
# Document: Vimeo
> Source: https://github.com/aelassas/wexflow/wiki/Vimeo
```xml
```
---
# Document: VimeoListUploads
> Source: https://github.com/aelassas/wexflow/wiki/VimeoListUploads
```xml
```
---
# Document: Wait
> Source: https://github.com/aelassas/wexflow/wiki/Wait
```xml
```
---
# Document: WebToHtml
> Source: https://github.com/aelassas/wexflow/wiki/WebToHtml
```xml
```
---
# Document: WebToScreenshot
> Source: https://github.com/aelassas/wexflow/wiki/WebToScreenshot
```xml
```
---
# Document: Wexflow Security
> Source: https://github.com/aelassas/wexflow/wiki/Wexflow-Security
# Table of Contents
1. [Wexflow Security](https://github.com/aelassas/wexflow/wiki/Wexflow-Security#wexflow-security)
1. [Production Security Recommendations](https://github.com/aelassas/wexflow/wiki/Wexflow-Security#production-security-recommendations)
1. [JWT Configuration](https://github.com/aelassas/wexflow/wiki/Wexflow-Security#jwt-configuration)
1. [.NET 4.8](https://github.com/aelassas/wexflow/wiki/Wexflow-Security#net-48)
1. [.NET 9.0+](https://github.com/aelassas/wexflow/wiki/Wexflow-Security#net-90)
1. [Summary](https://github.com/aelassas/wexflow/wiki/Wexflow-Security#summary)
# Wexflow Security
Wexflow uses a secure authentication mechanism based on:
- **JWT (JSON Web Tokens)**
- **PBKDF2-hashed passwords**
- **HttpOnly secure cookies**
- **HTTPS/SSL**
These layers ensure that your workflows and API endpoints are well-protected in both development and production environments.
## Production Security Recommendations
For secure deployments:
- Enable [HTTPS/SSL](https://github.com/aelassas/wexflow/wiki/SSL) to encrypt all traffic.
- Use **strong passwords** and update the default credentials.
- Store the JWT secret securely and avoid hardcoding sensitive values.
- Avoid storing JWT tokens in `localStorage`; Wexflow uses **HttpOnly cookies** instead.
- Configure reasonable JWT expiration to reduce risk if a token is ever leaked.
These best practices help protect Wexflow from:
- Cross-Site Scripting (XSS)
- Cross-Site Tracing (XST) — Wexflow disables the HTTP `TRACE` method
- Cross-Site Request Forgery (CSRF)
- Man-in-the-Middle (MITM) attacks
- Token theft or misuse
- Weak password storage
## JWT Configuration
You can configure the **JWT secret key** and **token expiration time** in both .NET 4.8 and .NET 9.0+ versions.
### .NET 4.8
Edit the file:
```
C:\Program Files\Wexflow\Wexflow.Server.exe.config
```
Add or update these entries under ``:
```xml
```
- `JwtSecret`: Symmetric secret key used to sign JWTs. Must be at least 128 bits (16 bytes); 256 bits (32 bytes) is safer.
- `JwtExpireAtMinutes`: Token expiration duration in minutes (e.g., 1440 = 24 hours).
### .NET 9.0+
Open the JSON configuration file:
```
Wexflow.Server/appsettings.json
```
And set:
```json
{
"JwtSecret": "b7a3c04f10e84c3f95a3f3497bda8e32",
"JwtExpireAtMinutes": 1440
}
```
- Keep this file out of source control if you're storing secrets directly.
- Consider using environment variables or a secure secrets manager in production.
## Summary
By using:
- JWTs with expiration
- Strong symmetric keys
- Encrypted cookies
- PBKDF2 for password hashing
- HTTPS for secure transport
Wexflow significantly reduces common attack surfaces for workflow automation platforms.
---
# Document: Wmi
> Source: https://github.com/aelassas/wexflow/wiki/Wmi
```xml
```
---
# Document: Workflow Notifications via SSE
> Source: https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE
## Table of Contents
- [Introduction](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE#introduction)
- [Prerequisites](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE#prerequisites)
- [How It Works](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE#how-it-works)
- [Steps to Use SSE](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE#steps-to-use-sse)
- [Example Event Payload](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE#example-event-payload)
- [Statuses](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE#statuses)
- [Summary](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE#summary)
- [Sample SSE Clients](https://github.com/aelassas/wexflow/wiki/Workflow-Notifications-via-SSE#sample-sse-clients)
## Introduction
Wexflow allows clients to subscribe to real-time notifications via Server-Sent Events (SSE) for workflow job status updates. This enables your application to receive a push notification when a workflow job finishes or stops, without polling the API repeatedly.
You can subscribe to a Server-Sent Events (SSE) endpoint that notifies your client when a workflow job finishes or stops.
## Prerequisites
SSE is available in the **.NET 9.0+** versions of Wexflow:
- `wexflow-x.x-windows-netcore.zip`
- `wexflow-x.x-linux-netcore.zip`
- `wexflow-x.x-macos-netcore.zip`
Ensure you are using one of these distributions. SSE is not available in the .NET Framework 4.8 version.
## How It Works
1. You **start a workflow** using the REST API.
2. You receive a `jobId` in the response.
3. You **subscribe to the SSE endpoint** using that `workflowId` and `jobId`.
4. Wexflow will push a notification to your client when the job finishes or stops.
5. The SSE connection is automatically closed after sending the final event.
## Steps to Use SSE
### 1. Start a Workflow
Use the REST API to start a workflow and receive the `jobId`:
```
POST /api/v1/start?w={workflowId}
Authorization: Bearer
```
For example:
```
POST http://localhost:8000/api/v1/start?w=41
```
### 2. Connect to the SSE Endpoint
Connect to the following endpoint using the returned `jobId`:
```
GET /api/v1/sse/{workflowId}/{jobId}
Authorization: Bearer
Accept: text/event-stream
```
For example:
```
GET http://localhost:8000/api/v1/sse/41/66aae8f4-e3ab-4fdc-8db2-a0a7692d8906
```
The server will keep the connection open until the workflow job ends.
## Example Event Payload
Once the workflow finishes or stops, you will receive an event like:
```js
{
"workflowId": 41,
"jobId": "66aae8f4-e3ab-4fdc-8db2-a0a7692d8906",
"status": "Done",
"name": "Workflow_Wait",
"description": "Workflow_Wait"
}
```
To test SSE, try running a workflow like `Workflow_Wait` with a 10-second delay.
## Statuses
Here are the possible statuses sent by the SSE event:
| Status | Description |
|--|-|
| Pending | Job is queued and waiting to start |
| Running | Job is currently in progress |
| Done | Job completed successfully |
| Failed | Job ended with an error |
| Warning | Job completed with warnings |
| Disabled | Job or workflow is disabled |
| Stopped | Job was manually stopped |
| Rejected | Job was rejected or not accepted |
## Summary
- The SSE event returns JSON with:
`{ workflowId, jobId, status, name, description }`
- Connection closes automatically when the workflow job ends.
- Always include a valid **JWT Bearer token** in the request headers.
- Useful for real-time job monitoring or triggering actions upon workflow completion.
## Sample SSE Clients
Here are SSE client examples in different languages:
- [C# SSE Client](https://github.com/aelassas/wexflow/wiki/C%23-SSE-Client)
- [PowerShell SSE Client](https://github.com/aelassas/wexflow/wiki/PowerShell-SSE-Client)
- [JavaScript SSE Client](https://github.com/aelassas/wexflow/wiki/JavaScript-SSE-Client)
- [PHP SSE Client](https://github.com/aelassas/wexflow/wiki/PHP-SSE-Client)
- [Python SSE Client](https://github.com/aelassas/wexflow/wiki/Python-SSE-Client)
- [Go SSE Client](https://github.com/aelassas/wexflow/wiki/Go-SSE-Client)
- [Rust SSE Client](https://github.com/aelassas/wexflow/wiki/Rust-SSE-Client)
- [Ruby SSE Client](https://github.com/aelassas/wexflow/wiki/Ruby-SSE-Client)
- [Java SSE Client](https://github.com/aelassas/wexflow/wiki/Java-SSE-Client)
- [C++ SSE Client](https://github.com/aelassas/wexflow/wiki/CPP-SSE-Client)
You can find the source code for all these SSE clients in the [samples/clients/sse](https://github.com/aelassas/wexflow/tree/main/samples/clients/sse) directory of the main Wexflow repository.
These examples serve as practical starting points to integrate Wexflow Push Notifications (SSE) into your own applications regardless of the tech stack you're using.
---
# Document: Workflow
> Source: https://github.com/aelassas/wexflow/wiki/Workflow
```xml
```
---
# Document: XmlToCsv
> Source: https://github.com/aelassas/wexflow/wiki/XmlToCsv
```xml
```
---
# Document: Xslt
> Source: https://github.com/aelassas/wexflow/wiki/Xslt
```xml
```
---
# Document: YamlToJson
> Source: https://github.com/aelassas/wexflow/wiki/YamlToJson
```xml
```
---
# Document: YouTube
> Source: https://github.com/aelassas/wexflow/wiki/YouTube
```xml
```
---
# Document: YouTubeListUploads
> Source: https://github.com/aelassas/wexflow/wiki/YouTubeListUploads
```xml
```
---
# Document: YouTubeSearch
> Source: https://github.com/aelassas/wexflow/wiki/YouTubeSearch
```xml
```
---
# Document: Zip
> Source: https://github.com/aelassas/wexflow/wiki/Zip
```xml
```
---