Skip to main content

How export works

A NodeGX project can be exported as an ordinary React app: React 19, Vite and TypeScript, with no NodeGX runtime inside it. You can read it, change it, build it and host it anywhere. The editor's Export React code command and the @nodegx/export package run the same code, so what this page shows is what you get from either.

Every front-end node you can place from the node picker today exports: 130 of 130. Across all 180 node types in the catalog, the ones that do not are deprecated, cannot be placed, or run only on the backend. They are listed at the bottom of this page, each with the reason.

The pipeline​

The export is four pure steps over the project files. Nothing opens a window and nothing is run.

  1. Parse. The project folder (nodegx.project.json and each component's nodes.json and connections.json) is read into an intermediate representation: plain data, with every node keeping the id it has in the project.
  2. Analyse. Each component's tree is walked and every node gets a decision: it renders as an element, it becomes state, it compiles away into an expression, it becomes a call to a small library, or it is refused. Signal wires become the bodies of event handlers, and value wires become reads.
  3. Emit. The decisions are written out as TSX components, one CSS module per component, the app shell with its routes, and the build files.
  4. Report. Everything refused is written into the app as EXPORT-REPORT.md, with the reason in the exporter's own words. The exporter never guesses: a node it has no rule for is left out and named, not translated approximately.

The source is in packages/nodegx-export/src: parse/, ir/, analyze/ and emit/, in that order.

A worked example​

A page with a count and two buttons. Add one increases a Counter, Reset sets it back to zero, and a String Format turns the count into a sentence for the Text. Everything below is generated: this project is exported every time the page is.

The graph​

The page's nodes (components/Pages/Home/nodes.json):

[
{
"id": "page",
"type": "Page",
"label": "Home",
"children": [
"column"
],
"parameters": {
"title": "Click Counter",
"urlPath": ""
}
},
{
"id": "column",
"type": "Group",
"label": "Column",
"parent": "page",
"children": [
"countText",
"addButton",
"resetButton"
],
"parameters": {
"flexDirection": "column",
"alignItems": "flex-start",
"rowGap": {
"value": 12,
"unit": "px"
},
"paddingTop": {
"value": 40,
"unit": "px"
},
"paddingLeft": {
"value": 24,
"unit": "px"
}
}
},
{
"id": "countText",
"type": "Text",
"label": "Count",
"parent": "column",
"parameters": {
"fontSize": {
"value": 24,
"unit": "px"
}
}
},
{
"id": "addButton",
"type": "net.noodl.controls.button",
"label": "Add one",
"parent": "column",
"parameters": {
"label": "Add one"
}
},
{
"id": "resetButton",
"type": "net.noodl.controls.button",
"label": "Reset",
"parent": "column",
"parameters": {
"label": "Reset"
}
},
{
"id": "counter",
"type": "Counter",
"label": "Clicks",
"parameters": {
"startValue": 0
}
},
{
"id": "message",
"type": "String Format",
"label": "Message",
"parameters": {
"format": "Clicked {count} times"
}
}
]

And its wires (connections.json):

FromOutputToInput
Add one (Button)onClickClicks (Counter)increase
Reset (Button)onClickClicks (Counter)reset
Clicks (Counter)currentCountMessage (String Format)count
Message (String Format)formattedCount (Text)text

What the export wrote​

12 files:

  • EXPORT-REPORT.md
  • README.md
  • index.html
  • package.json
  • src/App.tsx
  • src/main.tsx
  • src/pages/Home.module.css
  • src/pages/Home.tsx
  • src/styles/base.css
  • src/styles/tokens.css
  • tsconfig.json
  • vite.config.ts
src/pages/Home.tsx
// @nodegx:generated (visual — provenance markers complete in EXP-007)
import { useState } from 'react';

import styles from './Home.module.css';

/** Home. */
export function HomePage() {
// From the Counter node "Clicks" — Increase/Decrease/Reset write it, Count reads it.
const [clicks, setClicks] = useState<number>(0);

return (
<div className={styles.page}>
<title>Click Counter</title>

<p className={styles.countText}>{`Clicked ${clicks} times`}</p>

<button onClick={() => setClicks((v) => v + 1)}>Add one</button>

<button onClick={() => setClicks(0)}>Reset</button>
</div>
);
}
src/pages/Home.module.css
/* @nodegx:generated (visual) */

.page {
display: flex;
flex-direction: column;
align-items: flex-start;
padding-top: 40px;
padding-left: 24px;
row-gap: 12px;
}

.countText {
margin: 0;
font-size: 24px;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
src/App.tsx
// @nodegx:generated (scaffold — placeholder markers complete in EXP-007)
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';

import { HomePage } from './pages/Home';

export function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/home" element={<HomePage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
);
}

What each node became​

NodeIn the React app
Pagethe page component HomePage, its <title>, and the route / in App.tsx
Groupa <div> whose layout is a class in Home.module.css
Texta <p> whose text is the String Format, inlined
Buttona <button> with its label and an onClick
Counterone useState; Increase becomes setClicks((v) => v + 1) and Reset becomes setClicks(0)
String Formata template string, `Clicked ${clicks} times`, compiled away where its output lands
Page Routerthe react-router-dom routes in App.tsx

There is no NodeGX runtime in the result. The Counter is not an object with methods any more: its state is one hook and its actions are the lines that call the setter. String Format leaves nothing behind but the string. This is what the export aims for with every node: the code a React developer would have written.

What the export claims​

Every export carries its own EXPORT-REPORT.md. This is the part that says what the export does and does not promise, from the report written for this example. "The list above" is the report's list of what was refused:

Every line of this app was generated by rule, and nothing here has been run. Where the exporter had a rule for a node, it emitted code from it; where it did not, it refused and said so above. It never guesses.

Those rules were written against the runtime source of each node and are covered by the exporter’s own tests. That is a claim about the rules, not about your project: your graph was not executed, its behaviour was not recorded, and the generated code was not compared against it. Nothing in this app has been verified against the app you built.

So the useful reading of the list above is "this is what is missing", and the useful reading of everything not on it is "this is what the exporter believed it could translate" — which is worth testing, exactly as you would test code you wrote yourself.

Data and the backend​

An app that uses records, users or cloud functions exports with a small client that talks to its NodeGX backend over the backend's HTTP API. The exported app is the front end; the backend stays where it is, and the nodes that only run inside cloud functions have no React form.

Known limits​

  • The export reads the project files as written. The editor patches some older graphs when it opens them, and where the two disagree the export follows the file.
  • An exported app does not yet pass npm run build in every case. Build it and run it before you rely on it.
  • Nothing in the exported app is run by the exporter. The report says what was translated by rule; testing the app is yours.
  • Export is one-way. Editing the exported code does not change your NodeGX project, and exporting again overwrites what you changed.

Other frameworks​

Not built yet. React is the only export today, and no other framework is promised. The plan is not a second generator per framework, which would mean a full set of per-node rules for each one, kept in step forever. It is to port the exported React app to Svelte or Vue with AI assistance, and to check the port by replaying the same recorded behaviour traces the React export can be checked against.

What makes that plausible is that the node model does not depend on React: React is confined to how visual nodes render. Each node's behaviour is written down as an executable spec, shown on its page under Under the hood, and that is the reference any port has to match.

Every node, and whether it exports​

From the export ledger, which classifies every node type in the catalog. 138 export to React, 25 do not yet and 17 run only on the backend.

Exports to React (138)​

CategoryNodes
AnimationAnimate To Value, States
Cloud ServicesCloud File, Cloud Function, Log In, Log Out, Query Records, Record, Request Magic Link, Set User Properties, Sign File URL, Sign In With, Sign Up, Subscribe To Changes, Upload File, User
Component UtilitiesComponent Inputs, Component Object, Component Outputs, Parent Component Object, Set Component Object Properties, Set Parent Component Object Properties
CustomCodeCSS Definition, Expression, Function, Script, Visual Function
DataAction Dispatcher, Action Handler, Add Record Relation, Array, Array Filter, Array Map, Clear Array, Create New Array, Create New Object, Create Record, Delete Record, Filter Records, Global Store, HTTP Request, Insert Object Into Array, JSON Stream Parser, Object, Optimistic Update, Parse CSV, Parse Feed, Parse XML, Pattern Extractor, Remove Object From Array, Remove Record Relation, Repeater Item, Run Tasks, Server-Sent Events, Set Global Store, Set Object Properties, Set Variable, State History, State Snapshot, Static Array, Stream Buffer, Subscribe to Store, Text Accumulator, To CSV, Undo / Redo, Update Record, Variable, WebSocket
EventsReceive Event, Send Event
InterpolationColor Blend
LogicAnd, Condition, Inverter, Or, Switch, Value Changed
MathCounter, Number Remapper
NavigationClose Popup, External Link, Navigate, Navigate To Path, Page Inputs, Pop Component Stack, Push Component To Stack, Show Popup
String ManipulationString Format, Substring, Unique Id
UtilitiesBoolean To String, Date Add, Date Compare, Date Difference, Date Parts, Date To String, Delay, Hash, Log, Now, On App Error, Open File Picker, Random Bytes, Repeat, Screen Resolution, String Mapper, UUID
VariablesBoolean, Color, Number, String
VisualButton, Button, Checkbox, Checkbox, Columns, Component Children, Component Stack, Drag, Dropdown, Group, Icon, Image, Label, Options, Page, Page Router, Radio Button, Radio Button, Radio Button Group, Range, Repeater, Shape, Slider, Text, Text Input, Text Input, Video

Not exported yet (25)​

Deprecated (23). Nobody building today is offered these, and a project that still has one exports it as a named gap in the report: Animation, Transition, Cloud Function, Model, Query Collection, Request Password Reset, Reset Password, Send Email Verification, Verify Email, Component Object, Parent Component Object, Array, Object, REST, Variable, Number Blend, Script Downloader, Signal To Index, Device Orientation, Globals, Index To String, Field Set, Form.

NodeWhy
Array Changeddeliberately out of scope — not offered in the node picker, so it cannot be placed. It exists for graphs that already have one, and those export as named deferrals
Object Changeddeliberately out of scope — not offered in the node picker, so it cannot be placed. It exists for graphs that already have one, and those export as named deferrals

Backend only (17)​

These run inside cloud functions, so the front end never sees them: Add User To Role, Create User, Delete User, Get User Roles, HMAC, JWT Sign, JWT Verify, List Users In Role, Model Request, Remove User From Role, Request, Response, Secret, Send Email, Update User, Verify Session Token, Aggregate Records.