> For the complete documentation index, see [llms.txt](https://developer.vario-software.de/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.vario-software.de/documentation/scripting/workflows.md).

# Workflows

VARIO Cloud Workflows let you run server-side JavaScript in response to entity lifecycle events in the ERP — an account being saved, a document being created, a CRM deal changing state, and more. Workflows are the primary mechanism for automating business processes, validating data before it is persisted, and orchestrating multi-step logic with user interactions.

***

## Workflow Structure

Every VARIO Cloud workflow consists of a **trigger** and a chain of **elements** connected by successors:

```
Trigger (e.g. account.BEFORE_SAVE)
  → START_EVENT
    → SCRIPT_TASK
      → EXCLUSIVE_SPLIT_GATEWAY
        → SCRIPT_TASK (Branch A)
        → SCRIPT_TASK (Branch B)
      → EXCLUSIVE_JOIN_GATEWAY
    → END_EVENT_SUCCESS
```

***

## Triggers

The trigger determines **when** a VARIO Cloud workflow runs and **what data** is available to your scripts.

### BEFORE\_SAVE vs AFTER\_SAVE

This is the most important distinction in VARIO Cloud workflow scripting — it determines whether your script can modify the entity directly or must read and update it via service calls:

|                       | BEFORE\_SAVE                                                       | AFTER\_SAVE                                                   |
| --------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------- |
| **Execution**         | Synchronous — blocks the save                                      | Asynchronous — runs after save completes                      |
| **Input**             | The entity to save (e.g. `accountToSave`) and its previous version | The saved entity's ID (e.g. `accountId`) and previous version |
| **Can modify entity** | Yes — changes to the ToSave entity are persisted automatically     | Yes — but must read by ID and update via service methods      |
| **Can cancel save**   | Yes — `throw 'message'` aborts with an error shown to the user     | No                                                            |

BEFORE\_SAVE scripts receive the entity object directly and can modify it in place — every change is persisted with the save. AFTER\_SAVE scripts only receive the entity's ID, so they must read the entity via its service, apply changes, and explicitly call the update method.

### Other Triggers

Beyond BEFORE\_SAVE and AFTER\_SAVE, triggers exist for specific lifecycle events such as `document.CREATE`, `document.TRANSFER`, and `document.BEGIN_EDITING`. These behave like AFTER\_SAVE — they run asynchronously and provide IDs rather than entity objects.

The data available to each trigger is accessible via `ctx.availableInput` and `ctx.parameters`. The exact fields depend on the trigger and are visible in the workflow editor.

***

## Script Structure

Every workflow script in VARIO Cloud starts with an import that determines its type. Scripts can be written inline in each element or saved centrally as [Script Modules](/documentation/scripting/script-modules.md) and referenced by name.

### SCRIPT\_TASK

```javascript
import workItem from "work_item_script";

workItem.setAction((ctx) =>
{
  // Your business logic
});
```

You can optionally add a **guard** to skip the element, and a **prepare** step to set up variables before the action:

```javascript
workItem.setPrepare((ctx) =>
{
  // Optional — runs before setAction, e.g. to initialize variables
});

workItem.setGuard((ctx) =>
{
  // Return true to execute setAction, false to skip this element
  return ctx.availableInput.accountToSave.customer !== null;
});

workItem.setAction((ctx) =>
{
  // Only runs if guard returned true
});
```

### EXCLUSIVE\_SPLIT\_GATEWAY

```javascript
import workItem from "work_item_split_gateway";

workItem.setSplit((ctx) =>
{
  const targetCategory = ctx.availableInput.targetCategory;

  const creditNoteTypes = [
    'CUSTOMER_CREDIT_NOTE_WITH_STOCK',
    'CUSTOMER_CREDIT_NOTE_WITHOUT_STOCK',
    'SUPPLIER_CREDIT_NOTE_WITH_STOCK',
    'SUPPLIER_CREDIT_NOTE_WITHOUT_STOCK'
  ];

  if (creditNoteTypes.includes(targetCategory))
  {
    return 'TRANSFER_INTO_CREDIT_NOTE';
  }

  return 'END_WORKFLOW';
});
```

The returned string must match a key in the element's `successors` map.

***

## The Context Object (ctx)

Every script in a VARIO Cloud workflow receives a context object `ctx` with the following properties:

| Property             | Description                                                            |
| -------------------- | ---------------------------------------------------------------------- |
| `ctx.availableInput` | Trigger-specific data — the entity being saved, IDs, previous versions |
| `ctx.services`       | All available scripting services                                       |
| `ctx.parameters`     | Workflow parameters and user task results                              |

***

## Reading and Modifying Data

How you read and modify ERP data in VARIO Cloud depends on the trigger type.

### BEFORE\_SAVE — Direct Modification

In BEFORE\_SAVE triggers, `ctx.availableInput` contains the entity object that is about to be saved, named with a `ToSave` suffix — e.g. `accountToSave`, `documentToSave`, or `crmDealToSave`. This is the actual object being persisted. Any property you set on it is automatically included in the save without calling a service method. Because the save is still in progress, you can also cancel it by throwing an error.

```javascript
import workItem from "work_item_script";

workItem.setAction((ctx) =>
{
  const document = ctx.availableInput.documentToSave;
  if (!document) return;

  if (!document.documentCategory || !document.documentCategory.startsWith('CUSTOMER'))
  {
    return;
  }

  if (document.reverseCharge === true)
  {
    const vatId = document.vatId;

    if (!vatId || vatId.trim() === '')
    {
      throw 'Bei Belegen mit §13b (Reverse Charge) muss eine USt.-ID angegeben werden.';
    }

    ctx.services.logger.info('§13b Validierung erfolgreich: USt.-ID ist vorhanden.');
  }
});
```

### AFTER\_SAVE — Read, Modify, Update

In AFTER\_SAVE triggers, read the entity by ID, modify it, and call the service's update method:

```javascript
import workItem from "work_item_script";

workItem.setAction((ctx) =>
{
  if (ctx.availableInput.previousAccount != null)
  {
    return;
  }

  const account = ctx.services.accountService.readById(ctx.availableInput.accountId);
  const createdBy = ctx.services.userAndGroupService.findUserById(account.info.createdFrom);

  if (createdBy?.username !== 'niko.neumann') return;

  let taskDto = ctx.services.crmTaskService.createNewDtoByTemplate('Adress-Prüfung');
  ctx.services.crmTaskService.create(taskDto);
});
```

### Document Updates

When updating documents via service (e.g. in AFTER\_SAVE or CREATE triggers), `documentService` requires a request wrapper. In `document.ON_MERGE_CHANGES` triggers, the document is available as `documentToSave` and can be modified directly — like any other BEFORE\_SAVE entity.

```javascript
workItem.setAction((ctx) =>
{
  const { documentId } = ctx.parameters;
  const { documentService, accountService, deliveryMethodService, paymentMethodService, utils, logger } = ctx.services;

  const document = documentService.readById(documentId);
  const account = accountService.readById(document.accountId);

  if (account.types.includes('CUSTOMER'))
  {
    return;
  }

  if (!document.deliveryMethodRef)
  {
    logger.info('Setze Versandart: DL');
    const deliveryMethod = deliveryMethodService.findByLabel('DL');
    document.deliveryMethodRef = utils.toApiReference(deliveryMethod);
  }

  if (!document.paymentMethodRef)
  {
    logger.info('Setze Zahlungsart: UEB');
    const paymentMethod = paymentMethodService.findByLabel('UEB');
    document.paymentMethodRef = utils.toApiReference(paymentMethod);
  }

  const updateRequest = documentService.getUpdateDocumentRequest();
  updateRequest.document = document;
  documentService.update(updateRequest);
});
```

***

## Validation and Cancellation

In BEFORE\_SAVE triggers, throw an error to cancel the save:

```javascript
workItem.setAction((ctx) =>
{
  const account = ctx.availableInput.accountToSave;

  if (!account.defaultAddress.postcode)
  {
    throw 'A postal code is required.';
  }

  if (account.defaultAddress.country.isoAlpha2 === 'DE'
    && account.defaultAddress.postcode.length !== 5)
  {
    throw 'German postal codes must be exactly 5 digits.';
  }
});
```

The error message is displayed to the user and the save operation is aborted.

***

## Logging

Use `ctx.services.logger` to write log entries:

```javascript
ctx.services.logger.info('PLZ-Validierung erfolgreich abgeschlossen.');
ctx.services.logger.warn('Unexpected value encountered.');
ctx.services.logger.error('Failed to update document.');
```

| Method              | Description           |
| ------------------- | --------------------- |
| `logger.info(msg)`  | Informational message |
| `logger.warn(msg)`  | Warning               |
| `logger.error(msg)` | Error                 |

Log entries are visible in the workflow execution log in the ERP.

***

## VQL in Workflows

Use `ctx.services.vqlService` to query ERP data within scripts:

```javascript
const results = ctx.services.vqlService.queryAll(
  `SELECT id, number FROM article.query WHERE number = '12345' LIMIT 1`
);

if (results && results.length > 0)
{
  const articleId = results[0].id;
}
```

{% hint style="info" %}
`vqlService.queryAll()` returns a `List<Map>`. Access fields directly by their attribute path: `results[0].id`, `results[0].number`.
{% endhint %}

***

## User Tasks

VARIO Cloud workflows support user interaction through `USER_TASK` elements. A user task pauses workflow execution and prompts the user for input. The user's response is available in subsequent scripts via `ctx.parameters`:

```javascript
// In the element after a USER_TASK with resultName 'closingReasonId'
workItem.setAction((ctx) =>
{
  const crmDealService = ctx.services.crmDealService;
  const dealId = ctx.parameters['crmId'];
  const closingReasonId = ctx.parameters['closingReasonId'];

  crmDealService.dealWon(dealId, closingReasonId);
});
```

***

## Custom Fields

VARIO Cloud [Custom Fields](/documentation/fundamentals/custom-fields.md) are accessed directly on entity objects in scripts:

```javascript
// Read
const externalId = account.custom.myapp.externalId;
```

```javascript
// Write (in BEFORE_SAVE)
account.custom.myapp.externalId = 'EXT-123';
account.custom.myapp.syncedAt = ctx.services.utils.dateTimeNow();
```

***

## Comparing with Previous State

Both BEFORE\_SAVE and AFTER\_SAVE triggers in VARIO Cloud provide the previous version of the entity. Use it to detect field-level changes and react only when specific values have changed:

```javascript
workItem.setAction((ctx) =>
{
  const current = ctx.availableInput.crmDealToSave;
  const previous = ctx.availableInput.crmDealPrevious;

  // Only act if the state actually changed
  if (current.state.id === previous.state.id) return;

  if (current.state.label === 'Won')
  {
    // Deal just became "Won"
  }
});
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developer.vario-software.de/documentation/scripting/workflows.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
