From recipe-skills
Base skill for Workato recipe development. Provides foundational knowledge for recipe JSON structure, trigger types, control flow patterns, datapill syntax, and formulas. Connector-specific skills extend this base.
How this skill is triggered — by the user, by Claude, or both
Slash command
/recipe-skills:workato-recipesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This document provides foundational knowledge for AI agents to generate valid Workato recipe JSON. This is the base skill that connector-specific skills (stripe-recipes, salesforce-recipes, etc.) extend.
control-flow/foreach.mdcontrol-flow/if-else.mdcontrol-flow/repeat-while.mdcontrol-flow/stop.mdcontrol-flow/try-catch.mdfundamentals/config-section.mdfundamentals/datapill-syntax.mdfundamentals/recipe-structure.mdlint-rules.jsonpatterns/adhoc-http-actions.mdpatterns/api-platform-artifacts.mdpatterns/custom-connector-actions.mdpatterns/jwt-auth.mdpatterns/python-snippets.mdpatterns/variables-and-lists.mdskill.yamltemplates/api-endpoint-complete.template.jsontemplates/api-endpoint-trigger.template.jsontemplates/callable-recipe-complete.template.jsontemplates/control-flow-examples.template.jsonThis document provides foundational knowledge for AI agents to generate valid Workato recipe JSON. This is the base skill that connector-specific skills (stripe-recipes, salesforce-recipes, etc.) extend.
.recipe.json files to understand project structure and patternstemplates/ directory in each skill{recipe_name}.recipe.json (lowercase, underscores for spaces){action}-{number} format (e.g., search-contact-001, return-success-005)ALWAYS use descriptive UUIDs, regardless of what existing recipes in a project use:
search-contact-001
create-customer-002
if-found-003
return-success-004
return-error-005
NEVER use random hex UUIDs:
a1b2c3d4-e5f6-7890-abcd-ef1234567890 ← ANTI-PATTERN
11111111-1111-1111-1111-111111111111 ← ANTI-PATTERN
NOTE: Recipes created in Workato's declarative UI have random hash UUIDs. This is a platform limitation, NOT a pattern to follow. When you see random UUIDs in existing recipes, do NOT copy them. Always use descriptive UUIDs for new recipes and actions.
The code field is an OBJECT (the trigger itself), NOT an array wrapped in a recipe object.
Key points:
code is the trigger object directly, not wrapped in recipecode is NOT an array - actions go inside code.blocknumber starts at 0, not 1as should be "trigger" for callable recipesSee: fundamentals/recipe-structure.md for full structure with examples.
Every block must have a sequential number field:
| Block | Number |
|---|---|
| Trigger | 0 |
| First action | 1 |
| Second action | 2 |
| ... | ... |
Non-sequential numbers cause "out of sequence" errors that block recipe activation. When modifying recipes, always renumber all actions sequentially.
The workato provider is built-in and should NOT be in the recipe's config array. Only include config entries for external connectors requiring authentication (e.g., salesforce, stripe, gmail).
DO NOT put config blocks inside actions. Connections are defined ONLY in the top-level config array. Actions reference connections implicitly through the provider field.
{
"provider": "gmail",
"name": "adhoc_http",
"config": {
"account_id": {"name": "My Gmail"}
}
}
{
"code": { ... },
"config": [
{
"keyword": "application",
"provider": "gmail",
"account_id": {"name": "My Gmail"}
}
]
}
Recipe filenames must match the recipe's name field, converted to lowercase with spaces replaced by underscores. Workato normalizes filenames on pull, so mismatches cause filename changes.
| Recipe Name | Correct Filename |
|---|---|
"name": "Search Contact By Email" | search_contact_by_email.recipe.json |
"name": "Create Stripe Customer" | create_stripe_customer.recipe.json |
"name": "Handle Dialog Submit" | handle_dialog_submit.recipe.json |
WARNING: The
restprovider MUST usemake_request_v2as its action name — NOT__adhoc_http_action. Using the wrong action name causes Workato to silently strip all input config on import. See patterns/adhoc-http-actions.md for the fullmake_request_v2structure and examples.
| Topic | Documentation |
|---|---|
| Recipe Structure | fundamentals/recipe-structure.md |
| Config Section | fundamentals/config-section.md |
| Datapill Syntax | fundamentals/datapill-syntax.md |
| Variables & Lists | patterns/variables-and-lists.md |
| If/Else | control-flow/if-else.md |
| Try/Catch | control-flow/try-catch.md |
| Foreach Loops | control-flow/foreach.md |
| Repeat While Loops | control-flow/repeat-while.md |
| Stop Action | control-flow/stop.md |
| API Endpoint Trigger | triggers/api-endpoint.md |
| Callable Recipe Trigger | triggers/callable-recipe.md |
| Scheduler Trigger | triggers/scheduler.md |
| Messaging Topic Trigger | triggers/messaging-topic.md |
| Publish to Topic Action | triggers/messaging-topic.md |
| Adhoc HTTP Actions | patterns/adhoc-http-actions.md |
| JWT Bearer Auth | patterns/jwt-auth.md |
| API Platform Artifacts | patterns/api-platform-artifacts.md |
Workato supports multiple trigger types. Choose based on how the recipe will be invoked.
Use when: Recipe should be callable via external HTTP request (curl, webhooks, third-party systems).
Provider: workato_api_platform
Action: receive_request
RECOMMENDATION: Use API endpoint triggers for most recipes. They're easier to test via curl and more practical for real integrations than callable recipes.
Note: API endpoint recipes require companion
.api_endpoint.jsonand.api_group.jsonfiles in addition to the recipe JSON. See patterns/api-platform-artifacts.md for the complete artifact set and file formats.
{
"number": 0,
"provider": "workato_api_platform",
"name": "receive_request",
"as": "trigger",
"keyword": "trigger",
"input": {
"request": {
"content_type": "json",
"schema": [
{
"name": "email",
"label": "Email",
"type": "string",
"control_type": "text",
"optional": false,
"hint": "Customer email address"
},
{
"name": "name",
"label": "Name",
"type": "string",
"control_type": "text",
"optional": false
},
{
"name": "company",
"label": "Company",
"type": "string",
"control_type": "text",
"optional": true,
"hint": "Optional company name"
}
]
},
"response": {
"content_type": "json",
"responses": [
{
"name": "Success",
"http_status_code": "200"
},
{
"name": "Created",
"http_status_code": "201"
},
{
"name": "Bad Request",
"http_status_code": "400"
},
{
"name": "Server Error",
"http_status_code": "500"
}
]
}
},
"extended_output_schema": [
{
"label": "Request",
"name": "request",
"type": "object",
"properties": [
{
"name": "email",
"label": "Email",
"type": "string",
"control_type": "text"
},
{
"name": "name",
"label": "Name",
"type": "string",
"control_type": "text"
},
{
"name": "company",
"label": "Company",
"type": "string",
"control_type": "text"
}
]
}
],
"block": [
// Actions go here
]
}
| Field | Required | Description |
|---|---|---|
name | Yes | Field identifier (used in datapills) |
label | Yes | Display label in UI |
type | Yes | Data type: string, integer, boolean, date, date_time |
control_type | Yes | UI control: text, number, checkbox, date, select |
optional | Yes | true for optional, false for required |
hint | No | Help text for the field |
Define all possible HTTP responses in the trigger. The return_response action references these by name:
"responses": [
{ "name": "Success", "http_status_code": "200" },
{ "name": "Created", "http_status_code": "201" },
{ "name": "Bad Request", "http_status_code": "400" },
{ "name": "Conflict", "http_status_code": "409" },
{ "name": "Server Error", "http_status_code": "500" }
]
Access request fields directly (no body wrapper):
"path": ["request", "email"]
"path": ["request", "name"]
"path": ["request", "company"]
WRONG:
"path": ["request", "body", "email"]
curl -X POST "https://apim.workato.com/your-workspace/your-endpoint" \
-H "API-TOKEN: your-api-token" \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "name": "Test User"}'
Use when: Recipe should be called by other Workato recipes (internal).
Provider: workato_recipe_function
Action: execute
{
"provider": "workato_recipe_function",
"name": "execute",
"keyword": "trigger",
"input": {
"parameters_schema_json": "[...]",
"result_schema_json": "[...]"
}
}
See: triggers/callable-recipe.md
Use when: Recipe should run on a recurring time-based schedule (daily digests, periodic sync jobs) rather than in response to an external event.
Provider: clock
Action: scheduled_event
| Scenario | Trigger Type |
|---|---|
| External API access needed | API Endpoint |
| Called by other recipes only | Callable Recipe |
| React to messages from other recipes | Messaging Topic (subscriber) |
| Receive external webhooks | Webhook |
| Time-based execution | Scheduler |
When a recipe needs to call another callable recipe, use the workato_recipe_function provider with action type call.
The flow_id object MUST include ALL fields, including zip_name.
| Field | Required | Description |
|---|---|---|
name | Yes | Recipe display name |
folder | Yes | Folder containing the recipe |
folder_full_path | Yes | Full path from Home |
zip_name | YES | Path to recipe JSON file |
CRITICAL WARNING: Missing
zip_namecauses RECIPE MUTATION AT RUNTIME. Withoutzip_name, Workato will unpredictably modify the recipe's metadata during execution. This corruption persists and breaks all future invocations. The recipe will appear valid during import/testing but will corrupt itself when actually invoked. This is worse than a silent failure - it permanently corrupts the recipe.
WRONG (missing zip_name - WILL CORRUPT THE RECIPE):
"flow_id": {
"name": "Search contact by email",
"folder": "atomic-salesforce-recipes",
"folder_full_path": "Home/atomic-salesforce-recipes"
}
CORRECT (includes zip_name):
"flow_id": {
"name": "Search contact by email",
"folder": "atomic-salesforce-recipes",
"folder_full_path": "Home/atomic-salesforce-recipes",
"zip_name": "atomic-salesforce-recipes/search_contact_by_email.recipe.json"
}
Complete call action example:
{
"provider": "workato_recipe_function",
"name": "call",
"as": "call_search_contact",
"keyword": "action",
"input": {
"flow_id": {
"name": "Search contact by email",
"folder": "atomic-salesforce-recipes",
"folder_full_path": "Home/atomic-salesforce-recipes",
"zip_name": "atomic-salesforce-recipes/search_contact_by_email.recipe.json"
},
"parameters": {
"email": "#{_dp('{\"pill_type\":\"output\",\"provider\":\"workato_api_platform\",\"line\":\"api_trigger\",\"path\":[\"request\",\"email\"]}')}"
}
}
}
Workato provides different actions for returning data based on the trigger type.
Use when: Recipe uses workato_api_platform trigger and needs to return HTTP response.
Provider: workato_api_platform
Action: return_response
The pick_list in extended_input_schema maps the response names (defined in trigger) to HTTP status codes:
"pick_list": [
["Success", "200"], // "Success" from trigger → HTTP 200
["Created", "201"], // "Created" from trigger → HTTP 201
["Bad Request", "400"], // "Bad Request" from trigger → HTTP 400
["Server Error", "500"] // "Server Error" from trigger → HTTP 500
]
CRITICAL: The first element (e.g., "Success") must match exactly the name field from the trigger's responses array.
{
"number": 5,
"provider": "workato_api_platform",
"name": "return_response",
"as": "return_success",
"keyword": "action",
"uuid": "return-success-005",
"input": {
"http_status_code": "200",
"response": {
"customer_id": "#{_dp('{\"pill_type\":\"output\",\"provider\":\"stripe\",\"line\":\"create_customer\",\"path\":[\"body\",\"id\"]}')}",
"success": "true",
"error_message": "=null"
}
},
"extended_input_schema": [
{
"change_on_blur": true,
"control_type": "select",
"extends_schema": true,
"label": "Response",
"name": "http_status_code",
"pick_list": [
["Success", "200"],
["Created", "201"],
["Bad Request", "400"],
["Server Error", "500"]
],
"type": "string"
},
{
"label": "Response body",
"name": "response",
"type": "object",
"properties": [
{
"control_type": "text",
"label": "Customer ID",
"name": "customer_id",
"type": "string"
},
{
"control_type": "checkbox",
"label": "Success",
"name": "success",
"type": "boolean"
},
{
"control_type": "text",
"label": "Error Message",
"name": "error_message",
"type": "string"
}
]
}
],
"extended_output_schema": [
{
"change_on_blur": true,
"control_type": "select",
"extends_schema": true,
"label": "Response",
"name": "http_status_code",
"pick_list": [
["Success", "200"],
["Created", "201"],
["Bad Request", "400"],
["Server Error", "500"]
],
"type": "string"
},
{
"label": "Response body",
"name": "response",
"type": "object",
"properties": [
{
"control_type": "text",
"label": "Customer ID",
"name": "customer_id",
"type": "string"
},
{
"control_type": "checkbox",
"label": "Success",
"name": "success",
"type": "boolean"
},
{
"control_type": "text",
"label": "Error Message",
"name": "error_message",
"type": "string"
}
]
}
]
}
Use separate return_response actions for different scenarios:
return_success (200) → Happy path
return_created (201) → New record created
return_not_found (200) → Search found nothing (still success)
return_bad_request (400) → Invalid input
return_error (500) → Caught exception
Each action has the same extended_input_schema (with all response codes in pick_list) but different input.http_status_code values.
Older recipes may use this more verbose format with toggleCfg and toggle_field:
{
"provider": "workato_api_platform",
"name": "return_response",
"as": "return_success",
"keyword": "action",
"toggleCfg": {
"response.success": true
},
"input": {
"http_status_code": "200",
"response": {
"id": "#{datapill}",
"success": "true"
}
},
"extended_output_schema": [
{
"change_on_blur": true,
"control_type": "select",
"extends_schema": true,
"label": "Response",
"name": "http_status_code",
"pick_list": [
["Success", "200"],
["Error", "400"]
],
"type": "string"
},
{
"label": "Response body",
"name": "response",
"properties": [
{
"control_type": "text",
"label": "ID",
"name": "id",
"type": "string"
},
{
"control_type": "checkbox",
"label": "Success",
"render_input": "boolean_conversion",
"parse_output": "boolean_conversion",
"name": "success",
"type": "boolean",
"toggle_hint": "Select from option list",
"toggle_field": {
"label": "Success",
"control_type": "text",
"toggle_hint": "Use custom value",
"name": "success",
"type": "boolean"
}
}
],
"type": "object"
}
],
"extended_input_schema": [
{
"change_on_blur": true,
"control_type": "select",
"extends_schema": true,
"label": "Response",
"name": "http_status_code",
"pick_list": [
["Success", "200"],
["Error", "400"]
],
"type": "string"
},
{
"label": "Response body",
"name": "response",
"properties": [
{
"control_type": "text",
"label": "ID",
"name": "id",
"type": "string"
},
{
"control_type": "checkbox",
"label": "Success",
"render_input": "boolean_conversion",
"parse_output": "boolean_conversion",
"name": "success",
"type": "boolean",
"toggle_hint": "Select from option list",
"toggle_field": {
"label": "Success",
"control_type": "text",
"toggle_hint": "Use custom value",
"name": "success",
"type": "boolean"
}
}
],
"type": "object"
}
],
"uuid": "return-success-001"
}
CRITICAL fields:
http_status_code - HTTP status code (200, 400, 500, etc.)response - Response body matching the schema defined in triggerextended_input_schema.http_status_code.pick_list - Must map response names from trigger to status codesextended_input_schema.http_status_code.extends_schema: true - Required flagextended_input_schema.http_status_code.change_on_blur: true - Required flagextended_output_schema - Must mirror extended_input_schematoggleCfg - Optional, for boolean toggle fieldsCommon pattern:
http_status_code: "200" or "201"http_status_code: "400", "409", or "500"pick_list MUST match the response names defined in the trigger's response.responses arrayUse when: Recipe uses workato_recipe_function trigger and needs to return data.
Provider: workato_recipe_function
Action: return_result
{
"provider": "workato_recipe_function",
"name": "return_result",
"as": "return_result",
"keyword": "action",
"input": {
"result": {
"id": "#{datapill}",
"success": "true"
}
},
"extended_output_schema": [
{
"label": "Result",
"name": "result",
"properties": [
{
"control_type": "text",
"label": "ID",
"name": "id",
"type": "string"
},
{
"control_type": "checkbox",
"label": "Success",
"name": "success",
"type": "boolean"
}
],
"type": "object"
}
],
"extended_input_schema": [
{
"label": "Result",
"name": "result",
"properties": [
{
"control_type": "text",
"label": "ID",
"name": "id",
"type": "string"
},
{
"control_type": "checkbox",
"label": "Success",
"name": "success",
"type": "boolean"
}
],
"type": "object"
}
],
"uuid": "return-result-001"
}
Key fields:
result - Result object matching the schema defined in trigger's result_schema_jsonEvery block in a recipe requires these fields:
| Field | Required | Description |
|---|---|---|
number | Yes | Sequential step number |
keyword | Yes | Block type: trigger, action, if, else, try, catch, foreach, stop, repeat, while_condition |
uuid | Yes | Unique identifier (max 36 chars) |
as | Yes* | Step alias for datapill references |
*Required for actions, triggers, catch, foreach, and repeat. Optional for try, if, and else blocks. NOT used on while_condition.
| Block Type | Provider |
|---|---|
| Trigger | Required (e.g., workato_api_platform) |
| Action | Required (e.g., stripe, salesforce) |
| If/Else | NO provider field |
| Foreach | NO provider field |
| Repeat / While Condition | NO provider field |
| Catch | "provider": null (explicitly null) |
"search-customer-001", "return-success-001"WARNING: UUIDs longer than 36 characters will cause the recipe to be rejected during import. Keep names concise.
Actions that return or accept complex data need extended schemas.
WARNING: The
extended_input_schemaMUST fully define ALL fields in the action'sinputobject, including nested objects. If any input field is missing from the schema, Workato will silently drop that data during execution, causing cascading failures.
This is a Workato platform behavior that affects ALL connectors. Complex connectors (Salesforce, NetSuite, etc.) are particularly vulnerable due to deeply nested input structures.
Symptoms of incomplete schemas:
Agent requirement: When generating recipes with custom actions or complex inputs, ALWAYS verify that extended_input_schema mirrors the complete input structure.
EXCEPTION — Native connector internal parameters: Native connector actions have built-in parameters handled internally by the connector. These must NEVER appear in
extended_input_schema— if included, Workato creates duplicate fields in the UI, with theinputvalue routing to the EIS copy (leaving the native field blank). This applies to ALL native connectors, not just Salesforce. Examples:
- Salesforce
search_sobjects:sobject_name,limitare internals. Only user-facing filter fields (Id,AccountId,- Salesforce
search_sobjects_soql:queryis an internal. Empty EIS is correct.- Jira
search_issues_by_JQL:jqlis the internal field name (NOTquery). Empty EIS is correct. Note: action name is case-sensitive — must be uppercaseJQL.- General rule: If an action has a built-in required field visible in the UI, do NOT redeclare it in EIS. Use
input.{field_name}to set its value; the field name must match the connector's internal name (pull a blank action from the UI to discover it). See the connector-specific skill files for details on which parameters are connector internals.
Defines the output fields available from an action:
"extended_output_schema": [
{
"label": "Customer ID",
"name": "id",
"type": "string",
"control_type": "text"
}
]
Defines the input fields for an action. Must mirror the input structure exactly.
Simple flat input:
"input": {
"email": "[email protected]"
}
"extended_input_schema": [
{
"label": "Email",
"name": "email",
"type": "string",
"control_type": "text",
"optional": false
}
]
Nested input (e.g., custom HTTP actions):
"input": {
"path": "/v1/customers/search",
"verb": "get",
"input": {
"schema": "[...]",
"data": {
"query": "email:'[email protected]'",
"limit": "1"
}
}
}
"extended_input_schema": [
{
"label": "Path",
"name": "path",
"type": "string",
"control_type": "text"
},
{
"label": "Verb",
"name": "verb",
"type": "string",
"control_type": "select"
},
{
"label": "Request URL parameters",
"name": "input",
"type": "object",
"properties": [
{
"label": "Schema",
"name": "schema",
"type": "string",
"control_type": "text"
},
{
"label": "Data",
"name": "data",
"type": "object",
"properties": [
{
"label": "Query",
"name": "query",
"type": "string",
"control_type": "text"
},
{
"label": "Limit",
"name": "limit",
"type": "string",
"control_type": "text"
}
]
}
]
}
]
Before finalizing any action block, verify:
input has a corresponding entry in extended_input_schemainput have matching nested properties in the schemainput and schemainput.data object is fully definedWorkato formulas use a restricted subset of Ruby methods — not all Ruby methods are supported. Using an unsupported method will block recipe activation with no clear error message. Only use methods from the allowlist below.
Formulas are prefixed with =:
"input": {
"field_name": "=formula_expression"
}
Use .present? checks with ternary operator to conditionally set fields:
"FirstName": "=_dp('{\"pill_type\":\"output\",\"provider\":\"workato_recipe_function\",\"line\":\"trigger\",\"path\":[\"parameters\",\"first_name\"]}').present? ? _dp('{\"pill_type\":\"output\",\"provider\":\"workato_recipe_function\",\"line\":\"trigger\",\"path\":[\"parameters\",\"first_name\"]}') : skip"
Pattern:
=datapill.present? ? datapill : skip
present? - Checks if value exists and is not empty? value : skip - If present, use value; otherwise, skip the fieldskip - Special keyword to exclude field from the actionWorkato formulas are an allowlist of Ruby methods. If a method is not listed here, do not use it — it will block recipe activation. This list is sourced from the Workato formula documentation.
| Method | Description |
|---|---|
blank? | True if nil, empty, or whitespace only |
present? | True if not blank |
presence | Returns value if present, nil otherwise |
include? | True if string contains substring |
exclude? | True if string does not contain substring |
match? | True if string matches regex pattern |
starts_with? | True if string starts with prefix |
ends_with? | True if string ends with suffix |
is_true? | True if value is truthy |
is_not_true? | True if value is falsy |
strip | Remove leading/trailing whitespace |
lstrip | Remove leading whitespace |
rstrip | Remove trailing whitespace |
upcase | Convert to uppercase |
downcase | Convert to lowercase |
capitalize | Capitalize first letter |
titleize | Capitalize first letter of each word |
reverse | Reverse the string |
gsub | Replace all occurrences of pattern |
sub | Replace first occurrence of pattern |
strip_tags | Remove HTML tags |
scrub | Replace invalid byte sequences |
parameterize | Convert to URL-safe slug |
quote | Wrap in quotes |
length | Number of characters |
slice | Extract substring by position |
scan | Find all matches of pattern |
split | Split into array by delimiter |
ljust | Left-justify with padding |
rjust | Right-justify with padding |
encode | Encode to specified encoding |
transliterate | Transliterate to ASCII |
bytes | Convert to byte array |
bytesize | Size in bytes |
byteslice | Extract bytes by position |
to_s | Convert to string |
to_i | Convert to integer |
to_f | Convert to float |
ordinalize | Convert number to ordinal string (1st, 2nd, 3rd) |
to_country_alpha2 | Convert country name to ISO alpha-2 code |
to_country_alpha3 | Convert country name to ISO alpha-3 code |
to_country_name | Convert country code to name |
to_currency | Format as currency string |
to_currency_code | Convert to currency code |
to_currency_name | Convert to currency name |
to_currency_symbol | Convert to currency symbol |
to_phone | Format as phone number |
to_state_code | Convert state name to code |
to_state_name | Convert state code to name |
| Method | Description |
|---|---|
abs | Absolute value |
round | Round to specified precision |
ceil | Round up |
floor | Round down |
even? | True if even |
odd? | True if odd |
blank? | True if nil |
present? | True if not nil |
presence | Returns value if present, nil otherwise |
to_i | Convert to integer |
to_f | Convert to float |
to_s | Convert to string |
to_currency | Format as currency |
to_phone | Format as phone number |
| Method | Description |
|---|---|
now | Current timestamp |
today | Current date |
from_now | Duration from now (e.g., 30.days.from_now) |
ago | Duration ago (e.g., 1.hour.ago) |
strftime | Format with pattern (e.g., strftime('%Y-%m-%dT%H:%M:%SZ')) |
in_time_zone | Convert to timezone (e.g., in_time_zone("UTC")) |
beginning_of_hour | Start of current hour |
beginning_of_day | Start of current day |
beginning_of_week | Start of current week |
beginning_of_month | Start of current month |
beginning_of_year | Start of current year |
end_of_month | End of current month |
wday | Day of week (0=Sunday) |
yday | Day of year |
yweek | Week of year |
dst? | True if daylight saving time |
to_date | Convert to date |
to_time | Convert to time |
to_i | Convert to Unix epoch integer |
| Method | Description |
|---|---|
first | First element |
last | Last element |
index | Position of element |
count | Number of elements |
length | Number of elements |
where | Filter by condition |
except | Exclude by condition |
pluck | Extract field values |
format_map | Format each element |
join | Combine into string with separator |
smart_join | Join, skipping blank values |
concat | Append another array |
reverse | Reverse order |
sum | Sum of elements |
uniq | Remove duplicates |
flatten | Flatten nested arrays |
max | Maximum value |
min | Minimum value |
compact | Remove nil values |
blank? | True if empty |
include? | True if contains element |
exclude? | True if does not contain element |
present? | True if not empty |
presence | Returns array if present, nil otherwise |
to_csv | Convert to CSV string |
to_json | Convert to JSON string |
to_xml | Convert to XML string |
from_xml | Parse XML string |
encode_www_form | URL-encode as form data |
encode_url | URL-encode a string |
to_param | Convert to URL parameter string |
keys | Hash keys as array |
values | Hash values as array |
Any method not listed above will block recipe activation. Workato's formula language is a strict allowlist — standard Ruby methods like .each, .map, .chomp, .merge, .utc, and .to_a do not exist and will cause silent import failures.
Common equivalents:
in_time_zone("UTC") or strftime('%Y-%m-%dT%H:%M:%SZ') (not .utc).first / .last (not [0] or [n])json_parser connector's parse_json_v2 action (not .parse_json — no such formula method exists). See adhoc-http-actions.md for the pattern..where, .pluck, .format_map (not .select, .map, .each)Conditional assignment:
"Email": "=_dp('{...email...}').present? ? _dp('{...email...}') : skip"
String transformation:
"Status": "=_dp('{...status...}').upcase"
Null assignment:
"field_name": "=null"
When concatenating strings from multiple datapills, use formula mode (= prefix) with Ruby + operator.
WRONG (mixing #{} interpolation with concatenation - INVALID):
"guest_name": "#{_dp('{...first_name...}')} + ' ' + _dp('{...last_name...}'}"
CORRECT (use = prefix, no #{} wrapper):
"guest_name": "=_dp('{\"pill_type\":\"output\",\"provider\":\"workato_recipe_function\",\"line\":\"trigger\",\"path\":[\"parameters\",\"first_name\"]}') + ' ' + _dp('{\"pill_type\":\"output\",\"provider\":\"workato_recipe_function\",\"line\":\"trigger\",\"path\":[\"parameters\",\"last_name\"]}')"
Pattern:
"field": "=_dp('{...pill1...}') + ' ' + _dp('{...pill2...}')"
Key rules:
= prefix (formula mode) for concatenation#{}+ operator between strings' ' or '-'Provide a default value when an optional parameter is missing using .present? with ternary operator:
Pattern:
"field": "=_dp('{...}').present? ? _dp('{...}') : 'default_value'"
Example (default status to 'Reserved' when not provided):
"Status__c": "=_dp('{\"pill_type\":\"output\",\"provider\":\"workato_recipe_function\",\"line\":\"trigger\",\"path\":[\"parameters\",\"status\"]}').present? ? _dp('{\"pill_type\":\"output\",\"provider\":\"workato_recipe_function\",\"line\":\"trigger\",\"path\":[\"parameters\",\"status\"]}') : 'Reserved'"
Key points:
.present? checks if value exists and is not emptyWhen recipes have required return parameters, ALL code paths (success AND catch blocks) must provide values. In catch blocks where data isn't available, use =null:
WRONG: "customer_id": ""
CORRECT: "customer_id": "=null"
When you need to return a value from one of multiple possible sources (e.g., search result OR create result), use ternary syntax:
"customer_id": "=_dp('{...search_result...}').present? ? _dp('{...search_result...}') : _dp('{...create_result...}')"
This avoids the need for intermediate variables and works at both validation and runtime.
When connections are in the same folder as recipes, use empty string for folder:
"account_id": {
"zip_name": "my_connection.connection.json",
"name": "My Connection Name",
"folder": ""
}
When connections are in a different folder:
"account_id": {
"zip_name": "Connections/my_connection.connection.json",
"name": "My Connection Name",
"folder": "Connections"
}
| Mistake | Symptom | Fix |
|---|---|---|
recipe.code[] wrapper | Recipe doesn't render | Use code as object directly |
| Non-sequential action numbers | Activation error | Renumber sequentially from 0 |
workato provider in config | Push error | Remove - it's built-in |
| Empty string for required params | Activation error | Use =null |
+ concat with datapills at import | Validation error | Use ternary or single datapill |
| Random hex UUIDs | Poor maintainability | Always use descriptive UUIDs (don't copy existing random UUIDs) |
| Copying patterns from declarative UI recipes | Various errors | Use skill templates, not UI-generated recipes as reference |
See validation-checklist.md for the consolidated recipe validation checklist.
fundamentals/ directory for recipe structure, config, and datapill syntaxtriggers/ directory for detailed trigger documentationcontrol-flow/ directory for if/else, try/catch, foreach, stop, and repeat-while patternstemplates/ directory for starter templatesnpx claudepluginhub workato-devs/recipe-skills --plugin recipe-skillsData Tables connector recipes for Workato. Covers triggers, CRUD actions, search, upsert, batch operations, UUID column mapping, and table reference objects for the internal workato_db_table connector.
Provides proven architectural patterns for building n8n workflows, covering webhook processing, HTTP API integration, database operations, AI agents, batch processing, and scheduled tasks.
Provides proven architectural patterns for building n8n workflows, including webhook processing, HTTP API integration, database operations, AI agents, and scheduled tasks.