Course Content
Low-Code Plus Pro-Code
When to drag-and-drop, when to drop to TypeScript, and how to keep both maintainable
Two surfaces, one platform
Modern FDE-style platforms expose two ways to build:
- Low-code — declarative configuration. Drag-and-drop layouts, point-and-click bindings, YAML / JSON definitions of forms and pipelines. Built fast, edited by less-technical builders, capped in what it can express.
- Pro-code — full programmatic surface. TypeScript / Python / Java. Anything you can express in code is buildable. Maximum power, slower to ship, requires engineering skill.
Most engineers, given both, default to one. Junior FDEs default to pro-code because it is familiar. Some customer-shop engineers default to low-code because they came from a no-code tool. Both defaults are wrong. The right answer is to pick the right surface for each piece, deliberately, and to make the boundary between them clean.
This lesson is about how to make that call.
The case for low-code
When you can express something declaratively, it is almost always the right move. Here is why:
- Faster to build. A form that takes a low-code builder 20 minutes takes a TS engineer 2 hours.
- Faster to change. When Maria asks for a column reorder at 4:50 PM Friday, a low-code surface gets it done before you leave. A code change gets shipped Monday.
- Customer-extensible. The customer’s internal builder — often not a software engineer — can edit a low-code screen safely. They cannot edit your TypeScript.
- Audited automatically. Most low-code surfaces version-control configs natively; the diff is human-readable.
- Type-safe by construction. A declarative form bound to an action gets its types from the action — drift is impossible.
The cost: low-code surfaces have expressive ceilings. The day you hit one — and you will — you either contort the low-code into something hideous or you escape to pro-code.
The case for pro-code
When you need to:
- Express logic that the low-code surface doesn’t support (custom validators, computed values from outside the ontology, complex visualizations)
- Embed third-party libraries (a map widget, a CAD viewer, a specific chart library)
- Hit external APIs from inside the app (a vendor’s tracking number lookup)
- Build a UI pattern that doesn’t fit the platform’s components (a Gantt chart, a custom timeline)
- Optimize a hot path (a high-frequency live view that the declarative surface re-renders inefficiently)
Pro-code is required. There is no clever low-code workaround that won’t bite you in iteration 5.
The decision rule
A useful rule of thumb:
Default to low-code. Drop to pro-code at the first point where low-code starts to feel ugly.
“Ugly” here is specific. Some signals:
- You are stacking three conditional bindings to express one piece of logic
- You are storing logic in a “computed property formula” that is longer than 10 lines
- You are copying the same configuration across many screens and dreading a future change
- You catch yourself “abusing” the platform — using a feature for something it clearly was not designed for
- The low-code config is unreadable to a teammate
When that happens, escape. The escape itself is cheap. The slow corrosion of staying on a stretched low-code surface is expensive.
A taxonomy of FDE platform surfaces
Different platforms organize their surfaces differently, but a useful generalization across Foundry, Palantir AIP, and analogous platforms:
| Surface | Mode | What it expresses |
|---|---|---|
| Ontology designer | Low-code | Object types, link types, action types, datasources |
| Workshop / app builder | Low-code | Operational app screens — list/detail/form/action |
| Pipeline builder | Low-code | Data transformations between datasets |
| Function / transform code | Pro-code (TS/Py) | Custom logic embedded into the platform |
| Web SDK / app code | Pro-code (TS) | Fully custom app screens, embeddable widgets |
| Agent / AI workflows | Mixed | Declarative agent topology + code-defined tools |
Each row is a choice. For each new piece of work in your engagement, you pick a row. The choice is reversible — moving a screen from low-code to pro-code (or back) is usually a half-day job if the boundary is clean.
Composition: how the two surfaces talk
The boundary between low-code and pro-code is where engagements go right or wrong. Three patterns work well.
Pattern 1 — Low-code app, pro-code function
The app is built in the visual builder. One field on a form calls a pro-code function for a non-trivial calculation.
Example: the reassignment form’s “suggested driver” field is computed by a pro-code function that ranks drivers by distance + remaining capacity + reliability. The form is otherwise entirely declarative.
// Pro-code function exposed to the low-code surface
export function suggestDriverForStops(stop_ids: StopId[]): {
driver_id: DriverId;
score: number;
reasoning: string;
}[] {
const stops = ontology.stops.fetch(stop_ids);
const candidates = ontology.drivers.activeNear(stops[0].hub_id);
return rankCandidates(stops, candidates);
}The form references suggestDriverForStops(stop_ids) by name. Maria sees a ranked list of suggestions. The form is otherwise three text fields and a submit button — pure low-code.
Pattern 2 — Pro-code shell, low-code widgets
The app’s outer chrome (navigation, layout, auth gates) is pro-code. The interior screens are low-code widgets embedded as iframes or components.
Example: an executive dashboard built in pro-code (for layout flexibility and embedding) hosts a “live operations” widget that comes from the low-code builder. Two builders can maintain each surface independently.
Pattern 3 — Low-code orchestration, pro-code steps
A workflow is wired declaratively (step 1 → step 2 → step 3, each step typed), but individual steps are pro-code functions when their logic is non-trivial.
This is the most common pattern for agent workflows (covered in the next lesson) and for data pipelines.
A worked Northbound choice
For each piece of the iteration-1 and iteration-2 build, which surface?
| Piece | Surface | Why |
|---|---|---|
Load, Stop, Driver object types | Low-code (ontology designer) | Declarative, type-safe, customer-extensible |
assignDriverToStops action | Low-code with pro-code validator | Action shape declarative; one custom validator (tractor.isAvailableAt) in pro-code |
| Morning view screen | Low-code (workshop) | Table-shaped, fits the declarative surface |
| Slip highlight color logic | Low-code formula | ”if eta_delta_min > 15 then red” — declarative |
| ”Suggested driver” ranking | Pro-code function | Multi-source heuristic; non-trivial logic |
| Map widget on Doug’s view (iter 3) | Pro-code | Custom Mapbox embed |
| Daily on-time KPI calc | Low-code (function in ontology) | One SQL-like expression, no special logic |
| Slip-alert push to phone (iter 4) | Pro-code | External push provider integration |
| Customer tracking page (iter 6) | Pro-code | External-facing, custom auth, custom branding |
Notice the pattern: boring infrastructure-like pieces go in low-code; anything that touches an external system, has multi-source logic, or needs custom UX goes in pro-code.
A useful sanity check: count your pro-code files at end of iteration 6. If you have over 100, you are likely doing too much in code. If you have under 5, you are likely contorting low-code somewhere.
Anti-patterns
Four mistakes that show up over and over.
Anti-pattern 1 — The low-code maximalist
Symptom: every piece of logic is squeezed into the declarative surface, no matter how awkward. The customer has a “no-code” mandate from the CIO that you respect literally.
Why it fails: you end up with 8-line formulas across 40 screens, no test coverage, no refactor path, and any change requires touching 12 different config files. The customer’s “no-code” goal is undermined precisely because the system has become unmaintainable.
Fix: respect the intent of the no-code mandate (customer-extensible, fast to ship) while escaping to pro-code when the low-code starts to corrode. Document the boundary so the customer’s team understands where each surface starts.
Anti-pattern 2 — The pro-code maximalist
Symptom: senior engineer writes everything in TypeScript because “low-code is for amateurs.” Twelve weeks in, the customer’s internal builder cannot touch any screen.
Why it fails: hand-off is impossible. Every future change goes through your team. The platform’s velocity advantage was thrown away.
Fix: every piece of work that could be low-code should be, unless there’s a specific reason. Write the reason down when you escape to pro-code, so a future reviewer (yourself, the customer’s engineer) understands.
Anti-pattern 3 — The dirty boundary
Symptom: pro-code reaches deep into low-code internals; low-code formulas reference half-implemented pro-code functions. The two surfaces leak into each other.
Why it fails: changing either side breaks the other in non-obvious ways. The system loses its tested boundaries.
Fix: enforce a clean interface. Pro-code functions exposed to low-code have signed parameter types and named effects. Low-code screens call pro-code only through the documented surface. No “just import this internal helper” shortcuts.
Anti-pattern 4 — The reimplementation
Symptom: a feature exists in low-code, the engineer doesn’t like it, and rebuilds it in pro-code “for control.” Now there are two versions.
Why it fails: drift. The low-code version stays referenced from some screens; the pro-code version from others. Bug reports become “which version did you see?” investigations.
Fix: pick one. Delete the other. If pro-code wins, migrate every reference; if low-code wins, never start the pro-code version. Never both.
Designing the boundary for hand-off
The single most important boundary-design question: after you leave, who maintains each surface?
Map it out before you start building. A realistic Northbound plan:
| Surface | Maintained by |
|---|---|
| Ontology designer | Customer data engineer (after training) |
| Workshop app screens | Customer ops analyst (with FDE backup for first 6 months) |
| Pipeline builder | Customer data engineer |
| Pro-code functions | Customer senior engineer (or FDE retainer) |
| Pro-code web SDK app | Customer senior engineer (or FDE retainer) |
| Agent workflows | FDE retainer; transition customer engineer in year 2 |
Now build accordingly:
- Pieces marked “customer data engineer” should be all-low-code, no pro-code escapes
- Pieces marked “customer senior engineer” can mix surfaces but with clear documentation
- Pieces marked “FDE retainer” can lean into pro-code as needed, since handoff is deferred
This is the kind of foresight that distinguishes a senior FDE: the build today is shaped by who maintains it in 18 months.
The “ten-minute change” test
A useful gut check on your boundary choices: pick a likely future change and time-estimate it under both your current architecture and an alternative.
Examples of likely changes the customer will ask for:
- “Add a new column to the morning view” — should be 10 minutes (drag and drop)
- “Add a new status to
Load” — 30 minutes (ontology + one downstream update) - “Change the slip-highlight threshold from 15 to 20 minutes” — 5 minutes (config change)
- “Add a notification when a load slips more than 60 minutes” — 1 hour (new action, low-code rule)
- “Build a custom map widget for the dispatcher” — 2 days (pro-code)
If “add a column” is taking your customer’s engineer two days, something in your low-code/pro-code split is wrong — they shouldn’t be in code to do it.
Testing across the boundary
A practical concern: how do you test a system that spans both surfaces?
Pro-code is easy to unit test. Low-code is harder — most low-code surfaces don’t have native unit-test runners.
The pattern that works:
- Pro-code functions are unit tested in isolation.
- Action types (low-code declared) have a small fixture suite — run the action with realistic inputs, assert on the outcome.
- App screens (low-code workshop) get smoke-tested with a thin end-to-end script that loads the page, asserts on visible elements, exercises one action.
- Cross-boundary calls get a contract test: pro-code function exposed to low-code is tested against its declared signature; low-code references to pro-code are validated against the exposed surface.
This is a tighter testing posture than most low-code shops apply. Adopt it from week 3 onward and you don’t get to week 8 wondering why a deploy broke three screens.
Versioning across the boundary
Both surfaces need to be versioned together for a coherent deploy.
Most platforms support some form of “release” or “branch” that snapshots both low-code configs and pro-code artifacts together. Use it. Every deploy is a single versioned artifact, not a coordinated push of two separate things.
A bad sign: you find yourself saying “the new screen is deployed but the new function hasn’t shipped yet.” That means your release boundary is wrong — the two surfaces shipped on different timelines and the customer sees a broken state.
A short FAQ
“Won’t customer engineers prefer low-code?” Often, yes — once they can use it. But the FDE’s job is to fit the surface to the work, not to the preference. A customer engineer who tries to express a multi-source heuristic in a low-code formula will quickly learn why pro-code exists. Train them on both.
“What about pro-code generated by AI?” A real and growing pattern. AI-generated pro-code is still pro-code from a maintenance perspective — readability, tests, and the boundary discipline above all apply. Generating the code does not change where it lives in the architecture.
“What if the platform only has one surface?” You probably aren’t on an FDE-style platform. The whole power of these platforms is the layered surface. If you’re forced onto a one-surface tool, treat that as a constraint and work harder on modularity at the available layer.
“Is low-code ‘real’ engineering?” Yes. The composition discipline, the boundary design, the maintainability concerns are all genuine engineering. Be the FDE who treats it that way.
Key terms to remember
- Low-code surface — declarative, point-and-click, config-driven
- Pro-code surface — full programmatic, code-as-source-of-truth
- Expressive ceiling — the point where low-code cannot express what you need
- Escape to pro-code — moving a piece from low-code to pro-code when ugly
- Clean boundary — typed, documented interface between the two surfaces
- The “ten-minute change” test — likely future changes should be cheap under your architecture
- Release boundary — both surfaces deploy together as one versioned artifact
What’s next
You can build apps. The fastest-changing area of FDE work over the last three years has been AI — agents that read the ontology, call tools, draft actions, and amplify operators. The next lesson covers how to deploy agentic workflows responsibly: where they shine, where they fail, and how to keep a human in the loop on every consequential decision.