Skip to main content

Writing workflows

Syntax

# Start browser and navigate
StartWebBrowser("https://example.com")

# Navigate to another page
NewChangeUrlBrowserTab("https://example.com/products")

# With output variable
products = GetHtmlElements("//div[@class='item']")

# Keyword arguments
response = HttpRequest("https://api.example.com/data", method="POST")

# Activity name & description — set only when intent isn't obvious from type + inputs
# (above all raw-selector clicks; skip self-describing steps like Navigate/HttpRequest)
# _name: 2-3 word action label of what the step achieves (not the activity type)
# _description: what it achieves, the UI/state it assumes, and how you'd know it failed
# These are metadata (not inputs); they power automated failure diagnosis.
LeftClick("//button[@id='save']", _name="Save record",
_description="Submits the form; assumes the dialog is open. Fails if validation errors remain.")

# Variable references (no quotes)
WriteToExcel("output.xlsx", "Sheet1")

# Control flow
if items > 0:
for item in items:
WriteLine(item)
WriteToExcel("results.xlsx", "Results")
else:
WriteLine("No items found")

# Error handling
try:
ClickElement("//button[@id='submit']")
except Exception as err:
TakeScreenshotWeb("./errors", "error")

# Groups
with Group("Login Steps"):
StartWebBrowser("https://app.example.com/login")
TextInput("//input[@name='email']", "user@example.com")
ClickElement("//button[text()='Sign In']")

# Retry a block of steps as a group on failure
with RetryOnError(retries=3, delay=1):
response = HttpRequest("https://api.example.com/data")
WriteLine(response)

Core Activities

ActivitySignatureOutput
GetExecutionInfo()outputVariable
SetOutcome(outcome)-
WriteLine(text)-
HttpRequest(url, method="GET", body=..., extraHeaders=..., multipartNames=..., multipartData=..., debugInput=False)outputVariable
DownloadFile(url, extraHeaders=..., outputPath=...)outputVariable
ChatGPT(input, gptModel="gpt-3.5-turbo", maxTokens=4096, responseType="text")outputVariable
Regex(pattern, text)outputVariable
Delay(timer)-
Group()-
Assign(variables)-
LoadEnvVariables(environment)-
SetEnvironmentVariable(environmentId, variableName, deleteVariable=..., isSecret=False)outputVariable
ArrayAppend(array, elements)-
Watchdog(directoryPath, fileExtension, newFileVariable)-
RetryOnError(retries=1, delay, isRetrying=...)outputVariable
GenerateUniqueId()outputVariable
GetIntegerRandomNumber(lowerLimit=..., upperLimit=...)outputVariable
RunPython(code, environment=..., interpreterPath=...)outputVariable
SetupPythonEnvironment(githubToken=..., githubUrl=..., branch=..., repoPath=..., venvPath, requirementsPath=..., interpreterPath=...)pythonPath
RunPythonFile(filePath, interpreterPath, environment=..., args=...)stdout
Test(workflowId, parameters=...)outputVariable
Assert(condition="")outputVariable
LaunchApplication(fullPath, arguments=..., workingDirectory=..., runAsAdmin=...)outputVariable
TerminateApplication(processIdentifier, forceTerminate=...)outputVariable
IfCondition(condition="")-
While(condition="")-
ForEachLoop(collection="", iteratorName=..., iteratorIndex=...)-
TryCatch()outputVariable
Break()-
Continue()-
ExecuteChildWorkflow(workflowId, browserSessionStrategy="useCurrentBrowserSession", profilePath=..., fireForget=..., parameters=..., outputMappings=...)-
EndExecution(endMode="complete", errorMessage=...)-
TimeoutExecution(endMode="terminate", timeout=1440)-
SendSignal(workflowId, runId=..., data=...)-
ReceiveSignal(timeoutMinutes=...)-
LiveInput(variables=..., timeoutMinutes=...)-
SMTPEmail(useOwnCredentials=..., emailRecipient, emailSubject, emailBody, cc=..., bcc=..., attachmentsFilepath=...)-
OpenFile(filePath)-
ReadFileContent(filePath)outputVariable
CreateDirectory(path, failIfExists=False)-
DeleteDirectory(path, failIfMissing=False)-
SaveToFile(content, filePath, createDirectories=False)-
SaveImage(source, filePath, createDirectories=False)-
WriteToExcel(fullPath, sheetName, skipRow, writeExisting=..., rows=...)outputVariable
DeleteExcelRow(filePath, sheetName=..., deleteRow)-
ExtractDataExcel(filePath, sheetName=..., includeHiddenSheet, isCSV=False)outputVariable
DatabaseToExcel(dbTable="", directoryPath, optionalName)outputVariable
FileExists(document)outputVariable
FileCopy(document, location, delete=..., overwrite=...)outputVariable
FileDelete(location)-
FileRename(document, name)outputVariable
GetFolderFiles(folderPath)outputVariable
CombinePDFs(location, name, delete=..., candidates)outputVariable
SplitPDF(location, locationPages)outputVariable
ExtractPDFText(filePath)outputVariable
ExtractPDFTable(filePath, headers=..., mergeGap=..., maxRowGap=..., anchorColumn=..., requireAnyColumn=...)outputVariable
EditPDF(filePath, outputPath=..., replacements)outputVariable
MergePDFs(sources, outputPath, maxSize=..., oversizeBehavior="shrink")outputVariable
WatermarkPDF(filePath, imagePath, outputPath=..., skipFirstPage=False, pages=..., rotation=45, opacity=0.15, scale=0.85)outputVariable
UploadResource(filePath, resourceName, resourceType=..., description=..., tags=..., folder=...)outputVariable
DownloadResource(resourceId=..., resourceName=..., outputPath)outputVariable
ListResources(names=..., folder=..., resourceType=..., tags=..., limit=...)outputVariable
GetResourceInfo(resourceId=..., resourceName=...)outputVariable
DeleteResource(resourceId=..., resourceName=...)outputVariable
UpdateResource(resourceId=..., resourceName=..., resourceType=..., description=..., tags=..., folder=...)outputVariable
ReplaceAll(stringToProcess, toReplace=..., replaceWith=...)outputVariable
OCR(filePath)outputVariable
OCRText(projectId, text)outputVariable
TextSimilarity(textCompare, threshold=0.93, candidates)outputVariable
KeyDataExtractor(projectId, document)outputVariable
ExcelToTable(filePath, sheetName, sheetIndex=..., rowToSkip=1)outputVariable
ParseJson(text)outputVariable
JsonToString(variable)outputVariable
OpenDatabaseConnection(connectionString=..., databaseType="postgres")outputVariable
CloseDatabaseConnection(db)-
RunQuery(db, query)outputVariable
RunNonQuery(db, query)-
StartWebBrowser(url, browserSessionStrategy="useCurrentBrowserSession", profilePath=..., playwrightDownloadPath=...)-
AcceptPrompt(acceptMessage=...)-
DenyPrompt()-
NewChangeUrlBrowserTab(url)-
SwitchBrowserTab(url)-
RemoveBrowserTab(url)-
ReloadCurrentPage()-
WaitForElement(selector, iframeSelector=..., iframeSelectors=..., state="visible", timeout=30000)-
WaitForPageToLoad(seconds)-
TakeScreenshotWeb(directoryPath, optionalName)outputVariable
WebpageToPDF(filePath, format="A4", landscape=False, width=..., height=..., margin=..., scale=..., printBackground=False, preferCSSPageSize=False, media="print")outputVariable
ExecuteJavaScript(script, selector=..., iframeSelector=..., iframeSelectors=..., args=..., timeout=...)outputVariable
ClickElement(selector, iframeSelector=..., iframeSelectors=..., useBrowserDebugger=...)-
DoubleClickElement(selector, iframeSelector=..., iframeSelectors=..., useBrowserDebugger=...)-
TextInput(selector, iframeSelector=..., iframeSelectors=..., text=..., deletePrevious=..., writeSequentially=...)outputVariable
DropDownSelect(selector, iframeSelector=..., iframeSelectors=..., text=..., index=...)-
FormSubmit(selector, iframeSelector=..., iframeSelectors=...)-
AttachFile(selector, iframeSelector=..., iframeSelectors=..., filePath)-
MouseOver(selector, iframeSelector=..., iframeSelectors=...)-
WebElementFocus(selector, iframeSelector=..., iframeSelectors=...)-
SelectText(selector, selectorStart, selectorEnd)-
PageScroll(xcoordinate, ycoordinate)-
ScrollIntoView(selector, iframeSelector=..., iframeSelectors=...)-
CheckHtmlElement(selector, iframeSelector=..., iframeSelectors=..., timeout=5000)outputVariable
GetHtmlElementProperty(selector, iframeSelector=..., iframeSelectors=..., property)outputVariable
GetHtmlElements(selector, iframeSelector=..., iframeSelectors=...)outputVariable
GetHtmlChildren(selector, iframeSelectors=...)outputVariable
GetHtmlDataFromAttribute(selector, attribute, iframeSelectors=...)outputVariable
GetHtmlElementPosition(selector, iframeSelector=..., iframeSelectors=...)outputVariable
KeyDown(selector, iframeSelector=..., iframeSelectors=..., key)-
KeyUp(selector, iframeSelector=..., iframeSelectors=..., key)-
KeyDownUp(selector, iframeSelector=..., iframeSelectors=..., key=...)-
MouseDown(selector, iframeSelector=..., iframeSelectors=..., button="left")-
MouseUp(selector, iframeSelector=..., iframeSelectors=..., button="left")-
LoseFocus(selector, iframeSelector=..., iframeSelectors=...)-
TakeScreenshotDesktop(directoryPath=..., optionalName=...)outputVariable
TakeScreenshot(elementTargetMethod="xPath", xPath=..., xOffset=..., yOffset=..., checkOrphanedElements=..., disableGdi=..., referenceText=..., textMatchDistance=..., matchThreshold=..., indexInFoundItems=..., ocrProvider=..., referenceImage=..., expandX=..., expandY=..., elementScreenshotFilePathName=...)outputVariable
LeftClick(elementTargetMethod="xPath", xPath=..., xOffset=..., yOffset=..., checkOrphanedElements=..., useOsMouseForClick=..., referenceText=..., textMatchDistance=..., matchThreshold=..., indexInFoundItems=..., ocrProvider=..., referenceImage=...)-
LeftDoubleClick(elementTargetMethod="xPath", xPath=..., xOffset=..., yOffset=..., checkOrphanedElements=..., referenceText=..., textMatchDistance=..., matchThreshold=..., indexInFoundItems=..., ocrProvider=..., referenceImage=...)-
RightClick(elementTargetMethod="xPath", xPath=..., xOffset=..., yOffset=..., checkOrphanedElements=..., referenceText=..., textMatchDistance=..., matchThreshold=..., indexInFoundItems=..., ocrProvider=..., referenceImage=...)-
MouseHover(elementTargetMethod="xPath", xPath=..., xOffset=..., yOffset=..., checkOrphanedElements=..., referenceText=..., textMatchDistance=..., matchThreshold=..., indexInFoundItems=..., ocrProvider=..., referenceImage=...)-
MouseWheel(elementTargetMethod="xPath", xPath=..., xOffset=..., yOffset=..., checkOrphanedElements=..., referenceText=..., textMatchDistance=..., matchThreshold=..., indexInFoundItems=..., ocrProvider=..., referenceImage=..., wheelDelta=...)-
Drag(elementTargetMethod="xPath", xPath=..., xOffset=..., yOffset=..., checkOrphanedElements=..., referenceText=..., textMatchDistance=..., matchThreshold=..., indexInFoundItems=..., ocrProvider=..., referenceImage=..., deltaX=..., deltaY=...)-
KeyboardInput(elementTargetMethod="xPath", xPath=..., xOffset=..., yOffset=..., checkOrphanedElements=..., referenceText=..., textMatchDistance=..., matchThreshold=..., indexInFoundItems=..., ocrProvider=..., referenceImage=..., keysToSend, writeSequentially=...)-
GetText(xPath, checkOrphanedElements=False)outputVariable
GetDetails(xPath, checkOrphanedElements=False)outputVariable
GetChildren(xPath, propertyName="text", checkOrphanedElements=False)outputVariable
SetClipboard(text)-
GetClipboard()outputVariable
CheckElementExists(xPath, checkOrphanedElements=False)outputVariable
GetToggleState(xPath, checkOrphanedElements=False)outputVariable
ToggleElement(xPath, checkOrphanedElements=False)outputVariable
SetElementValue(xPath, keysToSend=..., confirmWithEnter=False, deselectWithArrowRight=False, selectAllKeys=..., copyKeys=..., pasteKeys=..., checkOrphanedElements=False)-
GetElementValue(xPath, checkOrphanedElements=False)outputVariable
SelectComboboxItem(xPath, comboboxItemText, checkOrphanedElements=False)-
GetSelectedItem(xPath, checkOrphanedElements=False)outputVariable
ExpandElement(xPath, checkOrphanedElements=False)-
CollapseElement(xPath, checkOrphanedElements=False)-
DesktopScrollIntoView(xPath, checkOrphanedElements=False)-
GetTableRowCount(xPath, checkOrphanedElements=False)outputVariable
GetTableColumnCount(xPath, checkOrphanedElements=False)outputVariable
GetTableCell(xPath, tableRow, tableColumn, checkOrphanedElements=False)outputVariable
GetDesktopTableRow(xPath, tableRow, checkOrphanedElements=False)outputVariable
GetTableRows(xPath, checkOrphanedElements=False)outputVariable
GetTableColumnHeaders(xPath, checkOrphanedElements=False)outputVariable
GetTableCellByHeaders(xPath, tableRowHeader, tableColumnHeader, checkOrphanedElements=False)outputVariable
SetTableCellValue(xPath, tableRow, tableColumn, valueToSet, checkOrphanedElements=False)-
SetTableCellValueByHeaders(xPath, tableRowHeader, tableColumnHeader, valueToSet, checkOrphanedElements=False)-
SendNtlmEmail(host=..., port=..., username=..., password=..., enableSsl=..., useDefaultCredentials=False, ignoreCertificateErrors=False, from, to=..., cc=..., bcc=..., subject=..., body=..., attachments=..., sendEmailAsHtml=..., bodyTemplateFilePath=..., tokens=...)-
Office365(permissionsType="application", emailAddress=..., clientSecret=..., clientId, tenantId)-
GetRange(workbookPath=..., worksheetName=..., rangeAddress=...)outputVariable
WriteCell(workbookPath=..., worksheetName=..., rangeAddress=..., values=...)-
AddTableRow(workbookPath=..., worksheetName=..., tableName=..., useSpecificRow=..., values=...)-
SubscribeRequest(notificationUrl)outputVariable
GetMessagesById(messageId=..., includeAttachments=...)outputVariable
MoveFile(filePath, targetPath=...)outputVariable
GetTable(workbookPath=..., worksheetName=..., tableName=...)outputVariable
GetTableRow(workbookPath=..., worksheetName=..., tableName=..., index=...)outputVariable
Microsoft365Email(fromAddress, toRecipients, emailSubject, emailBody, ccRecipients=..., bccRecipients=..., attachments=...)outputVariable
MicrosoftDownloadFile(filePath, targetPath=...)outputVariable
MicrosoftUploadFile(filePath, targetPath=...)outputVariable
SimpleAntiCaptcha(filePath, apiKey)outputVariable
Simple2captcha(filePath, apiKey)outputVariable
V2RecaptchaAnticaptcha(pageURL, siteKey, isInvisible=..., dataSValue=..., apiKey)-
V2Recaptcha2captcha(pageURL, siteKey, apiKey)-
GoogleDriveUploadFile(filePath, targetPath=...)outputVariable
GoogleDriveDownloadFile(fileName, downloadPath, targetPath=...)outputVariable
GoogleDriveDeleteFile(fileName, targetPath=...)outputVariable
GoogleDriveReadSpreadsheet(spreadsheetId, range)outputVariable
GoogleDriveWriteSpreadsheet(spreadsheetId, range, values)outputVariable
GmailSendEmail(to, subject, body)-
LoadNamedTable(tableName, as=...)outputVariable
SaveNamedTable(tableName, createNew=False, mode="upsert")outputVariable
CreateTable(tableName, schema=...)outputVariable
SetTableKeyColumns(tableName, keyColumns)outputVariable
TablePushMany(tableName, rows, upsert=False)outputVariable
TableUpsertRow(tableName, row, append=False)outputVariable
TableDeleteRow(tableName, keys)outputVariable
TableMerge(targetTable, sourceTable, upsert=False)outputVariable
TableQuery(tableName, outputTable=..., filters=..., orderBy=..., limit=..., columns=...)outputVariable
TableLookup(tableName, keys)outputVariable
TableMatch(tableName, criteria)outputVariable
TableImport(tableName, filePath, keyColumns=..., format="auto", sheetName="Sheet1", sheetIndex=..., rowToSkip=1, delimiter=",")outputVariable
TableExport(tableName, filePath, format="csv", delimiter=",", sheetName="Sheet1")outputVariable
DropTable(tableName)-
ApplyRuleSet(tableName, ruleSetName)outputVariable
ExportWithTemplate(tableName, templateName, outputPath)outputVariable
CallFunction(functionName, arguments=...)-
InvokePrimitive(primitiveId, input=..., context=...)outputVariable

Variables

  • Output: result = HttpRequest("https://...") stores response in result
  • Simple reference (bare name, no quotes): WriteLine(response) — passes the variable response
  • Complex expression (property access, arithmetic, etc.): WriteLine("{{ response.body }}") — use {{ }} inside a quoted string
  • NEVER use ${variable} syntax — that is not valid DSL
Use caseDSL syntax
Pass a variableWriteLine(myVar)
String literalWriteLine("hello world")
Property access / expressionWriteLine("{{ myVar.property }}")
ArithmeticWriteLine("{{ count + 1 }}")

For anything beyond simple property access — filtering, mapping, sorting, pattern matching, or registered function calls — see Expressions. Most RunPython blocks for list transforms or string manipulation are a single expression.

Workflow Options

# Set via create_workflow / update_workflow options field:
{
"headless": true,
"timeout": 60000,
"retryOnError": true,
"maxRetries": 3,
"expressionLanguage": "expr"
}

expressionLanguage is what the contents of {{ ... }} are written in, for the whole workflow: "expr" (expr-lang, the default and what a workflow that omits the key means), "js" (JavaScript, on goja) or "python" (a Python dialect, on Starlark — not CPython). Read it before writing any expression — the three share the strings, math, fuzzy, collections and path packages and nothing else, so an expression written for the wrong one stores fine and never evaluates.

Webhook Payload Contracts

When a workflow is triggered via /api/webhooks/{workflow_id}/{user_id}, the full JSON body posted by the caller is available as the webhookBody variable. The shape depends on the source — do not guess, check the contract below.

Microsoft Graph change notifications (Outlook, OneDrive, SharePoint, etc.)

Microsoft Graph wraps notifications in a value array. Even a single email change arrives as a one-element array. webhookBody IS that envelope:

{
"value": [
{
"changeType": "created",
"resource": "Users/<user-id>/Messages/<message-id>",
"resourceData": {
"@odata.type": "#Microsoft.Graph.Message",
"id": "<message-id>"
},
"subscriptionId": "<sub-id>",
"clientState": "<secret>",
"tenantId": "<tenant-id>",
"subscriptionExpirationDateTime": "2026-04-07T16:30:48+00:00"
}
]
}

Correct DSL expressions:

# Iterate every notification in the batch (Graph may coalesce several):
for notification in webhookBody['value']:
Assign({'messageId': '{{ notification["resourceData"]["id"] }}'})
emailData = GetMessagesById(messageId=messageId, includeAttachments=True)

# Or index the first one directly if you only expect one:
Assign({'messageId': '{{ webhookBody["value"][0]["resourceData"]["id"] }}'})

Incorrect — these will resolve to undefined:

webhookBody["resourceData"]["id"] # MISSING the value[0] envelope — broken
webhookBody["id"] # wrong level entirely

Microsoft Graph lifecycle events

Same {"value": [...]} envelope, but each item has lifecycleEvent instead of changeType. Lifecycle events are routed to a separate endpoint (/api/webhooks/lifecycleEvent/{workflow_id}) and handled by the platform — workflows rarely need to read them directly.

Generic / custom webhooks

webhookBody is the raw parsed JSON body as posted, with no envelope added by the platform. If a caller posts {"orderId": 42}, then webhookBody["orderId"] is 42. If a caller posts {"data": {"orderId": 42}}, then you must write webhookBody["data"]["orderId"]. Always confirm the sender's exact shape.

Tips

  • Browse the activity reference by category
  • Each activity page lists its full input and output schema
  • The designer validates a workflow before it saves
  • Start from a recipe for a working pattern