Build data flows for the Fuuz Industrial Operations platform (fuuz.com). Trigger on fuuz flow, data flow, flow design, web flow, backend flow, gateway flow, rfc, remote function call, or industrial automation workflows (OEE calculation, warehouse logic, external API integration, telemetry aggregation, production tracking). Covers flow architecture, node configuration (82 node types across 12 categories), state model (payload, context, claims, batches), flow patterns (ETL, reactive, dashboard API, batch fan-out), concurrency protection, naming conventions, and debugging. Cross-references fuuz-graphql for query/mutation syntax, fuuz-jsonata for expressions, fuuz-javascript for script nodes.
Version 1.4 | Last Updated: 2026-02-26
Build comprehensive, production-ready data flows for the Fuuz Industrial Operations platform.
Fuuz supports three flow execution contexts:
Backend Flows (most common) - Server-side processing triggered by schedules or data changes. Use for ETL, calculations, reactive updates, batch processing.
Web Flows - Invoked from front-end applications via request/response pattern. Use for dashboard APIs, screen interactions, real-time queries. Can interact with UI via formDialog, snackbar, searchTable nodes.
Gateway Flows - Run on Fuuz Gateway on-premise. Use for edge device interactions, local printing, device subscriptions.
The workflow state passed between nodes contains:
$ in JSONata)setContext, deep-merged via mergeContext, read via )$state.context$state.claims)$state.batches)The full state object is accessible via the $state binding in all data flow JSONata evaluations. See the fuuz-jsonata skill for the complete JSONata binding reference including $state paths.
Standard Output Port — Most transition nodes have a nextNodes property: { "title": "Output", "type": "array", "format": "port" }. When a node has only this standard port, it is noted as "Standard output port" rather than listed in the properties table.
JSONata Transform — format: "jsonata" indicates a JSONata expression. In JSONata, $ refers to the current scope — at the root level this is the input payload, but inside a nested expression (e.g., $.items.( $.value )) it shifts to the nested object. Use $$ to escape back to the root input payload from within a nested scope. The $state binding provides access to the full workflow state (e.g., $state.context, $state.claims).
Connection — Integration nodes typically have a connectionName or connectionNameTransform property referencing a named integration connection (HTTP, ODBC, FTP, etc.).
Return Errors — returnErrors (boolean): when true, failed requests return { error: { statusCode, message, info } } as payload instead of aborting the flow.
Degree of Parallelism — degreeOfParallelism (integer, default: 10): when payload is an array, how many parallel requests to execute simultaneously.
Combination Strategies — Used by Combine and Collect nodes:
index — results ordered by branch index (default for payload)first (labeled "Last") — uses the last received result (default for context)merge — deep merges all results| Format | Meaning |
|---|---|
jsonata | JSONata expression |
javascript | JavaScript code block |
graphql | GraphQL query/mutation string |
port | Output port connecting to downstream nodes |
sql | SQL query (used in inputProps mode) |
NodeTypes may specify these validation rules:
requireChangedName — node must be renamed from defaultrequireInputNode — must have at least one input connectionrequireOutputNode — must have at least one output connectionrequireWalkthrough — must have a completed walkthrough/noteminimumNoteLength (integer) — minimum character length for the node noteSingle-Purpose Flow Design — Each flow should represent ONE process, not multiple processes triggered by payload variations. Avoid multiple request nodes in a single flow. Flows with multiple entry points are hard to maintain and are better split into smaller, simpler flows.
Data-First Design Philosophy — Fuuz centers on data-first design. Start with the data model, then build flows around it, then design screens. This is a critical distinction for Fuuz development and applies to every project.
Ship Iteratively — Build, test, refine. Avoid building everything perfect upfront.
See the
fuuz-graphqlskill for complete GraphQL query syntax, mutation patterns, filtering, pagination, relationship traversal, and aggregations.
Fuuz provides two GraphQL APIs — specify which in query/mutate nodes via "api": "application" or "api": "system":
See the
fuuz-jsonataskill for complete JSONata syntax, 182+ platform bindings, and expression patterns. See thefuuz-javascriptskill for JavaScript sandbox restrictions, script node types, and code patterns.
Default to JSONata for data transformation, filtering, date/time operations, and query variable construction. Use JavaScript only when JSONata cannot express the logic (complex algorithms, stateful processing, regex complexity, try/catch error handling).
Important: All Fuuz JSONata custom bindings are also accessible in JavaScript transforms. Use the same syntax: $moment(), $schedule(), $numeral() etc.
See the
fuuz-jsonataskill for complete JSONata syntax rules, operator reference, and all platform bindings.
In JSONata, $ refers to the current context which changes in chained operations:
$.data.(
$; /* NOW refers to each element in $.data array */
$$; /* ALWAYS refers to root payload */
);
Rule: When calling chained functions, capture the payload first: $data := $ then use $data, or use $$ for root.
$[].{} produces an array of objects (CORRECT). $.{} collects values into arrays per field (WRONG).
/* CORRECT — [{a:1,b:2}, {a:3,b:4}] */
$[].{ "a": fieldA, "b": fieldB }
/* WRONG — { "a": [1,3], "b": [2,4] } */
$.{ "a": fieldA, "b": fieldB }
Always use $[].{} when iterating over arrays to produce arrays of objects.
Always store durations as milliseconds (Int), NOT ISO 8601 duration strings.
Duration fields in GraphQL responses may come back in mixed formats:
{ text: "PT0S", milliseconds: 0 }"PT0M"[{ text: "...", milliseconds: 0 }]Use this robust parsing function:
function getDurationMs(val) {
if (val === null || val === undefined) return 0;
if (typeof val === 'number') return val;
if (Array.isArray(val)) return getDurationMs(val[0]);
if (typeof val === 'object' && val.milliseconds !== undefined) return val.milliseconds;
if (typeof val === 'string') return $moment.duration(val).asMilliseconds();
return 0;
}
When using Array nodes, downstream mutations may receive undefined if the payload isn't wrapped:
// CORRECT — wrap output for Array node safety
return [$.payload];
// WRONG — may produce undefined in downstream mutations
return $.payload;
See references/common-pitfalls.md for more critical patterns including:
External Flows (10 minute timeout):
Internal Flows (20 minute effective timeout):
Data Changes node functions like topic/webhook triggers - same queue mechanism, just different event source.
CRITICAL: Always provide proper naming and descriptions for every flow generated.
Use the format: {Primary Fuuz Object} {direction/verb} {External Platform} {External Object} {Event Type}
Examples:
Locations From NetSuite Bins — standard integration namingLocations To NetSuite Bins on Data Change — event-driven integration namingWorkOrder Process Defaults From Fuuz Default Product Strategy Processes on Data ChangecalculateOeeHourly, processQualityFailures, getDashboardMetricsNote: Renaming a flow doesn't change its ID. To rename, export contents into an empty flow with the new name.
name field is required, but nodes can also have detailed descriptionsWhen user needs descriptions added:
Identify the trigger (schedule, data change, request), required data sources, transformations needed, and output destination.
Standard pattern: Trigger → Query (get data) → SetContext (store config) → Transform (calculate) → Mutate (write)
IMPORTANT: Always use the Query NODE over $query() JSONata function. The Query node is more performant because it splits transformation and API request into separate steps:
If you must use $query() in JavaScript, remember it returns a promise and requires await.
Common nodes in sequence:
For operations that may approach timeout limits:
Pattern: Master flow queries IDs only → Batch IDs → Broadcast → ExecuteFlow (child) processes each batch
Always wrap critical sections (updating same record from multiple triggers) with mutexLock/mutexUnlock. Use resourceIdTransform to lock on specific record ID.
Best Practice: Create saved versions of repetitive tasks for easier maintenance.
When to create saved transforms:
Saved Transform Types:
Benefits:
Example Usage:
{
"type": "savedTransformV2",
"name": "Calculate OEE Time Boundaries",
"data": {
"transformScriptLanguage": "JavaScript",
"transformId": { "id": "oeeTimeBoundaries", "label": "OEE Time Boundaries" },
"requestTransform": "$",
"responseTransform": "$",
"nextNodes": ["next-node-uuid"]
}
}
For complex visualizations, use Saved Visualizations:
references/visualization-library.md for complete configuration optionsPattern:
Pattern 1: Scheduled ETL
Schedule → JSONata (time window) → SetContext → Query (data) → JavaScript (process) → Mutate (write)
Pattern 2: Data Change Reactive Update
DataChanges → Unless (guard) → SetContext → Query (related) → JavaScript (calculate) → MutexLock → Mutate → MutexUnlock
Pattern 3: Web Flow Dashboard API
Request → JSONata (params) → SetContext → Query → JavaScript (calculate) → JSONata (format + push to screen) → Response
Pattern 4: Batch Processing with Fan-out
Schedule → Query (all records) → JavaScript (batch into groups) → Broadcast → Query (aggregate per batch) → Mutate → Response
Complex systems benefit from separating real-time and batch processing:
| Flow Type | Trigger | Use Case |
|---|---|---|
| Real-time | dataChanges on model Create | Per-value analysis — O(1) per record |
| Scheduled batch | schedule (hourly/daily) | Cross-record analysis — O(n) correlation, aggregation |
| Scheduled projection | schedule (hourly) | Forward-looking calculations using batch results |
Key principle: Real-time flows should be O(1) per incoming record. Any calculation requiring multiple records belongs in a scheduled batch flow.
Store ALL operational parameters in application Settings, not hardcoded in flows.
Query pattern (System API):
query GetSettings {
setting(where: { code: { _in: ["ml.prefix.key1", "ml.prefix.key2"] } }) {
edges { node { id code value } }
}
}
Parse with defaults in setContext (JSONata):
(
$settings := $.setting.edges.node;
$lookup := function($code, $default) {
$val := $settings[code = $code].value;
$val ? $number($val) : $default
};
{ "config": { "key1": $lookup('ml.prefix.key1', 60) } }
)
The $appConfig binding provides access to shared application configuration across all Fuuz components — Data Flows, Saved Scripts, Screens, and Data Model Triggers.
See references/flow-patterns.md for complete examples with node configurations.
Broadcasts are dangerous. The number of requests grows exponentially: (Number of Records) × (Number of Nodes after Broadcast) = Total Requests. 10 parts × 3 nodes = 30 requests. 1000 parts × 3 nodes = 3,000 requests. A script-based approach keeps it linear: 3 requests regardless of record count. In most cases, there is another way to solve the problem. Use script nodes that format requests in batch instead.
Performance Note: Broadcast + Combine — The Broadcast + Combine pattern is acceptable for small datasets but performs poorly over large datasets. When processing large arrays, prefer using nodes with built-in degreeOfParallelism support or handle iteration within a JSONata/JavaScript transform instead.
Always use both Batch Count and Batch Time together. If you only set Count to 5 and only 3 messages arrive, the node will never emit — it waits indefinitely for the remaining 2. Setting a Batch Time ensures the node flushes partial batches after the timeout.
Always add Mutex Unlock on ALL terminating routes. If any branch exits without unlocking, the lock persists until its TTL expires, blocking all other transactions on that resource.
Never loop back into the flow from the catch port — this can create infinite loops. Access error details via $state.lastError.
Use the Throw Error node to provide meaningful context when a flow fails. Build descriptive error messages that include relevant identifiers:
"PO " & $state.context.purchaseOrder.number & " failed creation"
This makes error logs actionable — operators can immediately identify which record failed and why.
$not(false))Calling Plex DS - POs_Get (key:12345)Pulling POs with status of OpenSee the
fuuz-graphqlskill for complete GraphQL API documentation, common query patterns, and API Explorer usage.
IMPORTANT: Flow query/mutate nodes use Relay-style connection syntax with underscore-prefix operators (_eq, _in, _not). This differs from the direct syntax (equals, in, not) shown in the API Explorer and documented in the fuuz-graphql skill.
All flow queries use Relay-style connections:
query GetData($where: ModelWhereInput) {
model(where: $where) {
edges {
node {
id field1 field2
}
}
}
}
Result traversal: $.model.edges.node or $.model.edges[*].node
Mutations require payload arrays:
{ "payload": [{ "create": { "field": "value" } }] } /* Create */
{ "payload": [{ "where": { "id": "x" }, "update": { "field": "value" } }] } /* Update */
Delete Mutations use payload array with individual where wrappers:
mutation DeleteRecords($payload: [ModelNameDeletePayloadInput!]!) {
deleteModelName(payload: $payload) { id }
}
JSONata: { "payload": $.modelName.edges.node.{ "where": { "id": id } } }
Use .{} mapping (no square brackets) — JSONata produces the array naturally from the node set.
Create Mutations wrap each record in "create":
{ "payload": $.arrayOfResults.{ "create": $ } }
Upsert Mutations need where, create, and update:
{ "payload": $.records.{ "where": { "uniqueField": uniqueField }, "create": $, "update": $ } }
No _nin support — use _not with _in: { "_not": { "assetId": { "_in": ["id1"] } } }
where: { id: { _in: [...] } } does NOT work at the mutation level. Use individual where wrappers inside the payload array instead (see Delete Mutations above).
OrderBy always uses "field" and "direction" as explicit keys, always wrapped in an array:
{ "orderBy": [{ "field": "createdAt", "direction": "DESC" }] }
$state.context survives through broadcast fan-out nodes. Safe to rely on context for timestamps, config values, and other shared data across broadcast branches.
Read references/node-catalog.md for complete node types and configurations.
| Category | Count | Description |
|---|---|---|
| Conditionals | 4 | Conditional routing and filtering |
| Context | 3 | Workflow context manipulation |
| Debugging | 2 | Logging and echo for development |
| Device Gateway | 5 | Device function execution and printing |
| Events | 6 | Event sources and pub/sub |
| Flow Control | 10 | Parallel execution, delays, error handling |
| Fuuz | 9 | Platform queries, mutations, documents |
| Integration | 27 | External system connectors |
| Notification | 2 | Push notification send/receive |
| Scripts | 5 | Custom transforms (JSONata, JS, saved) |
| Transformation | 8 | Data format conversion and array operations |
| Validation | 1 | Schema validation |
See the
fuuz-jsonataskill for the complete JSONata binding reference (182+ bindings), evaluation context, and expression patterns. See thefuuz-javascriptskill for JavaScript sandbox restrictions and script node patterns.
The fuuz-jsonata skill's references/ directory contains jsonata-bindings.md (full binding signatures) and time-schedule-bindings.md (schedule/moment functions).
{"plexResponse": $} instead of just $$$state.context values set via setContext nodes DO survive through broadcast fan-out nodesmergeContext when you need data from a query available alongside pipe data from another query. setContext replaces context; mergeContext adds to it.$ or $state.context$moment() for UTC dates, .tz(timezone) for local displayedges.node structure in GraphQL resultsfuuz-javascript skill for complete sandbox restrictions. Key: no .toFixed(), no let/const, no arrow functions, no template literals. Also see common-pitfalls.md.fuuz-javascript skill. Variable scoping is unreliable inside for loops in the Fuuz JS runtime; inline single-use values.setContext replaces context entirely; mergeContext adds to it. Use when you need data from a prior query alongside new pipe data.node-catalog.md - Complete node types with configuration examplesjsonata-bindings.mdfuuz-jsonata skill (fuuz-jsonata/references/jsonata-bindings.md). Load fuuz-jsonata for JSONata binding reference.graphql-patterns.mdfuuz-graphql skill for GraphQL query and mutation patterns.flow-patterns.md - Flow design philosophy, single-purpose principle, hybrid multi-flow design, flow control patterns, complete flow examples with node-by-node configurationstime-schedule-bindings.mdfuuz-jsonata skill (fuuz-jsonata/references/time-schedule-bindings.md). Load fuuz-jsonata for schedule-specific functions.common-pitfalls.md - CRITICAL - Data access patterns, duration handling, JS runtime limitations (.toFixed(), scoping), JSONata error handling, context management, debugging solutionssystem-schema.jsonfuuz-system-schema skill (158 system models, 2,676 fields). Load that skill when you need system model field definitions.system-seeded-values.mdfuuz-platform skill (fuuz-platform/references/system-seeded-values.md). Load fuuz-platform for seeded values.visualization-library.mdfuuz-platform skill (fuuz-platform/references/visualization-library.md). Load fuuz-platform for chart configuration.references/flow-design-patterns-advanced.md — Advanced flow patterns: array iteration rules, duration storage, memory wrapping, pre-population, broadcast optimization, structured logging, validation tracking, OEE calculation patterns, telemetry aggregationreferences/flow-context-reference.md — Flow JSON envelope structure, 21 node types with production examples, dataChanges payload structure, $scheduleGroup() pattern, production JSONata transformsreferences/flow-essentials.md — Quick reference: node categories, webhook URL format and configuration, 5 manufacturing flow patterns (OEE calculation, inventory move webhook, asset lifecycle, fan-out integration, planned schedule generation), timezone handling rules, flow JSON structure, flow documentation standardfuuz-graphql/SKILL.md) — GraphQL query/mutation syntax, API Explorer usage, filtering, pagination, relationship traversal, and aggregations. Consult when building query/mutate nodes or using the API Explorer.fuuz-jsonata/SKILL.md) — JSONata expression language reference with 182+ platform bindings. Consult for transform expressions, syntax rules, and custom binding documentation.fuuz-javascript/SKILL.md) — JavaScript sandbox restrictions and script node patterns. Consult when writing JavaScript transform nodes.fuuz-schema/SKILL.md) — Data model design and Fuuz package generation. Consult when building flows that query or mutate custom data models — the schema skill defines the models, field types, and relationships your flows will interact with.fuuz-screens/SKILL.md) — Screen creation and UI design. Consult when building web flows (Screen type) that support dashboards, HMIs, or custom screen actions via $executeFlow().fuuz-platform/SKILL.md) — Shared platform reference. Consult for connector configurations, device driver details, system seeded values, visualization library, platform glossary, and cross-skill task routing.fuuz-system-schema/SKILL.md) — Complete System API GraphQL schema (158 system models, 2,676 fields). Load when you need exact field names/types for system models.IMPORTANT: Application API schemas are customer/application-specific and NOT included in this skill.
When users need Application API schema information:
System API schema is available in the companion fuuz-system-schema skill — load it when you need system model definitions.
Initial Release - Comprehensive Fuuz data flow development skill
Core Features:
Reference Documentation:
Platform Knowledge:
Maintainer: Fuuz Development Team Distribution: fuuz-flows-v1.skill
Training Data Update - Integrated new standards from TeamChatTrainingDocs and ML Script Training
Flow Design Standards (new top-level section):
Naming Conventions:
{Primary Fuuz Object} {direction/verb} {External Platform} {External Object} {Event Type} formatFlow Control:
JavaScript Runtime:
'use strict'; requirement at top of all JS scriptsGraphQL Patterns:
_in mutation-level limitation warningfield/direction keysExisting sections verified and confirmed current:
Training Data Integration - Incorporated authoritative data-flow-nodes and JSONata training data
New SKILL.md Sections:
$ vs $$ scope, connection, returnErrors, degreeOfParallelism, combination strategies (index, first, merge)jsonata-bindings.md Replaced:
$[].{} vs $.{})~>, transform operator | |, conditional operators)New Reference File:
references/flow-essentials.md — Flow quick reference with node categories, webhook URL format (https://api.{environment}.fuuz.app/webhook/post/{path}), 5 manufacturing patterns (scheduled OEE, inventory move webhook with validation cascade, asset lifecycle tracking, fan-out integration, planned schedule generation), timezone handling rules, flow JSON structure overview, flow documentation standardReference Documentation Update - Baked advanced flow design patterns and flow context reference into skill
New Reference Files:
references/flow-design-patterns-advanced.md — Comprehensive flow design patterns covering array iteration rules, duration storage as milliseconds, memory wrapping for Array nodes, pre-population strategy, broadcast optimization, structured logging, validation tracking, OEE calculation patterns (ISO 22400), telemetry aggregation pipelines, workcenter state management, and quality code handlingreferences/flow-context-reference.md — Complete flow JSON envelope structure, 21 node types with production-extracted examples, dataChanges payload structure (value.before/value.after), $scheduleGroup() pattern, production JSONata transforms, web flow parameter access ($metadata, $components), and full connection graph examples for scheduled OEE, reactive OEE, telemetry aggregation, alarm creation, and dashboard APIsNew Critical Rules Added to SKILL.md:
$[].{} vs $.{} distinction with examplesreturn [$.payload] pattern to prevent undefined in downstream mutationsmergeContextunless guard nodes before mutations: $count($.model.edges.node) = 0. Prevents empty payload errors.