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.
- Parse. The project folder (
nodegx.project.jsonand each component'snodes.jsonandconnections.json) is read into an intermediate representation: plain data, with every node keeping the id it has in the project. - 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.
- Emit. The decisions are written out as TSX components, one CSS module per component, the app shell with its routes, and the build files.
- 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):
| From | Output | To | Input |
|---|---|---|---|
| Add one (Button) | onClick | Clicks (Counter) | increase |
| Reset (Button) | onClick | Clicks (Counter) | reset |
| Clicks (Counter) | currentCount | Message (String Format) | count |
| Message (String Format) | formatted | Count (Text) | text |
What the export wrote
12 files:
EXPORT-REPORT.mdREADME.mdindex.htmlpackage.jsonsrc/App.tsxsrc/main.tsxsrc/pages/Home.module.csssrc/pages/Home.tsxsrc/styles/base.csssrc/styles/tokens.csstsconfig.jsonvite.config.ts
// @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>
);
}
/* @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;
}
// @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
| Node | In the React app |
|---|---|
| Page | the page component HomePage, its <title>, and the route / in App.tsx |
| Group | a <div> whose layout is a class in Home.module.css |
| Text | a <p> whose text is the String Format, inlined |
| Button | a <button> with its label and an onClick |
| Counter | one useState; Increase becomes setClicks((v) => v + 1) and Reset becomes setClicks(0) |
| String Format | a template string, `Clicked ${clicks} times`, compiled away where its output lands |
| Page Router | the 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 buildin 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)
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.
| Node | Why |
|---|---|
| Array Changed | deliberately 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 Changed | deliberately 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.