Solution Engineer Technical Guide
Build on Fuuz like you built it
Understanding the stack under Fuuz — MongoDB, GraphQL APIs, the Data Flow Engine, Fuuz JSONata bindings, and a deploy-anywhere infrastructure — isn't background knowledge. It's how you write better screens, faster flows, cleaner integrations, and diagnose problems in minutes instead of hours.
MongoDB GraphQL APIs Fuuz JSONata Deploy-Anywhere Infrastructure WebSockets
THE STACK
What's Running Under
Everything You Build
Every screen, data flow, integration, and data model you create in Fuuz runs on this stack. Knowing how each layer works and how they talk to each other changes how you build and how fast you debug.
Client Layer Screen Designer · Mobile · External Apps · IoT Devices
Data Flow Engine Low-code / Pro-code workflow engine · Web, Backend & Gateway flows
Transformation Layer Fuuz JSONata Bindings · JavaScript · $state · $components · $metadata
GraphQL APIs Multiple typed APIs · Schema-validated · Real-time via separate WebSocket service
MongoDB Document Store · JSON-native · Multi-tenant · Indexed collections
Deploy-Anywhere Infrastructure Auto-scale · CDN · IAM · High availability · Managed operations
The Builder's Insight
When something isn't working, the stack gives you a clear isolation strategy. Data wrong? Check the document shape via a GraphQL query. Query returning unexpected results? Check predicates and filters. Transform producing bad output? Check the JSONata or JS expression in the transform console. Screen not rendering? Inspect the element binding. Each layer has a distinct responsibility and a distinct debugging approach.
Real-time: WebSockets, Not GraphQL Subscriptions
Real-time updates in Fuuz — live screen refresh, push events, shop floor alerts — are delivered via a dedicated WebSocket subscription service, separate from the GraphQL APIs. The end result is the same (live data without polling), but it is a distinct service, not a GraphQL subscription.
MongoDB is your data layer
Documents, not rows. Schema lives in the Fuuz data model designer. Document structure determines how simple or complex your queries, flows, and bindings will be.
GraphQL APIs are your data contract
Multiple typed APIs serve different parts of the platform. Filtering and aggregation happen here via predicates and _aggregate queries — not in JSONata transforms.
Data Flow Engine is your workflow layer
The low-code / pro-code engine that orchestrates logic — API calls, transforms, conditionals, error handling, integrations. Sits above GraphQL in the stack.
Fuuz JSONata is your expression language
Fuuz extends standard JSONata with platform-specific bindings: $components, $metadata, $state, join functions, and more. Standard JSONata is just the foundation.
BSON document reads / writes
GraphQL queries / mutations
typed queries / mutations / predicates
managed infrastructure
WebSocket events (real-time)
JSONata / JavaScript transforms
MONGODB
Think in Documents, Not Tables
The biggest conceptual shift for engineers from SQL-backed environments. MongoDB stores JSON documents, not rows. How you design documents affects query performance, screen binding complexity, and flow logic.
| Concept | SQL World | MongoDB / Fuuz |
|---|---|---|
| Data unit | Row in a table | JSON document in a collection |
| Relationships | Foreign keys + JOINs | Nested sub-documents or ID references |
| Schema changes | ALTER TABLE + migration + risk | Add field in schema designer → live instantly, zero downtime |
| Optional data | Nullable column on every row | Field simply absent if not applicable |
| Variable shapes | EAV tables or wide nullable rows | Each document carries its own field set |
| Filtering / aggregation | SQL WHERE / GROUP BY | GraphQL predicates and _aggregate queries at the API layer |
| Record ID | Primary key (varies) | Always id in Fuuz GraphQL — no underscore prefix |
On Embedding: Conceptually Sound, Practically Evolving
The embed-vs-reference pattern is the right mental model for document databases. In Fuuz, full embedded type support is actively in development. Apply the principles in your data model design now — as platform support deepens, models designed with this thinking will be well-positioned. Discuss with your team lead which patterns are best supported in your current release.
The #1 Mistake from SQL Backgrounds
Over-normalizing the data model. Creating a separate Fuuz model for every sub-entity because "that's how you do it in SQL." Before splitting, ask: will this data ever be queried independently, or does it always travel with its parent? If it always travels with its parent — it's a candidate for embedding. Fewer models often means simpler queries, cleaner flows, and easier screen bindings.
GRAPHQL APIs
GraphQL: Where
Filtering and Fetching Live
Fuuz exposes multiple GraphQL APIs. This is where you declare what data you want, apply filters and predicates, and run aggregations. Filtering and grouping happen here at the API layer — not in JSONata transforms.
Fuuz GraphQL Conventions — Know These Before You Build
Connection pattern: All results are wrapped in edges { node { ... } }. Your data lives on node.
Sorting: Use orderBy: [{ field: "fieldName", direction: "asc" }] — not sort.
Aggregation: Place the group field and _aggregate { id { count } } together inside edges.node — there is no separate groupBy argument.
Record ID: Always id — not _id.
Filtering: Use where variables — never fetch everything and filter in JSONata.
Mutations write data
Creates, updates, and deletes all go through GraphQL mutations. Always return id in the mutation selection set to confirm the write and enable chained operations.
Declare exactly what you need
Shape the query to match exactly what the screen or flow needs. No over-fetching a 40-field record to display 3 fields on a table row.
Filter via predicates
GraphQL predicates are your WHERE clause. Filter by field values, date ranges, and relationships at the API layer before data ever reaches your screen or flow.
Traverse relationships in one call
Walk from Work Center → Work Orders → Operations in a single query. One roundtrip returns the full graph — no chained REST-style calls.
Aggregate at the query layer
Use _aggregate queries for counts, sums, and grouping. Runs server-side — far more efficient than fetching all records and counting in JSONata.
Schema-validated in real time
The GraphQL input in Fuuz autocompletes and validates against the schema as you type. Field name errors and type mismatches are caught immediately, before runtime.
API EXPLORER & QUERY BUILDER
Learn GraphQL the
Fuuz Way — No Memorization Required
The fastest way to learn Fuuz GraphQL isn't reading docs — it's using the built-in API Explorer and Query Builder. These no-code tools generate correct GraphQL for you in real time, with full field descriptions, type information, and variable guidance at every step.
Relationships — one click away
Any related model (campaign, batchType, qualityStatus) appears as an expandable node in the Query Builder. Click it to include related fields — no JOIN syntax required. The builder handles the nested structure automatically.
Where to Find It
Navigate to System → Business Intelligence → API Explorer in any Fuuz tenant. The API Explorer gives you a live GraphQL environment with the Query Builder tool accessible via the Tools menu at the top of the editor. Everything here runs against your actual tenant schema in real time.
Query Builder — Visual query construction
Open via Tools in the API Explorer. Pick the model you want to query, check the fields you want returned, and the Query Builder writes the GraphQL for you in real time. No syntax to memorize — just click.
Full field descriptions built in
Click the pencil icon next to any field to see its full description, data type, and any field-level arguments available — rounding options for floats, format options for durations, and more. The schema documents itself.
Live GraphQL code view
Click the </> GraphQL Code button in the Query Builder to see the exact query being written as you select fields. Copy it directly into a screen binding or flow node — already correctly structured.
Variable explorer
Click the $ button in the Query Builder to see all available variables for a query — $where, $orderBy, $first, $after — with their exact types. No guessing at variable names or shapes.
Baked-in field arguments reduce scripting
Different field types expose built-in transformation arguments. Duration fields offer text (ISO8601) or milliseconds format options. Float fields offer rounding direction (Up, Nearest, Down). These eliminate JSONata transforms for common formatting needs.
The Recommended Learning Path for GraphQL in Fuuz
Step 1: Open API Explorer → Tools → Query Builder.
Step 2: Select your model and check the fields you need.
Step 3: Click the pencil on any field to read its description and available arguments.
Step 4: Click $ to see available variables and their types.
Step 5: Click </> GraphQL Code to view the generated query.
Step 6: Copy the query into your screen binding or flow node and test it in the Explorer first.
Do this a dozen times across different models and you will have internalized Fuuz GraphQL faster than any documentation could teach it.
Field Arguments Reduce Scripting
Before reaching for a JSONata transform to format a value, check whether the field already has a built-in argument for it. Duration fields can return ISO8601 text or milliseconds. Float fields can round up, nearest, or down. These are configured directly in the Query Builder — click the pencil icon on the field to see what arguments are available for any given field type.
DATA FLOW ENGINE
The Low-code / Pro-code
Workflow Engine
The Fuuz Data Flow Engine sits above the GraphQL layer — it orchestrates logic, calls APIs, handles errors, and applies transforms. Think of it as your application's business logic layer, built visually with the option to drop into JavaScript for complex operations.
Web Flows
Triggered by user interactions on screens. Also serve as Remote Function Calls (RFC) — callable from any external system. Web Flows with screen context have full access to $components and all screen elements directly within the flow engine. They are also MCP tools, enabling agentic workflow integration.
Backend Flows
Server-side logic triggered by events, schedules, or API calls. Used for integration logic, data processing, ERP write-backs, and operations that should not run on the client.
Gateway Flows
Expose Fuuz logic to external systems as callable endpoints. Used when external systems need to push data in or trigger Fuuz operations programmatically.
Integration Without Rip-and-Replace
Integrating external systems with Fuuz gives you all the benefits of the platform — flows, GraphQL queries, MCP tools, and agentic workflows — without replacing existing systems. Interact with external data where it lives while orchestrating it through Fuuz
Data Flows are MCP Tools & Agentic Workflow Builders
Every Fuuz Data Flow is also an MCP (Model Context Protocol) tool. This means flows can be orchestrated by AI agents, and Integration nodes can be used to build deterministic agentic workflows for any business process or integration scenario — without any additional tooling or infrastructure.
Fuuz is the Only Enterprise Platform with Self-Documenting APIs
When you create or extend a data model in Fuuz, the GraphQL APIs are automatically generated and updated — no separate API development step required. Save any query or mutation from the Explorer and it immediately appears in your external API list. Create topics and webhooks for real-time event-driven integrations. Web Flows are RFC endpoints callable from any external system.
Pub/Sub, Topics & Webhooks
Fuuz supports Pub/Sub messaging — create your own topics and web hooks for real-time data exchange between systems. This enables event-driven integrations without polling, and powers live data exchange across Fuuz and external platforms.
Saved Queries & Mutations
Any GraphQL query or mutation can be saved directly from the API Explorer — instantly adding it to your external API list. This is how Fuuz generates self-documenting APIs automatically from your data models. Create your own APIs without writing any server code.
Stack Position: DFE is Above GraphQL
The Data Flow Engine sits above the GraphQL API layer. Flows call GraphQL APIs to read and write data, then use the Transformation Layer to process results. Understanding this ordering matters when tracing what triggered what during a debug session.
Debugging Flows: Use the Console and Flow Logs
When a flow isn't behaving as expected, your primary tools are the Flow Console and execution logs. Every transform node exposes its input and output in the console — inspect the actual data at each step without adding separate debug nodes. Check the console of the relevant transform section before assuming the problem is upstream.
FUUZ JSONATA BINDINGS
Not Just JSONata —
Fuuz JSONata
Standard JSONata is the foundation, but Fuuz extends it substantially with platform-specific bindings. These give you access to screen state, user identity, flow context, join operations, and more. When you're building in Fuuz, you're using Fuuz JSONata — significantly more powerful than the open-source baseline.
Two Execution Contexts — Know Which One You're In
Screen expressions run client-side. You have access to $components, $metadata, $card, data, and $$.
Data Flow expressions run server-side. You have access to $state, $state.context, and $state.claims. Exception: in Web Flows that run with screen context, you also have access to screen elements directly within the flow engine — $components and related screen bindings are available there.
Fuuz JSONata includes 200+ custom bindings built specifically for the platform, with more released regularly. Standard open-source JSONata is just the starting point.
$components- Interact with Screen Element
| Expression | What It Does |
|---|---|
$components.Form1.fn.save() |
Save the form |
$components.Form1.fn.setValue("field", val) |
Set a single field value programmatically |
$components.Form1.formState.dirty |
true when unsaved changes exist |
$components.Form1.formState.valid |
true when all validations pass |
$components.Form1.data.fieldName |
Read a field value directly |
$components.Table1.fn.search() |
Trigger table search / refresh |
$components.Table1.selectedRows |
Array of all selected rows |
$components.Table1.selectedRows[0].id |
First selected row's ID |
$components.Screen.fn.setContextValue("k", v) |
Set a single screen context key |
$components.Screen.fn.mergeContext({...}) |
Shallow-merge values into screen context |
$components.Screen.context.myKey |
Read a screen context value |
$metadata — URL, User, Tenant
| Expression | What It Does |
|---|---|
$metadata.urlParameters.id |
URL path parameter (e.g. /record/:id) |
$metadata.querystring.tab |
Query string parameter (?tab=details) |
$metadata.user.id |
Logged-in user ID |
$metadata.user.email |
Logged-in user email |
$metadata.user.roles |
Array of user roles |
$metadata.tenant.id |
Current tenant ID |
$metadata.tenant.name |
Current tenant name |
$metadata.settings.timezone |
User timezone |
$metadata.settings.dateFormat |
User date format string |
$state — Data Flow Context
| Expression | What It Does |
|---|---|
$ |
Current node payload — shorthand for $state.payload |
$state.payload |
Full current node input payload |
$state.context |
Shared flow context (set via Set/Merge Context nodes) |
$state.context.myKey |
Read a specific context value |
$state.claims.userId |
Triggering user ID from auth |
$state.claims.tenantId |
Tenant ID from auth claims |
$state.claims.roles |
User roles from auth claims |
$state.metadata.flowId |
Current flow ID |
$state.lastError |
Error from last caught error node |
data, $card & $$ — Element-level Bindings
| Expression | What It Does |
|---|---|
data.fieldName |
Read a field value inside a Form element |
data.active ? "Yes" : "No" |
Ternary on a boolean form field |
$card.data.id |
Card record ID — use inside Cards elements (not data.id) |
$card.data.status |
Card record field value |
$$.newValue |
New value in an onChange handler |
$$.oldValue |
Previous value in an onChange handler |
$isNotNilOrEmpty($$.newValue) and $not($$.newValue = $$.oldValue) |
Guard: only fire when non-empty and actually changed |
Fuuz Join Functions & Utility Bindings
Critical JSONata Syntax Gotchas
| Gotcha | Correct Usage |
|---|---|
No ! operator |
Use $not(expr) instead of !expr |
String concat is & |
"a" & " " & "b" — not + |
Equality is = |
x = "val" — not == or === |
| Membership check | x in [1,2,3] — not .includes() |
No null coalescing ?? |
$exists(val) ? val : "default" |
| Cards vs Form data | Inside Cards: $card.data.x — not data.x |
Context is $ |
$ = entire current input. Confirm shape in the console before writing transforms. |
MENTAL MODEL
Coming from SQL?
Reframe These 5 Things
These are the specific mental model shifts that trip up experienced engineers when they first build on Fuuz. These are the closest useful parallels — not a 1:1 mapping, the platforms are genuinely different.
| SQL / Relational Normalize everything. Every sub-entity gets its own table. Reconstruct the object at query time with JOINs. | → | MongoDB / Fuuz Design for your access pattern. Data that always travels together belongs together. Think in objects first, storage second. |
| SQL / Relational Schema changes are risky: ALTER TABLE, migration scripts, deployment windows, potential table locks. | → | MongoDB / Fuuz Add a field in the Fuuz schema designer — live instantly, zero downtime. Schema field changes are low-risk, fast tasks. |
| Embedded Documents (NoSQL) Full embedded document support is a common expectation from MongoDB backgrounds. | → | Fuuz JSON Schema & JSON Objects Fuuz supports JSON schema and JSON objects within data models. Full embedded document support is on the roadmap. Enumerations are handled by creating joins between models — a clean, queryable pattern. |
| SQL / Legacy ERPs You need a database diagram and extensive documentation just to know where to start building a query. | → | Fuuz Application Graph + Query Builder Go straight to the Query Builder — all relationships are exposed by clicking. Use the Application Graph to see every relationship between data models, screens, and flows in a single visual knowledge graph. Fuuz is the only platform on earth with this feature. |
| SQL WHERE / GROUP BY Filter and aggregate in the database query engine using SQL clauses. | → |
GraphQL predicates / edges.node._aggregate
Filter via where variables. Group and aggregate using edges.node.fieldName + _aggregate { id { count } }. Sort using orderBy: [{ field, direction }]. JSONata reshapes — it does not filter.
|
| SQL JOINs Combine data from multiple tables using INNER JOIN, LEFT JOIN, computed by the DB engine. | → | Fuuz JSONata $innerJoin / $leftOuterJoin Fuuz provides join functions to combine result sets from multiple GraphQL queries in a flow transform. |
| On-prem / Hosted SQL Infrastructure is your team's problem. Backups, upgrades, scale events, DR planning — all on you. | → | Deploy-Anywhere Infrastructure / Fuuz All infrastructure is managed. Focus entirely on application delivery. Application performance on large datasets is still your design responsibility. |
APPLICATION GRAPH
The Knowledge Graph
No Other Platform Has
The Fuuz Application Designer includes an Application Graph — a live, visual knowledge graph of your entire application. It shows every relationship between data models, screens, and data flows in a single interactive view. This is not a static diagram — it reflects your actual deployed application in real time.
Fuuz is the Only Platform on Earth With This Feature
In traditional systems, understanding how data, screens, and logic relate to each other requires digging through documentation, database diagrams, and source code. In Fuuz, open the Application Designer and select Application Graph. Every data model, every screen, and every flow that interacts with that data is visible immediately — no documentation required. This is your starting point for any new engagement, troubleshooting session, or impact analysis.
Data models as nodes
Every Fuuz data model appears as a node in the graph. Relationships between models are shown as edges — the same relationships exposed in the Query Builder and GraphQL schema.
Screens and flows connected
The graph shows which screens and data flows interact with each data model — giving you instant visibility into the impact of any schema change before you make it.
No documentation required
Unlike SQL-backed systems where you need a database diagram and documentation just to begin, the Application Graph is your documentation. Start here on every new engagement.
For Engineers from Legacy Systems — This Should Be a Revelation
If you have spent years in point solutions or legacy ERP environments, you know how much time is lost just trying to understand the shape of the system before you can build anything. The Application Graph eliminates that entirely. Combine it with the Query Builder — which exposes all relationships by clicking — and you can go from zero context to writing correct GraphQL in minutes, not days.
BUILD PATTERNS
Patterns Every SE
Should Internalize
Recurring patterns across MES, WMS, CMMS, and integration builds. Apply them consistently and you will build faster, with fewer bugs, and more maintainable solutions.
PATTERN 01
Design the query before the model
Start with what the screen needs to display. Design your document shape to make that query simple. Model for the access pattern — not in the abstract.
PATTERN 05
Request only what the screen uses
A table row showing 3 fields should request only 3 fields. Over-fetching adds payload weight and makes bindings harder to read and maintain.
PATTERN 02
Filter in GraphQL, reshape in JSONata
Use where variables to filter and edges.node._aggregate to count/sum. Sort via orderBy: [{ field, direction }]. Use JSONata to reshape the already-filtered response only. Never flip these roles.
PATTERN 06
Know your JSONata context
Screen: $components, $metadata, data, $card. Flow: $state. Confusing these produces silent failures, not errors.
PATTERN 03
Always return id in mutations
After any GraphQL mutation, return id in the selection set. Confirms the write and gives you the ID for any chained operations in the same flow.
PATTERN 07
Map ERP writes to mutations early
When scoping ERP integrations (NetSuite, Plex, BC), identify target GraphQL mutations before writing any flow logic. The mutation schema defines exactly what Fuuz accepts.
PATTERN 04
Check the console before assuming upstream
Every transform section exposes input/output in the console. Inspect it there first before assuming the problem is in the GraphQL query
PATTERN 08
Scope field additions as low-risk
Adding a field to a model is a fast, zero-downtime task. Flow logic, behavioral changes, and performance tuning on large collections — that's where real effort lives.
The SE Build Loop
1. Confirm document shape (schema designer) → 2. Write GraphQL query with predicates → 3. Use JSONata to reshape result → 4. Bind to screen element or flow output → 5. Inspect each layer in the console/logs independently before declaring it working end-to-end.
TROUBLESHOOTING
Debug by Layer,
Not by Guessing
The stack gives you a clear isolation strategy. Each layer has a distinct failure signature. Start at the data layer and work up — every time.
| Symptom | Suspect Layer | Where to Look |
|---|---|---|
Screen shows no data |
GraphQL query / predicate | Run the query standalone. Is the predicate returning records? Is the field name correct? |
Wrong data on screen |
JSONata binding / wrong field | Inspect the raw GraphQL response first. Correct there? If yes, check the JSONata expression in the transform console. |
Screen loads slowly |
Dynamic expressions / missing indexes | Most common causes: (1) dynamic transformed fields like visible bindings executing too frequently, (2) complex filters on large collections without an index. |
Flow not triggering |
Trigger config / event mismatch | Confirm the trigger event matches the action being performed. Check flow execution logs. |
Flow erroring mid-run |
Specific node failure | Open flow execution history to identify the failing node. Inspect the console of that transform to see what input it received. |
GraphQL mutation fails |
Wrong field name / type mismatch | Read the error — GraphQL errors are explicit. Fuuz uses id not _id. Autocomplete in the editor flags these as you type. |
Integration writes missing |
Mutation not running / wrong tenant | Confirm mutation ran in flow execution logs. Confirm correct tenant context. Check id returned in mutation response. |
JSONata producing null |
Wrong context / wrong field path | Are you in a screen or a flow? $state only works in flows. $components / $metadata only in screens. Inside Cards: use $card.data.x not data.x. |
The Debug Hierarchy — Follow This Order Every Time
Step 1: Does the data exist? (Run a GraphQL query to confirm)
Step 2: Does the query return it correctly? (Run the exact binding query with your predicates standalone)
Step 3: Does the JSONata transform it correctly? (Inspect input/output in the transform console)
Step 4: Does the screen element bind correctly? (Check element property mapping)
Skipping steps leads to false diagnoses and wasted time. Layer by layer, every time.
SAAS + DEPLOY-ANYWHERE INFRASTRUCTURE
What Managed SaaS Means
for How You Build and Scope
Fuuz running as managed SaaS on a deploy-anywhere infrastructure changes what belongs in your delivery scope, what customers inherit automatically, and how to think about application-level performance and design.
Schema changes are instant
Field additions deploy without downtime. Iterate on your data model incrementally across sprints — no upgrade events or deployment windows needed.
Security is inherited
Encryption at rest and in transit, IAM, audit logging — all built into the platform infrastructure layer. Don't scope custom security plumbing. It's already there.
Infrastructure is never in your scope
No servers, no database setup, no backups, no failover planning. The platform manages all of it, regardless of where it’s deployed. Your entire scope is application delivery.
Platform vs. app release cadence
Core platform infrastructure updates continuously. However, Fuuz application feature releases follow a managed schedule. Confirm feature availability for your release version when scoping.
Platform scale vs. app performance
The platform scales automatically regardless of deployment target. But application performance on large datasets is your design responsibility — load-test against realistic data volumes before go-live.
Multi-site = configuration
Adding a new plant or region to an existing tenant is a configuration task. Multi-tenant architecture means global rollout doesn't create new infrastructure scope.
Don't Confuse Platform Scale with App Performance
Platform infrastructure scales automatically regardless of where Fuuz is deployed. But how your application handles large data sets is a design concern you own. Complex flow logic, unindexed queries on large collections, and overly dynamic screen bindings all affect performance under real-world data volumes. Load test against realistic data before handing off to customers.
~0
Minutes of downtime
to add a field to a model
1
GraphQL call to retrieve
a fully hydrated object graph
∞
Platform scale headroom
— app perf is still your job