Skip to content

A saved campaign graph is two arrays: nodes, each carrying a type and a configuration object, and edges, each joining one node’s numbered output to another node’s input. Nothing else is stored on the graph — the campaign name, its page, and its active state live on the campaign, not in the graph. Saving writes a new version of both arrays.

FieldTypeRequiredDescription
idstringyesIdentifies the node inside this graph, and is what edges point at. Replaced with a fresh identifier when the graph is saved, so it is stable only within one version.
typestringyesOne of the types in node types, or one of the runtime-only types listed below.
configobjectyesThe node’s settings. An empty object is valid, and is what a freshly added node starts with.
canvas_xnumberyesHorizontal position of the node on the canvas. Restored when the graph is opened.
canvas_ynumberyesVertical position of the node on the canvas. Restored when the graph is opened.
FieldTypeRequiredDescription
idstringyesIdentifies the connection inside this graph.
from_node_idstringyesThe id of the node the connection leaves. Must appear in the same nodes array, or the connection is dropped on save.
to_node_idstringyesThe id of the node the connection enters. Must appear in the same nodes array, or the connection is dropped on save.
output_indexnumberyesWhich output of the source node the connection leaves from. 0 is the default output; on a condition, 0 is Match and 1 is No Match; on a random_split, 0 to N-1 are the arms in configured order.
labelstringnoDisplay text shown on the connection, taken from the name of the source output — for example Match or No Match. Executions are routed by output_index, never by label.

A comment triggers the campaign, a keyword list decides the branch, matching people get a direct message and a tag an hour later, and every other execution ends immediately.

trigger_commentconditionkeyword_listsend_dmdelay 3600sadd_tagstop Match (output 0) No Match (output 1)
{
"nodes": [
{
"id": "3f6c1a80-5d2b-4c11-9e64-7a0b8d43f215",
"type": "trigger_comment",
"config": { "post_scope": "all", "unique_per_user": false },
"canvas_x": 240,
"canvas_y": 40
},
{
"id": "9b1d47e2-08af-4d5c-bb37-2c6e5f90a4d1",
"type": "condition",
"config": {
"match_type": "keyword_list",
"keyword_list_id": 41,
"reverse_keywords": false,
"wait_for_reply": false
},
"canvas_x": 240,
"canvas_y": 200
},
{
"id": "c74a2f19-6e30-4b8d-8f52-19d7c3b6e084",
"type": "send_dm",
"config": {
"message_type": "text",
"text": "Thanks for commenting. The price list is on its way."
},
"canvas_x": 80,
"canvas_y": 380
},
{
"id": "1a58e63d-4c97-42f0-9b18-6d2f7ae5c390",
"type": "delay",
"config": { "delay_seconds": 3600, "only_if_no_reply": true },
"canvas_x": 80,
"canvas_y": 540
},
{
"id": "e2079b45-13cd-4a6e-8c71-5f83d0b29a6c",
"type": "add_tag",
"config": { "tag_ids": [12] },
"canvas_x": 80,
"canvas_y": 700
},
{
"id": "7d3fb0c6-92e1-4f57-a840-3b6c1d5e2087",
"type": "stop",
"config": {},
"canvas_x": 440,
"canvas_y": 380
}
],
"edges": [
{
"id": "5c8e1b73-2f40-4d19-9a6b-8e7d0c34f512",
"from_node_id": "3f6c1a80-5d2b-4c11-9e64-7a0b8d43f215",
"to_node_id": "9b1d47e2-08af-4d5c-bb37-2c6e5f90a4d1",
"output_index": 0,
"label": ""
},
{
"id": "b06d4a92-7e15-48c3-bf20-6a9c2d813e47",
"from_node_id": "9b1d47e2-08af-4d5c-bb37-2c6e5f90a4d1",
"to_node_id": "c74a2f19-6e30-4b8d-8f52-19d7c3b6e084",
"output_index": 0,
"label": "Match"
},
{
"id": "48f1c25e-3b7d-4096-8ea4-1c5b7f02d9a3",
"from_node_id": "9b1d47e2-08af-4d5c-bb37-2c6e5f90a4d1",
"to_node_id": "7d3fb0c6-92e1-4f57-a840-3b6c1d5e2087",
"output_index": 1,
"label": "No Match"
},
{
"id": "a91b7e04-6c58-4d2f-93a1-7e04b8c5f261",
"from_node_id": "c74a2f19-6e30-4b8d-8f52-19d7c3b6e084",
"to_node_id": "1a58e63d-4c97-42f0-9b18-6d2f7ae5c390",
"output_index": 0,
"label": ""
},
{
"id": "d5720af8-9134-4be6-8c07-2f6a5d18b3e9",
"from_node_id": "1a58e63d-4c97-42f0-9b18-6d2f7ae5c390",
"to_node_id": "e2079b45-13cd-4a6e-8c71-5f83d0b29a6c",
"output_index": 0,
"label": ""
}
]
}
Node typeKeys in config
trigger_commentpost_scope (all or specific), post_id (required when post_scope is specific), unique_per_user
trigger_dmunique_per_user
conditionmatch_type (any, contains, exact, keyword_list), text, keyword_list_id, reverse_keywords, wait_for_reply, filter_images, filter_gifs, filter_links
send_dmmessage_type (text, image, card, text_with_buttons), text, image_url, title, subtitle, buttons, buttons_text
reply_commenttext
send_commenttext, post_id
hide_comment, delete_commentalso_block_user (Facebook only)
delaydelay_seconds (1 to 31536000), only_if_no_reply
random_splitpaths, an array of { "percent": number } with each percent 0 to 100 and the total no more than 100
add_tag, remove_tagtag_ids, an array of numeric tag identifiers
stopnone

Node types describes what each key does and what each node does when an execution reaches it.

The canvas enforces these while a connection is being dragged, so an invalid connection never reaches a saved graph.

  1. A trigger is never the target of a connection.
  2. A node never connects to itself.
  3. A connection that would create a cycle is refused.
  4. A node accepts one incoming connection.
  5. Each output carries one outgoing connection.
  6. A trigger connects only to a condition.
  7. A condition never connects to another condition.
  8. Comment actions are unavailable when the trigger is trigger_dm.
  9. Downstream of a delete_comment, reply_comment and hide_comment are refused.

A graph holds one trigger in practice, though the shape of the data allows more than one. A graph whose nodes have configuration errors cannot be saved at all — the canvas reports the errors instead. Build a campaign walks through assembling a graph that satisfies all of this.

The runtime recognises eight further types that have no palette tile and no settings panel. A graph built on the canvas never contains them; they appear only through a path that bypasses the editor, so treat them as runtime-only rather than as features.

Node typeWhat the execution does
switchEvaluates each arm’s rules and follows the first arm that matches, or a default arm when one is configured. With no match and no default arm the execution ends.
wait_untilWaits until a configured time and then continues, scheduled the same way a delay is.
block_userBlocks the person and continues. Facebook only; on Instagram it does nothing and warns.
unhide_comment, typing_indicator, mark_seen, set_field, noteExecutable, but they carry no settings panel and the sources behind this page record no configuration for them.
  • Each save writes a new version of the graph. The campaign points at the newest version, and only that version’s trigger starts new executions.
  • Every node identifier is regenerated on save and the connections are remapped to the new identifiers. Identifiers are therefore stable within a version, not across saves.
  • A connection whose from_node_id or to_node_id is not in the same nodes array is dropped, and the rest of the graph still saves.
  • An empty graph — both arrays empty — saves successfully. With no trigger in it, nothing starts an execution.
  • An execution already in flight stays on the version it started on, so editing and saving a campaign never changes executions that are mid-flight.
  • Older versions are removed once no execution refers to them.
  • A postback button on a send_dm node gets its own output. On save the canvas expands each button branch into extra condition nodes and labels the connections with the button’s identifier; opening the graph collapses them back into button outputs.
  • An execution follows the connection whose output_index matches the output the node selected. When no connection carries that index, the execution completes at that node.
  • An execution that reaches a node it has already visited ends with a cycle error, which is why the editor refuses cycles in the first place.
  • The configuration keys in this reference are the ones to write. Some node types also answer to older key spellings, kept so that graphs saved earlier still run.