@directus/api 30.0.0 → 31.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +5 -0
- package/dist/auth/drivers/oauth2.js +17 -3
- package/dist/auth/drivers/openid.js +17 -3
- package/dist/controllers/mcp.d.ts +2 -0
- package/dist/controllers/mcp.js +33 -0
- package/dist/controllers/users.js +17 -7
- package/dist/controllers/versions.js +3 -2
- package/dist/database/errors/dialects/mssql.d.ts +1 -1
- package/dist/database/errors/dialects/mssql.js +18 -10
- package/dist/database/migrations/20250813A-add-mcp.d.ts +3 -0
- package/dist/database/migrations/20250813A-add-mcp.js +18 -0
- package/dist/database/run-ast/README.md +46 -0
- package/dist/mcp/define.d.ts +2 -0
- package/dist/mcp/define.js +3 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/schema.d.ts +485 -0
- package/dist/mcp/schema.js +219 -0
- package/dist/mcp/server.d.ts +97 -0
- package/dist/mcp/server.js +310 -0
- package/dist/mcp/tools/assets.d.ts +3 -0
- package/dist/mcp/tools/assets.js +54 -0
- package/dist/mcp/tools/collections.d.ts +84 -0
- package/dist/mcp/tools/collections.js +90 -0
- package/dist/mcp/tools/fields.d.ts +101 -0
- package/dist/mcp/tools/fields.js +157 -0
- package/dist/mcp/tools/files.d.ts +235 -0
- package/dist/mcp/tools/files.js +103 -0
- package/dist/mcp/tools/flows.d.ts +323 -0
- package/dist/mcp/tools/flows.js +85 -0
- package/dist/mcp/tools/folders.d.ts +95 -0
- package/dist/mcp/tools/folders.js +96 -0
- package/dist/mcp/tools/index.d.ts +15 -0
- package/dist/mcp/tools/index.js +29 -0
- package/dist/mcp/tools/items.d.ts +87 -0
- package/dist/mcp/tools/items.js +141 -0
- package/dist/mcp/tools/operations.d.ts +171 -0
- package/dist/mcp/tools/operations.js +77 -0
- package/dist/mcp/tools/prompts/assets.md +8 -0
- package/dist/mcp/tools/prompts/collections.md +336 -0
- package/dist/mcp/tools/prompts/fields.md +521 -0
- package/dist/mcp/tools/prompts/files.md +180 -0
- package/dist/mcp/tools/prompts/flows.md +495 -0
- package/dist/mcp/tools/prompts/folders.md +34 -0
- package/dist/mcp/tools/prompts/index.d.ts +16 -0
- package/dist/mcp/tools/prompts/index.js +19 -0
- package/dist/mcp/tools/prompts/items.md +317 -0
- package/dist/mcp/tools/prompts/operations.md +721 -0
- package/dist/mcp/tools/prompts/relations.md +386 -0
- package/dist/mcp/tools/prompts/schema.md +130 -0
- package/dist/mcp/tools/prompts/system-prompt-description.md +1 -0
- package/dist/mcp/tools/prompts/system-prompt.md +44 -0
- package/dist/mcp/tools/prompts/trigger-flow.md +214 -0
- package/dist/mcp/tools/relations.d.ts +73 -0
- package/dist/mcp/tools/relations.js +93 -0
- package/dist/mcp/tools/schema.d.ts +54 -0
- package/dist/mcp/tools/schema.js +317 -0
- package/dist/mcp/tools/system.d.ts +3 -0
- package/dist/mcp/tools/system.js +22 -0
- package/dist/mcp/tools/trigger-flow.d.ts +8 -0
- package/dist/mcp/tools/trigger-flow.js +48 -0
- package/dist/mcp/transport.d.ts +13 -0
- package/dist/mcp/transport.js +18 -0
- package/dist/mcp/types.d.ts +56 -0
- package/dist/mcp/types.js +1 -0
- package/dist/services/authentication.js +36 -0
- package/dist/services/fields.js +4 -4
- package/dist/services/items.js +14 -4
- package/dist/services/payload.d.ts +7 -3
- package/dist/services/payload.js +26 -12
- package/dist/services/server.js +1 -0
- package/dist/services/tfa.d.ts +1 -1
- package/dist/services/tfa.js +20 -5
- package/dist/services/versions.d.ts +6 -4
- package/dist/services/versions.js +84 -25
- package/dist/types/auth.d.ts +2 -1
- package/dist/utils/versioning/deep-map-with-schema.d.ts +23 -0
- package/dist/utils/versioning/deep-map-with-schema.js +81 -0
- package/dist/utils/versioning/handle-version.d.ts +2 -2
- package/dist/utils/versioning/handle-version.js +47 -43
- package/dist/utils/versioning/split-recursive.d.ts +4 -0
- package/dist/utils/versioning/split-recursive.js +27 -0
- package/package.json +30 -29
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
Perform CRUD on Directus Operations within Flows. Operations are individual actions that execute sequentially in a flow,
|
|
2
|
+
processing and transforming data through the data chain.
|
|
3
|
+
|
|
4
|
+
<key_concepts>
|
|
5
|
+
|
|
6
|
+
- **Operations** are the building blocks of flows
|
|
7
|
+
- Each operation has a unique `key` that identifies it in the data chain
|
|
8
|
+
- Operations connect via `resolve` (success) and `reject` (failure) paths
|
|
9
|
+
- Data from each operation is stored under its key in the data chain </key_concepts>
|
|
10
|
+
|
|
11
|
+
<uuid_vs_keys>
|
|
12
|
+
|
|
13
|
+
## UUID vs Keys - Critical Distinction
|
|
14
|
+
|
|
15
|
+
**UUIDs** (System identifiers): `"abc-123-def-456"`
|
|
16
|
+
|
|
17
|
+
- Use in: `resolve`, `reject`, `flow`, operation `key` field in CRUD
|
|
18
|
+
- Generated automatically when operations are created
|
|
19
|
+
- Required for connecting operations
|
|
20
|
+
|
|
21
|
+
**Keys** (Human-readable names): `"send_email"`, `"check_status"`
|
|
22
|
+
|
|
23
|
+
- Use in: Data chain variables `{{ operation_key }}`
|
|
24
|
+
- You define these when creating operations
|
|
25
|
+
- Used to access operation results in subsequent operations </uuid_vs_keys>
|
|
26
|
+
|
|
27
|
+
<critical_syntax>
|
|
28
|
+
|
|
29
|
+
## Critical Syntax Rules - MEMORIZE THESE
|
|
30
|
+
|
|
31
|
+
**Condition Filters**: Use nested objects, NEVER dot notation
|
|
32
|
+
|
|
33
|
+
- ❌ `"$trigger.payload.status"`
|
|
34
|
+
- ✅ `{"$trigger": {"payload": {"status": {"_eq": "published"}}}}`
|
|
35
|
+
|
|
36
|
+
**Request Headers**: Array of objects, NOT simple objects
|
|
37
|
+
|
|
38
|
+
- ❌ `{"Authorization": "Bearer token"}`
|
|
39
|
+
- ✅ `[{"header": "Authorization", "value": "Bearer token"}]`
|
|
40
|
+
|
|
41
|
+
**Request Body**: Stringified JSON, NOT native objects
|
|
42
|
+
|
|
43
|
+
- ❌ `"body": {"data": "value"}`
|
|
44
|
+
- ✅ `"body": "{\"data\": \"value\"}"`
|
|
45
|
+
|
|
46
|
+
**Data Chain Variables**: Use operation keys, avoid `$last`
|
|
47
|
+
|
|
48
|
+
- ❌ `{{ $last }}` (breaks when reordered)
|
|
49
|
+
- ✅ `{{ operation_key }}` (reliable) </critical_syntax>
|
|
50
|
+
|
|
51
|
+
<required_fields>
|
|
52
|
+
|
|
53
|
+
## Required Fields Summary
|
|
54
|
+
|
|
55
|
+
**All Operations:**
|
|
56
|
+
|
|
57
|
+
- `flow` - UUID of parent flow
|
|
58
|
+
- `key` - Unique operation identifier
|
|
59
|
+
- `type` - Operation type
|
|
60
|
+
- `position_x`, `position_y` - Grid coordinates
|
|
61
|
+
- `resolve`, `reject` - Next operation UUIDs (null initially) </required_fields>
|
|
62
|
+
|
|
63
|
+
<available_operations> Core operations available in Directus:
|
|
64
|
+
|
|
65
|
+
- **condition** - Evaluate filter rules to determine execution path
|
|
66
|
+
- **exec** - Execute custom JavaScript/TypeScript code in sandboxed environment
|
|
67
|
+
- **item-create** - Create items in a collection
|
|
68
|
+
- **item-read** - Retrieve items from a collection
|
|
69
|
+
- **item-update** - Update existing items in a collection
|
|
70
|
+
- **item-delete** - Remove items from a collection
|
|
71
|
+
- **json-web-token** - Sign, verify, or decode JWT tokens
|
|
72
|
+
- **log** - Output debug messages to console
|
|
73
|
+
- **mail** - Send email notifications with templates
|
|
74
|
+
- **notification** - Send in-app notifications to users
|
|
75
|
+
- **request** - Make HTTP requests to external services
|
|
76
|
+
- **sleep** - Delay execution for specified time
|
|
77
|
+
- **throw-error** - Throw custom errors to stop flow execution
|
|
78
|
+
- **transform** - Create custom JSON payloads
|
|
79
|
+
- **trigger** - Execute another flow with iteration modes
|
|
80
|
+
|
|
81
|
+
If user has installed extensions from the Directus Marketplace, there may be more operations available than this. You
|
|
82
|
+
can read existing operations to see if they are using extensions operations. </available_operations>
|
|
83
|
+
|
|
84
|
+
<crud_actions>
|
|
85
|
+
|
|
86
|
+
### `read` - List Flow Operations
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"action": "read",
|
|
91
|
+
"query": {
|
|
92
|
+
"fields": ["id", "name", "key", "type", "flow", "resolve", "reject"],
|
|
93
|
+
"filter": { "flow": { "_eq": "flow-uuid" } },
|
|
94
|
+
"sort": ["position_x", "position_y"]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `create` - Add Operation to Flow
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"action": "create",
|
|
104
|
+
"data": {
|
|
105
|
+
"flow": "flow-uuid", // Required: Flow this operation belongs to
|
|
106
|
+
"key": "notify_user", // Required: Unique key for this operation
|
|
107
|
+
"type": "notification", // Required: Operation type
|
|
108
|
+
"name": "Send Notification", // Optional: Display name
|
|
109
|
+
"position_x": 19, // Required: Grid X position (use 19, 37, 55, 73...)
|
|
110
|
+
"position_y": 1, // Required: Grid Y position (use 1, 19, 37...)
|
|
111
|
+
"options": {
|
|
112
|
+
// Optional: Type-specific configuration (default: {})
|
|
113
|
+
// Configuration based on operation type
|
|
114
|
+
},
|
|
115
|
+
"resolve": null, // Required: UUID of next operation on success (null initially)
|
|
116
|
+
"reject": null // Required: UUID of next operation on failure (null initially)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `update` - Modify Operation
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"action": "update",
|
|
126
|
+
"key": "operation-uuid",
|
|
127
|
+
"data": {
|
|
128
|
+
"options": {
|
|
129
|
+
// Updated configuration
|
|
130
|
+
},
|
|
131
|
+
"resolve": "operation-uuid-here"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `delete` - Remove Operation
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"action": "delete",
|
|
141
|
+
"key": "operation-uuid"
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
</crud_actions>
|
|
146
|
+
|
|
147
|
+
<workflow_creation>
|
|
148
|
+
|
|
149
|
+
## Workflow Creation Process - **ESSENTIAL READING**
|
|
150
|
+
|
|
151
|
+
**⚠️ CRITICAL**: Follow this exact order or operations will fail
|
|
152
|
+
|
|
153
|
+
<workflow_steps>
|
|
154
|
+
|
|
155
|
+
### Step-by-Step Process:
|
|
156
|
+
|
|
157
|
+
1. **Create the flow** using the `flows` tool
|
|
158
|
+
2. **Create all operations** with null resolve/reject initially
|
|
159
|
+
3. **Link operations together** using the UUIDs returned from step 2
|
|
160
|
+
4. **Update the flow** to set the first operation as the entry point
|
|
161
|
+
|
|
162
|
+
### Why This Order Matters:
|
|
163
|
+
|
|
164
|
+
- Operations must exist before they can be referenced in resolve/reject fields
|
|
165
|
+
- UUIDs are only available after operations are created
|
|
166
|
+
- The flow needs at least one operation created before setting its entry point </workflow_steps>
|
|
167
|
+
|
|
168
|
+
<workflow_example>
|
|
169
|
+
|
|
170
|
+
### Complete Workflow Example:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
// Step 1: Create the flow first (using flows tool)
|
|
174
|
+
{
|
|
175
|
+
"action": "create",
|
|
176
|
+
"data": {
|
|
177
|
+
"name": "Email on Post Published",
|
|
178
|
+
"trigger": "event",
|
|
179
|
+
"options": {
|
|
180
|
+
"type": "action",
|
|
181
|
+
"scope": ["items.create"],
|
|
182
|
+
"collections": ["posts"]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Returns: {"id": "flow-uuid-123", ...}
|
|
187
|
+
|
|
188
|
+
// Step 2: Create operations with null connections initially
|
|
189
|
+
{"action": "create", "data": {
|
|
190
|
+
"flow": "flow-uuid-123",
|
|
191
|
+
"key": "check_status",
|
|
192
|
+
"type": "condition",
|
|
193
|
+
"position_x": 19, // First operation position
|
|
194
|
+
"position_y": 1,
|
|
195
|
+
"options": {
|
|
196
|
+
"filter": {
|
|
197
|
+
"$trigger": {
|
|
198
|
+
"payload": {
|
|
199
|
+
"status": {"_eq": "published"}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"resolve": null, // Set to null initially
|
|
205
|
+
"reject": null // Set to null initially
|
|
206
|
+
}}
|
|
207
|
+
// Returns: {"id": "condition-uuid-456", ...}
|
|
208
|
+
|
|
209
|
+
{"action": "create", "data": {
|
|
210
|
+
"flow": "flow-uuid-123",
|
|
211
|
+
"key": "send_email",
|
|
212
|
+
"type": "mail",
|
|
213
|
+
"position_x": 37, // Second operation position
|
|
214
|
+
"position_y": 1,
|
|
215
|
+
"options": {
|
|
216
|
+
"to": ["admin@example.com"],
|
|
217
|
+
"subject": "New post published",
|
|
218
|
+
"body": "Post '{{$trigger.payload.title}}' was published"
|
|
219
|
+
},
|
|
220
|
+
"resolve": null,
|
|
221
|
+
"reject": null
|
|
222
|
+
}}
|
|
223
|
+
// Returns: {"id": "email-uuid-789", ...}
|
|
224
|
+
|
|
225
|
+
// Step 3: Connect operations using UUIDs (NOT keys)
|
|
226
|
+
{"action": "update", "key": "condition-uuid-456", "data": {
|
|
227
|
+
"resolve": "email-uuid-789", // Use UUID from step 2
|
|
228
|
+
"reject": null // No error handling operation
|
|
229
|
+
}}
|
|
230
|
+
|
|
231
|
+
// Step 4: Update flow to set first operation (using flows tool)
|
|
232
|
+
{"action": "update", "key": "flow-uuid-123", "data": {
|
|
233
|
+
"operation": "condition-uuid-456" // First operation UUID
|
|
234
|
+
}}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
</workflow_example> </workflow_creation>
|
|
238
|
+
|
|
239
|
+
<positioning_system>
|
|
240
|
+
|
|
241
|
+
## Grid-Based Positioning - **ALWAYS SET POSITIONS**
|
|
242
|
+
|
|
243
|
+
**Grid Rules:**
|
|
244
|
+
|
|
245
|
+
- Each operation: 14x14 grid units
|
|
246
|
+
- Standard spacing: 18 units (19, 37, 55, 73...)
|
|
247
|
+
- Vertical start: `position_y: 1`
|
|
248
|
+
- Never use (0,0) - operations will overlap
|
|
249
|
+
|
|
250
|
+
**Common Patterns:**
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
// Linear flow
|
|
254
|
+
{"position_x": 19, "position_y": 1} // First
|
|
255
|
+
{"position_x": 37, "position_y": 1} // Second
|
|
256
|
+
{"position_x": 55, "position_y": 1} // Third
|
|
257
|
+
|
|
258
|
+
// Branching (success/error)
|
|
259
|
+
{"position_x": 19, "position_y": 1} // Main
|
|
260
|
+
{"position_x": 37, "position_y": 1} // Success (same row)
|
|
261
|
+
{"position_x": 37, "position_y": 19} // Error (lower row)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
</positioning_system>
|
|
265
|
+
|
|
266
|
+
<operation_examples> <condition> Evaluates filter rules to determine path
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"type": "condition",
|
|
271
|
+
"options": {
|
|
272
|
+
"filter": {
|
|
273
|
+
"$trigger": {
|
|
274
|
+
"payload": {
|
|
275
|
+
"status": { "_eq": "published" }
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
<filter_examples>
|
|
284
|
+
|
|
285
|
+
```json
|
|
286
|
+
// Check if field exists
|
|
287
|
+
{
|
|
288
|
+
"filter": {
|
|
289
|
+
"$trigger": {
|
|
290
|
+
"payload": {
|
|
291
|
+
"website": {"_nnull": true}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Multiple conditions (AND) - CORRECTED SYNTAX
|
|
298
|
+
{
|
|
299
|
+
"filter": {
|
|
300
|
+
"$trigger": {
|
|
301
|
+
"payload": {
|
|
302
|
+
"_and": [
|
|
303
|
+
{"status": {"_eq": "published"}},
|
|
304
|
+
{"featured": {"_eq": true}}
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
</filter_examples> </condition>
|
|
313
|
+
|
|
314
|
+
<item_operations> **Create Items:**
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"type": "item-create",
|
|
319
|
+
"options": {
|
|
320
|
+
"collection": "notifications",
|
|
321
|
+
"permissions": "$trigger",
|
|
322
|
+
"emitEvents": true,
|
|
323
|
+
"payload": {
|
|
324
|
+
"title": "{{ $trigger.payload.title }}",
|
|
325
|
+
"user": "{{ $accountability.user }}"
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Read Items:**
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
{
|
|
335
|
+
"type": "item-read",
|
|
336
|
+
"options": {
|
|
337
|
+
"collection": "products",
|
|
338
|
+
"permissions": "$full",
|
|
339
|
+
"query": {
|
|
340
|
+
"filter": { "status": { "_eq": "active" } },
|
|
341
|
+
"limit": 10
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Update Items:**
|
|
348
|
+
|
|
349
|
+
```json
|
|
350
|
+
{
|
|
351
|
+
"type": "item-update",
|
|
352
|
+
"options": {
|
|
353
|
+
"collection": "orders",
|
|
354
|
+
"permissions": "$trigger",
|
|
355
|
+
"emitEvents": true,
|
|
356
|
+
"key": "{{ $trigger.payload.id }}",
|
|
357
|
+
"payload": { "status": "processed" }
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Delete Items:**
|
|
363
|
+
|
|
364
|
+
```json
|
|
365
|
+
{
|
|
366
|
+
"type": "item-delete",
|
|
367
|
+
"options": {
|
|
368
|
+
"collection": "temp_data",
|
|
369
|
+
"permissions": "$full",
|
|
370
|
+
"key": ["{{ read_items[0].id }}"]
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
</item_operations>
|
|
376
|
+
|
|
377
|
+
<exec>
|
|
378
|
+
Execute custom JavaScript/TypeScript in isolated sandbox
|
|
379
|
+
|
|
380
|
+
**⚠️ SECURITY WARNING**: Scripts run sandboxed with NO file system or network access
|
|
381
|
+
|
|
382
|
+
```json
|
|
383
|
+
{
|
|
384
|
+
"type": "exec",
|
|
385
|
+
"options": {
|
|
386
|
+
"code": "module.exports = async function(data) {\n // Validate input\n if (!data.$trigger.payload.value) {\n throw new Error('Missing required value');\n }\n \n // Process data\n const result = data.$trigger.payload.value * 2;\n \n // Return must be valid JSON\n return {\n result: result,\n processed: true\n };\n}"
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Common Use Cases**: Data transformation, calculations, complex logic, formatting, extracting nested values </exec>
|
|
392
|
+
|
|
393
|
+
<mail>
|
|
394
|
+
Send email notifications with optional templates
|
|
395
|
+
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"type": "mail",
|
|
399
|
+
"options": {
|
|
400
|
+
"to": ["user@example.com", "{{ $trigger.payload.email }}"],
|
|
401
|
+
"subject": "Order Confirmation",
|
|
402
|
+
"type": "markdown", // "markdown" (default), "wysiwyg", or "template"
|
|
403
|
+
"body": "Your order {{ $trigger.payload.order_id }} has been confirmed.",
|
|
404
|
+
"cc": ["cc@example.com"], // Optional
|
|
405
|
+
"bcc": ["bcc@example.com"], // Optional
|
|
406
|
+
"replyTo": ["reply@example.com"] // Optional
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Template Mode:**
|
|
412
|
+
|
|
413
|
+
```json
|
|
414
|
+
{
|
|
415
|
+
"type": "mail",
|
|
416
|
+
"options": {
|
|
417
|
+
"to": ["{{ $trigger.payload.email }}"],
|
|
418
|
+
"subject": "Welcome!",
|
|
419
|
+
"type": "template",
|
|
420
|
+
"template": "welcome-email", // Template name (default: "base")
|
|
421
|
+
"data": {
|
|
422
|
+
"username": "{{ $trigger.payload.name }}",
|
|
423
|
+
"activation_url": "https://example.com/activate/{{ $trigger.payload.token }}"
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
</mail>
|
|
430
|
+
|
|
431
|
+
<notification>
|
|
432
|
+
Send in-app notifications to users
|
|
433
|
+
|
|
434
|
+
```json
|
|
435
|
+
{
|
|
436
|
+
"type": "notification",
|
|
437
|
+
"options": {
|
|
438
|
+
"recipient": ["{{ $accountability.user }}"], // User ID(s) to notify
|
|
439
|
+
"subject": "Task Complete",
|
|
440
|
+
"message": "Your export is ready for download",
|
|
441
|
+
"permissions": "$trigger",
|
|
442
|
+
"collection": "exports", // Optional: Related collection
|
|
443
|
+
"item": "{{ create_export.id }}" // Optional: Related item ID
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
</notification>
|
|
449
|
+
|
|
450
|
+
<request>
|
|
451
|
+
Make HTTP requests
|
|
452
|
+
|
|
453
|
+
```json
|
|
454
|
+
{
|
|
455
|
+
"type": "request",
|
|
456
|
+
"options": {
|
|
457
|
+
"method": "POST",
|
|
458
|
+
"url": "https://api.example.com/webhook",
|
|
459
|
+
"headers": [
|
|
460
|
+
{
|
|
461
|
+
"header": "Authorization",
|
|
462
|
+
"value": "Bearer {{ $env.API_TOKEN }}"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"header": "Content-Type",
|
|
466
|
+
"value": "application/json"
|
|
467
|
+
}
|
|
468
|
+
],
|
|
469
|
+
"body": "{\"data\": \"{{ process_data }}\", \"timestamp\": \"{{ $trigger.timestamp }}\"}"
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Real Example (Netlify Deploy Hook)**:
|
|
475
|
+
|
|
476
|
+
```json
|
|
477
|
+
{
|
|
478
|
+
"type": "request",
|
|
479
|
+
"options": {
|
|
480
|
+
"method": "POST",
|
|
481
|
+
"url": "https://api.netlify.com/build_hooks/your-hook-id",
|
|
482
|
+
"headers": [
|
|
483
|
+
{
|
|
484
|
+
"header": "User-Agent",
|
|
485
|
+
"value": "Directus-Flow/1.0"
|
|
486
|
+
}
|
|
487
|
+
],
|
|
488
|
+
"body": "{\"trigger\": \"content_updated\", \"item_id\": \"{{ $trigger.payload.id }}\"}"
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
</request>
|
|
494
|
+
|
|
495
|
+
<json_web_token> Sign, verify, or decode JWT tokens - **CONSOLIDATED EXAMPLE**
|
|
496
|
+
|
|
497
|
+
```json
|
|
498
|
+
{
|
|
499
|
+
"type": "json-web-token",
|
|
500
|
+
"options": {
|
|
501
|
+
"operation": "sign", // "sign", "verify", or "decode"
|
|
502
|
+
|
|
503
|
+
// For SIGN operations:
|
|
504
|
+
"payload": {
|
|
505
|
+
"userId": "{{ $trigger.payload.user }}",
|
|
506
|
+
"role": "{{ $trigger.payload.role }}"
|
|
507
|
+
},
|
|
508
|
+
"secret": "{{ $env.JWT_SECRET }}",
|
|
509
|
+
"options": {
|
|
510
|
+
"expiresIn": "1h",
|
|
511
|
+
"algorithm": "HS256"
|
|
512
|
+
},
|
|
513
|
+
|
|
514
|
+
// For VERIFY/DECODE operations:
|
|
515
|
+
"token": "{{ $trigger.payload.token }}"
|
|
516
|
+
// "secret": "{{ $env.JWT_SECRET }}", // Required for verify, not for decode
|
|
517
|
+
// "options": {"algorithms": ["HS256"]}, // For verify
|
|
518
|
+
// "options": {"complete": true} // For decode
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
</json_web_token>
|
|
524
|
+
|
|
525
|
+
<other_operations> **Transform JSON:**
|
|
526
|
+
|
|
527
|
+
```json
|
|
528
|
+
{
|
|
529
|
+
"type": "transform",
|
|
530
|
+
"options": {
|
|
531
|
+
"json": {
|
|
532
|
+
"combined": {
|
|
533
|
+
"user": "{{ $accountability.user }}",
|
|
534
|
+
"items": "{{ read_items }}",
|
|
535
|
+
"timestamp": "{{ $trigger.timestamp }}"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Trigger Flow:**
|
|
543
|
+
|
|
544
|
+
```json
|
|
545
|
+
{
|
|
546
|
+
"type": "trigger",
|
|
547
|
+
"options": {
|
|
548
|
+
"flow": "other-flow-uuid",
|
|
549
|
+
"payload": { "data": "{{ transform_result }}" },
|
|
550
|
+
"iterationMode": "parallel", // "parallel", "serial", "batch"
|
|
551
|
+
"batchSize": 10 // Only for batch mode
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
**Sleep:**
|
|
557
|
+
|
|
558
|
+
```json
|
|
559
|
+
{
|
|
560
|
+
"type": "sleep",
|
|
561
|
+
"options": { "milliseconds": 5000 }
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Log:**
|
|
566
|
+
|
|
567
|
+
```json
|
|
568
|
+
{
|
|
569
|
+
"type": "log",
|
|
570
|
+
"options": { "message": "Processing item: {{ $trigger.payload.id }}" }
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Throw Error:**
|
|
575
|
+
|
|
576
|
+
```json
|
|
577
|
+
{
|
|
578
|
+
"type": "throw-error",
|
|
579
|
+
"options": {
|
|
580
|
+
"code": "CUSTOM_ERROR",
|
|
581
|
+
"status": "400",
|
|
582
|
+
"message": "Invalid data: {{ $trigger.payload.error_details }}"
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
</other_operations> </operation_examples>
|
|
588
|
+
|
|
589
|
+
<data_chain_variables> Use `{{ variable }}` syntax to access data:
|
|
590
|
+
|
|
591
|
+
- `{{ $trigger.payload }}` - Trigger data
|
|
592
|
+
- `{{ $accountability.user }}` - User context
|
|
593
|
+
- `{{ operation_key }}` - Result from specific operation (recommended)
|
|
594
|
+
- `{{ operation_key.field }}` - Specific field from operation result
|
|
595
|
+
|
|
596
|
+
**⚠️ Avoid `$last`:** While `{{ $last }}` references the previous operation's result, avoid using it in production
|
|
597
|
+
flows. If you reorder operations, `$last` will reference a different operation, potentially breaking your flow. Always
|
|
598
|
+
use specific operation keys like `{{ operation_key }}` for reliable, maintainable flows. </data_chain_variables>
|
|
599
|
+
|
|
600
|
+
<permission_options> For operations that support permissions:
|
|
601
|
+
|
|
602
|
+
- `$trigger` - Use permissions from the triggering context (default)
|
|
603
|
+
- `$public` - Use public role permissions
|
|
604
|
+
- `$full` - Use full system permissions
|
|
605
|
+
- `role-uuid` - Use specific role's permissions </permission_options>
|
|
606
|
+
|
|
607
|
+
<real_world_patterns> <data_processing_pipeline>
|
|
608
|
+
|
|
609
|
+
### Data Processing Pipeline
|
|
610
|
+
|
|
611
|
+
Read → Transform → Update pattern:
|
|
612
|
+
|
|
613
|
+
```json
|
|
614
|
+
// 1. Read with relations
|
|
615
|
+
{
|
|
616
|
+
"flow": "flow-uuid", "key": "invoice", "type": "item-read",
|
|
617
|
+
"position_x": 19, "position_y": 1,
|
|
618
|
+
"options": {
|
|
619
|
+
"collection": "os_invoices",
|
|
620
|
+
"key": ["{{$trigger.payload.invoice}}"],
|
|
621
|
+
"query": {"fields": ["*", "line_items.*", "payments.*"]}
|
|
622
|
+
},
|
|
623
|
+
"resolve": "calc-operation-uuid"
|
|
624
|
+
}
|
|
625
|
+
// 2. Calculate totals
|
|
626
|
+
{
|
|
627
|
+
"flow": "flow-uuid", "key": "calculations", "type": "exec",
|
|
628
|
+
"position_x": 37, "position_y": 1,
|
|
629
|
+
"options": {
|
|
630
|
+
"code": "module.exports = async function(data) {\n const invoice = data.invoice;\n const subtotal = invoice.line_items.reduce((sum, item) => sum + (item.price * item.quantity), 0);\n const tax = subtotal * 0.08;\n return { subtotal, tax, total: subtotal + tax };\n}"
|
|
631
|
+
},
|
|
632
|
+
"resolve": "update-operation-uuid"
|
|
633
|
+
}
|
|
634
|
+
// 3. Update with results
|
|
635
|
+
{
|
|
636
|
+
"flow": "flow-uuid", "key": "update_invoice", "type": "item-update",
|
|
637
|
+
"position_x": 55, "position_y": 1,
|
|
638
|
+
"options": {
|
|
639
|
+
"collection": "os_invoices",
|
|
640
|
+
"payload": "{{calculations}}",
|
|
641
|
+
"key": ["{{$trigger.payload.invoice}}"]
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
</data_processing_pipeline>
|
|
647
|
+
|
|
648
|
+
<error_handling_branching>
|
|
649
|
+
|
|
650
|
+
### Error Handling with Branching
|
|
651
|
+
|
|
652
|
+
```json
|
|
653
|
+
// Main operation with error handling
|
|
654
|
+
{
|
|
655
|
+
"flow": "flow-uuid", "key": "main_operation", "type": "request",
|
|
656
|
+
"position_x": 19, "position_y": 1,
|
|
657
|
+
"resolve": "success-operation-uuid",
|
|
658
|
+
"reject": "error-operation-uuid"
|
|
659
|
+
}
|
|
660
|
+
// Success path
|
|
661
|
+
{
|
|
662
|
+
"flow": "flow-uuid", "key": "success_notification", "type": "notification",
|
|
663
|
+
"position_x": 37, "position_y": 1
|
|
664
|
+
}
|
|
665
|
+
// Error path (lower row)
|
|
666
|
+
{
|
|
667
|
+
"flow": "flow-uuid", "key": "error_log", "type": "log",
|
|
668
|
+
"position_x": 37, "position_y": 19
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
</error_handling_branching> </real_world_patterns>
|
|
673
|
+
|
|
674
|
+
<common_mistakes>
|
|
675
|
+
|
|
676
|
+
1. **DO NOT** create operations without a flow - create flow first
|
|
677
|
+
2. **DO NOT** use operation keys in resolve/reject - use UUIDs (see <workflow_example> above)
|
|
678
|
+
3. **DO NOT** try to reference operations that do not exist yet
|
|
679
|
+
4. **DO NOT** use duplicate keys within the same flow
|
|
680
|
+
5. **DO NOT** create circular references in resolve/reject paths
|
|
681
|
+
6. **DO NOT** forget to handle both success and failure paths
|
|
682
|
+
7. **DO NOT** pass stringified JSON - use native objects (except request body)
|
|
683
|
+
8. **DO NOT** leave operations at default position (0,0) - see <positioning_system> above
|
|
684
|
+
9. **DO NOT** use dot notation in condition filters - see <critical_syntax> above
|
|
685
|
+
10. **DO NOT** use wrong format for request operations - see <critical_syntax> above </common_mistakes>
|
|
686
|
+
|
|
687
|
+
<troubleshooting>
|
|
688
|
+
<invalid_foreign_key>
|
|
689
|
+
### "Invalid foreign key" Errors
|
|
690
|
+
|
|
691
|
+
This typically means you're trying to reference an operation that doesn't exist:
|
|
692
|
+
|
|
693
|
+
- Verify the operation UUID exists by reading operations for the flow
|
|
694
|
+
- Check that you're using UUIDs (36 characters) not keys (short names)
|
|
695
|
+
- Ensure operations are created before being referenced </invalid_foreign_key>
|
|
696
|
+
|
|
697
|
+
<operation_not_executing>
|
|
698
|
+
|
|
699
|
+
### Operation Not Executing
|
|
700
|
+
|
|
701
|
+
- Check the resolve/reject chain for breaks
|
|
702
|
+
- Verify the first operation is set as the flow's `operation` field
|
|
703
|
+
- Confirm all required operation options are provided </operation_not_executing>
|
|
704
|
+
|
|
705
|
+
<overlapping_operations>
|
|
706
|
+
|
|
707
|
+
### Overlapping Operations in Visual Editor
|
|
708
|
+
|
|
709
|
+
If operations appear stacked at (0,0) in the flow editor:
|
|
710
|
+
|
|
711
|
+
```json
|
|
712
|
+
// Fix by updating each operation's position
|
|
713
|
+
{"action": "update", "key": "operation-uuid", "data": {
|
|
714
|
+
"position_x": 19, "position_y": 1
|
|
715
|
+
}}
|
|
716
|
+
{"action": "update", "key": "other-operation-uuid", "data": {
|
|
717
|
+
"position_x": 37, "position_y": 1
|
|
718
|
+
}}
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
</overlapping_operations> </troubleshooting>
|