steps[] reference - action DSL
A step is a single instruction inside a flow. Every step has an id, an
action, and (depending on the action) a handful of action-specific fields.
See also: flows, connector,
simple-vs-federated.
Step skeleton
{
"id": "<unique-within-flow>",
"action": "<one of the v3 actions; see below for the closed set>",
"when": { /* optional WhenCondition */ }
// action-specific fields below
}id must be unique within the flow (enforced by FlowV3._unique_step_ids).
The action set is closed: any value outside the list above fails with
Unknown action '<x>'; must be one of ['associate_many', 'create', 'create_many',
'delete', 'delete_many', 'find', 'notify', 'plugin', 'post', 'skip', 'transform',
'update', 'update_many', 'upsert', 'upsert_many']The 10 baseline actions are available under config version: "3.0". The 5
*_many bulk actions (upsert_many, create_many, update_many,
delete_many, associate_many) require version: "3.1" per
connector.md.
StepV3 declares model_config = ConfigDict(extra="allow"), which means
unknown step-level fields pass validation silently. Treat the per-action
tables below as the authoritative list of fields the runtime consumes; typos
at the step level will not be caught at draft-save time.
associateis reserved. Per the comment at (internal plugsync tooling - see the source repo) line 77, theassociateaction name is reserved for a future Plan 6b feature and must not be used in v3 configs. Trying to publish a config that uses it will fail validation with theUnknown actionerror above.
transform
Reshapes data from one schema into another, typically inbound source into
canonical.
| Field | Required | Notes |
|---|---|---|
entity | yes | The entity name being transformed. |
to_schema | yes | Target schema key (usually canonical). |
from_schema | no | Source schema key. Defaults to inbound payload. |
Missing field error: transform step requires entity and to_schema.
Example:
{"id": "to_canonical", "action": "transform", "entity": "contact",
"from_schema": "fooshop", "to_schema": "canonical"}find
Look up an existing target record by criteria.
| Field | Required | Notes |
|---|---|---|
target | yes | Schema key to query (e.g., hubspot). |
entity | yes | Entity name. |
by | yes | dict of {property: jsonpath} criteria. |
Missing field error: find step requires target, entity, by.
Example:
{"id": "find_company", "action": "find", "target": "hubspot",
"entity": "company", "by": {"domain": "$.canonical.domain"}}upsert, create, update, delete
Four variants of writing to a target. They share required fields and differ
in semantics (upsert = create-or-update, create = insert only, update =
modify only, delete = remove).
| Field | Required | Notes |
|---|---|---|
target | yes | Schema key. |
entity | yes | Entity name. |
data | no | JSONPath or inline dict of properties. |
associations | no | List of association payloads attached to the upserted/created record. Shape is consumer-specific (HubSpot uses {to: {id, type}, types: [...]}). The runtime forwards them verbatim. |
Missing field error: <action> step requires target and entity (where
<action> is the actual action name, e.g., upsert step requires target and entity).
upsert example:
{"id": "upsert_hubspot", "action": "upsert", "target": "hubspot",
"entity": "company", "data": "$.payload"}update may additionally take update_method ("PUT" or "PATCH") and
update_url when the target REST endpoint deviates from the default. These
fields are declared on StepV3 but are not enforced by the action validator;
they are consumed by the runtime when present.
post
Generic HTTP POST. Used for outbound flows pushing to a target’s REST API.
| Field | Required | Notes |
|---|---|---|
target | yes | The schema key whose credential_name provides auth. |
entity | no | Logical entity (informational, for logs). |
url | yes | Absolute or schema-base-relative URL. |
body | no | JSONPath or inline dict. |
headers | no | dict of headers. |
Missing field error: post step requires target and url.
Example (HubSpot -> Compass):
{"id": "push_compass", "action": "post", "target": "compass_outbound",
"entity": "company", "url": "/api/companies/upsert",
"body": "$.payload.object.properties"}notify
Send a notification (Slack, email, webhook, etc. depending on target type).
| Field | Required | Notes |
|---|---|---|
target | yes | Notification target. |
Missing field error: notify step requires target.
plugin
Invoke a plugin function. Currently this runs as an in-process Python plugin; migration to AWS Lambda is tracked in issue #190.
| Field | Required | Notes |
|---|---|---|
module | yes | Plugin module name. |
function | yes | Function name within the module. |
Missing field error: plugin step requires module and function.
upsert_many, create_many, update_many, delete_many (v3.1)
Bulk variants for high-volume ingestion. Process a batch of records through one HubSpot batch API call instead of N single calls.
| Field | Required | Notes |
|---|---|---|
target | yes | Must equal "hubspot". Bulk actions are HubSpot-only. |
entity | yes | Entity name. |
items | yes | JSONPath expression resolving to a list of records to process. |
chunk_size | no | Batch size (1-100). Defaults to HubSpot’s API limit. |
output | no | JSONPath into the batch response, used by later steps in the same flow. |
reconcile | no | When true, the runtime emits per-record sync events after the batch call. |
Missing field errors:
<action> step requires items<action> step requires entity<action> step requires target 'hubspot'
Example:
{"id": "bulk_upsert", "action": "upsert_many", "target": "hubspot",
"entity": "contact", "items": "$.canonical.contacts", "chunk_size": 50,
"output": "$batch_out", "reconcile": true}associate_many (v3.1)
Bulk association creation. Attaches multiple records to a target record in one batch call.
| Field | Required | Notes |
|---|---|---|
items | yes | JSONPath resolving to a list of association payloads. |
Missing field error: associate_many step requires items.
Example:
{"id": "assoc_deals", "action": "associate_many",
"items": "$.canonical.deal_associations"}skip
No-op. Paired with when to express conditional branches.
{"id": "skip_if_test", "action": "skip",
"when": {"equals": {"$.payload.env": "test"}}}The action validator imposes no required fields on skip; it is only useful
in combination with a when condition.
when conditions
The optional when field can carry any of the seven WhenCondition variants
from (internal plugsync tooling - see the source repo):
| Variant | Shape |
|---|---|
WhenExists | {"exists": "<jsonpath>"} |
WhenFound | {"found": "<step-id>"} |
WhenEquals | {"equals": {"<jsonpath>": <value>}} |
WhenGt | {"gt": {"<jsonpath>": <value>}} |
WhenLt | {"lt": {"<jsonpath>": <value>}} |
WhenIn | {"in": {"<jsonpath>": [<values...>]}} |
WhenNot | {"not": <inner WhenCondition>} (recursive) |
When a when condition evaluates to false the runtime skips the step and
continues with the next one.