A binary blob with 30 years of business rules inside
Every .fmb file begins with a 512-byte header whose first four bytes identify the file as a Forms module, followed by a version stamp, a character-set code, and a pointer to the root object in the object tree. Past that header, the file is an interleaved sequence of type-tagged objects with variable-length payloads: blocks, items, triggers, record groups, LOVs, canvases, each one referencing others by integer IDs. Oracle has never formally published the layout. We’ve now parsed 2,400 of these files across migration projects, and the contents are remarkably consistent. A European logistics operator handed us 612 of them last year — 184 MB total, holding 28,400 triggers, 9,100 LOVs, and roughly 1.2 million lines of embedded PL/SQL.
The company had lost the source control history in 2011. The .fmb files were the source of truth.
I remember sitting in a windowless meeting room in Warsaw in 2006, staring at a hex editor alongside a colleague from Oracle Support, trying to figure out why a 6i .fmb wouldn’t open in the Forms 10g Builder. The file opened fine on one machine and threw an ORA-06508 on another. After two days we traced it to a single byte in the module header that encoded character set metadata. That was the week I stopped trusting Oracle’s internal tooling to round-trip its own files cleanly, and started making byte-level backups before every upgrade.
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 — 38% of total logic in our sample sits in WHEN-VALIDATE-ITEM alone.
-- 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 — the JSON format we built the entire pipeline around — 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.