npm.io
0.1.0 • Published 3d ago

saltcorn-workflow-kit

Licence
MIT
Version
0.1.0
Deps
9
Size
185 kB
Vulns
0
Weekly
0

saltcorn-workflow-kit

Advanced workflow nodes for Saltcorn — AI integrations, logic controls, integrations, and messaging in one plugin.

23 nodes across 4 categories:

Category Nodes
AI Anthropic Chat, OpenAI Chat, Gemini Chat, AI Router
Logic Human Approval, Check Approval Status, Retry Counter, Scheduled Delay, Set Variable, If/Else Branch, Handlebars Template, JSON Transform, Loop Iterator, Debug Log
Integration HTTP Request, DB Query, Trigger Workflow, Google Sheets, Airtable
Messaging Twilio SMS, Slack Message, Email (SMTP/SendGrid), WhatsApp Message

Installation

# From your Saltcorn root
npm install saltcorn-workflow-kit

Then in Saltcorn admin → Plugins → Install from npm → saltcorn-workflow-kit.

Or for local development:

cd saltcorn-workflow-kit
npm install
# In Saltcorn admin → Plugins → Install local plugin → point to this directory

Common Patterns

{{ }} interpolation

Most text fields support {{ variable_name }} substitution from the current workflow context:

Hello {{ first_name }}, your order {{ order_id }} is ready.

Nested paths work too: {{ user.email }}, {{ address.city }}.

Workflow context (ctx.row)

Every step receives the full workflow context as ctx.row. Each step's return object is merged into the context — later steps can read variables set by earlier steps.


AI Nodes

wfkit_anthropic_chat — Anthropic Chat

Send a prompt to Claude (Anthropic) and store the response as a workflow variable.

Field Type Description
api_key String Anthropic API key (sk-ant-...). Supports {{ }}.
model Select Model to use. Default: claude-haiku-4-5-20251001.
system_prompt Textarea Optional system/persona instructions. {{ }} supported.
user_message Textarea The user message to send. {{ }} supported. Required.
max_tokens Integer Max tokens in the response. Default: 1024.
temperature Float 0–1, creativity. Default: 0.7.
output_var String Variable to store the text response. Default: ai_response.

Returns: { [output_var], ai_response, ai_model, ai_input_tokens, ai_output_tokens }

Example next_step expression:

ai_response.includes("urgent") ? "step_escalate" : "step_continue"

wfkit_openai_chat — OpenAI Chat

Send a prompt to OpenAI (GPT-4o, GPT-4, etc.) and store the response.

Field Type Description
api_key String OpenAI API key (sk-...).
model Select Model. Default: gpt-4o-mini.
system_prompt Textarea System instructions. {{ }} supported.
user_message Textarea User message. {{ }} supported. Required.
max_tokens Integer Max response tokens. Default: 1024.
temperature Float 0–2. Default: 0.7.
output_var String Variable for response text. Default: ai_response.

Returns: { [output_var], ai_response, ai_model, ai_finish_reason, ai_input_tokens, ai_output_tokens }


wfkit_gemini_chat — Gemini Chat

Send a prompt to Google Gemini (via Generative Language API).

Field Type Description
api_key String Google AI Studio API key.
model Select Model. Default: gemini-1.5-flash.
system_prompt Textarea System instructions. {{ }} supported.
user_message Textarea User message. {{ }} supported. Required.
max_tokens Integer Max output tokens. Default: 1024.
temperature Float 0–2. Default: 0.7.
output_var String Variable for response. Default: ai_response.

Returns: { [output_var], ai_response, ai_model, ai_finish_reason }


wfkit_ai_router — AI Router

Route workflow execution based on AI classification of input text. The AI labels the input according to your defined categories, then sets a branch variable you can use in next_step.

Field Type Description
provider Select anthropic, openai, or gemini.
api_key String API key for the chosen provider.
model String Model name. Defaults to a fast model per provider.
input_text String Text to classify. {{ }} supported.
categories Textarea JSON array of category objects: [{"name":"urgent","description":"..."},...]
branch_var String Variable set to the matched category name. Default: ai_route.
confidence_var String Variable set to confidence 0–1. Default: ai_confidence.
fallback_category String Category to use if classification fails. Default: unknown.

Returns: { [branch_var], [confidence_var], ai_route, ai_confidence, ai_reasoning }

Example next_step:

ai_route === "billing" ? "step_billing" : ai_route === "support" ? "step_support" : "step_general"

Logic Nodes

wfkit_human_approval — Human Approval

Pause a workflow pending a human decision. Sends approval/reject links via email. The decision fires a wfkit_approval_decided event for downstream triggers.

Field Type Description
to_emails String Comma-separated approver emails. {{ }} supported.
label String Short description shown in the email subject. {{ }} supported.
details Textarea Optional body text. {{ }} supported.
base_url String Your Saltcorn app's public URL (e.g. https://app.example.com).
expires_hours Integer Link validity in hours. Default: 72.
channel String Optional event channel for filtering downstream.
context_fields String Comma-separated field names to snapshot into the approval record.
token_var String Variable to store the approval token. Default: approval_token.

Returns: { [token_var], approval_token, approval_status, approve_url, reject_url, status_url, created_at }

HTTP routes registered (GET, no CSRF):

  • /wfkit/approval/:token/approve — HTML confirmation page, fires event
  • /wfkit/approval/:token/reject — HTML rejection page, fires event
  • /wfkit/approval/:token/status — JSON status check

Requires SMTP configured in Saltcorn admin → Settings → Email.


wfkit_approval_status — Check Approval Status

Poll the current status of a pending approval by token. Returns the decision for use in next_step expressions.

Field Type Description
token String Approval token (e.g. {{ approval_token }}). Required.
decision_var String Variable to store decision: approved, rejected, pending, expired. Default: approval_decision.

Returns: { [decision_var], approval_decision, status, decided_at, decided_by }

Typical pattern: poll this node every N minutes, use next_step:

approval_decision === "approved" ? "step_execute" :
approval_decision === "rejected" ? "step_cancel" : "step_poll"

wfkit_retry_counter — Retry Counter

Track how many times a workflow step has been retried. Use with next_step to loop back or give up after N attempts.

Field Type Description
counter_var String Variable that holds the count. Default: retry_count.
max_retries Integer Maximum allowed retries before exhausted. Default: 3.
reset Bool Reset the counter to 0 on this execution. Default: false.
exhausted_var String Boolean variable set to true when max is reached. Default: retry_exhausted.

Returns:

{
  [counter_var],      // incremented count (Integer)
  [exhausted_var],    // true when count >= max_retries
  retry_count,        // same as [counter_var]
  retry_exhausted,    // same as [exhausted_var]
  attempts_remaining, // max_retries - count
  max_retries
}

next_step pattern (loop with give-up):

retry_exhausted ? "step_fail" : "step_attempt"

wfkit_delay — Scheduled Delay

Pause workflow execution for a specified duration.

  • ≤ 5 minutes: sleeps synchronously (occupies a worker).
  • > 5 minutes: sets wake_at and returns delay_status: "deferred" — the workflow engine must implement its own scheduling to re-invoke this step at wake_at.
Field Type Description
amount Integer How long to wait. Required.
unit Select ms, seconds, minutes, hours, days. Default: seconds.
label String Optional log label.

Returns:

{
  delay_status,    // "completed" or "deferred"
  delay_ms,        // actual ms waited / requested
  wake_at,         // ISO timestamp (deferred mode only)
  delay_unit,
  delay_amount
}

wfkit_set_variable — Set Variable

Set one or more workflow variables with typed assignment. Supports literal values, expressions, and cross-variable references.

Field Type Description
assignments Textarea JSON array of { name, value, type } objects.

Assignment types:

Type Behaviour Example value
string {{ }} interpolated "Hello {{ name }}"
number Number(value) "42"
bool truthy strings (true, 1, yes) → true "true"
expr JavaScript expression, row in scope "amount * 1.2"

Example assignments JSON:

[
  { "name": "full_name",     "value": "{{ first_name }} {{ last_name }}", "type": "string" },
  { "name": "tax",           "value": "amount * 0.18",                    "type": "expr"   },
  { "name": "is_large_order","value": "amount > 1000",                    "type": "expr"   }
]

Returns: one key per assignment name, plus set_variable_count.


wfkit_if_else — If/Else Branch

Evaluate a JavaScript condition against the workflow context and set a branch variable. Supports else-if chains.

Field Type Description
condition String JS expression. All context variables are in scope.
branch_var String Variable set to the matched branch value. Default: branch.
value_if_true String Value for branch_var when condition is true. Default: "true".
value_if_false String Value when no condition matches. Default: "false".
else_if Textarea JSON array of { condition, value } objects to check in order.

Returns: { [branch_var], branch, branch_matched, condition_result }

Example:

condition:       amount > 1000
value_if_true:   high
value_if_false:  low

Else-if chains:

[
  { "condition": "status === 'vip'",   "value": "vip_path"   },
  { "condition": "status === 'member'","value": "member_path" }
]

next_step using branch:

branch === "high" ? "step_review" : "step_auto_approve"

wfkit_template — Handlebars Template

Render a Handlebars template against the workflow context and store the result.

Field Type Description
template Textarea Handlebars template string. Required.
output_var String Variable to store the rendered string. Default: template_output.
partials Textarea JSON object { "name": "template string" } for {{> name}}.
strict Bool Throw on undefined variables instead of rendering empty. Default: false.

Built-in helpers:

Helper Example Output
{{upper str}} {{upper name}} ALICE
{{lower str}} {{lower name}} alice
{{trim str}} {{trim " hi "}} hi
{{default val fallback}} {{default nickname "friend"}} value or friend
{{json val}} {{json items}} ["a","b"]
{{currency val}} {{currency amount}} 1,234.56
{{date val}} {{date created_at}} locale date string
{{#if (eq a b)}} {{#if (eq status "open")}} conditional
{{#if (gt a b)}} {{#if (gt amount 100)}} numeric compare
{{#if (and a b)}} logical and/or

Returns: { [output_var], template_output }

Example template:

Dear {{upper name}},

Your order #{{order_id}} ({{currency amount}}) is {{status}}.

{{#if (gt amount 500)}}
Your VIP discount has been applied.
{{/if}}

wfkit_json_transform — JSON Transform

Query and transform JSON data using JSONata expressions.

Field Type Description
expression String JSONata expression. Required.
input_var String Variable to use as expression root. If blank, full ctx.row is the input.
output_var String Variable to store the result. Default: transform_result.
bindings Textarea JSON object of extra variables bound into the expression.
on_empty Select What to do when result is null/undefined: null, empty_array, empty_string, error. Default: null.

Behaviour of input_var:

  • With input_var: JSONata operates on row[input_var] directly. Use bare expressions: [status="open"] to filter an array.
  • Without input_var: JSONata operates on the full row. Write expressions like orders[status="open"] to reference a named variable.

Returns: { [output_var], transform_result, transform_type, transform_count }

Examples:

Filter open orders (full row as input, no input_var):

orders[status = "open"].{ "id": id, "total": amount }

Sum order amounts:

$sum(orders.amount)

Reshape:

items.{ "label": name, "price": "$" & $string(price) }

wfkit_loop — Loop Iterator

Iterate over an array one item per workflow pass. On each pass returns one item; set next_step to jump back to the loop until loop_done is true.

Modes:

Mode Description
cursor Returns one item per pass via an index variable.
collect Filter an array with a JSONata expression, return the whole filtered array at once.
map Transform each item with a JSONata expression, return the new array at once.
reduce Aggregate: sum, min, max, concat, first, last.
Field Type Description
array_var String Variable that holds the array to iterate. Required.
mode Select cursor, collect, map, reduce. Default: cursor.
item_var String Variable for the current item (cursor mode). Default: loop_item.
index_var String Variable for the current index (cursor mode). Default: loop_index.
expression String JSONata for collect/map/reduce modes.
reduce_op Select sum, min, max, concat, first, last (reduce mode).
output_var String Output variable for collect/map/reduce. Default: loop_result.

Cursor mode returns:

{
  loop_item,      // current item (null when done)
  loop_index,     // current 0-based index
  loop_done,      // true when exhausted
  loop_length,    // array.length
  [item_var],     // alias for loop_item
  [index_var],    // alias for loop_index
}

Cursor loop pattern:

  1. Step A (wfkit_loop, mode=cursor): sets loop_item, loop_done
  2. Step B: process loop_item
  3. Step C: next_step = loop_done ? "step_finish" : "step_a"

Collect/map/reduce return { [output_var], loop_result } in a single pass.


wfkit_debug_log — Debug Log

Dump workflow context to the server log for debugging. Never throws — safe to leave in place.

Field Type Description
label String Log label for grep. Default: wfkit:debug.
mode Select all (every variable), selected (named variables), diff (changed since last checkpoint).
variables String Comma-separated names (selected mode only).
checkpoint_var String Snapshot variable (diff mode). Default: debug_checkpoint.
max_depth Integer Truncate values longer than this many chars. Default: 500.
enabled Bool Set false to silence without removing. Default: true.

Returns: {} (pass-through), except diff mode which returns { [checkpoint_var] }.

Log format (structured JSON, visible in saltcorn serve output):

{
  "label": "after_approval",
  "mode": "all",
  "step_count": 5,
  "variables": { "name": "\"Alice\"", "amount": "1200" }
}

Integration Nodes

wfkit_http_request — HTTP Request

Make an HTTP/HTTPS request to any external API.

Field Type Description
url String Request URL. {{ }} supported. Required.
method Select GET, POST, PUT, PATCH, DELETE. Default: GET.
headers Textarea JSON object of request headers. {{ }} supported.
body Textarea Request body (JSON or text). {{ }} supported.
body_type Select json, text, form. Default: json.
auth_type Select none, bearer, basic.
auth_token String Token (bearer) or user:pass (basic). {{ }} supported.
timeout_ms Integer Request timeout in ms. Default: 10000.
output_var String Variable for response body. Default: http_response.
fail_on_error Bool Throw if status >= 400. Default: true.

Returns: { [output_var], http_response, http_status, http_ok, http_headers }

Example: call a REST API and get a JSON body:

url:    https://api.example.com/orders/{{ order_id }}
method: GET
auth_type: bearer
auth_token: {{ api_key }}

wfkit_db_query — DB Query

Query any Saltcorn table. Returns rows, a single row, a count, or an aggregate.

Field Type Description
table_name String Saltcorn table name. Required.
operation Select getRows, getRow, countRows, aggregate.
where Textarea JSON object of filter conditions. {{ }} in values.
order_by String Column to sort by.
order_desc Bool Descending order. Default: false.
limit Integer Max rows to return.
offset Integer Skip N rows.
aggregate_field String Field to aggregate (sum, min, max, avg).
aggregate_fn Select sum, min, max, avg, count.
output_var String Variable for results. Default: db_result.

Where clause operators:

{ "status": "open" }                          // equals
{ "amount": { "gt": 100 } }                  // greater than
{ "amount": { "gte": 100, "lt": 1000 } }     // range
{ "tag": ["featured", "sale"] }               // IN array
{ "deleted_at": null }                        // IS NULL

Returns:

  • getRows{ [output_var], db_result, db_count }
  • getRow{ [output_var], db_result, db_found }
  • countRows{ db_count, [output_var] }
  • aggregate{ db_aggregate, [output_var] }

wfkit_trigger_workflow — Trigger Workflow

Launch another Saltcorn workflow (trigger) from within a workflow.

Field Type Description
trigger_name String Name of the Saltcorn trigger to fire. {{ }} supported. Required.
mode Select fire-and-forget (emitEvent), direct-async (no await), direct-sync (await result). Default: fire-and-forget.
payload Textarea JSON object merged as the trigger's row. {{ }} supported.
extra_fields Textarea key=value lines (one per line) added to the payload. {{ }} supported.
event_type String Event type for fire-and-forget mode. Default: trigger name.
channel String Event channel for fire-and-forget.
output_var String Variable for sync mode result. Default: triggered_result.

Modes:

  • fire-and-forget: emits a Saltcorn event; fastest, no result.
  • direct-async: calls trigger without awaiting; no result.
  • direct-sync: calls trigger and waits; result available in output_var.

Returns: { trigger_fired, trigger_name, trigger_mode, [output_var] }


wfkit_google_sheets — Google Sheets

Read and write Google Sheets using a Service Account (no OAuth flow needed).

Setup:

  1. Google Cloud Console → create a Service Account → create JSON key.
  2. Share the sheet with the service account email.
  3. Paste the JSON key content into the service_account_json field.
Field Type Description
service_account_json Textarea Full service account JSON key. Required.
spreadsheet_id String From the sheet URL: .../spreadsheets/d/{ID}/edit. {{ }} supported.
range String A1 notation: Sheet1!A1:D100. {{ }} supported.
operation Select readRange, readRows, appendRow, updateRange, clearRange.
row_values Textarea JSON array for write ops: ["A","B","C"] or [["A","B"],["C","D"]].
value_input_option Select USER_ENTERED (parse dates/formulas) or RAW. Default: USER_ENTERED.
output_var String Variable for read results. Default: sheets_result.

Operations:

Op Description Returns
readRange Raw 2D array [[row1col1,...],...]
readRows Rows as objects (row 1 = headers) [{ Name:"Alice", Age:"30" },...]
appendRow Append one or more rows sheets_appended_rows, sheets_updated_range
updateRange Overwrite a range sheets_updated_rows, sheets_updated_range
clearRange Clear cells sheets_cleared, sheets_range

wfkit_airtable — Airtable

Read and write Airtable records via the Airtable Web API.

Setup: Create a Personal Access Token at airtable.com/create/tokens with data.records:read and/or data.records:write scopes.

Field Type Description
api_token String Personal access token. Required.
base_id String Base ID (starts with app). {{ }} supported.
table_name String Table name or ID. {{ }} supported.
operation Select listRecords, getRecord, createRecord, updateRecord, deleteRecord.
filter_formula String Airtable formula: AND({Status}='open',{Amt}>100). {{ }} supported.
sort_field String Sort by field name.
sort_direction Select asc or desc.
max_records Integer Max records (listRecords). Default: 100.
view String Restrict to a named view.
record_id String Record ID (starts with rec). {{ }} supported.
fields Textarea JSON fields for create/update: { "Name": "{{ name }}" }.
output_var String Variable for results. Default: airtable_result.

Records are returned as flat objects: { id: "rec...", Name: "Alice", Status: "open" }

Returns (listRecords): { [output_var], airtable_result, airtable_count, airtable_raw }


Messaging Nodes

wfkit_twilio_sms — Twilio SMS

Send an SMS via the Twilio REST API.

Field Type Description
account_sid String Twilio Account SID (AC...).
auth_token String Twilio Auth Token.
from String Sender number in E.164 format or a Messaging Service SID.
to String Recipient number in E.164 format. {{ }} supported.
body Textarea Message text (max 1600 chars). {{ }} supported.
output_var String Variable for message SID. Default: sms_sid.

Returns: { [output_var], sms_sid, sms_status, sms_to, sms_from }


wfkit_slack_message — Slack Message

Post a message to a Slack channel or user via Incoming Webhook or Bot Token.

Field Type Description
webhook_url String Incoming Webhook URL (https://hooks.slack.com/...).
bot_token String Bot OAuth token (xoxb-...) — alternative to webhook.
channel String Channel (#general) or user ID (@U...). Required for bot token mode. {{ }} supported.
text Textarea Message text. Slack mrkdwn supported. {{ }} supported.
blocks Textarea JSON Block Kit array. {{ }} supported.
username String Display name override.
icon_emoji String Emoji icon (:robot_face:).
thread_ts String Reply in thread: pass the parent message timestamp. {{ }} supported.
output_var String Variable for response. Default: slack_response.

Returns: { [output_var], slack_ok, slack_ts, slack_channel }


wfkit_email — Email (SMTP/SendGrid)

Send an email via Saltcorn's built-in SMTP, custom SMTP, or SendGrid.

Field Type Description
transport Select saltcorn (use configured SMTP), smtp (custom), sendgrid.
to String Recipient(s), comma-separated. {{ }} supported. Required.
from String Sender address. {{ }} supported.
subject String Email subject. {{ }} supported.
body Textarea Email body (HTML or plain text). {{ }} supported.
cc String CC addresses. {{ }} supported.
bcc String BCC addresses. {{ }} supported.
smtp_host String SMTP host (smtp transport).
smtp_port Integer SMTP port. Default: 587.
smtp_user String SMTP username.
smtp_pass String SMTP password.
smtp_secure Bool TLS. Default: false.
sendgrid_api_key String SendGrid API key (sendgrid transport).
output_var String Variable for result. Default: email_result.

Returns: { [output_var], email_sent, email_to, email_message_id }


wfkit_whatsapp_message — WhatsApp Message

Send a WhatsApp message via the Meta Cloud API (Graph API v18+).

Setup:

  1. Create a Meta App at developers.facebook.com → add WhatsApp product.
  2. Generate a System User access token.
  3. Get your Phone Number ID (numeric ID, not the phone number).
Field Type Description
access_token String Meta system user access token.
phone_number_id String Numeric Phone Number ID from Meta Developer Console.
to String Recipient phone in E.164 format. {{ }} supported.
message_type Select text, template, interactive.
text_body Textarea Message text (text type). WhatsApp markdown: *bold*, _italic_. {{ }} supported.
preview_url Bool Enable link previews in text messages.
template_name String Approved template name (template type).
template_language String Language code: en_US, en. Default: en_US.
template_components Textarea JSON array of variable substitutions for the template.
interactive_json Textarea Full interactive object per Meta API spec.
output_var String Variable for message ID. Default: whatsapp_message_id.

Returns: { [output_var], whatsapp_message_id, whatsapp_to, whatsapp_type, whatsapp_wa_id }

Template components example:

[
  {
    "type": "body",
    "parameters": [
      { "type": "text", "text": "{{ customer_name }}" },
      { "type": "text", "text": "{{ order_id }}" }
    ]
  }
]

Workflow Recipes

Retry with exponential back-off
Step 1  wfkit_retry_counter        counter_var=attempt, max_retries=3
Step 2  wfkit_http_request         url=https://api.example.com/...
           [on error → Step 1]
Step 3  [on success → continue]
           next_step: retry_exhausted ? "step_fail" : "step_1"
Human-in-the-loop
Step 1  wfkit_human_approval       to_emails={{ manager_email }}, label=Order {{ order_id }}
Step 2  wfkit_delay                amount=5, unit=minutes   (poll interval)
Step 3  wfkit_approval_status      token={{ approval_token }}
           next_step: approval_decision === "approved" ? "step_proceed"
                    : approval_decision === "rejected" ? "step_cancel"
                    : "step_2"   ← keeps polling
Step 4  wfkit_email                (proceed confirmation)
AI classification → branch
Step 1  wfkit_ai_router            input_text={{ user_message }},
                                   categories=[{"name":"billing"},{"name":"support"},{"name":"sales"}]
           next_step: ai_route === "billing" ? "step_billing"
                    : ai_route === "support" ? "step_support"
                    : "step_sales"
Loop over DB rows
Step 1  wfkit_db_query             table=orders, operation=getRows,
                                   where={"status":"pending"}, output_var=orders
Step 2  wfkit_loop                 array_var=orders, mode=cursor, item_var=order
Step 3  wfkit_email                to={{ order.email }}, subject=Your order {{ order.id }}
           next_step: loop_done ? "step_done" : "step_2"
Render email body with template
Step 1  wfkit_template             template=<template content>, output_var=email_body
Step 2  wfkit_email                body={{ email_body }}, to={{ customer_email }}

Custom Event Types

The plugin registers one custom Saltcorn event type:

Event Description
wfkit_approval_decided Fired when an approval link is clicked. Payload: { token, decision, context, label }. Use channel for filtering.

Environment / Credentials

Integration Credential location
Anthropic, OpenAI, Gemini API key field per node (or {{ credentials.xxx }})
Twilio account_sid + auth_token fields
Slack Webhook URL or xoxb- bot token field
WhatsApp (Meta) access_token + phone_number_id fields
Google Sheets Service Account JSON key pasted into node config
Airtable Personal Access Token field
Email (saltcorn transport) Saltcorn Admin → Settings → Email (SMTP)
Email (custom SMTP) smtp_* fields per node
Email (SendGrid) sendgrid_api_key field per node

All credential fields support {{ }} interpolation, so you can store secrets as Saltcorn credentials and reference them as {{ credentials.my_key }}.

Keywords