# Workflows

> Move, copy, rename or delete files automatically, branch on conditions, copy to your own bucket and send notifications, on upload, on a schedule or on demand.

Updated 2026-10-24

A workflow is a trigger and a set of steps that act on one file. When a partner uploads `invoice.csv`, a workflow can check the extension, move the file to `/inbox/2026/10/`, copy it to your S3 bucket and post to Slack, all without a script.

Open a server, go to **Workflows** and click **New workflow**. Start from a blank workflow or a template. The editor shows the workflow as a tree. Click a step to change it in the panel on the right, and click **+** on a line to add a step there.

New workflows start paused. Save as often as you like while you build it, then switch it **On**.

## Triggers

| Trigger | When it runs |
| --- | --- |
| When a file event happens | After an upload, first upload, rename, download or first download. Add filters on the path or username to narrow it down. |
| Run manually | Only when you run it with **Run on a file**, from the file manager or with the [API](/docs/api). |
| On a schedule | On a cron schedule in your time zone, for every file in a folder that matches. Pro and Business plans. |

Any workflow can be run by hand on one file, whatever its trigger and even while it's paused. In the file manager, open a file's menu and choose **Run workflow**.

### Schedules

A scheduled workflow scans one SFTP user's folder (optionally with subfolders) and starts a run for each file in it. To act only on some files, start the workflow with a Condition step on **Age (days)** or on **File name** with a pattern such as `*.csv`. Each scan handles up to 1,000 files and schedules can run at most every 5 minutes.

Files stay in the folder unless a step moves or deletes them, so the next scan finds them again. A nightly cleanup is a good fit: a Condition step checks **Age (days)** is more than 30, and the **Yes** branch deletes the file. A schedule that only copies files will copy them again on every run.

## Steps

| Step | What it does |
| --- | --- |
| Condition | Checks rules and continues on the **Yes** or the **No** branch. |
| Move or rename | Moves the file to a new path. Later steps work on the file at its new path. |
| Copy | Copies the file to another path on the server. Later steps keep working on the original. |
| Delete | Removes the file. |
| Copy to bucket | Copies the file to one of your [storage connections](/docs/storage), up to 5 GB per file. Pro and Business plans. |
| Notify | Sends a webhook, an email or a Slack, Discord or Teams message, like an [event listener](/docs/webhooks). |

Move and copy refuse to replace a file that's already at the destination unless you switch on **Replace a file that's already there**. They never replace a folder.

Steps run as the SFTP user the file belongs to, so they follow that user's folder, shared folders and quota.

### Conditions

A condition has one or more rules and continues with **Yes** when all of them match, or any of them if you choose so. Rules can look at:

- **Path**, **File name**, **Folder** and **Extension** (compared without the dot and ignoring case)
- **User**, the SFTP user the file belongs to
- **Event**, what started the run: `upload`, `rename`, `manual`, `schedule` and so on
- **Size** in bytes and **Age** in days or minutes since the file was last changed

The **matches pattern** operator takes a glob. `*` matches any characters within a name, `**` matches across folders and `?` matches one character. For example `/in/**/*.csv` matches every CSV file anywhere under `/in`.

## Variables

Destinations and object keys can use variables. Click the **{ }** button next to the field to insert one. The preview under the field shows the result for an example file.

| Variable | Example for `/in/report.csv` |
| --- | --- |
| `{path}` | `/in/report.csv` |
| `{dir}` | `/in` |
| `{name}` | `report.csv` |
| `{basename}` | `report` |
| `{ext}` | `csv` |
| `{user}` | `acme` |
| `{yyyy}` `{mm}` `{dd}` | `2026` `10` `24` |
| `{hh}` `{mi}` `{ss}` | `09` `05` `03` |
| `{timestamp}` | `1792833903` |
| `{run_id}` | The run's id |

Dates and times are UTC and are taken when the run starts. A destination that ends in `/` keeps the file name, so `/archive/{yyyy}/` puts `report.csv` at `/archive/2026/report.csv`.

## Runs

Every run is listed under **Runs** in the editor for 30 days. Open one to see it on the canvas: the steps it went through are marked, the branch it took is highlighted and you can click a step to see what it did.

A step that fails because of a temporary problem, such as a webhook that doesn't answer, is retried three times over a few minutes. Problems that retrying can't fix, such as a file already at the destination, fail the run straight away.

To retry a failed run, open it and click **Retry from the failed step**. It continues with the workflow's current steps, so you can fix the problem first. Steps that already succeeded aren't repeated.

Changes made by workflows show up in your logs with the protocol `Workflow`. Event listeners see them like any other change, but they never start other workflows, so a workflow can't trigger itself in a loop.

## Webhooks from workflows

Notify steps send the same payload as [event listeners](/docs/webhooks#payload), with a fresh download link and an extra `workflow` field:

```json
{
  "event": "upload",
  "full_path": "/inbox/2026/10/report.csv",
  "protocol": "Workflow",
  "workflow": { "id": "…", "name": "CSV router", "run_id": "…", "cause": "upload" }
}
```

Each workflow has one signing secret for all of its webhook steps. It's shown in the panel of any webhook step. [Verify the signature](/docs/webhooks#verifying-signatures) like a listener's.

## Limits

| | Solo | Starter | Pro | Business |
| --- | --- | --- | --- | --- |
| Workflows per server | 1 | 3 | 25 | 100 |
| Scheduled workflows | | | ✓ | ✓ |
| Copy to bucket | | | ✓ | ✓ |

A workflow can have up to 30 steps.
