Generic inventory software treats a bill of materials as a list: part number, quantity, done. A manufacturing BOM is not a list. It's a versioned graph — parent assemblies pointing to child components, each link carrying a quantity, a unit of measure, a revision, and a date range in which that link is even valid. Flatten that graph into a spreadsheet-shaped table and the software will still produce numbers. They'll just be confidently wrong.
Here's the arithmetic, kept illustrative: an engineering change swaps a $1.20 fastener for a $0.80 one, effective on units built after March 15. If the system stores "the BOM" as one mutable row instead of two effective-dated revisions, every unit built before March 15 gets re-costed at $0.80 retroactively, and every unit built after keeps costing at $1.20 until someone notices the report doesn't match the purchase orders. On a run of 10,000 units that's a swing of thousands of dollars in cost of goods sold that never actually happened.
The cost of being wrong shows up in three places at once: purchasing orders the wrong quantity of the wrong revision, finance reports a COGS figure that doesn't reconcile with what was actually consumed, and quality can't tell you which serial numbers contain the part that just failed in the field. None of these are edge cases. They're what the BOM exists to answer.
A Bill of Materials Is a Versioned Graph, Not a Parts List
A real BOM is multi-level: a finished good explodes into sub-assemblies, which explode into components, which explode into raw material. Some of those sub-assemblies are phantom items — groupings that exist in the design for organizational reasons but are never stocked or purchased on their own. If your data model treats every line as a stocked, purchasable part, the shop floor gets a pick list for an item that doesn't exist in the warehouse, and someone burns twenty minutes figuring out that "assembly 4400-A" is actually just a label for four other parts.
The fix isn't complicated, but it has to be explicit in the schema: every BOM line needs a flag for whether the child is a real, stockable part or a phantom that should be exploded further before it reaches purchasing or the pick list. Software that skips this distinction works fine in a demo with three-part products and falls over the first time a customer has a real assembly tree.
Engineering BOM and Manufacturing BOM Solve Different Problems
An EBOM is organized around design intent — how the product is functionally structured, which is how engineers think about it and how design changes propagate. An MBOM is organized around how the thing actually gets built — sequence of operations, fixtures, consumables, and intermediate states that never show up in the design document at all. They describe the same product from two different jobs.
Software that insists on "one BOM per product" forces a bad tradeoff. Either engineering loses traceability back to the design intent because the structure has been rearranged for the shop floor, or the shop floor gets handed a document organized by design hierarchy that doesn't match the order operations actually happen in. Both failure modes are common in off-the-shelf inventory tools that were built for retail or distribution first and had manufacturing bolted on later — the same pattern we've seen in other domains where a generic platform gets stretched past what its schema was designed for, which is worth reading about across the sectors we build for.
Effectivity Dates Decide What Shipped, Latest Revision Doesn't
The single rule that keeps a BOM honest: never edit a revision in place. When an engineering change order lands, you close out the old revision and insert a new one with its own effective window. "What's on the BOM right now" is the wrong question for anything that's already been built — the right question is "what was effective on the date this specific unit was built."
- Close the current revision by setting its
effective_todate to the ECO's cutover date. - Insert the new revision's lines with
effective_fromset to that same date, never earlier. - Attach the ECO number and approver to the new revision so the change is auditable, not just present.
- Decide explicitly whether open work orders inherit the new revision or finish out on the old one — don't let the system default this silently.
That fourth step is the one generic tools get wrong most often. An in-progress work order that silently picks up a mid-build revision change produces a physical unit that matches neither BOM.
Cost Rollups Have to Freeze at the Time of Build
A multi-level BOM's cost is the sum of its children's costs, rolled up recursively. The mistake is computing that rollup live against current catalog prices instead of freezing it at the moment a work order actually consumes inventory. Current catalog price answers "what would this cost to build today." A frozen snapshot answers "what did this unit actually cost," and only one of those numbers belongs in your COGS.
A BOM cost rollup that isn't frozen at build time is a number that moves after you've already shipped the part.
This is the same discipline that shows up in any ledger-adjacent system: a number that keeps recalculating against a moving reference isn't a record, it's an estimate wearing a record's clothes. We wrote about the same failure mode in payment systems in our piece on reconciliation and floats — a BOM cost history has to be append-only for the same reason a transaction ledger does.
Where-Used Queries Are the Recall Insurance Policy
When a supplier tells you a lot of fasteners failed a fatigue test, the question isn't "which BOM uses this part." It's a narrower, harder question, and your software needs to answer all four parts of it in minutes, not days:
- Which finished assemblies contain this part number, across every revision that ever used it.
- Which of those assemblies were actually built, not just designed, within the affected lot's date range.
- Which serial or lot numbers of the finished good received material from the affected supplier lot.
- Which customers or locations received those specific serial numbers.
If any one of those joins requires a manual spreadsheet reconciliation instead of a query, the recall clock is running against you while someone builds that spreadsheet by hand. This is the kind of costed, traceable line-item structure we built for construction takeoff and estimating on BuildAEC — the domain differs, but the requirement that every quantity trace back to its source and its cost at the moment it was consumed is identical.
A Minimal Schema That Survives Revisions
CREATE TABLE bom_line (
id uuid PRIMARY KEY,
parent_part_id uuid REFERENCES part(id),
child_part_id uuid REFERENCES part(id),
quantity numeric NOT NULL,
uom text NOT NULL,
is_phantom boolean NOT NULL DEFAULT false,
revision text NOT NULL,
eco_number text,
effective_from date NOT NULL,
effective_to date,
UNIQUE (parent_part_id, child_part_id, revision, effective_from)
);
Every field earns its place. is_phantom stops purchasing from ordering a non-stocked grouping. eco_number ties the revision to an approved change instead of an untracked edit. effective_from/effective_to mean cost and pick-list queries can always ask "what was true on this date" instead of "what's true right now." Nothing here is exotic — it's the difference between a table that answers audit questions and one that only answers today's questions.
When You Don't Need Full PLM
None of this is free. Effectivity-dated BOMs, frozen cost snapshots, and full where-used indexing add real schema complexity and real UI complexity — someone has to review and approve every revision instead of just editing a spreadsheet cell. For a job shop building one-off custom units with no recall exposure and no regulatory audit trail, that machinery can slow down engineering changes without buying back any real risk. A simpler versioned BOM with a manual approval step and a revision log is often the right call there.
The line to watch is volume and exposure, not company size. The moment you're building the same design more than a handful of times, selling into a regulated industry, or carrying any warranty or recall liability, the versioning and effectivity structure pays for itself the first time something goes wrong and you need to answer "which units have this part" in an afternoon instead of a week. If you're scoping this kind of system now, our services page and how we approach fixed-scope engagements cover how we sequence exactly this kind of domain modeling before writing application code, and our portfolio has more on the costed, traceable systems we've shipped. If you want to talk through where your BOM structure actually needs to sit on that spectrum, reach out.
Frequently asked
What's the difference between an engineering BOM and a manufacturing BOM?
An EBOM reflects design intent — how the product is functionally structured for engineering. An MBOM reflects build sequence — operations, fixtures, and consumables the shop floor actually uses, which rarely matches the design hierarchy. Software that forces both into one document breaks traceability on one side or build accuracy on the other.
How should software handle BOM effectivity dates?
Never edit a BOM revision in place. Close the old revision's effective window and insert a new revision starting on the change's cutover date, tagged to its engineering change order. This lets any query ask what was true on a given build date instead of only what's true right now.
Why does BOM cost need to be frozen at build time instead of calculated live?
A live rollup against current catalog prices tells you what a unit would cost to build today, not what it actually cost. Cost of goods sold has to reflect the component prices in effect when the work order consumed inventory, which means snapshotting the rollup at build time, not recalculating it later.
Do small manufacturers need full PLM-style BOM versioning?
Not always. Low-volume, custom, one-off builds with no recall or regulatory exposure can often get by with a lighter revision log and manual approval. Full effectivity-dated versioning earns its complexity once you're repeating a design at volume or carrying warranty or compliance liability on it.
What is a where-used query and why does it matter for recalls?
It's the query that, given a part or lot number, returns every finished assembly that contains it, which of those were actually built in the affected date range, which serial numbers received the bad lot, and which customers received those units. Without it as a real query, a recall becomes a manual spreadsheet exercise under time pressure.