Reviewed by the Excel & Access Developers team, drawing on 15+ years designing manufacturing inventory, BOM, and shop-floor operations databases in Microsoft Access. Last updated August 2026.
Quick Answer
Excel is fine for manufacturing inventory only while one person owns a short, stable parts list and a bad cell reference won't stop a production run. Microsoft Access is the better tool the moment you have linked parts, locations, receipts, issues, and BOMs — and more than one person updating stock. Access is not a full ERP: treat it as a controlled inventory system, or as a front end over SQL Server, until MRP depth and plant-wide concurrency outgrow it.
Key takeaways
- Excel's failure mode in manufacturing is predictable: duplicate part numbers, overwritten "final" files, and no one sure which copy is live.
- Access should store every stock movement as its own transaction row, so on-hand quantity is calculated, never pasted or typed over.
- A split front end/back end isn't optional once receiving and production both need to enter data — it's the difference between a stable system and a corrupted one.
- BOM explosions, reorder lists, and job-cost reporting are ordinary Access queries; they're a manual, error-prone ritual in Excel.
- Upgrade to ERP or an Access-front-end-over-SQL-Server setup when you need multi-plant planning, deep standard costing, or heavier concurrent shop-floor traffic than a well-built Access app can carry.
If your current "system" is a shared inventory workbook plus a separate BOM spreadsheet, you're already past what Excel does well — our broader Excel vs Access comparison walks through the platform decision in more depth, and the manufacturing inventory system case study shows what a finished build looks like in practice.
At a Glance: Access vs Excel for Manufacturing Inventory
| Question | Excel | Access |
|---|---|---|
| Good for a single planner, short parts list? | Yes | Overkill |
| Safe with 2+ people editing stock at once? | No — overwrites, locked files | Yes, if split front end/back end |
| Keeps a real transaction history? | Rarely — usually just current qty | Yes — every movement is a row |
| Handles multi-level BOMs cleanly? | No — manual, error-prone | Yes — linked BOM tables + queries |
| Typical cost to stand up | Low, but rising hidden cost over time | Low-to-moderate build cost, low ongoing cost |
| Ceiling before you need something bigger | Hits fast | Small-to-midsize plant, single site |
Why Spreadsheets Break Down in Manufacturing Inventory
Manufacturing inventory is relationship-heavy by nature: one part lives in multiple locations, one receipt creates multiple line movements, one work order issues multiple components, and one BOM header has multiple component lines. A spreadsheet flattens all of that into rows that get copied, filtered, and occasionally sorted by accident — which is exactly how the "official" quantity stops matching what's on the shelf.
Version conflicts and "final_v3" workbooks
Receiving updates one copy of the workbook. Production updates another. Purchasing exports a third to send a vendor a quote. By Friday, nobody can say with confidence which file is the live one. A relational database solves this structurally, not procedurally — Access keeps a single data store with many forms and users pointed at it, so there's no "which copy is real" question to begin with.
No real transaction history
Most inventory spreadsheets store only the current quantity. When someone overwrites a cell, the history of how that number got there is gone. In Access, every receipt, issue, transfer, and adjustment should be recorded as its own row, so you can always answer why on-hand changed and when — not just what it currently reads.
BOM and job issues don't fit flat sheets
Handling a multi-level Bill of Materials, or issuing components against a specific job, requires joining several related lists together — which is exactly what a spreadsheet can't do gracefully. It's a natural fit for a relational structure; our guides on designing an Access database and setting up table relationships cover the underlying design principles in detail.
The 4-Table Inventory Model
Nearly every manufacturing inventory system we've built in Access, regardless of industry or size, sits on the same four-table backbone. We call it the 4-Table Inventory Model, and it's the fastest way to check whether an inventory database is actually built correctly:
- Parts — one row per SKU, component, or finished good (description, unit of measure, reorder point, preferred vendor).
- Locations — one row per stockroom, floor bin, or WIP location, so a part can exist in more than one place without being duplicated in the parts master.
- InventoryTransactions — one row per receipt, issue, transfer, or adjustment. This table is the system's memory; on-hand quantity is always derived from it, never stored as a standalone editable number.
- BOMHeader / BOMDetail — one row per parent assembly, and one row per component line underneath it, with a quantity-per that supports multi-level explosions.
Most shops eventually extend this with WorkOrders (to tie issues to a specific job) and Vendors (to tie receipts to a purchase order). But if a system doesn't have at least these four tables cleanly separated and linked by key, it's still a spreadsheet wearing an .accdb file extension — and it will eventually fail the same way a spreadsheet does.
What Inventory Tasks Manufacturing Teams Actually Run in Access
Parts master and locations
A Parts table holds the SKU, description, unit of measure, reorder point, and preferred vendor. A separate Locations table (stockroom, floor bin, WIP staging) lets the same part exist in more than one physical place without duplicating the part record itself.
Receiving and put-away
Receipts become rows in InventoryTransactions with `TranType = "Receipt"` and a positive `QtyChange`. A receiving form stamps the date, PO reference, and location automatically, so purchasing and the floor stay aligned without a side conversation.
Issues to jobs and work orders
Issues use a negative `QtyChange` (or a signed quantity plus a type field, depending on how you want to report it). Linking an optional `WorkOrderID` lets you report material cost by job without maintaining a separate shadow spreadsheet just for that.
BOM and kit explosions
`BOMHeader` plus `BOMDetail` (parent PartID, component PartID, quantity-per) answer questions like "what do I need on hand to build 50 units of Assembly A?" Keep explosions inside queries and reports — the moment someone starts pasting explosion results into a static column, you've recreated the spreadsheet problem inside Access.
Reorder and shortage lists
A query comparing on-hand (the sum of transactions) against `ReorderPoint`, or comparing BOM demand for open jobs against available stock, replaces the weekly "highlight the red cells" ritual that most Excel-based shops run manually.
Access vs Excel for Manufacturing Inventory Tracking
| Excel | Access | |
|---|---|---|
| Data structure | Flat sheet(s); quantity often a single editable cell | Parts, Locations, Transactions, and BOMs linked by key |
| Multiple editors | Overwrites, emailed copies, locked files | Safe when properly split front end/back end |
| Audit trail | Easy to lose the moment a cell is overwritten | Transaction rows preserve full history automatically |
| BOM / job issues | Manual, error-prone, hard to explode correctly | Built for it — forms plus queries |
| Reporting | Manual pivot tables, rebuilt each time | Live queries and reports off current data |
| Best fit | One planner, a short, stable parts list | Small manufacturer or plant office running real inventory operations |
For a broader look at when spreadsheets stop scaling in general, see signs your business has outgrown Excel; for a head-to-head on the two platforms outside of manufacturing specifically, our Access vs Excel comparison guide is the deeper resource.
Access vs Dedicated ERP or Manufacturing Software
Access is a platform you shape to your process. ERP and manufacturing suites are finished products with built-in MRP, shop-floor modules, and multi-plant costing already designed for you. Plenty of shops run Access successfully for stockrooms and light WIP tracking for years; they move up when planning engines and system integrations — not raw inventory tracking — become the real bottleneck.
| Need | Prefer Access | Prefer ERP / manufacturing suite |
|---|---|---|
| Custom screens and reports built around your process | Strong — you control the design | Vendor-limited, often needs a consultant to customize |
| Accurate on-hand and full history | Strong, if the tables are designed correctly | Strong |
| Full MRP and capacity planning | Weak — not what it's built for | Strong |
| Small team on a Windows LAN | Strong, low recurring cost | Often a higher total cost of ownership |
| Multi-plant operations, deep GL costing | Risky to build yourself | Stronger, purpose-built for this |
See how this plays out for manufacturers specifically in our manufacturing solutions overview.
Common Problems Manufacturers Hit With Access Inventory Files
Nearly every "Access ate our inventory data" story we've been called in on traces back to one of three root causes: an unsplit file being shared over Wi-Fi or a synced cloud folder, a spreadsheet-shaped table design pretending to be a database, or someone editing an on-hand cell by hand instead of posting a transaction.
When something actually breaks, these are the fastest paths to a fix:
- File won't open at all → [Access database will not open](/blog/access-database-wont-open-fix)
- Locked for editing with no one else apparently in it → [Access database locked for editing](/blog/access-database-locked-for-editing-fix)
- Corruption after a crash or network drop → [corruption recovery](/blog/access-database-corruption-recovery) and, to stop it from recurring, [corruption prevention](/blog/prevent-access-database-from-corruption)
- Getting slow as transaction history grows → [multi-user best practices](/blog/access-database-multi-user-best-practices) and [why Access slows down with large data](/blog/why-access-slow-large-data)
- Outgrowing the current design entirely → [common mistakes when scaling an Access database](/blog/common-mistakes-when-scaling-ms-access-database)
Split your front end and back end — and keep the back end off cloud sync
Data tables belong in a back-end file on a stable, mapped network share; each workstation gets its own front-end copy with forms, reports, and VBA. Two mistakes cause most of the "inventory system is down" tickets we see: the linked-table path breaking after a server move, and a back-end file sitting in a OneDrive or SharePoint sync folder, where the sync client's own file locking collides with Access's record-level locking.
Concurrent receiving and production
Two people editing one unsplit file during receiving and a kit pull at the same time is exactly how record locks and bad writes start. Split the database before the second shift needs access to the same data, not after.
Spreadsheet-shaped "databases"
A single table with PartNumber, Description, Bin, Jan, Feb, Mar… as columns fails the first time there's a mid-month adjustment or a part that lives in two locations at once. Use the 4-Table Inventory Model above instead of month columns.
How to Build a Manufacturing Inventory Database in Access, Step by Step
Step 1: Define parts, locations, and transactions as separate tables
| Table | One row means |
|---|---|
| Parts | One SKU, component, or finished good |
| Locations | One bin, stockroom, or WIP location |
| InventoryTransactions | One receipt, issue, transfer, or adjustment |
| BOMHeader | One parent assembly revision |
| BOMDetail | One component line on a BOM |
| WorkOrders | One job or build order (optional) |
Step 2: Set primary keys and relationships
Use AutoNumber keys (`PartID`, `LocationID`, `TranID`, `WorkOrderID`) and put foreign keys on the "many" side (`InventoryTransactions.PartID`, `BOMDetail.ParentPartID`, `BOMDetail.ComponentPartID`). Turn on referential integrity so Access physically won't let you delete a part that still has movement history against it.
On-hand quantity for a part, across all locations:
SELECT PartID, Sum(QtyChange) AS OnHand
FROM InventoryTransactions
GROUP BY PartID;Step 3: Build receiving and issue forms
Require `PartID`, `LocationID`, `Qty`, and `TranType` before anything saves, and use VBA to stamp defaults and block zero or negative quantities where they don't belong.
Post a receipt from a receiving form:
Private Sub cmdPostReceipt_Click()
On Error GoTo Handler
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim qty As Double
If IsNull(Me!PartID) Or IsNull(Me!LocationID) Or IsNull(Me!Qty) Then
MsgBox "Part, location, and quantity are required.", vbExclamation
Exit Sub
End If
qty = CDbl(Me!Qty)
If qty <= 0 Then
MsgBox "Receipt quantity must be greater than zero.", vbExclamation
Exit Sub
End If
Set db = CurrentDb
Set rs = db.OpenRecordset("InventoryTransactions", dbOpenDynaset, dbAppendOnly)
rs.AddNew
rs!PartID = Me!PartID
rs!LocationID = Me!LocationID
rs!TranType = "Receipt"
rs!QtyChange = qty
rs!TranDate = Nz(Me!TranDate, Date)
rs!Reference = Nz(Me!PONumber, "")
rs!EnteredBy = Nz(Environ("USERNAME"), "unknown")
rs.Update
rs.Close
MsgBox "Receipt posted.", vbInformation
Me.Requery
Exit Sub
Handler:
MsgBox "Could not post receipt: " & Err.Number & " — " & Err.Description, vbCritical
End SubIssue components to a work order (a negative movement, with a stock check first):
Private Sub cmdIssueToJob_Click()
On Error GoTo Handler
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim qty As Double
Dim onHand As Variant
If IsNull(Me!PartID) Or IsNull(Me!LocationID) Or IsNull(Me!Qty) Or IsNull(Me!WorkOrderID) Then
MsgBox "Part, location, quantity, and work order are required.", vbExclamation
Exit Sub
End If
qty = CDbl(Me!Qty)
If qty <= 0 Then
MsgBox "Issue quantity must be greater than zero.", vbExclamation
Exit Sub
End If
onHand = Nz(DSum("QtyChange", "InventoryTransactions", _
"PartID = " & Me!PartID & " AND LocationID = " & Me!LocationID), 0)
If onHand < qty Then
MsgBox "Insufficient stock. On hand: " & onHand, vbExclamation
Exit Sub
End If
Set db = CurrentDb
Set rs = db.OpenRecordset("InventoryTransactions", dbOpenDynaset, dbAppendOnly)
rs.AddNew
rs!PartID = Me!PartID
rs!LocationID = Me!LocationID
rs!WorkOrderID = Me!WorkOrderID
rs!TranType = "Issue"
rs!QtyChange = -qty
rs!TranDate = Date
rs.Update
rs.Close
MsgBox "Issued to job.", vbInformation
Exit Sub
Handler:
MsgBox "Issue failed: " & Err.Number & " — " & Err.Description, vbCritical
End SubStep 4: Add reorder alerts and a low-stock flag
Show on-hand vs. reorder point live on the part form:
Private Sub Form_Current()
On Error Resume Next
Dim onHand As Double
If IsNull(Me!PartID) Then
Me!txtOnHand = 0
Exit Sub
End If
onHand = Nz(DSum("QtyChange", "InventoryTransactions", "PartID = " & Me!PartID), 0)
Me!txtOnHand = onHand
If Not IsNull(Me!ReorderPoint) Then
Me!txtNeedsReorder = (onHand <= CDbl(Me!ReorderPoint))
End If
End SubFlag every part currently below its reorder point (an admin-triggered button; test on a copy of the database first):
Public Sub FlagLowStockParts()
On Error GoTo Handler
Dim db As DAO.Database
Dim sql As String
Set db = CurrentDb
sql = "UPDATE Parts SET LowStockFlag = True " & _
"WHERE Nz((SELECT Sum(QtyChange) FROM InventoryTransactions " & _
" WHERE InventoryTransactions.PartID = Parts.PartID), 0) " & _
" <= Nz(ReorderPoint, 0)"
db.Execute sql, dbFailOnError
MsgBox db.RecordsAffected & " part(s) flagged low stock.", vbInformation
Exit Sub
Handler:
MsgBox "FlagLowStockParts failed: " & Err.Number & " — " & Err.Description, vbCritical
End SubStep 5: Explode multi-level BOMs correctly
A single-level BOM query is a simple join. A multi-level BOM — where a component is itself an assembly with its own BOM underneath it — needs recursion, because you can't know in advance how many levels deep the structure goes.
Recursive BOM explosion: total component demand to build a given quantity of a parent part:
Public Sub ExplodeBOM(ByVal parentPartID As Long, ByVal buildQty As Double, _
ByRef results As Collection)
' results collects "PartID|Qty" strings; roll them up by PartID after the call.
On Error GoTo Handler
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim sql As String
Dim lineQty As Double
Set db = CurrentDb
sql = "SELECT ComponentPartID, QtyPer FROM BOMDetail WHERE ParentPartID = " & parentPartID
Set rs = db.OpenRecordset(sql, dbOpenSnapshot)
Do While Not rs.EOF
lineQty = rs!QtyPer * buildQty
results.Add rs!ComponentPartID & "|" & lineQty
' If this component is itself a parent assembly, recurse into its BOM.
If DCount("*", "BOMDetail", "ParentPartID = " & rs!ComponentPartID) > 0 Then
ExplodeBOM rs!ComponentPartID, lineQty, results
End If
rs.MoveNext
Loop
rs.Close
Exit Sub
Handler:
MsgBox "BOM explosion failed at PartID " & parentPartID & ": " & Err.Description, vbCritical
End SubThese are starter patterns, not a finished system. Lot and serial tracking, average or standard costing, and full MRP logic need a fuller transaction ledger and usually benefit from a developer's eye — or a clear decision to move that specific piece to an ERP.
When to Bring in a Developer or Upgrade Your System
Stay on Excel only while one person owns a short parts list and a wrong cell won't stop a job.
Move to Access — or hire someone to build it right — the moment receiving and production share data, BOMs matter, and you need real transaction history. Access database design and development and our general Access development services are the right starting points.
Plan an ERP or SQL Server upgrade when you need plant-wide MRP, deep product costing, or more concurrent users than a well-split Access app can reasonably carry. Many manufacturers keep Access as the front-end interface while SQL Server holds the underlying tables — a natural next step covered in our guide to common mistakes when scaling an Access database.
See Our Manufacturing Inventory Work
Still tracking inventory in a shared Excel file that everyone's afraid to touch? Take a look at how we approach manufacturing systems, review the inventory case study above, and book a free consult if you want a production-ready Access build.
Manufacturing SolutionsIs Microsoft Access Still a Good Choice for Inventory in 2026?
Yes — with the right expectations. Microsoft still ships Access as part of Microsoft 365, and it remains one of the most common tools quietly running production, receiving, and warehouse operations underneath small and midsize manufacturers. It isn't the right tool for multi-plant MRP or deep standard costing, and it was never meant to be. But for a single site on a Windows network with a moderate number of concurrent users, a properly designed Access system built on the 4-Table Inventory Model is still one of the fastest, lowest-cost ways to replace spreadsheet chaos with an actual database — and it's a system your own team can maintain, not a black box you're locked out of.
Frequently Asked Questions
Yes, for most manufacturers with more than one person touching stock. Access links parts, locations, transactions, and BOMs by key instead of duplicating data on every row, and supports multiple simultaneous users through a split front end/back end. Excel remains the right tool for a single planner managing a short, stable parts list.
At minimum four: Parts (or Items), Locations, InventoryTransactions, and BOMHeader/BOMDetail. Most shops add WorkOrders and Vendors as they grow. Never repeat a part description on every movement row, and never store months as spreadsheet-style columns.
It can fully replace spreadsheet-based tracking for small-to-midsize shops needing accurate on-hand quantities, transaction history, and reorder alerts. It's not a substitute for multi-plant MRP or deep standard costing — though many shops keep Access as the front end while SQL Server holds the data.
Never edit a quantity field directly. Insert a row into InventoryTransactions for every receipt, issue, transfer, and adjustment. On-hand is then the sum of QtyChange — always derived from history, never typed over.
Yes, if the database is split: data in a back-end file on a stable network share, forms and code in a front-end copy on each workstation. Keep the back end off OneDrive or SharePoint sync folders, since cloud sync locking conflicts with Access's own record locking.
Use BOMHeader for the parent assembly and BOMDetail for each component line with a quantity-per. Multi-level assemblies need a recursive query or VBA function to explode demand correctly down every level — never flatten a multi-level BOM into a single spreadsheet row.
Yes. Microsoft continues to ship Access with Microsoft 365, and it remains common under production and warehouse operations at small and midsize manufacturers. For a single site with a moderate number of concurrent users, it's still one of the fastest, lowest-cost ways to get a real relational inventory system running.
When receiving and production need to edit stock concurrently, when BOMs or lot/serial tracking get complex, when reported quantities stop matching the shop floor, or when you're migrating years of Excel history and can't risk a fragile do-it-yourself model.
Next Steps
List out your parts, your locations, and the movements you post today — receive, issue, adjust. If that already spans more than one workbook, it's time to design an Access transaction model around the 4-Table Inventory Model above. Want to see it done first? Review our manufacturing solutions and the manufacturing inventory system case study, or contact us for a free consultation on a production build.
Need help moving from advice to implementation?
We can review the workbook, Access database, or workflow behind this article and tell you the safest next step before you spend time fixing the wrong thing.