All Categories
  • 1st Steps
  • Authentication
  • Branding
  • Changelogs
  • Collaboration
  • Compliance
  • Customization
  • Desktop Client
  • External Storage
  • Frequently Asked Questions
  • Installation
  • Migrations
  • Mobile Clients
  • Nextcloud Context Chat
  • Nextcloud Flow (Windmill integration)
  • Nextcloud Talk
  • Operations
  • Partner Products
  • Roundcubemail
  • Scalability
  • Security
  • 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.

    Switching the input format to JavaScript expressions

    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.

    Adding the authentication resource to a script

    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.

    How to import a flow

    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