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
| Activity | Signature | Output |
|---|---|---|
| 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 inresult - Simple reference (bare name, no quotes):
WriteLine(response)— passes the variableresponse - Complex expression (property access, arithmetic, etc.):
WriteLine("{{ response.body }}")— use{{ }}inside a quoted string - NEVER use
${variable}syntax — that is not valid DSL
| Use case | DSL syntax |
|---|---|
| Pass a variable | WriteLine(myVar) |
| String literal | WriteLine("hello world") |
| Property access / expression | WriteLine("{{ myVar.property }}") |
| Arithmetic | WriteLine("{{ 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