|
| 1 | +--- |
| 2 | +title: Creating Custom Activities |
| 3 | +description: How to develop, configure, and deploy custom manual or automatic activities for your process flows. |
| 4 | +--- |
| 5 | + |
| 6 | +It is possible to create your own activities that can be used in the editor and then executed on your own server when they are reached in the process. These activities can be either **manual tasks**—made available to users in their worklists for processing—or **automatic activities**, which run immediately without user interaction. |
| 7 | + |
| 8 | +In this walk‑through we build an automatic **ChatGPT** activity. In the editor you will be able to supply a *system prompt* and a *user input*. At runtime the node calls the OpenAI API and writes ChatGPT’s reply back into the workflow. The sample process lets a user enter a topic, asks ChatGPT to explain it, and finally shows the explanation to the user. |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | +## Step 1 – General Details |
| 13 | + |
| 14 | +Open **`Shop → Create Activity`**. The wizard shows all steps you will complete. |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | +In the second screen provide the basic metadata: |
| 19 | + |
| 20 | +1. **Display name** shown in the editor |
| 21 | +2. **Short description** for the shop |
| 22 | +3. **Icon name** (see the catalogue at [Lucide.dev](https://lucide.dev/icons/)) |
| 23 | +4. **Execution mode** – *Manual* (user works on a form your server renders) or *Automatic* (logic runs on your server without user input) |
| 24 | +5. **Detailed description** (Markdown allowed)—use this to document what the node does and how to configure it. |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +## Step 2 – Editor Options |
| 29 | + |
| 30 | +Define which **configuration fields** the editor should show when someone drops your activity onto the canvas. The values entered there are sent to your server at runtime. |
| 31 | + |
| 32 | + |
| 33 | + |
| 34 | +Click **Add new Option** to insert a field. The list below the button lets you configure each option; the preview on the right shows how the panel will appear in the editor. |
| 35 | + |
| 36 | + |
| 37 | + |
| 38 | +Available option types: |
| 39 | + |
| 40 | +| Type | Purpose | |
| 41 | +| --- | --- | |
| 42 | +| **Text** | Static explanatory text shown between fields. | |
| 43 | +| **Input / Textarea** | Single‑line or multi‑line text input. | |
| 44 | +| **Select / Select with custom** | Drop‑down list. You can nest further options that appear depending on the selected value. *Select with custom* additionally lets the editor user type their own value. Tip: use `{availableVariables}` as an option value to let users pick any existing process variable. | |
| 45 | +| **Checkbox** | Boolean switch; nested options can depend on its state. | |
| 46 | +| **Variable Name Input** | Text field where the editor user defines the **target variable** that will receive the node’s output. | |
| 47 | +| **Separator** | Horizontal rule to visually group fields. | |
| 48 | +| **Row** | Container that arranges child options side by side. | |
| 49 | + |
| 50 | +> **Important** Every option needs a *label*—internally the label is used as its key. |
| 51 | +
|
| 52 | +## Step 3 – Set Up Your Server Endpoint |
| 53 | + |
| 54 | +Tell the wizard where the workflow engine should send the webhook when the node runs. |
| 55 | + |
| 56 | + |
| 57 | + |
| 58 | +The next page offers **downloadable code templates** (ZIP) already wired with your chosen options: |
| 59 | + |
| 60 | +- **Automatic mode** – Node.js and Python stubs implementing an API endpoint that responds to the engine. |
| 61 | +- **Manual mode** – A Next.js component that receives the instance data and renders a simple UI. |
| 62 | + |
| 63 | +Make sure you adjust the endpoint URL inside the template if necessary and to implement your business logic. |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +### Payload contract |
| 68 | + |
| 69 | +Your server receives the option values **plus** three technical fields: |
| 70 | + |
| 71 | +| Field | Meaning | |
| 72 | +| --- | --- | |
| 73 | +| `responsePath` | POST your success payload here. | |
| 74 | +| `errorResponsePath` | POST error details here. | |
| 75 | +| `flowElementInstanceId` | Correlates your reply with the running activity. | |
| 76 | + |
| 77 | +💡 Always return **HTTP 200 OK immediately** to avoid workflow timeouts. |
| 78 | + |
| 79 | +### Example – ChatGPT server (Node.js) |
| 80 | + |
| 81 | +```javascript |
| 82 | +const express = require('express'); |
| 83 | +const axios = require('axios'); |
| 84 | +const app = express(); |
| 85 | + |
| 86 | +app.use((req, res, next) => { |
| 87 | + res.header('Access-Control-Allow-Origin', '*'); |
| 88 | + res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); |
| 89 | + res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); |
| 90 | + if (req.method === 'OPTIONS') return res.sendStatus(200); |
| 91 | + next(); |
| 92 | +}); |
| 93 | +app.use(express.json()); |
| 94 | + |
| 95 | +app.post('/call', async (req, res) => { |
| 96 | + // 1 — acknowledge immediately |
| 97 | + res.sendStatus(200); |
| 98 | + |
| 99 | + const { responsePath, errorResponsePath, flowElementInstanceId } = req.body; |
| 100 | + const { openAIAPIKey, prompt, additionalData, outputs } = req.body.data; |
| 101 | + const outputVar = outputs.outputVariableName; |
| 102 | + |
| 103 | + try { |
| 104 | + const openAiResp = await axios.post( |
| 105 | + 'https://api.openai.com/v1/chat/completions', |
| 106 | + { |
| 107 | + model: 'gpt-4o', |
| 108 | + messages: [ |
| 109 | + { role: 'system', content: prompt }, |
| 110 | + { role: 'user', content: additionalData }, |
| 111 | + ], |
| 112 | + }, |
| 113 | + { |
| 114 | + headers: { |
| 115 | + 'Content-Type': 'application/json', |
| 116 | + Authorization: `Bearer ${openAIAPIKey}`, |
| 117 | + }, |
| 118 | + }, |
| 119 | + ); |
| 120 | + |
| 121 | + await axios.post(responsePath, { |
| 122 | + flowElementInstanceId, |
| 123 | + data: { [outputVar]: openAiResp.data.choices[0].message.content }, |
| 124 | + }); |
| 125 | + } catch (err) { |
| 126 | + await axios.post(errorResponsePath, { |
| 127 | + flowElementInstanceId, |
| 128 | + errorMessage: `ChatGPT call failed: ${err.message}`, |
| 129 | + }); |
| 130 | + } |
| 131 | +}); |
| 132 | + |
| 133 | +app.listen(process.env.PORT || 3030, () => |
| 134 | + console.log('ChatGPT node listening …'), |
| 135 | +); |
| 136 | +``` |
| 137 | + |
| 138 | +## Step 4 – Use the Activity |
| 139 | + |
| 140 | +Once saved, the activity appears for everyone under **Shop**. The detail page shows its metadata and an option preview. Click **➕ Add to Editor** to make it available in the **Editor**. Your own created nodes are listed under **My Activities** and your saved nodes for the editor can be managed under **Saved Activities**. |
| 141 | + |
| 142 | + |
| 143 | + |
| 144 | +In the sample workflow below, a previous node stores the user‑entered topic in the variable `{input}`. The ChatGPT node reads this value via the **Additional Data** field. |
| 145 | + |
| 146 | + |
| 147 | + |
| 148 | +Finally, the process shows ChatGPT’s reply (`{chatGPTAnswer}`) to the user in an info‑text task. |
| 149 | + |
| 150 | + |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +By following these steps you can package any custom logic—whether a simple webhook or a complex external service—into reusable activities for your processes. |
| 155 | + |
0 commit comments