# 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](#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("name")` | | Output of an earlier activity | `getOutputFrom("Activity", "Result")` | `Output.From("Activity", "Result")` / `Output.From(...)` | | 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.` | 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()` / `set(value)` where `` 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()` — the accessor keeps the **exact** input name (`name` → `getname()`). - **Activity outputs**: `getFrom()`, 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. ```js // 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(name)`, `SetVariable(name, value)`, `GetInput(name)`, and `.ExpressionExecutionContext` for the raw context | | `Variable` (alias `Variables`) | `WorkflowVariablesProxy` | `Get(name)`, `Get(name)`, `Set(name, value)` + a typed property per variable | | `Input` (alias `Inputs`) | `WorkflowInputsProxy` | `Get(name)` + a typed property per workflow input | | `Output` | `OutputProxy` | `From(activity, outputName)`, `From(...)`, `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` | 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. ```csharp var order = Input.Get("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.