docs/elsa/scripts.md

9.3 KiB

Scripts: JavaScript & C#

Elsa can run code inside a workflow two ways:

  1. Script activities — Run JavaScript (Elsa.RunJavaScript) and Run C# (Elsa.RunCSharp). Both take a Script input and produce a Result output.
  2. Inline expressions — any activity input can hold an expression instead of a literal. The syntax is chosen per value: JavaScript, CSharp or Liquid.

Both routes evaluate against the current execution context, so they can read workflow inputs, read/write workflow variables and read the outputs of activities that already ran. This page lists exactly what is callable.

JavaScript runs on the Jint engine. C# runs on Roslyn scripting. The lists below are taken from the engine source (Elsa 3.8.2, elsa-core/src/modules/Elsa.Expressions.JavaScript and elsa-core/src/modules/Elsa.Expressions.CSharp). Only what is listed here is wired by this host — in particular JS has no console, no CLR access and no getConfig (see Limits).

Reading and writing data

What you want JavaScript C#
Read a workflow variable getVariable("Name") ExecutionContext.GetVariable("Name") / Variable.Get("Name")
Write a workflow variable setVariable("Name", value) ExecutionContext.SetVariable("Name", value) / Variable.Set("Name", value)
Variable as a property variables.Name Variable.Name (typed, see note)
Read a workflow input getInput("name") Input.Get<T>("name")
Output of an earlier activity getOutputFrom("Activity", "Result") Output.From("Activity", "Result") / Output.From<T>(...)
Result of the previous activity getLastResult() Output.LastResult
Current workflow instance id getWorkflowInstanceId() WorkflowInstanceId
Correlation id getCorrelationId() / setCorrelationId(v) CorrelationId (get/set)
Workflow instance name getWorkflowInstanceName() / setWorkflowInstanceName(v) WorkflowInstanceName (get/set)
Definition id / version getWorkflowDefinitionId(), getWorkflowDefinitionVersionId(), getWorkflowDefinitionVersion() — (use the workflow variables/inputs instead)

Notes:

  • getVariable / Variable.Get are exact-name. The typed C# property Variable.Name is generated from the Pascalized variable name and looks the variable up by that Pascalized name, so use Variable.Get("exactName") when a variable is not already PascalCase.
  • The activity reference in getOutputFrom / Output.From is the activity's name or id (name is what the designer shows). The output name is the one from the activity descriptor — Result for code activities such as W4C.W4cEcho, W4C.RunSql, Elsa.RunJavaScript.
  • Reading an output of an activity that has not run yet returns null.

JavaScript API

Built-in functions

Workflow / context:

Function Returns
getWorkflowDefinitionId() definition id (string)
getWorkflowDefinitionVersionId() definition version id (string)
getWorkflowDefinitionVersion() version number (int)
getWorkflowInstanceId() workflow instance id (string)
getCorrelationId() / setCorrelationId(value) correlation id
getWorkflowInstanceName() / setWorkflowInstanceName(value) instance name
getInput(name) workflow input value
getVariable(name) / setVariable(name, value) variable value
variables.<name> variable value (write-back supported)
getOutputFrom(activity, outputName) output of another activity
getLastResult() result of the previous activity

Helpers:

Function Purpose
toJson(value) JSON string (enums as strings, full Unicode)
isNullOrEmpty(value) / isNullOrWhiteSpace(value) string checks
parseGuid(value) Guid from string
newGuid() / newGuidString() / newShortGuid() new Guids
bytesToString / bytesFromString UTF-8 byte ↔ string
bytesToBase64 / bytesFromBase64 byte[] ↔ base64
stringToBase64 / stringFromBase64 string ↔ base64
streamToBytes(stream) / streamToBase64(stream) Stream → bytes/base64

Outcomes (only meaningful in Run JavaScript, whose Script input supports them): setOutcome(name) and setOutcomes(names[]). If neither is called the activity completes with the default outcome Done. Declare the possible names in the activity's PossibleOutcomes input so they appear on the node.

Deprecated but present: getGuidString(), getShortGuid().

Generated accessors

For convenience the engine also injects name-based accessors:

  • Variables: get<Name>() / set<Name>(value) where <Name> is the variable name Pascalized (counter → getCounter()). These use the real variable name internally, so they are reliable for any variable name.
  • Workflow inputs: get<inputName>() — the accessor keeps the exact input name (name → getname()).
  • Activity outputs: get<Output>From<ActivityName>(), both Pascalized (Result from activity Echo → getResultFromEcho()).

The explicit functions above are the stable API; the accessors are a shorthand.

Registered .NET types

DateTime, DateTimeOffset, TimeSpan, Guid, Random, LogPersistenceMode, plus the types of the workflow's declared variables. They are usable as static classes, e.g. DateTime.UtcNow, Guid.NewGuid().

Values and results

  • The value of the last expression, or an explicit top-level return (return outside a function is allowed), becomes the script result.
  • A returned Promise is awaited/unwrapped by the engine.
  • If the result is not null, Run JavaScript writes it to the Result output.
// totals an order, stores it in a variable and returns a JSON summary
const order = getInput("order");
const total = order.items.reduce((sum, item) => sum + item.price, 0);
setVariable("Total", total);

const echo = getOutputFrom("Echo", "Result"); // result of an earlier node
if (total > 1000) setOutcome("Rejected"); else setOutcome("Approved");

return toJson({ total, echo });

C# API

The script is a Roslyn C# script. Its globals are an instance of Elsa.Expressions.CSharp.Models.Globals, which exposes:

Global Type Use
ExecutionContext ExecutionContextProxy GetVariable(name), GetVariable<T>(name), SetVariable(name, value), GetInput<T>(name), and .ExpressionExecutionContext for the raw context
Variable (alias Variables) WorkflowVariablesProxy Get(name), Get<T>(name), Set(name, value) + a typed property per variable
Input (alias Inputs) WorkflowInputsProxy Get<T>(name) + a typed property per workflow input
Output OutputProxy From(activity, outputName), From<T>(...), LastResult
Outcome OutcomeProxy Set(params string[] outcomeNames)
Args ArgumentsProxy arguments passed by the caller of the evaluator (Run C# passes none — advanced/internal)
WorkflowInstanceId string workflow instance id
CorrelationId string? (get/set) correlation id
WorkflowInstanceName string? (get/set) instance name
Arguments IDictionary<string, object> raw argument dictionary

Result and outcomes:

  • The script's last expression / return value becomes the script result; when not null it is written to the Run C# Result output.
  • Call Outcome.Set("Approved") (or Outcome.Set("Approved", "Rejected")) to choose an outcome; the default is Done. Declare them in PossibleOutcomes.
  • await works in the script.

Imports and references:

  • Default namespaces: Elsa.Expressions.CSharp, System, System.Linq, System.Collections.Generic, System.Dynamic, System.Text.Json, System.Text.Json.Serialization, System.Text.Json.Nodes.
  • Default assemblies: Elsa.Expressions.CSharp, System.Linq/System.Runtime, System.Text.Json, System.Collections, System.Linq.Expressions (ExpandoObject) and Microsoft.CSharp (dynamic). Nothing else (no System.Net.Http, no System.IO) unless the host adds it.
var order = Input.Get<Order>("order");
var total = order.Items.Sum(item => item.Price);
Variable.Total = total;                                  // typed var (PascalCase)
ExecutionContext.SetVariable("Total", total);            // exact-name API

var echo = Output.From("Echo", "Result");
Outcome.Set(total > 1000 ? "Rejected" : "Approved");

return JsonSerializer.Serialize(new { total, echo });

Limits

  • JavaScript is the safe sandbox. This host calls UseJavaScript() without options, so AllowClrAccess and AllowConfigurationAccess stay false: there is no clr, no getConfig, and no console. The extra JS libraries (lodash, moment) are not installed. The only extension points are the registered types and the built-ins listed above.
  • C# is not a sandbox. Scripting:CSharp:AllowHostCodeExecution is true in this deployment (including production), so Run C# can run arbitrary code in the Elsa host process — filesystem, network, DI services and every tenant's data. Only trusted authors should be allowed to edit flows. Turning the flag off removes Run C# and the CSharp syntax from the palette entirely.
  • Both engines cache compiled scripts for one day (sliding), and both run in-process with the caller's cancellation token.