A binary blob with 30 years of business rules inside
A European logistics operator handed us 612 .fmb files last year. Total size: 184 MB. Inside those files sat 28,400 triggers, 9,100 LOVs, and roughly 1.2 million lines of embedded PL/SQL — none of it stored as text, all of it serialized into a format Oracle has never formally published. The company had lost the source control history in 2011. The .fmb files were the source of truth.
That scenario is the norm, not the exception. We’ve now parsed 2,400 .fmb files across migration projects, and the contents are remarkably consistent.
What a .fmb file actually is
A .fmb is a proprietary binary serialization of an Oracle Forms module. It’s not a database dump and it’s not a simple AST. It’s a tree of objects — blocks, items, triggers, canvases, windows, LOVs, record groups, program units — each one tagged with a type code and a variable-length payload. The format has shifted subtly across Forms 4.5, 6i, 10g, 11g, and 12c, but the core structure is stable.
The companion files tell a similar story. A .fmx is the compiled runtime binary. A .fmt is a text export Oracle provides, but it’s lossy — layout coordinates, font metadata, and some property defaults get dropped or rewritten. Migrations that rely only on .fmt miss between 4% and 9% of the original behavior.
The object tree, by the numbers
Across our sample, an average .fmb contains:
- 14 data blocks
- 96 items
- 47 triggers
- 18 LOVs and record groups
- 6 canvases
- 2,100 lines of embedded PL/SQL
The distribution is heavily skewed. The top 10% of files hold more than half the total logic. We’ve seen single .fmb files with 480 triggers and 14,000 lines of PL/SQL — usually the general ledger posting screen or the order entry workhorse.
Where the business logic actually lives
Forms developers had four places to put code: form-level triggers, block-level triggers, item-level triggers, and program units attached to the module. In practice, most logic ends up at the item level, inside WHEN-VALIDATE-ITEM, POST-QUERY, and KEY-NEXT-ITEM.
-- Typical WHEN-VALIDATE-ITEM payload inside a .fmb
IF :ORDERS.TOTAL_AMOUNT > 50000 AND :ORDERS.APPROVAL_ID IS NULL THEN
MESSAGE('Orders over 50000 require approval');
RAISE FORM_TRIGGER_FAILURE;
END IF;
That snippet is stored inside the binary as a length-prefixed string attached to an item node. Finding it requires walking the object tree. Multiply by 47 triggers per file and 612 files and the extraction becomes a parser problem, not a grep problem.
The properties nobody documents
Beyond code, each item carries between 80 and 140 properties: default values, format masks, query conditions, navigation hints, database column bindings, and layout coordinates. Around 30% of these properties encode behavior that developers assumed was “just how Forms works” — cascading LOVs triggered by format mask inheritance, for example, or implicit commit behavior tied to the Database Block property.
When we rebuild a screen in TypeScript, these implicit properties account for most of the surprises. The visible trigger code is the easy part.
Parsing without Oracle’s runtime
Most migration tools call Forms Builder or the Forms API to read .fmb files. That approach works, but it requires a licensed Oracle install and caps extraction throughput at roughly 40 files per hour. We built a direct binary parser that reads the object tree without the runtime. It processes the same 612 files in under 90 seconds and produces a JSON descriptor for each module.
The descriptor becomes the input to everything downstream: the TypeScript generator, the control inventory for audit, and the visual diff tool that compares old and new screens side by side.
The takeaway
A .fmb file is not a black box. It’s a well-structured tree wrapped in an undocumented binary envelope. Once the envelope is open, 30 years of business rules become searchable, diffable, and migrable — in that order. Every migration we’ve shipped started with a complete parse of the source files, because every shortcut at that stage surfaces as a defect six months later.