Inside nearly every Oracle Forms application there’s a procedure named something like validate_line_item or calc_interest_accrual, wrapped in a PL/SQL package that’s been edited by eleven different people since 2003. That procedure is where the business actually lives. Everything else — the canvas, the LOVs, the triggers — is plumbing around it.
Migration succeeds or fails on what happens to that procedure. A rewrite means a developer reads it, builds a mental model, and retypes it in TypeScript. That’s where the Australian GST exemption added in 2014 quietly disappears. A translation means the system reads the PL/SQL, identifies structural patterns, and emits TypeScript that implements identical behavior. The logic survives. The language changes. The architecture improves.
What changes
The runtime. PL/SQL executes inside the Oracle Database. TypeScript executes in Node.js or the browser. That single shift unlocks API exposure, horizontal scaling, and containerized deployment — three capabilities the database engine was never designed to provide.
The type system. PL/SQL types map cleanly onto the primitives defined in the TypeScript handbook. NUMBER becomes number. VARCHAR2 becomes string. DATE becomes Date. Record types become interfaces. The mapping is mechanical, not interpretive.
In 2002, during my first serious PL/SQL package refactor, I renamed a single NUMBER variable from p_amt to p_amount in a shared pricing package and redeployed at midnight. The next morning three downstream applications started throwing PLS-00306 errors because they’d been calling the procedure with positional binds against a half-dozen overloaded signatures. I spent eleven hours rolling back and re-aligning overloads, fueled by the worst vending-machine coffee in Krakow. That’s the week I learned that in PL/SQL the real contract isn’t the code, it’s the signature, and any migration that ignores the binding conventions will pay for it downstream.
The error handling. PL/SQL exceptions become structured try/catch blocks. RAISE_APPLICATION_ERROR becomes typed error responses with HTTP status codes that downstream systems can actually consume.
The data access. Embedded SQL becomes API calls. SELECT FROM contractors WHERE... becomes this.api.getContractors(filters). The query still hits the same Oracle tables, but through a governed REST layer rather than direct database coupling. One thing the data access layer can’t safely skip is the database triggers and package state the forms have been leaning on for decades — that side usually holds 2-3x more code than the .fmb files.
What stays the same
Every validation rule. If the PL/SQL requires a positive amount, the TypeScript does too. We’ve broken down the trigger pattern that holds the bulk of Forms business logic in our guide to translating WHEN-VALIDATE-ITEM to TypeScript validators.
Every calculation. If the PL/SQL computes tax as amount * rate / 100, the TypeScript performs identical arithmetic to identical precision.
Every conditional branch. If the PL/SQL has CASE WHEN status = 'APPROVED' THEN..., the TypeScript carries the same conditional structure.
Every workflow step. If orders over $50K need VP approval before status can change, the new code enforces the same gate. The behavior is identical because the rules are identical.
What the migration unlocks
The new architecture isn’t just a language swap. It enables capabilities Oracle Forms never could:
- Unit testing. Each validation becomes an independently testable function. Forms made meaningful test coverage nearly impossible.
- Version control. The TypeScript lives in Git. Every change has a commit, an author, and a diff. PL/SQL inside .fmb files had none of that.
- API exposure. Logic that was trapped inside the database becomes callable from mobile apps, partner systems, or AI agents.
- Parallel development. Multiple engineers work different modules simultaneously without stepping on each other’s files.
The honest caveats
Does every line translate cleanly? No. Oracle Forms has quirks — implicit commits, navigator-driven screen flow, canvas-coordinate positioning — that don’t have direct modern equivalents. Those require architectural decisions during migration, and we make them deliberately.
But the core business logic — the rules, calculations, validations, and workflows the company actually depends on — translates reliably. Math is math. A constraint on a credit limit is a constraint on a credit limit, regardless of which language enforces it. That mechanical preservation is why structured migration beats both manual rewrites and code translation.