# Build a workflow in the builder

> A hands-on tutorial in Kustomer's visual workflow builder. Build a workflow that tags conversations from VIP customers, and see the JSON each builder action writes. Covers the workflows list, the canvas, triggers, the data picker, conditions, variables, testing, logs, saving and enabling, and what the builder can't do.

Source: https://developer.kustomer.com/docs/workflows/build-in-the-builder

The workflow builder is a visual editor for the same JSON document you can read and write with the [Workflows API](https://developer.kustomer.com/docs/workflows/workflow-json). Every node you add and every field you fill writes a specific piece of that JSON. Once you can see the JSON behind each click, the builder stops being mysterious, and you can switch between the two whenever it's convenient.

In this tutorial you'll build one workflow from start to finish: **when a conversation is created, look up its customer, and if the customer is a VIP, tag the conversation**. After each builder action, you'll see the JSON it produces.

_A stylized mock of the classic workflow builder that builds the Tag VIP conversations workflow one action at a time, next to a JSON so far panel. The steps are: create the workflow (name tag-vip-conversations, displayName Tag VIP conversations, enabled false); choose the Kustomer Conversation Created trigger (eventName kustomer.conversation.create); add an action step, which gets a generated ID and a plain always line from the trigger ({ op: true, values: \[true] }); pick Customer: Lookup (By ID) (action kustomer.customer.find); fill the identifier field from the data picker, which inserts /#steps.1.customer; add a condition branch named Customer is VIP that continues only if step 2's Custom > VIP equals true; add a Conversation: Add Tags step with id /#steps.1.id and the VIP tag's ID; save, which creates a new version and adds appVersion to each step; and switch the workflow on, which sends only enabled: true. Prev, next and replay buttons step through the actions._

This page is about the builder. If you'd rather write the JSON yourself, follow [Write a workflow in JSON](https://developer.kustomer.com/docs/workflows/write-workflow-json), which writes a find-or-create workflow for an order web hook one key at a time and creates it through the API. For the ideas underneath both, read [How workflows run](https://developer.kustomer.com/docs/workflows/how-workflows-run) and [Data and variables](https://developer.kustomer.com/docs/workflows/data-and-variables).

## Before you start

You need:

- Access to **Settings › Platform › Workflows** in your Kustomer org.
- A Boolean [custom attribute](https://developer.kustomer.com/docs/concepts/custom-attributes) on customers that marks VIPs. This tutorial calls it **VIP**, stored as `custom.vipBool`. Use your own field if you have one.
- A tag for VIP conversations, and its ID. Workflows store tags as IDs, not names. If a conversation already has the tag, you can see its ID in that conversation's `tags` array, for example in a workflow's logs.
- A test customer with the VIP field turned on, so you can check the result.

> **Which builder this page describes:** The labels on this page are from the classic workflow builder. If your org uses a newer version of the builder, some labels and layouts differ, but it saves the same workflow JSON.

## Find your way around

### The workflows list

**Settings › Platform › Workflows** lists every workflow in your org.

| Part                 | What's there                                                                                             |
| -------------------- | -------------------------------------------------------------------------------------------------------- |
| Filters              | **User Created**, **App Created**, **All Triggers**, **Scheduled Workflows**, **Callable Workflows**     |
| Columns              | **Name**, **Errors**, **Updated**, **Actions**. Each workflow is either **Activated** or **Deactivated** |
| Row actions          | **Duplicate**, **Export JSON**, **View Error Logs**                                                      |
| **Template Gallery** | Prebuilt workflows. Choose **Use This Workflow** to start from one                                       |

Workflows installed by an app show "App workflows cannot be edited". You can still turn them on and off.

### The canvas

The canvas draws the workflow as a tree, from the trigger at the top down to the last steps.

| On the canvas                   | What it is                                                                                                                       | In the JSON                                                    |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| **Trigger** node, badge **1**   | The event that starts a run. Shows "Choose Trigger" until you pick one                                                           | `trigger`, always `"id": "1"`                                  |
| **Action** node, numbered badge | A step that runs an action. Shows "Choose Action" in red until it has an action and no errors                                    | An entry in `steps[]` with an `action`                         |
| Condition node                  | A condition on the path to the next step. Shows its name, or "Edit Condition" when it's unnamed                                  | A transition with a `condition` and `meta.name`                |
| Plain line                      | A path that's always taken                                                                                                       | A step's only transition, `{ "op": "true", "values": [true] }` |
| **+** below a node              | Adds what comes next: **Action**, **Condition Step**, **Condition Branch**, or **Paste** after you've copied a step or condition | New `steps[]` and `transitions[]` entries                      |

> **Badge numbers aren't step IDs:** The canvas numbers steps 1, 2, 3… by their position in the tree. In the JSON, every step except the trigger has a generated short ID, such as `B1gTq2Lxk`, and references use that ID: `/#steps.B1gTq2Lxk.custom.vipBool`. Hover over a node's badge and choose **Copy Step ID** to get it. Only the trigger is always `1`.

## Create the workflow and pick a trigger

#### Create a custom workflow

On the workflows list, select **Add Workflow**, then **Custom Workflow** ("Build your workflow from a clean slate"). In **Create New Workflow**, fill in:

- **Workflow Title**: `Tag VIP conversations`
- **Workflow Description**: `Adds the VIP tag to new conversations from customers whose VIP field is true.`

Leave the **Callable Workflow** toggle off, and select **Create**. The title allows letters, numbers, spaces, `-`, `_` and `.`.

```json title="What Create writes"
{
  "name": "tag-vip-conversations",
  "description": "Adds the VIP tag to new conversations from customers whose VIP field is true.",
  "meta": { "displayName": "Tag VIP conversations" }
}
```

The title becomes two things: `name`, lowercased with spaces turned into `-`, and `meta.displayName`, exactly as you typed it. `name` must be unique in your org. New workflows start with `enabled` set to `false`, so nothing runs until you switch the workflow on.

#### Choose the trigger

Select the **Choose Trigger** node. In the side panel:

- Type a **Trigger Title**, such as `New conversation`.
- Under **Trigger Information**, set **Trigger App** to **Kustomer** and **Trigger Event** to **Conversation Created**. **Details** describes the event.

```json title="trigger"
{
  "id": "1",
  "eventName": "kustomer.conversation.create",
  "meta": { "displayName": "New conversation" },
  "transitions": []
}
```

The trigger's output is step `1`: the new conversation, flattened, so its ID is `/#steps.1.id` and its customer's ID is `/#steps.1.customer`. [Triggers and actions](https://developer.kustomer.com/docs/workflows/triggers-and-actions#built-in-triggers) lists every built-in trigger and its builder name.

## Add a step and fill its params

#### Add an action

Select **+** below the trigger and choose **Action**. A new node appears with badge **2**, connected by a plain line.

```json title="trigger.transitions and steps"
{
  "trigger": {
    "id": "1",
    "eventName": "kustomer.conversation.create",
    "transitions": [{ "target": "B1gTq2Lxk", "condition": { "op": "true", "values": [true] } }]
  },
  "steps": [{ "id": "B1gTq2Lxk", "transitions": [] }]
}
```

The plain line is a transition whose condition always passes. The new step gets a generated ID; yours will be different from `B1gTq2Lxk`.

#### Pick the action

Select the new node. In the side panel, type an **Action Title**, `Look up the customer`. Under **Action Information**, set **Action App** to **Kustomer** and **Action Event** to **Customer: Lookup (By ID)**.

```json title="steps[0]"
{
  "id": "B1gTq2Lxk",
  "action": "kustomer.customer.find",
  "meta": { "displayName": "Look up the customer" },
  "params": {},
  "transitions": []
}
```

The conversation only holds the customer's ID. To read the customer's VIP field, you look the customer up first.

#### Fill the identifier with the data picker

**Required fields** now shows a count of 1: `identifier`. Select the field. A searchable picker opens, listing the earlier steps by number and name with their output fields. Under **1**, choose **Customer**.

```json title="steps[0].params"
{ "identifier": "/#steps.1.customer" }
```

The picker sets the **whole field** to one reference, `/#steps.<stepId>.<path>`. The field shows the step number and the field's name rather than the raw path.

### How the data picker works

The picker is the easiest way to get paths right, because it builds them from each step's real output. It lists:

- **Earlier steps**, by number and name, with their output fields. Choosing one writes `/#steps.<stepId>.<path>`. A nested field shows as a trail, such as **Custom > VIP**.
- **Variables**. Choosing one writes `/#variables.<variableId>`. See [Use workflow variables](#use-workflow-variables).
- **Functions**, the `/#fn:` helpers, each with "Returns:" and "Example:". See the [Expressions reference](https://developer.kustomer.com/docs/workflows/expressions-reference#functions).
- **Value lists** that depend on the field, such as **Queues**, **Boolean Values** (True and False), **System Klasses** and **System Methods**, and pickers for users, teams, statuses and priorities.

Optional inputs live under **Optional fields**, which shows how many you've used out of the total. Use **Add field** ("Search fields...") to add one or several, and **Remove field** to take one off. Removing a field deletes its key from `params`.

#### Mix text and values in code view

The picker can't combine text with a value. For that, turn on **Toggle Code View** (the `</>` icon) and type a [template](https://developer.kustomer.com/docs/workflows/conditions-and-expressions#templates):

```text
Order {{{steps.1.custom.orderNumberStr}}} needs a VIP follow-up
```

What you type is saved when you press `Enter` or click away, and it's converted to the field's type first. The JSON can differ from what you typed:

| Field type | You type         | Saved as                                     |
| ---------- | ---------------- | -------------------------------------------- |
| Array      | `vip,priority`   | `["vip", "priority"]` (split on commas)      |
| Number     | `15`             | `15`                                         |
| Boolean    | `true`           | `true`                                       |
| Array      | `/#steps.1.tags` | `["/#steps.1.tags"]` (see the warning below) |
| Any        | (empty)          | The param is removed                         |

For a **tags** field, `[]` means "clear all tags". For a **queue** field, an empty value means "unassign".

> **References in array fields get wrapped:** A reference typed into an **array** field's code view is wrapped in a list, so `/#steps.1.tags` is saved as `["/#steps.1.tags"]`. To pass a whole array from an earlier step, set the param to the bare string `"/#steps.1.tags"` in **Edit workflow JSON**.

A field is highlighted when what you typed doesn't match its type. Values that start with `/#`, and values that are a whole `{{…}}` template, are always accepted. **Object** fields, the ones that take JSON, open a JSON editor instead, and invalid JSON blocks saving with "Fix invalid JSON in action parameters before saving".

## Add a condition

So far the workflow looks up every new conversation's customer. Now add the check: continue only if that customer is a VIP. In the builder, a condition sits on the path between two steps. In the JSON, it's the `condition` of a transition.

#### Add a condition branch

Select **+** below **Look up the customer** and choose **Condition Branch**. A condition node labelled "Edit Condition" appears. It's shown in an error style until it has a complete rule and a step to go to.

#### Name it and set the rule

Select the condition node. Type a name in the title field: `Customer is VIP`. Then, under **Continue Only If**:

1. **Select field**: under **2**, choose **Custom > VIP**.
2. Operator: **Equals**. A Boolean field only offers **Equals** and **Not equal**.
3. **Select Value**: `true`.

```json title="steps[0].transitions[0] (no target yet)"
{
  "condition": { "op": "eq", "values": ["/#steps.B1gTq2Lxk.custom.vipBool", "true"] },
  "meta": { "name": "Customer is VIP" }
}
```

The field is on step 2's output, so the path uses step 2's generated ID, not the number 2. The builder saves values other than numbers as you typed them, so `true` is stored as the string `"true"`. That works: `eq` casts the right side to the left side's type, so `"true"` becomes the boolean `true`. If a customer has no VIP value at all, the condition doesn't match.

#### Add the tag step

Select **+** below the condition and choose **Action**. Title it `Add the VIP tag`, and set **Action App** to **Kustomer** and **Action Event** to **Conversation: Add Tags**. Fill the two required fields:

- `id`: from the picker, under **1**, choose **ID**. That's the conversation's ID.
- `tags`: turn on **Toggle Code View** and paste the VIP tag's ID.

```json title="steps[0].transitions"
{
  "transitions": [
    {
      "target": "rkM8xV0Zp",
      "condition": { "op": "eq", "values": ["/#steps.B1gTq2Lxk.custom.vipBool", "true"] },
      "meta": { "name": "Customer is VIP" }
    }
  ]
}
```

```json title="steps[1]"
{
  "id": "rkM8xV0Zp",
  "action": "kustomer.conversation.append-tags",
  "meta": { "displayName": "Add the VIP tag" },
  "params": { "id": "/#steps.1.id", "tags": ["64f1c0ffee5ca1ab1e000002"] },
  "transitions": []
}
```

The condition now has a `target`. **Add Tags** adds the tag without touching the conversation's other tags. The new step has no transitions, so the run ends after it.

### The condition editor

Every condition in the builder has the same parts: **Select field**, an operator, and **Select Value**. Use **Toggle Code View** to type a raw path or a template instead of picking a field. Try it here: build a condition on the tutorial's data and test it against three sample conversations.

_An interactive condition editor modeled on the builder's Continue Only If panel. You pick a field from step 1 (Conversation Created: Name, Channels, Priority, Created At) or step 2 (Look up the customer: Custom > VIP), an operator offered for that field's type using the builder's labels (Exists, Does not exist, Equals, Not equal, Greater than, Less than, Before, After, Contains, Does not contain and so on), and a value. Add Condition turns the rule into a Multiple Conditions (And) group, which you can switch to Or. The condition JSON updates live, and three sample conversations, a VIP email, a normal chat and an email from a customer with no VIP value, each show match or no match with the reason: for example, eq casts the string true to a boolean, contains on text is case-sensitive, and Not equal passes when the field is missing._

The operator list depends on the field's type. Each label maps to one `op` in the JSON, and [Conditions and variables](https://developer.kustomer.com/docs/workflows/conditions-and-expressions#operators) explains exactly when each passes.

| Label                     | `op`     | Label                    | `op`           |
| ------------------------- | -------- | ------------------------ | -------------- |
| Exists                    | `exists` | Less than                | `lt`           |
| Does not exist            | `dne`    | Less than or equal       | `lte`          |
| Equals                    | `eq`     | Before                   | `lt-date`      |
| Not equal                 | `neq`    | After                    | `gt-date`      |
| Greater than              | `gt`     | Contains                 | `contains`     |
| Greater than or equal     | `gte`    | Does not contain         | `not-contains` |
| Multiple Conditions (And) | `and`    | Multiple Conditions (Or) | `or`           |

| Field type                            | Operators offered                                                                                             |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Text                                  | Exists, Does not exist, Equals, Not equal, Contains, Does not contain                                         |
| Date (date-time text)                 | Exists, Does not exist, Equals, Not equal, Before, After                                                      |
| Number                                | Exists, Does not exist, Equals, Not equal, Greater than, Greater than or equal, Less than, Less than or equal |
| Boolean                               | Equals, Not equal                                                                                             |
| List                                  | Exists, Does not exist, Equals, Not equal, Contains, Does not contain                                         |
| Unknown, or a path typed in code view | All twelve comparisons                                                                                        |

The builder never offers the `true` and `false` operators. It writes `true` for you when it draws a plain line. Number values use a number input, so they're saved as numbers.

### Combine rules with and and or

Select **Add Condition** to turn a single rule into a **Multiple Conditions (And)** group that holds the existing rule plus a new, empty one. Change the group to **Multiple Conditions (Or)** with its operator dropdown. Groups can hold other groups.

```json title="VIP and arrived by email"
{
  "op": "and",
  "values": [
    { "op": "eq", "values": ["/#steps.B1gTq2Lxk.custom.vipBool", "true"] },
    { "op": "contains", "values": ["/#steps.1.channels", "email"] }
  ]
}
```

There's no "is one of" operator. To test "priority is 4 or 5", use an **Or** group of two **Equals** rules.

> **Case-insensitive text checks:** **Contains** on text is case-sensitive, so `"Refund for order 4417"` doesn't contain `refund`. Turn on **Toggle Code View** for the field and type a template that lowercases it, such as `{{{toLower steps.1.name}}}`, then compare it with a lowercase value.

### Branches and their order

A step can have several condition branches side by side, up to 10. **Their left-to-right order is their priority**: after the step runs, the branches are checked from left to right, and the first one that matches wins. Use the **Move left** and **Move right** arrows on a condition to reorder it. In the JSON, the order is the order of the step's `transitions` array.

If no branch matches, the run ends there. That's a normal, successful end, not an error. Put the most specific branch first, because a broad branch on the left swallows everything it matches.

### Condition steps

Sometimes you want to branch without running an action first. Choose **Condition Step** from the **+** menu: it's a step with no action, and its condition branches do the routing. For example, in a workflow triggered by **Message Created**, a step that sends emails about refunds one way and emails about shipping another:

```json title="A Condition Step with two branches"
{
  "id": "Hy3pQe7Lm",
  "meta": { "displayName": "Route by keyword" },
  "transitions": [
    {
      "target": "SkT0f9cQa",
      "meta": { "name": "Mentions refund" },
      "condition": { "op": "contains", "values": ["{{{toLower steps.1.subject}}}", "refund"] }
    },
    {
      "target": "rJ5vWn2Xb",
      "meta": { "name": "Mentions shipping" },
      "condition": { "op": "contains", "values": ["{{{toLower steps.1.subject}}}", "shipping"] }
    }
  ]
}
```

A subject that mentions both words only takes the left branch. To act on both, chain the checks instead: refund check, refund tag, shipping check, shipping tag. The [patterns](https://developer.kustomer.com/docs/workflows/patterns) page has more shapes like this.

## Save, test and enable

#### Save

Select **Save** and confirm: "Are you sure you wish to save this workflow? This will create a new version." Save sends the workflow's `steps`, `trigger` and `response`, and adds `appVersion` to each step, recording the app and version range of its action:

```json title="A saved step"
{
  "id": "B1gTq2Lxk",
  "action": "kustomer.customer.find",
  "appVersion": "kustomer-^1.2.1",
  "meta": { "displayName": "Look up the customer" },
  "params": { "identifier": "/#steps.1.customer" },
  "transitions": [ … ]
}
```

Your version range may differ. Save is disabled, with a tooltip, when there's nothing to save ("No unsaved changes"), when an object field has invalid JSON, or for an app's workflow.

#### Check the JSON

Open the options menu and choose **Edit workflow JSON**. The editor shows `name`, `description`, `trigger` and `steps` exactly as Kustomer stores them. Compare it with [the finished workflow](#the-finished-workflow) below.

The editor shows the **last saved version**, so save your canvas changes first. **Update** saves the JSON straight to the workflow, and Kustomer validates it. Malformed JSON shows "Invalid JSON.".

#### Try a dry run

If your options menu has **Test workflow**, use it before you switch the workflow on. It's a dry run in your browser: **it doesn't run actions or change data**.

1. Edit the **Sample Event Data**, or select **Regenerate from Workflow** to build it from the fields the workflow uses.
2. Select **Run test**. The **Execution Flow** shows how many steps ran, such as "3 steps executed", each step's **Input data** and **Output data**, and which branch was taken ("Next step") or skipped ("Transition not matched").
3. Because actions don't run, edit the lookup step's output to set `custom.vipBool` to `true`, then choose **Run from Here** to check the VIP branch.

#### Switch it on

Turn on the switch in the header. It takes effect immediately, and it's separate from Save: it changes only `enabled`.

```json title="What the switch sends"
{ "enabled": true }
```

You can't switch on a workflow while any condition lacks a target: "All conditions in active workflows must point to a target". Allow about a minute before the workflow starts matching new events.

#### Watch a real run

Open **View Logs** and turn **Debug Mode** on. Runs are recorded for the next hour, and the log shows the **TOP 100 LOG EVENTS**. Then create a conversation for your VIP test customer.

Each entry, such as "Log Event 1", says when the run completed and how many seconds it took, and names its **Last Step**. Open it to see **All Logged Steps For Event**, with every step's input and output. A run whose last step is the lookup means the VIP condition didn't match: check `custom.vipBool` in the lookup's output. Use [Debugging and logs](https://developer.kustomer.com/docs/workflows/debugging) for anything else.

Failed runs appear under **View errors** in the options menu, in **Workflow Errors**, as "Failed with Error at step …". Select **Clear All Errors** once you've fixed the cause. Temporary platform errors that Kustomer retries aren't listed there.

### The rest of the header

| Control                | What it does                                                                                                                                                                             |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Undo** and **Redo**  | Each names the change it will undo or redo                                                                                                                                               |
| Version picker         | **Latest version**, then earlier versions by date. An old version opens read-only. Kustomer keeps the 30 most recent versions                                                            |
| **Edit info**          | Opens **Edit Workflow Info**, with **Workflow Title** and **Workflow Description**. It saves at once, and **renaming the title also changes `name`**, so look workflows up by ID in code |
| **Workflow variables** | Opens the variables modal. See below                                                                                                                                                     |
| **View hook logs**     | For workflows triggered by a hook, what the hook received                                                                                                                                |
| **Delete workflow**    | "Deleting this workflow is immediate."                                                                                                                                                   |

## The finished workflow

This is what **Edit workflow JSON** shows after you save. Your step IDs, tag ID and `appVersion` ranges will differ.

```json title="tag-vip-conversations"
{
  "name": "tag-vip-conversations",
  "description": "Adds the VIP tag to new conversations from customers whose VIP field is true.",
  "trigger": {
    "id": "1",
    "eventName": "kustomer.conversation.create",
    "meta": { "displayName": "New conversation" },
    "transitions": [
      { "target": "B1gTq2Lxk", "condition": { "op": "true", "values": [true] } }
    ]
  },
  "steps": [
    {
      "id": "B1gTq2Lxk",
      "action": "kustomer.customer.find",
      "appVersion": "kustomer-^1.2.1",
      "meta": { "displayName": "Look up the customer" },
      "params": { "identifier": "/#steps.1.customer" },
      "transitions": [
        {
          "target": "rkM8xV0Zp",
          "condition": { "op": "eq", "values": ["/#steps.B1gTq2Lxk.custom.vipBool", "true"] },
          "meta": { "name": "Customer is VIP" }
        }
      ]
    },
    {
      "id": "rkM8xV0Zp",
      "action": "kustomer.conversation.append-tags",
      "appVersion": "kustomer-^1.2.1",
      "meta": { "displayName": "Add the VIP tag" },
      "params": { "id": "/#steps.1.id", "tags": ["64f1c0ffee5ca1ab1e000002"] },
      "transitions": []
    }
  ]
}
```

When you write JSON by hand, you can choose readable step IDs, such as `find-customer`, instead of generated ones. [Write a workflow in JSON](https://developer.kustomer.com/docs/workflows/write-workflow-json) shows how.

## Use workflow variables

Variables hold values you don't want written into the workflow itself, such as API keys and signing secrets. Each variable belongs to one workflow.

1. Open the options menu and choose **Workflow variables** ("Create variables to be used within this workflow."). Names can't contain spaces.
2. Fill in **Name** and **Value** and select **Save** on that row. Each row saves on its own, separately from the workflow's **Save** button.
3. Saved values are masked: you see `XXXXXXXXXXXXXXXXXXXXXXXX` followed by the last 6 characters. To rename a variable, select **Edit** and leave the value blank. Deleting a variable that's in use stops the workflow from working properly.

To use a variable in a field, choose it from the picker's **Variables** section. In object fields, the **Variables** button copies a ready-made template instead. Both reference the variable by its **ID**, not its name:

```json title="Variables in params"
{
  "uri": "/#variables.6531c0ffee5ca1ab1e0000a1",
  "headers": { "Authorization": "Bearer {{{variables.6531c0ffee5ca1ab1e0000a1}}}" }
}
```

The ID is the variable's 24-character ID. Because variables belong to one workflow, copying a workflow's JSON to another workflow or org breaks every variable reference until you create the variables there and update the IDs. [Data and variables](https://developer.kustomer.com/docs/workflows/data-and-variables#workflow-variables) covers variables in depth.

## Other kinds of trigger

### Hooks

Set **Trigger App** to **Hooks** to start a workflow from an [inbound webhook](https://developer.kustomer.com/docs/events/inbound-webhooks), an email hook or a form hook. **Add New Hook** opens **Add Kustomer Hook**, with **Title of Hook** and **Select Type**: **Email Hook**, **Web Hook** or **Form Hook**. **Hook Information** then shows the **Hook Address** that starts the hook, the **Hook Key**, and **Example Webhook Data**.

Paste a sample payload into **Example Webhook Data**. The builder reads field types from it and lists the fields in the data picker, so you can pick them instead of typing `/#steps.1.attributes.data.<field>`. The sample is only saved in your browser ("This data will only be saved locally").

### Callable workflows

Turn on **Callable Workflow** in **Create New Workflow** to make a workflow that other workflows run, usually later, with the Scheduler app. The toggle only appears when the Scheduler app is installed. The builder starts the trigger as:

```json
{ "id": "1", "callable": true, "schema": {}, "transitions": [] }
```

and opens **Edit Input Parameters** ("Data fields expected by this workflow"). On the **Input Parameters** tab, select **Add Parameter** for each input and give it a name, a type (**String**, **Number** or **Array**) and, if needed, **Required Field**. The **Output Parameters** tab declares what the workflow sends back. **Update** writes `trigger.schema` and `response.schema` as `{ "properties": { "<name>": { "type": "…" } }, "required": [ … ] }` in your draft. Save the workflow to keep them.

In a callable workflow, step `1` is just the inputs, so read them as `/#steps.1.<input>`. To run it, add the Scheduler app's action to another workflow: pick the callable workflow, fill its inputs, and choose **Select a Time Type**, then **Set an Exact Date and Time** or **Set a Relative Time**. See [Callable and scheduled workflows](https://developer.kustomer.com/docs/workflows/triggers-and-actions#callable-and-scheduled-workflows).

## What the builder can't do

> **There's no editor for error cases:** The classic builder doesn't show or edit `errorCases`. To decide what happens when a step fails, add them in **Edit workflow JSON** or with the API.

```json title="Added in Edit workflow JSON: stop quietly if the tag can't be added"
{
  "id": "rkM8xV0Zp",
  "action": "kustomer.conversation.append-tags",
  "params": { "id": "/#steps.1.id", "tags": ["64f1c0ffee5ca1ab1e000002"] },
  "errorCases": [
    {
      "condition": { "op": "and", "values": [{ "op": "eq", "values": ["/#error.status", 400] }] },
      "errorAction": { "type": "stop-processing" }
    }
  ],
  "transitions": []
}
```

The error is at `/#error`, never at `/#steps.<id>.error`. Error cases don't apply to a `5xx` from a Kustomer action: Kustomer retries the run from step 1, so earlier steps run again. See [Handle errors](https://developer.kustomer.com/docs/workflows/conditions-and-expressions#handle-errors).

The builder also follows the rules of the workflow JSON:

- **No loops.** A transition can't go back to a step that's already on its path. The canvas warns "⚠️ Cycle detected: … already visited", and saving is rejected. To repeat work later, schedule a callable workflow.
- **One end per branch.** Give each branch its own final step, even if two branches end with the same action.
- **Unique branches.** Two identical transitions on one step are rejected.

### Export JSON, Duplicate and Import

**Export JSON** and **Duplicate** copy `name`, `description`, `meta`, `steps` and `trigger`, and nothing else:

- The exported name is `export-<name>--<uuid>`, with the display name prefixed "Export". A duplicate's name starts with `copy-`, with the display name prefixed "Copy".
- `enabled` isn't copied, so copies start switched off. Neither is `logging`.
- A callable workflow's output parameters (`response`) are dropped. Add them again with **Edit Input Parameters**.
- Variables aren't copied, and their IDs belong to the original workflow. Create the variables on the copy and update every reference.

To load a file, select **Add Workflow**, then **Import Workflow** ("Import workflow with a JSON file"), choose a `.json` file and select **Import**. The file is created as is, so its `name` must be valid and unique in the org. When you copy a workflow to another org, also replace org-specific IDs such as tags, teams and users; see [Workflow JSON](https://developer.kustomer.com/docs/workflows/workflow-json#create-and-update-with-the-api).

### Deleting steps

Deleting a step opens **Delete Step** ("What would you like to delete?") with two choices: **Delete this step only**, which reconnects the steps below it to its parent, or **Delete this step and all steps below**. A step with several branches has to be deleted with everything below it. A condition can't be deleted while steps hang off it.

## Builder vocabulary ↔ JSON

| In the builder                    | In the JSON                                                                                                                                           |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Workflow Title                    | `name` (lowercased, spaces become `-`) and `meta.displayName` (as typed)                                                                              |
| Workflow Description              | `description`, up to 4,096 characters                                                                                                                 |
| The on/off switch                 | `enabled`. New workflows start `false`                                                                                                                |
| **Debug Mode** in View Logs       | `logging: true`, which records runs for one hour                                                                                                      |
| Trigger Title                     | `trigger.meta.displayName`                                                                                                                            |
| Trigger App and Trigger Event     | `trigger.eventName`, such as `kustomer.conversation.create`                                                                                           |
| Callable Workflow toggle          | `trigger: { "id": "1", "callable": true, "schema": { … }, "transitions": [ … ] }`. Kustomer sets `eventName` to `kustomer.workflow.<workflowId>.call` |
| Input Parameters tab              | `trigger.schema.properties.<name>.type` and `trigger.schema.required`                                                                                 |
| Output Parameters tab             | `response.schema.properties` and `response.schema.required`                                                                                           |
| Badge **1**                       | The trigger, `id: "1"`. Its output is `/#steps.1…`                                                                                                    |
| Action node                       | A `steps[]` entry: `id`, `action`, `params`, `transitions`, `appVersion`, `meta.displayName`                                                          |
| Action Title                      | The step's `meta.displayName`                                                                                                                         |
| Action App and Action Event       | `action`, such as `kustomer.conversation.append-tags`, plus `appVersion`, such as `kustomer-^1.2.1`                                                   |
| Required and optional fields      | `params.<name>`. Removing an optional field deletes the key                                                                                           |
| Picking a step's field            | `"/#steps.<stepId>.<path>"`, as the whole value                                                                                                       |
| Picking a variable                | `"/#variables.<variableId>"`                                                                                                                          |
| Picking a function                | `"/#fn:<name>,<path>,…"`                                                                                                                              |
| Code view text with `{{…}}`       | A Handlebars template string                                                                                                                          |
| A condition                       | A transition: `{ "target", "condition": { "op", "values": [left, right] }, "meta": { "name" } }`                                                      |
| Multiple Conditions (And) or (Or) | `"condition": { "op": "and" \| "or", "values": [ { "op", "values" }, … ] }`                                                                           |
| Condition Branch                  | Another entry in the same step's `transitions`, in left-to-right order                                                                                |
| Condition Step                    | A step with no `action`, whose `transitions` do the branching                                                                                         |
| A plain line between two nodes    | The step's only transition, `{ "op": "true", "values": [true] }`                                                                                      |
| **Delete this step only**         | The parent's transition is re-pointed to the deleted step's child                                                                                     |

## Next
