[{"data":1,"prerenderedAt":453},["ShallowReactive",2],{"application-guide-nav":3,"agmd-node-red-good-form":118},[4,12,16,22,25,31,36,42,47,53,58,64,70,75,81,87,93,98,103,108,112],{"guide":5,"slug":6,"title":7,"navOrder":8,"parent":9,"blurb":10,"path":11},"flowfuse","overview","Overview",1,null,"The map of the FlowFuse guide — apps, architectures, and a worked example.","\u002Fapplication-guide\u002Fflowfuse\u002Foverview\u002F",{"guide":13,"slug":6,"title":7,"navOrder":8,"parent":9,"blurb":14,"path":15},"node-red","The map of the Node-RED guide — the pattern families that turn an app into a clean flow.","\u002Fapplication-guide\u002Fnode-red\u002Foverview\u002F",{"guide":5,"slug":17,"title":18,"navOrder":19,"parent":9,"blurb":20,"path":21},"foundations","Foundations",2,"The foundation to build on: what FlowFuse is, its core pieces, and how code is shared across teams.","\u002Fapplication-guide\u002Fflowfuse\u002Ffoundations\u002F",{"guide":13,"slug":17,"title":18,"navOrder":19,"parent":9,"blurb":23,"path":24},"The handful of concepts you need to build with Node-RED, and how they fit together.","\u002Fapplication-guide\u002Fnode-red\u002Ffoundations\u002F",{"guide":5,"slug":26,"title":27,"navOrder":28,"parent":9,"blurb":29,"path":30},"app-delivery-methods","App delivery methods",3,"Two different units of code, delivered two ways. Ship the whole app — a complete, versioned project promoted through environments — or publish one reusable piece — a package the whole team installs and upgrades in one place. Pick by what you're shipping: the app, or a part of it.","\u002Fapplication-guide\u002Fflowfuse\u002Fapp-delivery-methods\u002F",{"guide":13,"slug":32,"title":33,"navOrder":28,"parent":9,"blurb":34,"path":35},"patterns","Patterns","The moves that turn an architecture into a clean, reusable flow — find the seams and reuse well, then handle data on the right paths.","\u002Fapplication-guide\u002Fnode-red\u002Fpatterns\u002F",{"guide":5,"slug":37,"title":38,"navOrder":39,"parent":26,"blurb":40,"path":41},"hardware-apps","Hardware apps",3.1,"The three shapes a FlowFuse app takes when it runs on a device. Pick by how much varies per site: nothing (Packaged App), a few settings (Configurable App), or you assemble it yourself (Edge Building Block).","\u002Fapplication-guide\u002Fflowfuse\u002Fhardware-apps\u002F",{"guide":13,"slug":43,"title":44,"navOrder":39,"parent":32,"blurb":45,"path":46},"design-patterns","Design patterns","The structural choices you select for a flow: find the seams it breaks into, then reuse each piece at the lightest level that solves it — link in\u002Fout, link call, subflow, or packaged node.","\u002Fapplication-guide\u002Fnode-red\u002Fdesign-patterns\u002F",{"guide":5,"slug":48,"title":49,"navOrder":50,"parent":26,"blurb":51,"path":52},"software-apps","Software apps",3.2,"The three shapes a FlowFuse app takes when it runs on the platform. Pick by what it needs: a headless job (Packaged App), a user-facing app driven by data (Data-Driven App), or a reusable piece other apps embed (Shared Building Block).","\u002Fapplication-guide\u002Fflowfuse\u002Fsoftware-apps\u002F",{"guide":13,"slug":54,"title":55,"navOrder":50,"parent":32,"blurb":56,"path":57},"handling-data","Handling data","Classify each signal by shape, purpose and direction, then pick the methods it needs — separate the paths, pace the flow, hold state in context, and manage config. The methods you select to move a flow's data.","\u002Fapplication-guide\u002Fnode-red\u002Fhandling-data\u002F",{"guide":13,"slug":59,"title":60,"navOrder":61,"parent":32,"blurb":62,"path":63},"good-form","Good form",3.3,"A clean flow isn't luck — it's a handful of habits. Wire for reading, lay it out on a grid, decouple UI from logic, catch errors where you can see them, and keep data on a stable contract. Follow these and a flow stays readable, reusable, and out of spaghetti.","\u002Fapplication-guide\u002Fnode-red\u002Fgood-form\u002F",{"guide":5,"slug":65,"title":66,"navOrder":67,"parent":9,"blurb":68,"path":69},"data-plane","Data plane",4,"Before you pick where things run, decide how data is handled. Two stores come built into every FlowFuse server install — the Team Broker and relational Tables — exposed to every instance with nothing extra to stand up. Everything else you bring your own: run it (a time-series DB, an existing database, a model) and expose it to the fleet over Project Link, no inbound ports. This is the data plane the architectures on the next pages all sit on.","\u002Fapplication-guide\u002Fflowfuse\u002Fdata-plane\u002F",{"guide":13,"slug":71,"title":72,"navOrder":67,"parent":9,"blurb":73,"path":74},"worked-examples","Worked examples","Turn an app concept into a Node-RED flow — or a few — leaning on the design patterns and data handling. The method, then the OEE apps end to end.","\u002Fapplication-guide\u002Fnode-red\u002Fworked-examples\u002F",{"guide":13,"slug":76,"title":77,"navOrder":78,"parent":71,"blurb":79,"path":80},"oee-edge-aggregator","OEE - Edge Aggregator",4.1,"The edge app from the OEE use case as a Node-RED flow — a straight-line flow packaged as a subflow and configured per line (its PLC tags, via a config UI and a get-config node), with the data treated as a stream and its counts held in context.","\u002Fapplication-guide\u002Fnode-red\u002Foee-edge-aggregator\u002F",{"guide":13,"slug":82,"title":83,"navOrder":84,"parent":71,"blurb":85,"path":86},"oee-central-dashboard","OEE - Central Dashboard",4.2,"The cloud app from the OEE use case as a Node-RED flow — one link out fanning to two link ins on separate tabs (dashboard and batched history), so the live and history paths stay separate and easy to read.","\u002Fapplication-guide\u002Fnode-red\u002Foee-central-dashboard\u002F",{"guide":5,"slug":88,"title":89,"navOrder":90,"parent":9,"blurb":91,"path":92},"architectures","Architectures",5,"Every FlowFuse deployment is the same building blocks arranged for where it runs — pick the world you're designing for.","\u002Fapplication-guide\u002Fflowfuse\u002Farchitectures\u002F",{"guide":5,"slug":94,"title":95,"navOrder":96,"parent":88,"blurb":9,"path":97},"it-architectures","IT architectures",5.1,"\u002Fapplication-guide\u002Fflowfuse\u002Fit-architectures\u002F",{"guide":5,"slug":99,"title":100,"navOrder":101,"parent":88,"blurb":9,"path":102},"ot-architectures","OT architectures",5.2,"\u002Fapplication-guide\u002Fflowfuse\u002Fot-architectures\u002F",{"guide":5,"slug":104,"title":105,"navOrder":106,"parent":88,"blurb":9,"path":107},"iiot-architectures","IIoT architectures",5.3,"\u002Fapplication-guide\u002Fflowfuse\u002Fiiot-architectures\u002F",{"guide":5,"slug":71,"title":72,"navOrder":109,"parent":9,"blurb":110,"path":111},6,"Start from a use case, break it into apps, and draw the architecture that ties them together — the same method a FlowFuse Proof of Value runs.","\u002Fapplication-guide\u002Fflowfuse\u002Fworked-examples\u002F",{"guide":5,"slug":113,"title":114,"navOrder":115,"parent":71,"blurb":116,"path":117},"worked-example","OEE, end to end",6.1,"One use case — OEE across three lines — broken into two apps and two shared services, then drawn out end to end.","\u002Fapplication-guide\u002Fflowfuse\u002Fworked-example\u002F",{"id":119,"title":60,"blurb":62,"body":120,"description":132,"extension":446,"guide":13,"meta":447,"navOrder":61,"navTitle":60,"navigation":448,"parent":32,"path":449,"seo":450,"slug":59,"stem":451,"__hash__":452},"applicationGuideDoc\u002Fapplication-guide\u002Fnode-red\u002Fgood-form.md",{"type":121,"value":122,"toc":443},"minimark",[123,126,133,145,436],[124,125,60],"h1",{"id":59},[127,128,129],"p",{},[130,131,132],"strong",{},"Good form — how not to make spaghetti",[127,134,135,136,144],{},"A clean flow isn't luck — it's a handful of habits. The golden rule: ",[130,137,138,139,143],{},"shared things are ",[140,141,142],"em",{},"called"," rather than funneled into, and every beginning is its own left-justified path."," Everything below follows from that.",[146,147,148,221,266,330,375],"guide-tabs",{},[149,150,152,158,164,180,185],"guide-tab",{"label":151},"Wire for reading",[127,153,154,157],{},[130,155,156],{},"Call shared things; don't funnel into them"," — many paths wiring into one shared node is the core spaghetti anti-pattern.",[159,160],"flow-diagram",{":edges":161,":nodes":162,"align":163},"[\"a>call\",\"b>call\",\"c>call\",{\"from\":\"call\",\"to\":\"svc\",\"dir\":\"both\",\"label\":\"result back\"}]","[{\"id\":\"a\",\"label\":\"path A\",\"accent\":\"indigo\",\"col\":1,\"row\":1},{\"id\":\"b\",\"label\":\"path B\",\"accent\":\"indigo\",\"col\":1,\"row\":2},{\"id\":\"c\",\"label\":\"path C\",\"accent\":\"indigo\",\"col\":1,\"row\":3},{\"id\":\"call\",\"label\":\"link call\",\"sub\":\"the shared service\",\"accent\":\"teal\",\"col\":2,\"row\":2},{\"id\":\"svc\",\"label\":\"SQL \u002F broker \u002F model\",\"sub\":\"one dependency\",\"accent\":\"green\",\"col\":3,\"row\":2}]","left",[127,165,166,167,170,171,174,175,179],{},"When many paths share a dependency — a DB pool, a broker, a model call — don't wire them all ",[140,168,169],{},"into"," one node (a funnel). Expose it once as a service each path ",[130,172,173],{},"calls"," with a ",[176,177,178],"code",{},"link call","; the result returns and per-path handling happens after. The shared node keeps exactly one wire in and stays reusable.",[127,181,182],{},[130,183,184],{},"The rules",[186,187,188,203,209,215],"ul",{},[189,190,191,194,195,198,199,202],"li",{},[130,192,193],{},"No funnels into a shared dependency"," — when many paths need the same service, they should ",[140,196,197],{},"call"," it, not all wire into it. (Aggregators like a ",[176,200,201],{},"join"," node that legitimately gather many inputs are the exception — that's their job, not a funnel.)",[189,204,205,208],{},[130,206,207],{},"Each beginning is its own path"," — one straight, left-justified path per entry; don't merge them through a shared front door.",[189,210,211,214],{},[130,212,213],{},"Flow left → right"," — beginning → prep → service call → format → sink. A backward (right-to-left) wire reads as tangle.",[189,216,217,220],{},[130,218,219],{},"A crossing is a missing link node"," — if two wires cross, row-align the column with its targets or bridge it with a link node. Same for any wire longer than the canvas — a link node reads cleaner.",[149,222,224,230,262],{"label":223},"Lay it out",[127,225,226,229],{},[130,227,228],{},"One column per role, everything on the grid"," — layout is what makes a flow scannable at a glance.",[186,231,232,238,244,250,256],{},[189,233,234,237],{},[130,235,236],{},"One entry column"," — every beginning (inject, http in, mqtt in) shares one left edge. That column is the visual anchor.",[189,239,240,243],{},[130,241,242],{},"Columns by role"," — beginning → prep → service call → format → sink, left to right, each role in its own column.",[189,245,246,249],{},[130,247,248],{},"On the grid, no overlaps"," — snap to the 20px grid, leave a gap between neighbours, and never let two nodes (or two groups) overlap.",[189,251,252,255],{},[130,253,254],{},"Align and pad groups"," — left-justify group boxes to a common column, keep members inside the box with a little padding, and drop empty groups.",[189,257,258,261],{},[130,259,260],{},"Comments are short labels"," — a couple of words on the canvas; put the detail in the comment's info field, not a paragraph that runs off-page.",[159,263],{":edges":264,":nodes":265,"align":163},"[\"begin>prep\",\"prep>call\",\"call>fmt\",\"fmt>sink\"]","[{\"id\":\"begin\",\"label\":\"beginning\",\"sub\":\"one entry column\",\"accent\":\"indigo\",\"col\":1,\"row\":1},{\"id\":\"prep\",\"label\":\"prep\",\"col\":2,\"row\":1},{\"id\":\"call\",\"label\":\"service call\",\"accent\":\"teal\",\"col\":3,\"row\":1},{\"id\":\"fmt\",\"label\":\"format\",\"col\":4,\"row\":1},{\"id\":\"sink\",\"label\":\"sink\",\"accent\":\"indigo\",\"col\":5,\"row\":1}]",[149,267,269,275,280,283],{"label":268},"Decouple UI from logic",[127,270,271,274],{},[130,272,273],{},"Widgets are an API"," — the backend sends a display-ready view-model; the frontend emits intent. Treat the dashboard-to-logic boundary like a client and a server.",[159,276],{":edges":277,":nodes":278,":legend":279},"[{\"from\":\"fe\",\"to\":\"be\",\"label\":\"intent · { action, payload }\"},{\"from\":\"be\",\"to\":\"fe\",\"label\":\"view-model · display-ready\",\"dashed\":true},\"be>db\"]","[{\"id\":\"fe\",\"label\":\"widget\",\"sub\":\"renders + emits\",\"accent\":\"indigo\"},{\"id\":\"be\",\"label\":\"flow logic\",\"sub\":\"holds the truth\",\"accent\":\"indigo\"},{\"id\":\"db\",\"label\":\"SQL database\",\"sub\":\"records\",\"accent\":\"green\"}]","[{\"line\":\"neutral\",\"label\":\"intent · frontend → backend\"},{\"line\":\"neutral\",\"dashed\":true,\"label\":\"view-model · backend → frontend\"}]",[127,281,282],{},"The frontend renders state and emits intent; the backend holds the truth. When you build payloads inside templates or cram logic next to a widget, every change touches both.",[186,284,285,291,297,307,324],{},[189,286,287,290],{},[130,288,289],{},"Reads"," — the backend sends a finished, display-ready view-model. Templates bind and display; they never fetch, transform, or decide.",[189,292,293,296],{},[130,294,295],{},"Writes"," — the widget emits one consistent intent message: an action plus a payload. The backend decides what it means.",[189,298,299,302,303,306],{},[130,300,301],{},"One place for state"," — hold state in one shared ",[176,304,305],{},"global"," object the widgets read. The wire carries events, not fat objects, and never a live subscription per widget.",[189,308,309,315,316,319,320,323],{},[130,310,311,314],{},[176,312,313],{},"ui-template"," is the escape hatch"," — reach for it for the one custom widget, never to build the whole UI as one block. Let it auto-size (",[176,317,318],{},"height=\"0\"",") so content isn't clipped, and remember tables ",[130,321,322],{},"replace, not append",".",[189,325,326,329],{},[130,327,328],{},"Give every page an on-load trigger"," — dashboard widgets emit only on real interaction, so a page that waits for a click to populate opens empty. Fire the load path on page-show.",[149,331,333,339,344,347],{"label":332},"Catch errors visibly",[127,334,335,338],{},[130,336,337],{},"Every work path has a catch"," — otherwise errors drop silently and you're debugging blind.",[159,340],{":edges":341,":nodes":342,":legend":343},"[{\"from\":\"in\",\"to\":\"work\"},{\"from\":\"work\",\"to\":\"sink\",\"label\":\"ok\"},{\"from\":\"work\",\"to\":\"catch\",\"label\":\"in scope\",\"accent\":\"red\",\"dashed\":true},{\"from\":\"catch\",\"to\":\"errpath\",\"accent\":\"red\",\"dashed\":true}]","[{\"id\":\"in\",\"label\":\"in\",\"col\":1,\"row\":1},{\"id\":\"work\",\"label\":\"work\",\"sub\":\"may throw\",\"col\":2,\"row\":1},{\"id\":\"sink\",\"label\":\"sink\",\"sub\":\"on success\",\"col\":3,\"row\":1},{\"id\":\"catch\",\"label\":\"Catch\",\"sub\":\"scoped to the work\",\"accent\":\"red\",\"col\":2,\"row\":2},{\"id\":\"errpath\",\"label\":\"log · notify · return\",\"sub\":\"the error path\",\"accent\":\"red\",\"col\":3,\"row\":2}]","[{\"line\":\"neutral\",\"label\":\"success\"},{\"line\":\"red\",\"dashed\":true,\"label\":\"Catch scope · not a wire\"}]",[127,345,346],{},"Anything that talks to the outside world — an HTTP call, a DB write, a broker publish, a model call — will fail sometimes. Route those failures somewhere you control. (A Catch node has no input wire — it registers to catch errors from every node in its scope automatically; the dashed line marks that scope, not a connection you draw.)",[186,348,349,355,361],{},[189,350,351,354],{},[130,352,353],{},"No work without a catch"," — a tab with function \u002F request \u002F DB \u002F link-call \u002F AI nodes and no Catch node drops its errors silently.",[189,356,357,360],{},[130,358,359],{},"Scope the catch to cover the path"," — make sure every reachable work node is in the catch's scope, or the ones outside it throw where nothing is listening.",[189,362,363,366,367,370,371,374],{},[130,364,365],{},"In a shared service, format and return the error"," — return it via ",[176,368,369],{},"link out"," in return mode so the caller sees ",[176,372,373],{},"msg.error"," and a bad call never hangs.",[149,376,378,384],{"label":377},"Keep data on a contract",[127,379,380,383],{},[130,381,382],{},"Swap a source by keeping the message shape"," — good seams mean a data change touches one node, not the whole flow.",[186,385,386,400,420,426],{},[189,387,388,391,392,395,396,399],{},[130,389,390],{},"Stable msg contract"," — a query returns rows on the same property; the broker path is ",[176,393,394],{},"msg.topic"," + ",[176,397,398],{},"msg.payload",". Keep the shape and only one node changes when you swap the source behind it.",[189,401,402,405,406,409,410,413,414,416,417,419],{},[130,403,404],{},"SQL goes on the property your node reads"," — the Postgres query node reads ",[176,407,408],{},"msg.query"," (with ",[176,411,412],{},"msg.params"," for parameters); the mysql \u002F sqlite nodes read ",[176,415,394],{},". Either way it's never ",[176,418,398],{}," — put SQL on the wrong property and the query silently runs empty.",[189,421,422,425],{},[130,423,424],{},"Parameterize"," — use parameterized queries and quote case-sensitive identifiers; don't string-build SQL into the payload.",[189,427,428,431,432,435],{},[130,429,430],{},"Preserve the message through the chain"," — return context (a callback, a link-call return) has to survive every hop, so keep functions and query nodes passing ",[176,433,434],{},"msg"," through.",[437,438,440],"callout",{"icon":439},"i-lucide-check",[127,441,442],{},"Follow these and a flow reads cleanly — beginnings in one column, shared things called not funneled, UI and logic on their own sides of a contract, errors on a path you can see, and data on a stable shape. That's good form.",{"title":444,"searchDepth":67,"depth":67,"links":445},"",[],"md",{},true,"\u002Fapplication-guide\u002Fnode-red\u002Fgood-form",{"title":60,"description":132},"application-guide\u002Fnode-red\u002Fgood-form","NH1VvRfsrjBsUrxsW45WUkBCDP3a7GYj7imK-nFMFv0",1787070953824]