Skip to content

Customize and program workflows

A script is a step in the Workflow designer that runs code: it reads the document and the workflow, computes, decides the step’s exit and sets fields, tags, files, hints and notifications. You write it in TypeScript or plain JavaScript, with completion for your workspace’s document types and fields. Scripts are part of the Workflow designer add-on and need the same permission as the designer.

This page explains the concepts, walks you through your first script and describes the editor. Every function of the SDK is in the API reference, every message of the check under Scripts: troubleshooting.

A script belongs to a workflow like its other steps. You edit it in the workflow’s draft, and only Publish puts it into effect (Publish and versions):

  • When you publish, Acriva checks every script of the draft again on the server and freezes it with the version. A script with an error keeps the version from being published, whatever the editor showed before.
  • A published version never changes. Running workflows stay on the scripts of the version they started with.
  • A new draft starts with the published version’s scripts, as it starts with its steps. A copy of a workflow takes its scripts along too.
  • Use as draft under Versions takes that version’s scripts along with its steps. The scripts of steps the version doesn’t have leave the draft; Undo brings those steps and their scripts back, but a script the version replaced keeps the version’s code.
  • All scripts of a version use the same version of the SDK.

You drag the Script step from the Steps panel onto the canvas. It names:

  • the script file, such as cost-center.ts: lower-case letters, digits and hyphens, starting with a letter or digit, at most 63 characters before the ending .ts. A workflow has at most 20 script steps;
  • the hook: when the script runs and what it may do (below);
  • the inputs: the document’s fields the script reads. A script sees only the fields its step names as inputs, and never the document’s text;
  • the return values: named values with a type (text, number, amount, date, yes/no, reference or a fixed choice) that later conditions read. They come from outputs in the code;
  • the exits: when a return value is a fixed choice, each of its values becomes an exit of the step. When the script returns none, the workflow goes on to the default next step. When the script fails, it goes to the step’s failure exit if it has one, otherwise to the default next step as well. Either way the workflow goes on.

The hook decides when a script runs and which changes it may make.

Hook When What the script may change
onDocumentArrived as soon as a document has arrived and been recognized, before the first step everything: fields, tags, files, notifications, hints, the exit
beforeApproval before an approval or review only a hint on that task; otherwise it only reads
onApproved after an approval everything, but only fields that are empty and that the workflow declares
onRejected after a rejection everything
onTimer after a deadline or a schedule everything; after an approval, fields only when they are empty and declared

A script never sets the fields Finance reads for an incoming invoice (such as the amounts and the vendor), because it would change an invoice that has already been reviewed or approved.

A script runs in an isolated environment without network, without files and without access to other workspaces. It only collects its changes: Acriva checks them after the run and then applies them as the workflow. These limits apply to every run:

Limit Value
Computing time 2 seconds (waiting for a data source doesn’t count)
Duration of a run 45 seconds, including the wait for a free slot
Memory 64 MB
Return values, changes and exit together 256 KB
Console 1,000 lines or 64 KB; a longer line is cut after 1,000 characters
Changes at most 100 per run, of them at most 5 notifications to at most 20 recipients each
Texts a hint at most 500 characters, a notification at most 1,000, a text field or text return value at most 1,000
Data sources 10 calls per run, each at most 5 seconds, at most 30 seconds together
Data tables 50 lookups per run
Regular expressions 250 milliseconds per expression
Size of a script 64 KB
Concurrent runs 20 per workspace

What happens when a limit is exceeded is described under Limits exceeded.

A script may:

  • read the fields its step names as inputs, plus the document type, the document’s tags and files, and the workflow’s step and the last outcome of a task (ctx.document, ctx.instance);
  • ask for the time of the run, today’s date in Germany and a random number (ctx.now(), ctx.today(), ctx.random());
  • write lines to the console (ctx.log);
  • depending on the hook, set fields, add existing tags, file the document into a file, notify members or roles, and attach a hint to the next task;
  • read data sources and data tables that your workspace has set up and that its step names as inputs (ctx.sources, ctx.tables). A data table answers a row by its key (lookup) or up to 100 rows that match up to three columns (query), at most 50 times per run. A refused call reaches the script as an Error it can catch; a data table the step doesn’t name ends the run with table-not-declared (see Limits exceeded).

A script may not:

  • use the network (fetch, XMLHttpRequest, WebSocket): it reads external data through a data source;
  • create code at run time (eval, Function), load modules (require, import()) or import anything but '@acriva/workflow-sdk';
  • wait (setTimeout, setInterval): a workflow waits with a timer step;
  • use Date.now() or Math.random(), which the sandbox doesn’t have: use ctx.now() and ctx.random();
  • notify a free email address: only members by their email address, and roles;
  • complete a task or approve anything. A person always approves.

The check reports most of these cases in the editor already, each with a code of its own; Date.now() and Math.random() fail only when the script runs.

  • The server decides. What the editor checks in the browser is a help. When you publish, Acriva checks every script itself, and only the version’s frozen, checked script runs.
  • Every run is in the history. Every run of a script appears in the document’s audit log as Workflow script ran, with its outcome and, for a failure, its code. Acriva deletes a run’s values (its inputs, return values, changes and console) 90 days after the workflow ended; the outcome and the code stay.
  • Changes as the workflow. What a script changes, the workflow changes, and it is in the document’s history like any other change a workflow makes. A script never gets more rights than the workflow has.
  • A script that crashes the sandbox three times is blocked for your workspace until you change the script and publish a new version.

The example sends an incoming invoice to the management or to the cost center depending on its gross amount, and gives the approval a hint.

  1. Under Settings › Workflows, open a custom workflow for incoming invoices in the designer, or create one (Create a workflow).

  2. Drag Script from the Steps panel behind the Intake.

  3. Name the file route.ts, choose the hook onDocumentArrived and, under Inputs, add the field of the gross amount.

  4. The return value route, a fixed choice with the values management and costCenter, comes from outputs in the code below. The step takes it over and gets two exits from it.

  5. Write the code:

    import { defineScript, money } from '@acriva/workflow-sdk';
    export default defineScript({
    hook: 'onDocumentArrived',
    outputs: { route: ['management', 'costCenter'] },
    run(ctx) {
    const amount = ctx.document.fields.totalGross;
    if (amount == null) return; // no amount: the default next step
    ctx.log('Gross amount', money.format(amount));
    if (amount > 500) {
    ctx.hint('Over €500: the management approves.');
    return ctx.branch('management');
    }
    return ctx.branch('costCenter');
    },
    });

    totalGross is the key of the incoming invoice’s Gross total field; the editor suggests the fields the step reads as its inputs as soon as you type ctx.document.fields.. A field that isn’t required can be empty, so the script checks for null first.

  6. Try the script in the test run on an incoming invoice. The test run shows the exit, the fields that would change and the console, and it changes nothing.

  7. Under Outputs, connect the exits route = 'management' and route = 'costCenter' to an approval each, and publish the workflow.

From then on the script runs for every new incoming invoice. You set a field the workflow declares with ctx.setField('costCenter', '4200'). No script sets the fields Finance reads for an incoming invoice; the editor suggests which fields a script may set.

The editor opens in the properties panel of a script step, with the tabs Code, Inputs, Outputs and Test run; Full screen gives it the whole width.

  • Completion: the editor knows the SDK and your workspace’s types: document types and their fields, file types, tags, cost centers and roles. It suggests them as you type and shows each function’s description.
  • Checks as you write: it checks types and rules while you write, with the same tools as the server. Every marker carries a code such as ACR-S012; Learn more leads to its entry under Scripts: troubleshooting. For some messages it offers a quick fix, for fetch for example Use a data source; Ctrl+. (⌘. on a Mac) opens it at the cursor. Errors keep the workflow from being published, warnings don’t.
  • By keyboard: in the editor, the Tab key indents. After Ctrl+M (Ctrl+Shift+M on a Mac) it leaves the editor for the next element; the same shortcut switches back.
  • Can’t publish: when you publish, the server checks the scripts once more. If it finds errors, the dialog Can’t publish lists each with its code, text, file and place. Go to the first error opens the script step with the cursor on the first error’s line, and a row’s arrow on its error.
  • Format puts the code into a consistent form. The status bar shows the language, the SDK version, the number of warnings and errors, line and column, and whether the script is saved.
  • Test run: it runs the script once on a sample document and changes nothing; more under Test run.

Full screen on the Code tab opens the workflow’s scripts over the whole window; Back to the designer closes it again, with the same draft and the script shown last selected. A script is only ever edited in one place: while full screen is open, the properties panel shows no editor. At the top right are Format, Test run (see Test run) and Publish.

  • Files: on the left are the workflow’s scripts under scripts, one per script step, and the SDK’s read-only types under types (acriva.d.ts). Each file opens in a tab of its own. workflow.yaml shows the draft as the workflow’s export writes it, read-only; it changes with each save of the draft. Below them are the workspace’s Data sources and the New fields in this version that publishing creates.
  • Hooks & API: on the right are the hooks and the functions of ctx, each with a short description. Search … (Ctrl+Shift+F, ⌘+Shift+F on a Mac) filters both lists. An entry expands to a longer description and an example: Insert puts the example at the cursor, Docs opens, in a new tab, the Hooks section of this guide for a hook, and a function’s entry in the SDK reference. The shown script’s hook is marked active.
  • Problems: below the editor are the shown script’s messages with their code, text, line and column, Learn more and, where there is one, the quick fix. Clicking a message (or pressing Enter on it) puts the cursor on its place. The last line says how the server’s type check of the last save went.
  • Output: here is what happened in full screen: saved, the result of the server’s check, formatted, a change made elsewhere, opening a file, and a file that couldn’t be loaded. After a test run come its console and a line with its result; above them is the result of the shown script’s last test run. Like its other lines, Output keeps the last 200 at most.
  • Git: the switch Files | Git at the top of the left side bar shows the workflow’s Git remote, its last push and pull, and pushing and pulling; more under Git: push and pull.
  • Narrow screens: below 1280 px wide, you edit the script in full screen only; Edit in full screen on the Code tab opens it. Inputs and Outputs stay in the properties panel.

The test run runs a step’s script once on a document of your choice, as a running workflow would. A test run changes nothing. Fields, filing, data source results and notifications are only shown, not saved or sent: the document stays as it is, no tag, Akte or hint is added, nobody is notified, and nothing appears in the document’s history or log.

  1. Select the script step on the canvas and open the Test run tab.
  2. Under Sample document, choose a document. The list shows the newest documents you may read, of the trigger’s document type when the workflow has one; Search documents searches them.
  3. Select Run. After that the button reads Run again.

The test run runs the draft’s saved script, which the server checks once more first. While the draft or the script has a change that isn’t saved yet, it waits for it. If the check finds errors, the script doesn’t run and the tab shows the messages; clicking a place opens it on the Code tab.

After the run, the tab shows:

  • the result: Succeeded or Failed, how long the run took and when, what the script returned (such as branch('gf')) and the next step. For a failure, its code, its text and, where known, the line and column in the script follow below; clicking them opens the place on the Code tab. The × button (Close the result) hides the result again.
  • Fields that would be set: each field with its value now and its new value; below them what else the run would do, such as adding a tag, filing the document in an Akte, sending a notification or showing a hint.
  • Usage: run time, memory, the calls of data sources and the lookups of data tables, each against the sandbox’s limits.
  • Console: the lines the script wrote to the console (ctx.log), with their time and level (info, log, warn, error); 1,000 lines at most, each 1,000 characters at most, 64 KB in all. Copy copies them.

On the canvas, the script step shows how long the last test run took, with a check mark for a run that succeeded and a cross for one that failed. The exit the run took is highlighted, and the next step carries Test run. This goes away when you close the result, change the script or change the draft.

In full screen, Test run in the top bar runs the shown script on the sample document you chose on the Test run tab. Without one, a small dialog asks for it; it too waits while the script has a change that isn’t saved yet. The console and the result appear under Output. When the result is of another document than the one chosen now, or the draft changed since, a note above it says so, as on the Test run tab.

For a test run:

  • The same limits apply as for a run in the workflow. On top of that, each person runs at most 10 test runs a minute; after that, the tab says when you can go on.
  • If a path from the trigger to the step passes an approval, the script behaves as after an approval: it sets only empty, declared fields (Hooks).
  • The document may have another document type than the trigger’s. A field the step may not set on that document makes the run fail with effect-refused, as it would in the workflow.
  • Only you see the result, and only while the designer is open.

The designer’s test run, by contrast, tries the whole draft, runs no scripts and stops at a script step.

On the Code tab, AI writes a script from your description, explains a message of the check and proposes a fix. If you like, it also suggests code while you type. AI only ever proposes: you decide what goes into the draft, and only a person publishes. The workspace needs AI to be set up for it; otherwise the editor says AI isn’t set up in this workspace.

  1. Select With AI in the file bar above the editor. The card Write with AI opens.
  2. Describe what the script should do, for example when to route an invoice to whom.
  3. Select Write (or press Ctrl+Enter in the text box). While the AI writes, Cancel stops it.

The answer appears in the editor as a proposal: the inserted lines are marked green, removed lines red. A bar at the bottom right says how many lines change, for example 5 lines inserted, and changes only the draft, the check runs afterwards; the status bar shows Proposal open.

  • Accept (or Ctrl+Enter, ⌘+Enter on a Mac, in the editor) takes the proposal over as one change, which Ctrl+Z undoes.
  • Discard (or Esc in the editor) throws it away.

Nothing is saved before you accept. After that the script is saved like any change you type, and the server checks it: a script with errors stays a draft with its messages and keeps the workflow from being published. A text accepted from AI stays marked in the editor (Accepted from AI, not saved yet) until the script is saved. A script saved with text accepted from AI stays flagged until the workflow is published or the draft is discarded: the dialog to publish names it under Written with AI, so you check what it does before you publish. A script that comes with an imported workflow isn’t flagged.

AI works on the saved script, so a request waits until your last change is saved. If the answer holds no script, the editor says The answer holds no script. Rephrase the description.

Every message of the check with a code such as ACR-S012 has two AI actions among its code actions, which the lightbulb beside the line or Ctrl+. (⌘. on a Mac) opens. The rule’s own quick fix comes first and costs nothing.

  • Explain error (AI, 1 credit) shows the answer under the editor, as Explanation of ACR-S012. Links in an explanation lead only to docs.acriva.de; any other address stays plain text. Close the explanation (×) hides it.
  • Fix error in the explanation, or Fix error (AI, 1 credit) among the code actions, proposes a fix the same way as Write with AI: as marked lines you Accept or Discard.

When you switch them on, the AI completes the line you are writing as grey text after the cursor: Tab accepts it, Esc dismisses it. A suggestion is at most the current statement, and it comes after a short pause in typing.

  1. In the editor’s status bar, select AI suggestions off. The popover AI in the editor opens.
  2. Switch on Suggestions while typing. The status bar then reads AI suggestions on.

The switch is off by default. It applies only to you, in this browser: on another computer or in another browser, the suggestions stay off until you switch them on there too.

Action AI credits
Write with AI, Explain error, Fix error 1 each
Suggestions while typing 1 per 10
  • You make at most 200 suggestions while typing a day. After that, the popover says No more suggestions while typing are possible today., and the status bar reads AI suggestions off until the next day.
  • Writing, explaining and fixing together allow at most 20 requests a minute per person.
  • A request you cancel costs nothing.
  • When the workspace has no AI credits left, the editor says The workspace has no AI credits left. and suggestions while typing stop. Who may see the AI usage also sees in the card Write with AI how many credits are left.

The model gets:

  • the script’s code, including the texts and comments in the script: anything you write into a comment or a string goes to the model as written;
  • the messages of the check;
  • the names and types of your fields, document types, data sources and data tables;
  • your description, and for a suggestion the text around the cursor.

It never gets values from documents, data sources or data tables, nor a test run’s document, result or console. Every AI request is metered and recorded like the other AI steps.

Inputs declares what the script reads:

  • Document fields: the fields of the workflow’s document type that the script reads as ctx.document.fields. Add field offers the type’s fields and the fields the workflow declares. A workflow without a document type has none.
  • Data sources and data tables: the data sources the script queries through ctx.sources. Add data source offers your workspace’s data sources, and Manage data sources leads to them. A data table comes in through the workflow file (see Data tables).
  • Always available: the document’s tags and files and the last task’s outcome, which every script reads without a declaration.

Outputs shows what the script gives back and where the workflow goes next:

  • Return values come from outputs in the code. The step takes over each key and type as soon as the editor has checked the code, so you declare them only once; while the code has a syntax error, the step keeps the ones it has. Each change is one step you can undo with Ctrl+Z. Used by names the later conditions that read a value, and This step’s exits marks the value the script branches on with ctx.branch.
  • Exits: a row per value of the branch, such as route = 'management', with the step it leads to, and Otherwise for any other result. A script that doesn’t branch has Next instead. The canvas draws the same exits, labeled with their value, and a connection you draw or remove there changes them here too.
  • On failure: where the workflow goes when the script fails (a limit exceeded, an exception, a refused change). With Step fails (default) the history records a failed step, and the workflow goes on as with Otherwise or Next.
  • Fields the script sets: the keys of ctx.setField in the code, each with its field: an existing field of the document type, or a field the workflow declares, marked new. Declare new field, or Declare beside a key no field has, adds a field the workflow needs: its key, kind, labels in German and English, the options of a selection and the document types it goes on. Declared fields apply to the whole workflow; publishing creates them on their document types, and the publish dialog lists them under New fields in this version.

A script with the hook beforeApproval only reads: it has no return values, doesn’t branch and sets no fields, so its Outputs has only Next and On failure.

Every script file ends in .ts, and you can write TypeScript or plain JavaScript in it: valid JavaScript is valid TypeScript too. TypeScript pays off because the check then finds more, such as a field that doesn’t exist or a text where an amount belongs. But even without type annotations, the editor infers the types from the SDK and checks with them.

  • Amounts are numbers in euros (Money); money.format(1249.5) returns '1.249,50 €', money.parse('1.249,50 €') the amount, and money.round rounds half away from zero to cents.
  • Days are texts YYYY-MM-DD (PlainDate) that compare with < and >; date.format, date.parse, date.addDays and date.daysBetween compute with them.
  • run may be async and wait for data sources and data tables (await ctx.sources.get(…)).
  • A switch over a value the SDK supplies (such as a hook or a task’s outcome) needs a default branch: the SDK grows only by additions within version 1, but an addition can bring a new value.
  • The API reference describes every function and type of the SDK, per version, with its changelog.
  • The workflow cookbook shows ready-made workflows with scripts, each with its code and the result a test checks.
  • Scripts: troubleshooting explains every code of the check, exceeded limits and common type errors.