Function
The Function block runs your own JavaScript, Python, or Shell code as one step of a workflow. Use it to reshape a value, run a calculation, call a CLI, or add logic no other block covers.
Configuration
Code
JavaScript is the default. The language field is part of the saved workflow and is
never removed when Sandbox configuration changes. Python remains available as a
language choice; Shell and custom Sandbox controls appear when a remote Function
sandbox provider is enabled. Reference an earlier output directly, with no quotes
around the tag, and read an environment variable with {{VAR}}:
const data = <api.data>;
return data.items.filter((i) => i.active).map((i) => i.id);data = <api.data>
__sim_result__ = [item["id"] for item in data["items"] if item["active"]]set -euo pipefail
active_ids=$(printf '%s' <api.data> | jq -c '[.items[] | select(.active) | .id]')
printf '__SIM_RESULT__=%s\n' "$active_ids"JavaScript returns a value with return. Python runs as a normal module: assign the value for downstream blocks to __sim_result__. A full script with functions, imports, and an if __name__ == '__main__': guard works as written; legacy snippets with a top-level return remain supported. print() is logging and goes to stdout rather than becoming the result.
Shell returns structured data by printing a line beginning with __SIM_RESULT__=. A valid JSON payload becomes an object, array, number, boolean, or string; a non-JSON payload becomes a string. The marker line is removed from stdout. Other command output remains available in stdout.
Secret placeholders in code
When an environment variable is the complete JavaScript or Python expression, prefer the unquoted form:
const apiKey = {{API_KEY}};Existing quoted and embedded forms are also supported, including "{{API_KEY}}", "Bearer {{API_KEY}}", and placeholders in template literals. Function and Custom Tool code use the same compiler at the execution boundary. It binds the secret separately from the source instead of pasting plaintext into your code, so quotes, backslashes, newlines, and string values such as "123" and "true" retain their exact contents and do not become JavaScript or Python literals of another type.
JavaScript regex literals can contain a placeholder:
const matcher = /^{{PATH_PATTERN}}$/i;The value is interpreted as raw regex pattern text and the literal's flags are preserved. If you need the value matched literally rather than as a pattern, escape regex metacharacters before constructing the expression.
In Shell, use the same {{KEY}} syntax and write "{{KEY}}" when the value should remain one scalar argument. Bare placeholders remain supported for existing workflows and retain Bash's native unquoted behavior, including word splitting and regex-pattern semantics. Placeholders also work inside quoted heredocs without enabling unrelated shell expansion:
cat > /tmp/request.txt <<'REQUEST'
Authorization: Bearer {{API_KEY}}
Home remains literal: $HOME
REQUESTSim supplies the rendered heredoc privately while preserving the quoted delimiter's literal $VAR, backtick, and command-substitution behavior.
Outputs
| Output | What it is |
|---|---|
<function.result> | The value your code returns (object, array, string, number, …) |
<function.stdout> | Anything printed with console.log() or print() |
Language
JavaScript without imports runs in a fast local sandbox. JavaScript with import or require, Python, and Shell run in the configured remote sandbox provider.
| JavaScript | Python | Shell | |
|---|---|---|---|
| Execution | Local when there are no imports; remote with imports | Always remote | Always remote |
| Return a value | return { … } | Assign __sim_result__ = { … } | Print __SIM_RESULT__={…} |
| HTTP requests | fetch() built in | requests or httpx | curl or an installed CLI |
| Best for | quick transforms and JSON | scripts, data science, charts, complex math | CLI workflows and system utilities |
Python and Shell require a remote sandbox. They are enabled by default on sim.ai; on a self-hosted instance, build and configure the provider's dedicated Function base first. Any Python figures you generate are captured as images automatically.
If no remote provider is configured, JavaScript without import or require
continues to run in Sim's local isolated VM. Missing E2B or Daytona configuration
does not disable that path. Remote-only code fails with an explicit configuration
error; Sim does not reinterpret Python or Shell as JavaScript.
The dedicated Function base has the same runtime and universal package contract on E2B and Daytona. It includes this data-science stack; use a workspace sandbox when another dependency must be present:
- Data and graphs:
pandas,numpy,scipy,xarray,numba,networkx - ML and NLP:
scikit-learn,gensim,nltk,spacy,textblob - Plots and images:
matplotlib,seaborn,plotly,bokeh,kaleido,pillow,opencv-python,scikit-image,imageio,tifffile - Audio:
librosa,soundfile - Web, parsing, and files:
requests,beautifulsoup4,lxml,openpyxl,xlrd,python-docx,xmltodict,PyYAML,tomlkit,simplejson,orjson,SQLAlchemy - Math:
sympy - Application helpers:
rich,typer,click,tqdm,Jinja2,pydantic,python-dateutil,pytz,psutil,filetype,python-slugify,parsedatetime,pytimeparse - Generic CLIs:
jq,yq,csvkit,zx,xmlstarlet,httpie,ripgrep,fd,bat,sqlite3,tar,gzip,bzip2, ZIP/XZ/7z, media/image tools, and standard network utilities
Vendor and service clients such as AWS CLI and GitHub CLI are not part of the universal base. Add them through the managed catalog. Database clients such as PostgreSQL, MySQL, and Redis can be added as system packages when available from the configured Debian repositories.
Sandboxes
A sandbox is a named environment your workspace maintains: a language, a list of pip or npm dependencies, optional Debian/APT system packages, and optional managed CLI tools. Select one on a Function block and its code can import the dependencies and run the commands it declares. Leave the selection empty to use the dedicated Function base.
Create and edit sandboxes in Settings → Sandboxes. Only workspace admins can
create or edit them. On sim.ai they need an active Max or Enterprise plan;
self-hosted deployments turn them on with SANDBOXES_ENABLED (see
self-hosted enterprise). The section is
usable only when the deployment also has a remote provider and immutable Function
base configured. The Function block hides its custom Sandbox selector when that
runtime is unavailable.
- Name the sandbox —
bigquery-etl,scraping, whatever the job is. - Pick the language. This selects pip or npm for the dependency list. Python and JavaScript blocks list matching sandboxes; Shell can use either kind.
- Paste your dependencies, one per line. Version pins are optional.
- Add system packages by Debian/APT package coordinate, one per line. Use this
for ordinary command-line utilities such as
jqandffmpeg. - Select any managed CLI tools that require a specialized, verified installer. The searchable catalog is grouped by cloud, Kubernetes, infrastructure, deployment, data and storage, and security tools. Every entry uses a pinned, integrity-checked vendor artifact.
Dependencies:
google-cloud-bigquery==3.25.0
pyairtable>=3.0
pandasSystem packages:
shellcheck
pandoc
graphvizThen open the block's advanced options and choose the sandbox under Sandbox.
The default and custom behavior is intentionally explicit:
- JavaScript without imports stays in the local isolated runtime for speed and ignores the sandbox selection.
- JavaScript with
importorrequireruns remotely. With no selection it uses the Function base; with a sandbox it gets that sandbox's npm packages and system packages and managed CLI tools. - Python and Shell always run remotely. With no selection they get only the Function base; with a sandbox they get its dependencies, system packages, and managed CLI tools.
When a Function block next loads its sandbox options, a successful lookup that confirms its selected sandbox was deleted clears that selection. Fetch or auth failures leave the workflow unchanged.
Two sandboxes with the same language, dependency list, system packages, and managed CLI tools share one build, so duplicating a set costs nothing. Editing any part of that install specification starts a new build. Runs already in flight keep using the old one. Deleting a sandbox frees its build once nothing else uses it.
Build status
On sim.ai, each sandbox specification is prebuilt into a reusable image, so runs pay no install cost. The status row in Settings shows Queued, Building, Ready, or Failed. A failed build reports what went wrong — a package that does not exist, a version that has no match, a resolver conflict — with the installer log behind a disclosure.
Running a block before its sandbox is Ready stops the run and shows you the status. A failed build is retried periodically on its own. To retry immediately, open the three-dot menu at the end of the failed status row and choose Retry build. Save or discard unsaved sandbox edits first so the retry always uses the visible spec.
On a self-hosted deployment using Daytona, dependencies, system packages, and managed CLI tools install inside the sandbox at the start of every run instead, adding startup time per execution. Prebuilt images require E2B.
System packages, managed CLIs, and one-off installs
Use System packages for CLIs available from the sandbox's Debian/APT
repositories. Entries may include an architecture or exact version, such as
package:architecture=version. They are validated, deduplicated, and installed
when the sandbox is built or prepared.
The Managed CLI tools selector covers CLIs that need more than a normal APT
installation, such as a pinned vendor archive, PATH setup, or command verification.
Service and vendor CLIs are intentionally not part of the universal Function base.
For example, after adding Google Cloud CLI to a sandbox, a Shell Function can
run bq directly:
set -euo pipefail
bq query --use_legacy_sql=false --format=json 'SELECT CURRENT_DATE() AS today'For a command needed only once, install it at the start of the Shell script. The remote sandbox is ephemeral, so the install applies only to that Function call and adds to its execution time:
set -euo pipefail
python -m pip install --quiet csvkit
csvcut -n /tmp/input.csvUse a custom sandbox instead when reproducibility or startup time matters. Put ordinary Debian utilities in System packages; use the managed selector only for one of its specialized installers. Arbitrary install commands are not saved in a sandbox specification.
What is allowed
Dependency fields accept package names and version specifiers only. URLs, git+
references, -e, local paths, --index-url, and npm aliases are rejected, with
the offending line number reported.
System packages use Debian coordinates in the form
package[:architecture][=version]. Flags, URLs, paths, whitespace, globs, and
shell syntax are rejected. A sandbox may declare up to 50 dependencies, 50 system
packages, and 10 managed CLI tools.
Scoping secrets for agent tools
When a Function block is used as an Agent tool, its code can read every workspace
secret by default — both {{MY_SECRET}} and environmentVariables['MY_SECRET'].
Use {{MY_SECRET}} when the value may appear in execution logs: a successful
double-brace substitution activates execution-trace masking,
while direct environmentVariables['MY_SECRET'] access alone does not activate
it by itself.
To narrow that, set Secret access to Selected secrets in the block's tool configuration and pick the names the code may read. Two things change:
- Only those secrets are injected.
{{OTHER_SECRET}}no longer resolves either. - The selected names are added to the tool's description, so the model knows
what it can reference. Values are bound server-side only when the Function runs;
they are not included in the model request. If a Function result contains an
exact secret value, the Agent model receives
{{NAME}}in its place. The raw runtime result and local side effects are not rewritten.
Leaving the default (All secrets) resolves the list at run time, so a secret added next month is included automatically.
Examples
Reshape an API response
The Function reads <api.data>, returns just the field the rest of the workflow needs, and exposes it as <extract.result>.
Validate input before writing
The Function sanitizes the form input and returns the clean value, which the API block sends as its body.
A worked example: loyalty score
const { purchaseHistory, accountAge, supportTickets } = <agent>;
const totalSpent = purchaseHistory.reduce((sum, p) => sum + p.amount, 0);
const purchaseFrequency = purchaseHistory.length / (accountAge / 365);
const ticketRatio = supportTickets.resolved / supportTickets.total;
const spendScore = Math.min((totalSpent / 1000) * 30, 30);
const frequencyScore = Math.min(purchaseFrequency * 20, 40);
const supportScore = ticketRatio * 30;
const loyaltyScore = Math.round(spendScore + frequencyScore + supportScore);
return {
customer: <agent.name>,
loyaltyScore,
loyaltyTier: loyaltyScore >= 80 ? 'Platinum' : loyaltyScore >= 60 ? 'Gold' : 'Silver',
};def calculate_loyalty(data):
purchase_history = data["purchaseHistory"]
account_age = data["accountAge"]
support_tickets = data["supportTickets"]
total_spent = sum(p["amount"] for p in purchase_history)
purchase_frequency = len(purchase_history) / (account_age / 365)
ticket_ratio = support_tickets["resolved"] / support_tickets["total"]
spend_score = min(total_spent / 1000 * 30, 30)
frequency_score = min(purchase_frequency * 20, 40)
support_score = ticket_ratio * 30
loyalty_score = round(spend_score + frequency_score + support_score)
tier = "Platinum" if loyalty_score >= 80 else "Gold" if loyalty_score >= 60 else "Silver"
return {
"customer": data["name"],
"loyaltyScore": loyalty_score,
"loyaltyTier": tier,
}
if __name__ == "__main__":
__sim_result__ = calculate_loyalty(<agent>)Large inputs
Sim hands a Function block its code, parameters, and resolved references in one request, so very large values are kept by reference rather than inlined.
Prefer a narrow reference over a whole large value: use <api.data.id> instead of <api.data>. If a JavaScript function without imports does reference a whole large value, Sim rewrites it to a lazy server-side read automatically.
Files are metadata-first: reading <file.name> or <file.url> does not load the file's contents. Read content on demand with the sim.files helpers (JavaScript without imports only):
const file = <readfile.file>;
const text = await sim.files.readText(file);
const chunk = await sim.files.readTextChunk(file, { offset: 0, length: 1024 * 1024 });
const bytes = await sim.files.readBase64Chunk(file, { offset: 0, length: 1024 * 1024 });sim.files.readText, readBase64, and the …Chunk variants stream from execution storage under memory caps. sim.values.read(ref) and sim.values.readArray(ref) read large value and array references. Chunk offset and length are byte-based, so for exact Unicode parsing prefer smaller structured references. For large generated data, write the result to a file or table with outputPath, outputSandboxPath, or outputTable instead of returning the whole payload inline.
The lazy sim.files and sim.values helpers are available only in JavaScript functions without imports. JavaScript with imports, Python, and Shell do not support them yet.
Best Practices
- Keep each function focused. One transform per block is easier to read, test, and debug.
- Handle errors. Wrap risky code in
try/catchand return a clear message, or let it throw to the error path. - Reference only what you need. Pull a narrow field rather than a whole large object to keep values out of the request body.
- Use stdout to debug.
console.log(),print(), and ordinary shell output land in<function.stdout>and the run logs.