From recipe-skills
Salesforce integration recipes for Workato. Enables AI agents to generate valid recipe JSON for Salesforce operations including record creation, updates, upserts, and searches using standard SObjects and custom objects.
How this skill is triggered — by the user, by Claude, or both
Slash command
/recipe-skills:salesforce-recipesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **⚠️ DEPENDENCY: Load the `workato-recipes` base skill first if not already loaded.**
⚠️ DEPENDENCY: Load the
workato-recipesbase skill first if not already loaded. This skill requires the base Workato knowledge for triggers, control flow, datapills, formulas, and recipe structure.
This skill provides Salesforce-specific knowledge for generating Workato recipes. It extends the workato-recipes base skill and focuses on Salesforce-specific patterns.
.recipe.json files to understand local patternstemplates/upsert-contact.json as referenceupsert-contact-001, search-account-002__c)["body"] wrapperWARNING: Never assume custom objects/fields exist. Always ask if uncertain whether an object or field is custom. Custom objects are org-specific and NOT portable.
Use this skill when building Workato recipes that:
Prerequisites:
workato-recipes base skill loadedEvery Salesforce recipe requires the salesforce provider in the config section:
{
"keyword": "application",
"provider": "salesforce",
"skip_validation": false,
"account_id": {
"zip_name": "Workspace Connections/salesforce_connection.connection.json",
"name": "Salesforce Connection Name",
"folder": "Workspace Connections"
}
}
Combined with trigger provider:
For callable recipe trigger:
"config": [
{ "provider": "workato_recipe_function", "account_id": null, ... },
{ "provider": "salesforce", "account_id": { ... }, ... },
{ "provider": "logger", "account_id": null, ... }
]
Standard SObjects (available in ALL Salesforce orgs):
Contact, Account, Lead, Opportunity, Case, Task, EventId, Name, Email, Phone, AccountId, etc.Custom Objects and Fields (specific to each Salesforce implementation):
Booking__c, Room__c, Property__cContact_Type__c, Unique_Email__c, Status__cConfig__mdt, Settings__mdt| Suffix | Type | Example | Notes |
|---|---|---|---|
__c | Custom Object/Field | Booking__c, Status__c | NOT standard across orgs |
__mdt | Custom Metadata Type | Config__mdt | NOT standard across orgs |
__e | Platform Event | Order_Event__e | NOT standard across orgs |
__x | External Object | SAP_Account__x | NOT standard across orgs |
| (none) | Standard Object/Field | Contact, Email | Standard across all orgs |
WARNING: When generating recipes that use custom objects or fields (anything with
__c,__mdt,__e,__x), you MUST:
- Never assume these objects/fields exist in the target environment
- Document clearly that the recipe requires specific custom objects/fields
- Ask the user if uncertain whether an object/field is custom or standard
- Use standard objects whenever possible for maximum portability
Example of proper documentation:
{
"name": "Upsert contact with custom type",
"description": "REQUIRES CUSTOM FIELD: Contact.Contact_Type__c (picklist)",
...
}
The Salesforce connector provides 32 native actions and 15 triggers. See lint-rules.json for the authoritative list of valid action and trigger names.
CRITICAL: All Salesforce actions require
dynamicPickListSelectionin addition to theinputblock. See dynamicPickListSelection below.
scheduled_sobject_soql_query — runs a SOQL query on a schedule to detect new/changed records. V2 variant: scheduled_sobject_soql_query_v2.change_data_capture — streams record changes as they happen. Requires CDC enabled on the SObject. Prefer over new_pushtopic_event (legacy).new_platform_event — listens for platform event messages.new_outbound_message — triggered by Salesforce workflow/process builder.sobject_created_bulk, sobject_batch_created, scheduled_sobject_bulk_v2_created, scheduled_sobject_bulk_v2_created_or_updated — for high-volume record processing.new_custom_object, updated_custom_object (+ _webhook variants for real-time).sobject_deleted — triggers on record deletion.Record CRUD:
upsert_sobject — Create or update by external ID. Best default for sync workflows. See detail below.update_sobject — Update by Salesforce record ID when you already have the ID. See detail below.delete_sobject — Delete by Salesforce record ID.create_custom_object / get_custom_object — Create or retrieve custom object records.search_sobjects with an Id filter or search_sobjects_soql with WHERE Id = '...'.Search:
search_sobjects — Exact field match only (equality). Has critical EIS rules. See detail below.search_sobjects_soql — Raw SOQL for complex criteria (LIKE, IN, OR, ORDER BY). See detail below.search_sobjects_soql_v2 — V2 of SOQL search.Bulk operations (thousands+ records):
insert_bulk_job / upsert_bulk_job / update_bulk_job — Bulk create/upsert/update. V1 variants (_v1 suffix) also available.retry_bulk_jobs — Retry failed bulk jobs.search_sobjects_soql_bulk_csv / _v2 — Bulk SOQL query returning CSV.Composite (multiple operations in one API call):
composite_create_sobject / composite_update_sobject / upsert_composite_sobjectApprovals: approve_process, reject_process, submit_process
Files & attachments: upload_file_content, get_attachment_body, get_combined_attachment
Reports & metadata: get_report_by_id, get_related, get_sobject_schema
Platform events: create_custom_platform_event
Data categories: list_data_category_groups, read_data_category_group
Uncovered operations: Use __adhoc_http_action for REST API endpoints, Tooling API, Metadata API, or custom Apex REST services not covered by native actions.
All Salesforce actions require dynamicPickListSelection in addition to the input block. This is used by the Workato UI for field discovery.
{
"provider": "salesforce",
"name": "upsert_sobject",
"as": "upsert_contact",
"keyword": "action",
"dynamicPickListSelection": {
"sobject_name": "Contact",
"query_field.primary_key": [
{ "label": "Email", "value": "Email" }
]
},
"input": {
"sobject_name": "Contact",
"query_field": { "primary_key": "Email" },
...
}
}
Note: Both dynamicPickListSelection.sobject_name AND input.sobject_name are required. They should have the same value.
Use when: Creating or updating records based on an external ID field.
{
"provider": "salesforce",
"name": "upsert_sobject",
"as": "upsert_contact",
"keyword": "action",
"dynamicPickListSelection": {
"sobject_name": "Contact",
"query_field.primary_key": [
{ "label": "Email", "value": "Email" }
]
},
"input": {
"sobject_name": "Contact",
"query_field": {
"primary_key": "Email"
},
"Email": "#{email_datapill}",
"FirstName": "#{first_name_datapill}",
"LastName": "#{last_name_datapill}"
}
}
Key fields:
sobject_name - SObject API name (e.g., Contact, Account, Booking__c)query_field.primary_key - Field to use for upsert matching (must be marked as External ID in Salesforce OR be a standard unique field)IMPORTANT: The query_field.primary_key value must be:
Email or IdUse when: Updating an existing record by Salesforce ID.
{
"provider": "salesforce",
"name": "update_sobject",
"as": "update_booking",
"keyword": "action",
"input": {
"sobject_name": "Booking__c",
"id": "#{booking_id_datapill}",
"Status__c": "#{status_datapill}",
"Check_Out_Date__c": "=checkout_date_datapill"
}
}
Key fields:
sobject_name - SObject API nameid - Salesforce 18-character record ID (required)Use when: Finding records by exact field match (equality only, no LIKE/IN/OR).
{
"provider": "salesforce",
"name": "search_sobjects",
"as": "search_contact",
"keyword": "action",
"dynamicPickListSelection": {
"sobject_name": "Contact"
},
"input": {
"sobject_name": "Contact",
"limit": "150",
"Email": "#{email_datapill}"
},
"extended_input_schema": [
{
"control_type": "text",
"label": "Email",
"name": "Email",
"type": "string"
}
]
}
Key fields:
sobject_name - SObject API namelimit - Max records to return (default 150)Output: Returns array of matching records
CRITICAL - EIS Rules for search_sobjects:
sobject_name and limit are connector internals — do NOT include them in extended_input_schema. Including them causes Workato to treat them as WHERE clause field filters, producing malformed SOQL.Email, Id, AccountId) MUST be in extended_input_schema or Workato silently drops them.IMPORTANT - Limit Parameter Type:
When accepting limit as a user input parameter, use integer type in the schema:
{
"name": "limit",
"type": "integer",
"control_type": "integer",
"parse_output": "integer_conversion"
}
Do NOT use "type": "number" - this causes Workato to treat values as floats, producing malformed SOQL (e.g., LIMIT 50.0) which Salesforce rejects.
Use when: Finding records with complex criteria (LIKE, IN, OR, ORDER BY, relationship fields) using raw SOQL.
CRITICAL: Use action name search_sobjects_soql, NOT search_sobjects with a query parameter. The query parameter is not recognized by search_sobjects via CLI push — it gets treated as a field name.
{
"provider": "salesforce",
"name": "search_sobjects_soql",
"as": "search_bookings",
"keyword": "action",
"dynamicPickListSelection": {
"sobject_name": "Booking"
},
"input": {
"sobject_name": "Booking__c",
"query": "Room__r.Room_Number__c = '#{_dp('{...room_number...}')}' AND Status__c NOT IN ('Cancelled', 'No Show')"
}
}
Workato's Salesforce connector does NOT support SOQL bind variable syntax (:variable). Use direct string interpolation instead.
WRONG (bind variable syntax - WILL NOT WORK):
"query": "Email = :#{_dp('...email...')}"
"query": "Room_Number__c = :#{_dp('...room...')}"
CORRECT (string values - wrap in single quotes):
"query": "Email = '#{_dp('{\"pill_type\":\"output\",\"provider\":\"workato_recipe_function\",\"line\":\"trigger\",\"path\":[\"parameters\",\"email\"]}')}'"
"query": "Room_Number__c = '#{_dp('{...room_number...}')}'"
CORRECT (dates/numbers - no quotes):
"query": "Check_In_Date__c <= #{_dp('{...date...}')}"
"query": "Amount__c > #{_dp('{...amount...}')}"
| Value Type | Syntax | Example |
|---|---|---|
| String | '#{datapill}' | Email = '#{_dp(...)}' |
| Date | #{datapill} | CreatedDate >= #{_dp(...)} |
| Number | #{datapill} | Amount > #{_dp(...)} |
| Boolean | #{datapill} | IsActive = #{_dp(...)} |
| Picklist | '#{datapill}' | Status = '#{_dp(...)}' |
Multi-condition query:
"query": "Room__r.Room_Number__c = '#{_dp('{...room_number...}')}' AND Status__c NOT IN ('Cancelled', 'No Show', 'Checked Out') AND Check_In_Date__c <= #{_dp('{...checkout_date...}')} AND Check_Out_Date__c >= #{_dp('{...checkin_date...}')}"
Date range query:
"query": "CreatedDate >= #{_dp('{...start_date...}')} AND CreatedDate <= #{_dp('{...end_date...}')}"
See: patterns/soql-query-syntax.md
Salesforce actions do NOT use the ["body"] wrapper in datapill paths.
This is consistent with Stripe and other native connectors:
// CORRECT for Salesforce
"path": ["Id"]
"path": ["Email"]
"path": ["Status__c"]
"path": ["Account", "Name"]
// WRONG for Salesforce - Do NOT use
"path": ["body", "Id"]
Record ID from upsert:
"#{_dp('{\"pill_type\":\"output\",\"provider\":\"salesforce\",\"line\":\"upsert_contact\",\"path\":[\"id\"]}')}"
Standard field:
"#{_dp('{\"pill_type\":\"output\",\"provider\":\"salesforce\",\"line\":\"search_contact\",\"path\":[\"Contact\",{\"path_element_type\":\"current_item\"},\"Email\"]}')}"
Custom field:
"#{_dp('{\"pill_type\":\"output\",\"provider\":\"salesforce\",\"line\":\"upsert_booking\",\"path\":[\"Status__c\"]}')}"
Relationship field (dot notation in Salesforce, nested in datapill):
"#{_dp('{\"pill_type\":\"output\",\"provider\":\"salesforce\",\"line\":\"search_contact\",\"path\":[\"Contact\",{\"path_element_type\":\"current_item\"},\"Account\",\"Name\"]}')}"
Search actions return arrays. Use path_element_type: current_item to access array elements:
"path": ["Contact", {"path_element_type": "current_item"}, "Id"]
Always specify the external ID field when upserting:
{
"provider": "salesforce",
"name": "upsert_sobject",
"as": "upsert_contact",
"input": {
"sobject_name": "Contact",
"query_field": {
"primary_key": "Unique_Email__c" // User specifies this field
},
"Unique_Email__c": "#{email}",
"Email": "#{email}",
"FirstName": "#{first_name}",
"LastName": "#{last_name}"
}
}
Pattern:
Use Workato formulas (from base skill) to conditionally set fields:
"FirstName": "=_dp('{...}').present? ? _dp('{...}') : skip",
"Phone": "=_dp('{...}').present? ? _dp('{...}') : skip"
This skips fields when values are empty, preventing overwrites with blanks.
Search for record, then update if found:
// Step 1: Search
{
"provider": "salesforce",
"name": "search_sobjects",
"as": "search_contact",
"input": {
"sobject_name": "Contact",
"Email": "#{email}"
}
}
// Step 2: Check if found
{
"keyword": "if",
"input": {
"conditions": [{
"operand": "present",
"lhs": "#{search_contact.Contact[].Id}"
}]
},
"block": [
// Update found record
{
"provider": "salesforce",
"name": "update_sobject",
"input": {
"sobject_name": "Contact",
"id": "#{search_contact.Contact[].Id}",
"Status": "#{new_status}"
}
}
]
}
Picklist fields have specific allowed values. Use exact API values:
"Status__c": "Confirmed", // Correct - exact picklist value
"Contact_Type__c": "Guest" // Correct - exact picklist value
Note: Picklist values are case-sensitive and must match Salesforce configuration.
Standard SObjects have required fields:
LastName (required)Name (required)Name, StageName, CloseDate (required)Custom objects may have different requirements based on implementation.
See validation-checklist.md for Salesforce-specific validation, which references the base checklist in workato-recipes/validation-checklist.md.
| Error | Cause | Solution |
|---|---|---|
| "Required field missing" | Missing required field | Add LastName for Contact, Name for Account, etc. |
| "Invalid field" | Field doesn't exist in org | Verify field API name, check if custom field exists |
| "Invalid external ID field" | Field not marked as External ID | Verify field is marked as External ID in Salesforce |
| "Duplicate value" | Unique constraint violation | Check for existing records before creating |
| "Invalid picklist value" | Wrong picklist value | Use exact API value from Salesforce picklist |
| Query returns no results unexpectedly | Using SOQL bind variable syntax | Replace :#{datapill} with '#{datapill}' for strings |
Like all Workato connectors, Salesforce actions require complete extended_input_schema definitions.
Salesforce schema entries include metadata fields:
{
"name": "Email",
"label": "Email",
"type": "string",
"control_type": "text",
"custom": false, // false for standard, true for custom
"optional": true,
"sfdc_createable": true, // Can be set on create
"sfdc_updateable": true // Can be updated
}
Custom field example:
{
"name": "Contact_Type__c",
"label": "Contact Type",
"type": "string",
"control_type": "select",
"custom": true, // Custom field
"optional": true,
"pick_list": "sobject_field_values_list",
"sfdc_createable": true,
"sfdc_updateable": true
}
See templates/ directory:
upsert-contact.json - Upsert contact with external IDsearch-contact.json - Search contact by emailupdate-record.json - Update record by Salesforce IDworkato-recipes - Recipe structure, triggers, control flow, formulastemplates/ directorypatterns/ directorynpx claudepluginhub workato-devs/recipe-skills --plugin recipe-skillsCreates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.