Creating a flow in Windmill
Nextcloud Flow integrates the Windmill workflow engine (https://www.windmill.dev/) to allow advanced custom workflows interacting with your Nextcloud instance.
To build a flow, you connect different scripts to each other in any way you need for your task. To create flows that work with your Nextcloud instance, you need four key elements:
- a trigger that starts the flow (we use webhooks for this)
- input parameters from that trigger which you can use in your scripts
- scripts that interact with your Nextcloud instance (including authentication credentials that provide these scripts access)
- interconnection of scripts to use results and transfer information
These elements are explained in detail below. At the end of the page, you can find an example flow including all these aspects.
Creating webhook triggers
Each workflow in Windmill that reacts to something happening in the Nextcloud instance is a listener to a Nextcloud Webhook Event. It will automatically register webhooks for the workflows you build using a "magic" listener script. You can also use Windmill to react to an event outside of Nextcloud. For implementing other services, please refer to the Windmill documentation.
The first script (after the “Input” block) in any workflow you build that should listen to a Nextcloud webhook must have “Summary” set to the exact, literal string CORE:LISTEN_TO_EVENT. It must be an empty script with two parameters that you should fill statically: events, which is a list of event IDs to listen to, and filters, a filter condition that allows more fine grained filtering for which events should be used. The filter condition as well as the available events with their payloads is documented in the webhook_listeners documentation.
You can copy the following Deno script for this:
export async function main(events: string[], filters: object) { }
To use it, you have to add an action in Windmill and choose the TypeScript (Deno) script language. If you do not want to use filters, you have to set it to empty by filling in {}.
Using webhook content as input parameters
In order to reference the workflow input (details about the event that triggered the webhook) in a script, use the flow_input variable. For example, flow_input.event.form.hash will reference the hash of a form from a nextcloud Forms event. As it is a JavaScript expression and not a static value, you have to switch the parameter input with the button next to it.

The fields available for each event are listed in the webhook_listeners documentation.
Writing scripts for Nextcloud
Some example scripts are provided on https://hub.windmill.dev/integrations/nextcloud or our Github repository for scripts. If you want to use a function that is not already represented there, you can easily write your own scripts using a skeleton script and our OCS API that provides lots of endpoints. You can check out the available endpoints in the documentation or you can install our OCS API Viewer app and check it out directly in your Nextcloud.
This is a skeleton for a script written in TypeScript (Bun). To use it, you have to add an action in Windmill and choose the correct script language. It includes everything necessary to use the Nextcloud OCS API, the parts marked like $THIS have to be filled in with your data.
import * as wmill from "windmill-client";
import createClient, { type Middleware } from "openapi-fetch";
type Nextcloud = {
baseUrl: string,
password: string,
username: string
};
export async function main(
ncResource: Nextcloud,
userId: string | null = null,
notificationUserId: string,
subject: string,
message: string,
subjectParameters: Array | null = null,
messageParameters: Array | null = null,
useAppApiAuth: boolean = false,
) {
const client = createClient<paths>({ baseUrl: ncResource.baseUrl });
const authMiddleware: Middleware = {
async onRequest({ request, options }) {
// fetch token, if it doesn’t exist
// add Authorization header to every request
request.headers.set("Authorization", `Basic ${btoa(ncResource.username + ':' + ncResource.password)}`);
if (useAppApiAuth) {
request.headers.set("AA-VERSION", ncResource.aa_version,);
request.headers.set("EX-APP-ID", ncResource.app_id,);
request.headers.set("EX-APP-VERSION", ncResource.app_version,);
request.headers.set("AUTHORIZATION-APP-API", btoa(
`${userId || ncResource.username}:${ncResource.password}`,
));
}
return request;
},
};
client.use(authMiddleware);
data = await client.GET("/ocs/v2.php/apps/$APP/$PATH/{$PATHPARAMETER}"", {
params: {
header: {
"OCS-APIRequest": true,
},
query: {
format: "json",
},
path: {
$PATHPARAMETER: "$VALUE",
},
},
body: {
$BODYPARAMETER: $VALUE,
},
});
return data;
}
The endpoint path you have to fill in is provided in the API documentation.
OCS uses two kinds of parameters: Path parameters that are part of the endpoint URL, and body parameters that are transmitted in the request body. In the example script, you can provide both kinds in the function parameters.
Remember to also adapt the HTTP method (GET, POST, PUT etc.) if needed.
The data variable receives the response of the HTTP request, so any data or error messages coming from the Nextcloud instance is available in there.
Authentication parameters
To authenticate against Nextcloud, the integrated Windmill instance uses an authentication method called AppAPI authentication. It provides a resource that you can use for the ncResource parameter of the script. It is available with the JavaScript expression resource('u/$USERID/exapp_resource'). The useAppApiAuth value should be true.

Sharing data between scripts
Each script in a workflow is automatically assigned a letter identifier. In order to reference results from previous steps in your parameters, use the results variable with the id of the step to reference as a sub property. For example, use results.e.submission.answers to use the answers of of a form submission retrieved via the “Get form submission from Nextcloud Forms” script identified with the letter “e”. You can identify the letters in the flow overview diagram.
Example workflow
To start creating your own scripts, you can import this YAML file and adapt it to your own needs. Remember to change
- the trigger event
- the filters
- the script(s) that model your desired task(s)
- the input parameters of the scripts
- the path of the Exapp resource
To import it, use the dropdown button in Windmill next to the "Create new flow" button, choose "Import from YAML" and paste the following content.

summary: Example Workflow
description: ""
value:
modules:
- id: a
value:
lock: |
{
"version": "3",
"remote": {}
}
type: rawscript
content: "export async function main(events: string[], filters: object) { }"
language: deno
input_transforms:
events:
type: static
value:
- OCA\Forms\Events\FormSubmittedEvent
filters:
type: static
value: {}
summary: CORE:LISTEN_TO_EVENT
- id: b
value:
tag: ""
lock: >-
{
"dependencies": {
"openapi-fetch": "latest"
}
}
//bun.lockb
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
type: rawscript
content: >-
import * as wmill from "windmill-client";
import createClient, { type Middleware } from "openapi-fetch";
type Nextcloud = {
baseUrl: string,
password: string,
username: string
};
export async function main(
ncResource: Nextcloud,
userId: string | null = null,
notificationUserId: string,
subject: string,
message: string,
subjectParameters: Array | null = null,
messageParameters: Array | null = null,
useAppApiAuth: boolean = false,
) {
const client = createClient<paths>({ baseUrl: ncResource.baseUrl });
const authMiddleware: Middleware = {
async onRequest({ request, options }) {
// fetch token, if it doesn’t exist
// add Authorization header to every request
request.headers.set("Authorization", `Basic ${btoa(ncResource.username + ':' + ncResource.password)}`);
if (useAppApiAuth) {
request.headers.set("AA-VERSION", ncResource.aa_version,);
request.headers.set("EX-APP-ID", ncResource.app_id,);
request.headers.set("EX-APP-VERSION", ncResource.app_version,);
request.headers.set("AUTHORIZATION-APP-API", btoa(
`${userId || ncResource.username}:${ncResource.password}`,
));
}
return request;
},
};
client.use(authMiddleware);
const {
data, // only present if 2XX response
error, // only present if 4XX or 5XX response
} = await client.POST("/ocs/v2.php/apps/notifications/api/{apiVersion3}/admin_notifications/{userId}", {
params: {
header: {
"OCS-APIRequest": true,
},
query: {
format: "json",
},
path: {
apiVersion3: "v3",
userId: notificationUserId,
},
},
body: {
subject: subject,
message: message,
subjectParameters: subjectParameters,
messageParameters: messageParameters
},
});
return message;
}
language: bun
input_transforms:
userId:
type: static
value: admin
message:
expr: flow_input.event.form.title
type: javascript
subject:
type: static
value: Hi
ncResource:
expr: resource('u/admin/exapp_resource')
type: javascript
useAppApiAuth:
type: static
value: true
messageParameters:
expr: ""
type: javascript
subjectParameters:
type: static
value: null
notificationUserId:
type: static
value: admin
summary: Example OCS call script (Sending a notification)
continue_on_error: false
- id: d
value:
lock: ""
type: rawscript
content: |-
# import wmill
def main(x: str):
print(x)
language: python3
input_transforms:
x:
expr: results.b
type: javascript
summary: "Example: use results of previous script"
schema:
$schema: https://json-schema.org/draft/2020-12/schema
properties: {}
required: []
type: object
Subscriber exclusive content
A Nextcloud Enterprise Subscription provides unlimited access to our knowledge base articles and direct access to Nextcloud engineers.